/* ==========================================================================
   Everything outside the builder: sign-in, campaigns, admin.
   ========================================================================== */

.gate {
  display: grid;
  place-items: center;
  min-height: 100vh;
  padding: 24px;
}

.gate-card {
  width: 100%;
  max-width: 380px;
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 24px 24px;
}

.gate-card h2 {
  font-family: var(--serif);
  font-size: 20px;
  margin: 0 0 4px;
  text-align: center;
}

.gate-card > .muted { text-align: center; font-size: 12px; margin: 0 0 16px; }

.stack { display: flex; flex-direction: column; gap: 12px; }
.stack .btn { width: 100%; justify-content: center; padding: 8px; }

.brand.as-link {
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  text-align: left;
}

/* ------------------------------------------------------------------- home */

.panel.home, .panel.campaign { max-width: 1100px; }

.campaign-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 12px;
  margin-top: 6px;
}

.campaign-card {
  text-align: left;
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius);
  padding: 12px 16px;
  cursor: pointer;
  color: inherit;
}

.campaign-card:hover { background: var(--bg-3); border-color: var(--line-2); border-left-color: var(--accent-2); }
.cc-name { font-family: var(--serif); font-size: 17px; margin-bottom: 3px; }
.cc-meta { font-size: 12px; }

/* --------------------------------------------------------------- campaign */

.campaign-cols {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
  gap: 16px;
  align-items: start;
  margin-top: 16px;
}

@media (max-width: 900px) { .campaign-cols { grid-template-columns: 1fr; } }

/* ------------------------------------------------------- the DM's screen */

/* The party strip wants room: three cards across before it wraps. */
.panel.campaign.dm { max-width: 1500px; }

.party-card { margin-bottom: 16px; }

/* the strip and the rail beside it. The rail is narrow and stays put: it is
   read constantly during a fight, and the strip is what scrolls */
.party-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 232px;
  gap: 12px;
  align-items: start;
  margin-top: 8px;
}

.party-main { min-width: 0; }

/* too narrow for a column beside the strip: the order goes above it instead,
   where it is still the first thing read, and stops short of the full width
   so the rows do not stretch into a ledger */
@media (max-width: 1100px) {
  .party-layout { grid-template-columns: 1fr; }
  /* scoped to the layout so it outranks the base .turn-rail rule below, which
     otherwise wins on order and left the order stuck over the cards */
  .party-layout > .turn-rail { order: -1; position: static; max-width: 560px; max-height: 46vh; }
}

.party {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(310px, 1fr));
  gap: 12px;
  margin-top: 8px;
}

/* ------------------------------------------------------ the turn tracker */

.turn-rail {
  position: sticky;
  top: 12px;
  padding: 8px 12px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  max-height: calc(100vh - 24px);
}

/* stacked: a name and a button cannot share one 232px line, and the name is
   the half you must not abbreviate */
.trk-head { display: flex; flex-direction: column; gap: 2px; }
.trk-btns { display: flex; gap: 6px; margin-top: 6px; }
.trk-btns .grow { flex: 1; justify-content: center; }
.trk-foot { display: flex; align-items: center; gap: 6px; justify-content: flex-end; }
.trk-round { font-size: 10px; letter-spacing: .06em; text-transform: uppercase; color: var(--ink-3); }

.trk-whose {
  display: block;
  min-width: 0;
  font-family: var(--serif);
  font-size: 16px;
  line-height: 1.2;
  color: var(--accent-2);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.trk-list { display: flex; flex-direction: column; overflow-y: auto; min-height: 0; }

.trk-row {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  text-align: left;
  background: none;
  border: 0;
  border-left: 2px solid transparent;
  border-radius: var(--radius);
  padding: 4px 6px 4px 5px;
  color: inherit;
  cursor: pointer;
  line-height: 1.2;
}

.trk-row:hover { background: var(--bg-3); }

/* whose turn it is, which is the one thing this panel exists to say */
.trk-row.now {
  background: color-mix(in srgb, var(--accent) 15%, transparent);
  border-left-color: var(--accent);
}

.trk-row.now .trk-name { font-weight: 600; color: var(--accent-2); }

.trk-init {
  flex: none;
  width: 24px;
  text-align: center;
  font-size: 14px;
  font-weight: 600;
  color: var(--ink-2);
}

.trk-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.trk-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; }
.trk-unseen { font-style: normal; color: var(--ink-3); margin-left: 4px; font-size: 10px; }

.trk-bar { display: block; height: 4px; background: var(--bg-4); border-radius: 3px; overflow: hidden; }
.trk-bar .fill { display: block; height: 100%; background: var(--green); }
.trk-row.hurt .trk-bar .fill { background: var(--accent); }
.trk-row.out .trk-bar .fill { background: var(--red); }
.trk-row.out .trk-name { color: var(--ink-3); text-decoration: line-through; }

.trk-n { flex: none; text-align: right; font-size: 12px; font-weight: 600; }
.trk-n i { font-style: normal; font-weight: 400; color: var(--ink-3); }
.trk-n .trk-ac { display: block; font-size: 9px; font-weight: 400; color: var(--ink-3); }

/* a creature reads as one at a glance, the way its card does */
.trk-row.npc .trk-init { color: var(--accent); }

/* a player's copy of the order: read, not clicked */
.trk-list.plain .trk-row { cursor: default; }
.trk-list.plain .trk-row:not(.now):hover { background: none; }
.trk-you { font-style: normal; font-size: 10px; color: var(--ink-3); margin-left: 4px; }

/* ------------------------------- the same tracker, loose on the board */

.trk-pip {
  position: fixed;
  top: 84px;
  right: 18px;
  z-index: 40;
  width: 236px;
  max-height: calc(100vh - 110px);
  display: flex;
  flex-direction: column;
  background: var(--bg-2);
  border: 1px solid var(--line-2);
  border-radius: 10px;
  box-shadow: 0 8px 26px rgb(0 0 0 / 45%);
  overflow: hidden;
}

.trk-pip.lifting { box-shadow: 0 14px 34px rgb(0 0 0 / 60%); opacity: .96; }
.trk-pip .trk-list { padding: 4px 6px 6px; }
.trk-pip.shut { width: auto; max-width: 300px; }

.pip-grip {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 7px 8px;
  background: var(--bg-3);
  border-bottom: 1px solid var(--line);
  cursor: grab;
  touch-action: none;
  user-select: none;
}

.trk-pip.lifting .pip-grip { cursor: grabbing; }
.pip-dots { color: var(--ink-3); font-size: 13px; line-height: 1; flex: none; }
.pip-who { flex: 1; min-width: 0; }
.pip-shut { flex: none; padding: 1px 6px; }
.pip-empty { padding: 12px; margin: 0; }

.pip-foot {
  padding: 6px 8px;
  border-top: 1px solid var(--line);
  background: var(--bg-3);
}

/* The summary: the other panel loose on the board. It opens at the bottom
   left - clear of the tracker, and of the DM's pens and grid buttons along
   the top, which a panel opened up there sat straight on - and scrolls inside
   itself, so a long card cannot push it off the screen. */
.sum-pip {
  top: auto;
  bottom: 18px;
  left: 18px;
  right: auto;
  width: 320px;
  max-width: calc(100vw - 36px);
  max-height: calc(100vh - 200px);
}
.sum-pip.shut { width: auto; max-width: 300px; }
.sum-body { overflow-y: auto; min-height: 0; padding: 6px; }
.sum-body .party { display: block; margin: 0; }
.sum-body .pc-card { border-top: 0; border-right: 0; border-bottom: 0; }
.sum-open { display: flex; justify-content: flex-end; }

/* the turn, written down from the board: the sheet's three slots, without
   the sentence explaining End turn that the sheet has room for */
.sum-turn { margin: 2px 0; }
.sum-body > .sum-turn { padding: 0 6px 6px; }
.sum-turn .turn-end { margin-top: 6px !important; }
.sum-turn .turn-end .tiny { display: none; }
.sum-turn .turn-end .btn { flex: 1; justify-content: center; }

/* it is their turn: said on the grip, which is what shows rolled up */
.sum-pip.my-turn { border-color: var(--accent); }
.sum-pip.my-turn .pip-grip { background: color-mix(in srgb, var(--accent) 22%, var(--bg-3)); }
.sum-pip.my-turn .trk-round { color: var(--accent-2); }
.sum-open .pc-name { font-size: 12px; }

.pc-card {
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-left: 3px solid var(--green);
  border-radius: var(--radius);
  padding: 8px 12px;
  display: flex;
  flex-direction: column;
  gap: 7px;
  min-width: 0;
}

/* the border says how the character is doing before anything is read */
.pc-card.hurt { border-left-color: var(--accent); }
.pc-card.out { border-left-color: var(--red); background: color-mix(in srgb, var(--red) 7%, var(--bg-2)); }
.pc-card.broken { border-left-color: var(--line-2); }

.pc-head { display: flex; align-items: center; gap: 8px; }

.pc-face {
  width: 38px;
  height: 38px;
  flex: none;
  object-fit: cover;
  border-radius: var(--radius);
  border: 1px solid var(--line-2);
}

.pc-face.blank { background: var(--bg-3); display: block; }

.pc-name {
  flex: 1;
  min-width: 0;
  display: block;
  text-align: left;
  background: none;
  border: 0;
  padding: 2px 4px;
  margin: -2px -4px;
  border-radius: var(--radius);
  color: inherit;
  cursor: pointer;
  line-height: 1.25;
}

.pc-name:hover { background: var(--bg-3); }
.pc-name b { font-family: var(--serif); font-size: 15px; color: var(--accent-2); }
.pc-name span { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.pc-init { flex: none; text-align: center; cursor: text; }

.pc-init input {
  width: 46px;
  text-align: center;
  font-size: 17px;
  font-weight: 600;
  padding: 2px;
  background: var(--bg-3);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

.pc-init input::placeholder { color: var(--ink-3); font-weight: 400; }
.pc-init span { display: block; font-size: 9px; letter-spacing: .06em; text-transform: uppercase; color: var(--ink-3); }

.pc-hp { display: flex; align-items: center; gap: 8px; }

.pc-hp-bar {
  flex: 1;
  display: flex;
  height: 9px;
  background: var(--bg-4);
  border-radius: 5px;
  overflow: hidden;
}

.pc-hp-bar .fill.ok { background: var(--green); }
.pc-hp-bar .fill.hurt { background: var(--accent); }
.pc-hp-bar .fill.out { background: var(--red); }
.pc-hp-bar .fill.temp { background: var(--blue); }

.pc-hp-n { font-size: 14px; font-weight: 600; white-space: nowrap; }
.pc-hp-n.hurt { color: var(--accent-2); }
.pc-hp-n.out { color: var(--red); }
.pc-hp-n .muted { font-weight: 400; font-size: 12px; }
.pc-hp-n .temp-n { color: var(--blue); font-size: 12px; }

.pc-death { display: flex; gap: 12px; font-size: 11px; }

.pc-stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 5px; }

.pc-stat {
  background: var(--bg-3);
  border-radius: var(--radius);
  padding: 3px 2px;
  text-align: center;
  line-height: 1.15;
}

.pc-stat b { display: block; font-size: 15px; }
.pc-stat b small { font-size: 10px; color: var(--ink-3); font-weight: 400; }
.pc-stat span { font-size: 9px; letter-spacing: .05em; text-transform: uppercase; color: var(--ink-3); }

.pc-saves { display: flex; gap: 4px; font-size: 11px; }
.pc-saves span { flex: 1; text-align: center; color: var(--ink-2); }
.pc-saves span.prof { color: var(--ink); font-weight: 600; }
.pc-saves i { display: block; font-style: normal; font-size: 8px; letter-spacing: .05em; color: var(--ink-3); }
.pc-saves span.prof i { color: var(--accent); }

.pc-cast, .pc-slots, .pc-atk, .pc-def {
  display: flex;
  flex-wrap: wrap;
  gap: 3px 12px;
  font-size: 11px;
  color: var(--ink-2);
}

.pc-cast i, .pc-atk i, .pc-def i { font-style: normal; color: var(--ink-3); }
.pc-atk b { color: var(--ink); font-weight: 600; }

/* To hit and damage, each under its own word: two signed numbers in a row
   with nothing between them is the one thing this line has to avoid. */
.pc-atk em { font-style: normal; white-space: nowrap; }
.pc-atk em i { margin-left: 3px; font-size: 8px; letter-spacing: .05em; text-transform: uppercase; }
.pc-atk .atk-hit { color: var(--ink); font-weight: 600; }
.pc-slots span.spent { color: var(--ink-3); text-decoration: line-through; }
.pc-conds { display: flex; flex-wrap: wrap; align-items: center; gap: 4px; }
.pc-conds .chip.cond-on { background: var(--red); border-color: var(--red); color: #fff; cursor: pointer; }
.pc-conds .chip.cond-on:hover { background: var(--bg-3); border-color: var(--red); color: var(--red); }
.pc-conds .chip.insp { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.pc-conds .info-dot { margin-right: 4px; }

/* the ＋ that opens the full list of conditions, and the off-screen chips */
.chip.add {
  background: none;
  border: 1px dashed var(--line-2);
  border-radius: 12px;
  padding: 2px 12px;
  font-size: 11px;
  color: var(--ink-3);
  cursor: pointer;
}

.chip.add:hover { border-color: var(--accent); color: var(--accent); }

.party-off {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin-top: 8px;
  padding: 7px 8px;
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

/* taking someone off the screen: quiet until the card is under the pointer */
.pc-off {
  flex: none;
  align-self: flex-start;
  background: none;
  border: 0;
  color: var(--ink-3);
  font-size: 12px;
  line-height: 1;
  padding: 3px 4px;
  cursor: pointer;
  opacity: 0;
}

.pc-card:hover .pc-off { opacity: 1; }
.pc-off:hover { color: var(--red); }

/* the eye: what the players can see the name of. Stays lit once it is on, so
   the DM can tell at a glance what has been revealed. */
.pc-eye {
  flex: none;
  align-self: flex-start;
  background: none;
  border: 0;
  padding: 3px 2px;
  font-size: 12px;
  line-height: 1;
  cursor: pointer;
  opacity: 0;
  filter: grayscale(1);
}

.pc-card:hover .pc-eye { opacity: .55; }
.pc-eye.on, .pc-card:hover .pc-eye.on { opacity: 1; filter: none; }
.pc-eye:hover { opacity: 1; }
.pc-card.mon.revealed { box-shadow: inset 3px 0 0 var(--accent); }

/* a creature the DM has named to the party */
.chip.foe { background: var(--bg-4); border-color: var(--line-2); color: var(--ink); }

/* ------------------------------------------------------- folding and filter */

.card > h3 { display: flex; align-items: center; gap: 8px; }

/* a real target: at 16px and the faintest ink it read as a stray dot */
.card-fold {
  flex: none;
  display: inline-grid;
  place-items: center;
  width: 22px;
  height: 22px;
  margin-left: -4px;
  background: none;
  border: 1px solid transparent;
  border-radius: 5px;
  padding: 0;
  color: var(--ink-2);
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
}

.card-fold:hover { color: var(--accent); background: var(--bg-3); border-color: var(--line); }
@media (any-pointer: coarse) { .card-fold { width: 30px; height: 30px; font-size: 16px; } }
.card.folded > .card-body { display: none; }
.card.folded > h3 { margin-bottom: 0; }

.card-filter {
  margin-left: auto;
  width: 140px;
  padding: 2px 7px;
  font-size: 11px;
  font-weight: 400;
  background: var(--bg-3);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  color: var(--ink);
}

.card-filter:focus { outline: none; border-color: var(--accent); width: 190px; }
.filtered-out { display: none !important; }

/* The bar across the strip has become the rail beside it; the card of whoever
   is acting is still ringed, so the strip and the rail agree at a glance. */
.pc-card.acting { box-shadow: 0 0 0 2px var(--accent); }

/* the DM's damage box on a player's card */
.pc-dmg { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }

/* and on a creature's: an amount, then which way it goes (party.js hpEntry) */
.hp-entry { display: inline-flex; align-items: center; gap: 3px; flex: none; }
.hp-entry input {
  width: 52px;
  text-align: center;
  padding: 2px;
  font-size: 12px;
  background: var(--bg-3);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
.hp-entry .btn { padding: 1px 8px; font-weight: 700; line-height: 1.3; }
.hp-entry .hp-dmg { color: var(--red); }
.hp-entry .hp-heal { color: var(--green); }
.mon-hp-now { font-size: 14px; }

.pc-dmg input {
  width: 52px;
  text-align: center;
  padding: 2px;
  font-size: 12px;
  background: var(--bg-3);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

.chip.conc { background: var(--blue); border-color: var(--blue); color: #fff; margin-left: auto; }

/* what the player says they have spent this turn */
.pc-used { display: flex; flex-wrap: wrap; gap: 3px 8px; font-size: 11px; color: var(--ink-2); }
.pc-used span { display: inline-flex; align-items: center; gap: 4px; min-width: 0; }
.pc-used i {
  font-style: normal;
  font-size: 9px;
  font-weight: 700;
  width: 13px;
  height: 13px;
  display: grid;
  place-items: center;
  border-radius: 3px;
  background: var(--accent);
  color: var(--accent-ink);
}
.pc-death .pips { display: inline-flex; gap: 3px; }

/* --------------------------------------------------------------- creatures */

.pc-card.mon { border-left-color: var(--blue); }
.pc-card.mon.hurt { border-left-color: var(--accent); }
.pc-card.mon.out { border-left-color: var(--red); }
.mon-face { display: grid; place-items: center; color: var(--ink-3); font-size: 15px; }

/* The face is a button now: it is where a creature's picture is set. Drawn
   the same as before until it has one, with a hint that it can be pressed.
   On a player's read-only copy it is a plain span, so everything but the
   pressing is written for both. */
.mon-face-wrap { position: relative; flex: none; }
.mon-face { padding: 0; background: var(--bg-3); overflow: hidden; }
button.mon-face { cursor: pointer; }
button.mon-face:hover { border-color: var(--accent); }
.mon-face img { width: 100%; height: 100%; object-fit: cover; display: block; }
.mon-face-x, .mon-face-next, .mon-face-gen {
  position: absolute;
  top: -6px;
  right: -6px;
  width: 16px;
  height: 16px;
  padding: 0;
  font-size: 9px;
  line-height: 16px;
  border-radius: 50%;
  background: var(--bg-2);
  border: 1px solid var(--line-2);
}
.mon-face-gen { top: auto; bottom: -6px; }
.pc-name.as-static { cursor: default; }
.pc-name.as-static:hover { background: none; }

/* The same card with its controls off, which is what a player is given for
   something they called up: the numbers are set in the type the boxes used,
   so the card keeps its shape whoever is reading it. */
/* What a player has on the board, under their own summary in the panel: the
   cards are always there, and the one on the turn pointer is picked out. */
.sum-summons { margin-top: 12px; }
.sum-summons .party { display: block; margin: 0 0 8px; }
.sum-summons .party.acting .pc-card { border-color: var(--accent); }

.pc-init.as-static { cursor: default; }
.pc-init.as-static b { display: block; padding: 2px; font-size: 17px; font-weight: 600; }
.mon-hp.as-static { font-size: 13px; font-weight: 600; }
.mon-hp.as-static .muted { font-weight: 400; }
/* a creature typed in by hand has only an AC to show, so let the boxes share
   whatever room there is rather than leaving gaps */
.mon-stats { grid-template-columns: repeat(auto-fit, minmax(70px, 1fr)); }
.pc-stat b small { display: block; font-size: 9px; }

.mon-hp { display: flex; align-items: center; gap: 3px; }
.mon-hp input {
  width: 44px;
  text-align: center;
  padding: 2px;
  font-size: 13px;
  font-weight: 600;
  background: var(--bg-3);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

.mon-hp input.max { font-weight: 400; color: var(--ink-3); }

.mon-list { max-height: 44vh; overflow-y: auto; margin: 12px 0; }

.mon-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 5px 2px;
  border-bottom: 1px solid var(--line);
}

.mon-row:last-child { border-bottom: 0; }
.mon-row .grow { display: flex; flex-direction: column; line-height: 1.3; }
.mon-custom-h { margin: 16px 0 2px; }
.field.tiny { width: 68px; }
.field.tiny span { font-size: 10px; }

/* the DM's own creatures, marked so they are not mistaken for the library */
.mon-row.mine { border-left: 2px solid var(--accent); padding-left: 7px; }

/* ------------------------------------------------------- creature editor */

/* what the creature is built on, above the form rather than in it: it is not
   a field, it is where every other field just came from */
.mon-base-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: 12px;
  padding: 7px 8px;
  background: var(--bg-3);
  border: 1px solid var(--line);
  border-left: 2px solid var(--accent);
  border-radius: var(--radius);
}

.mon-base-bar .grow { display: flex; flex-direction: column; line-height: 1.35; min-width: 140px; }

.mon-editor { display: flex; flex-direction: column; gap: 12px; }
/* wraps because the top row carries four fields, and a phone-width dialog
   would otherwise squeeze the alignment box to nothing */
.mon-editor .row { display: flex; gap: 8px; align-items: flex-end; flex-wrap: wrap; }
.mon-editor .row.end { align-items: center; }
.mon-editor .field { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.mon-editor .field > span { font-size: 11px; color: var(--ink-3); }

.mon-editor input[type="text"],
.mon-editor input[type="number"],
.mon-editor select,
.mon-editor textarea {
  background: var(--bg-3);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 6px 8px;
  width: 100%;
}

.mon-editor textarea { resize: vertical; font-family: var(--sans); }
.mon-editor .field:not(.grow) { flex: none; }
.mon-editor .field.grow { flex: 1 1 120px; }
.mon-editor .field:has(input[name="size"]), .mon-editor .field:has(select) { width: 120px; }
.mon-editor .field:has(input[name="challenge"]) { width: 70px; }
.mon-editor .field:has(input[name="ac"]),
.mon-editor .field:has(input[name="hp"]) { width: 96px; }

/* the three sections most creatures do not have, folded away until they do */
.mon-more { border-top: 1px solid var(--line); padding-top: 8px; }
.mon-more > summary { cursor: pointer; font-size: 11px; color: var(--ink-3); }
.mon-more > summary:hover { color: var(--accent); }
.mon-more[open] > summary { margin-bottom: 12px; }
.mon-more .field + .field { margin-top: 12px; }

/* six ability boxes, even and small */
.mon-editor .ability-row { gap: 6px; }
.mon-editor .ability-row .field { flex: 1; width: auto; }
.mon-editor .ability-row input { text-align: center; padding: 6px 2px; }

.char-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  align-items: center;
  gap: 8px;
  padding: 6px 0;
  border-bottom: 1px solid var(--line);
}

.char-row:last-of-type { border-bottom: 0; }

/* a player's row carries the DM's reroll control as well */
.char-row.with-rerolls { grid-template-columns: minmax(0, 1fr) auto auto auto; }
.reroll-ctl { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; white-space: nowrap; }
.reroll-ctl .btn.sm { padding: 1px 7px; }

.char-open {
  text-align: left;
  background: none;
  border: 0;
  padding: 4px 6px;
  border-radius: var(--radius);
  cursor: pointer;
  color: inherit;
  min-width: 0;
}

.char-open:hover { background: var(--bg-3); }
.ch-name { display: block; font-weight: 600; }
.ch-sub { display: block; font-size: 12px; }
.ch-when { font-size: 11px; white-space: nowrap; }

.tag-x {
  background: none;
  border: 0;
  color: inherit;
  opacity: .5;
  cursor: pointer;
  padding: 0 0 0 3px;
}
.tag-x:hover { opacity: 1; color: var(--red); }

/* ------------------------------------------------------------------ admin */

.invite-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 8px 0;
  border-bottom: 1px solid var(--line);
}

.invite-row:last-child { border-bottom: 0; }
.invite-row.spent { opacity: .55; }

.invite-link {
  display: block;
  font-size: 11px;
  color: var(--accent-2);
  word-break: break-all;
  margin-top: 2px;
}

/* ------------------------------------------------------------ save state  */

.save-state {
  font-size: 11px;
  padding: 2px 8px;
  border-radius: 10px;
  border: 1px solid transparent;
  white-space: nowrap;
}

.save-state.pending { color: var(--ink-3); }
.save-state.saving { color: var(--ink-3); }
.save-state.saved { color: var(--green); }
.save-state.error, .save-state.signed-out { color: var(--red); border-color: var(--red); }

@media print { .save-state { display: none; } }

/* ------------------------------------------------------------ info popover */

.info-dot {
  flex: none;
  width: 15px;
  height: 15px;
  margin-left: auto;
  border-radius: 50%;
  border: 1px solid var(--line-2);
  background: transparent;
  color: var(--ink-3);
  font: 600 10px/1 var(--serif);
  cursor: help;
  padding: 0;
}

.info-dot:hover { border-color: var(--accent); color: var(--accent); }
.opt.on .info-dot { border-color: rgba(26, 21, 8, .4); color: var(--accent-ink); }
.tag .info-dot { width: 13px; height: 13px; margin-left: 4px; }

.info-pop {
  position: fixed;
  z-index: 60;
  width: min(460px, calc(100vw - 24px));
  max-height: 72vh;
  overflow-y: auto;
  background: var(--bg-2);
  border: 1px solid var(--line-2);
  border-radius: 8px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, .55);
  padding: 16px 16px;
  overscroll-behavior: contain;
}

.info-head h4 { margin: 0; font-family: var(--serif); font-size: 18px; color: var(--accent-2); }
.info-head .muted { font-size: 11px; }

.info-facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 3px 12px;
  margin: 8px 0;
  padding: 8px 0;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}

.info-facts dt { font-size: 9px; text-transform: uppercase; letter-spacing: .06em; color: var(--ink-3); }
.info-facts dd { margin: 0; font-size: 13px; }

/* a whole combatant card in a popover, opened from a token on the board */
.info-pop.card-pop { width: 340px; max-width: 92vw; padding: 0; background: none; border: 0; box-shadow: none; }
.info-pop.card-pop .party { display: block; }
.info-pop.card-pop .pc-card { margin: 0; box-shadow: 0 8px 28px rgba(0, 0, 0, .55); }

/* what the thing the wrapper names actually does */
.info-twin { margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--line); }

.info-body { font-size: 13.5px; line-height: 1.55; }
.info-body p { margin: 0 0 7px; }

@media print { .info-dot, .info-pop { display: none !important; } }

/* the play sheet already shows everything the rail summarises */
.main.no-rail { grid-template-columns: 190px minmax(0, 1fr); }
@media (max-width: 1100px) { .main.no-rail { grid-template-columns: 160px minmax(0, 1fr); } }
/* Below 1000px the steps are a strip along the top instead - see tablet.css,
   which is loaded after this one and so is the last word on it. */

/* ---------------------------------------------------------------- wayfinding
   The banner is always home; the crumbs say where you are and how to step
   back up. Present on every screen so navigation never dead-ends. */

.gate-brand { text-align: center; margin-bottom: 16px; }

.crumbs {
  display: flex;
  align-items: baseline;
  gap: 6px;
  min-width: 0;
  font-size: 13px;
  color: var(--ink-3);
}

.crumbs a {
  color: var(--ink-2);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  white-space: nowrap;
}

.crumbs a:hover { color: var(--accent); border-bottom-color: var(--accent); }
.crumbs i { font-style: normal; opacity: .5; }

.crumbs [aria-current] {
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

@media (max-width: 820px) { .crumbs { display: none; } }

.flush-top { margin-top: 0; }

/* the banner is a button on most screens; keep it looking like the heading */
.brand.as-link:hover { color: var(--accent-2); }
.brand.as-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ----------------------------------------------------------- DM approvals */

.requests { margin-top: 12px; }

.request-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 6px 0;
  border-bottom: 1px solid var(--line);
}

.request-row:last-child { border-bottom: 0; }
.request-row span { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.request-row.approved { color: var(--green); }
.request-row.declined { color: var(--ink-3); text-decoration: line-through; }

.browse-row { display: flex; align-items: flex-start; gap: 8px; }
.browse-row > details { flex: 1; min-width: 0; }

/* how many of a stack are actually in hand */
.inv-held {
  font-size: 10px;
  font-weight: 700;
  color: var(--accent-ink);
  background: var(--accent);
  border-radius: 3px;
  padding: 0 4px;
  margin-left: 3px;
}

/* ---------------------------------------------------------------- summons

   A summon is an ordinary creature card with a note saying whose it is, so it
   only needs telling apart from the ones the DM put there: a green edge, the
   party's colour, rather than the red of something opposite them. */

.pc-card.mon.summoned { border-left-color: var(--green); }
.pc-card.mon.summoned.hurt { border-left-color: var(--accent); }
.pc-card.mon.summoned.out { border-left-color: var(--red); }

.summon-by {
  display: block;
  color: var(--green);
  letter-spacing: .02em;
}

.summon-btn { flex: none; }

/* A druid's card in Wild Shape: what they are now, above the numbers that are
   now the form's. */
.pc-shape {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 3px 0 3px 7px;
  border-left: 2px solid var(--green);
  font-size: 12px;
}
.pc-shape .grow { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 5px; min-width: 0; }
.pc-shape b { font-family: var(--serif); font-size: 14px; color: var(--accent-2); }
.ws-paw { font-size: 12px; }

/* ------------------------------------------------- conditions on a clock

   A chip says how it ends without a legend: a number is rounds left, a ⟲ is a
   save that could end it early, both together is both. A save actually owed
   right now pulses, because it is the one thing on the screen the table is
   waiting on. */

.chip.cond-on.due {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink, #201802);
  animation: cond-due 1.4s ease-in-out infinite;
}

@keyframes cond-due {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 60%, transparent); }
  50% { box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 0%, transparent); }
}

@media (prefers-reduced-motion: reduce) {
  .chip.cond-on.due { animation: none; }
}

/* the ask itself, on the card beside the chips */
.cond-ask {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  flex-basis: 100%;
  margin-top: 4px;
  padding: 4px 7px;
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}

.cond-ask b { color: var(--accent-2); }
.cond-ask .tiny { color: var(--ink-2); margin-right: auto; }

/* how each condition ends, in the dialog */
.cond-ends { display: flex; flex-direction: column; gap: 2px; }

.cond-end-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 0;
  border-bottom: 1px solid var(--line);
}

.cond-end-row:last-child { border-bottom: 0; }

/* waiting on the server (app.js dispatch): pressed, and says so */
.btn.busy { opacity: .6; cursor: progress; pointer-events: none; }
.btn.busy::after {
  content: '';
  width: 10px;
  height: 10px;
  margin-left: 6px;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  display: inline-block;
  vertical-align: -1px;
  animation: btn-spin .7s linear infinite;
}
@keyframes btn-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .btn.busy::after { animation: none; } }

/* ------------------------------------------------ campaign page: two tabs

   Both panes are drawn every time; the tab on <main> decides which shows, so
   switching is instant and a live redraw keeps the one you were on. */

.camp-head { margin-bottom: 4px; }
.camp-tabs {
  display: flex;
  gap: 2px;
  margin: 12px 0 12px;
  border-bottom: 1px solid var(--line);
}
.camp-tab {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: none;
  border: 0;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  padding: 8px 16px;
  color: var(--ink-2);
  font: inherit;
  font-size: 14px;
  cursor: pointer;
}
.camp-tab:hover { color: var(--ink); }
.camp-tab[aria-selected="true"] { color: var(--ink); border-bottom-color: var(--accent); font-weight: 600; }
.panel.campaign[data-tab="table"] .camp-pane[data-pane="campaign"],
.panel.campaign[data-tab="campaign"] .camp-pane[data-pane="table"] { display: none; }
.camp-pane .campaign-cols { margin-top: 0; }

.needs-you {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 12px;
  padding: 8px 12px;
  border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--line));
  background: color-mix(in srgb, var(--accent) 10%, var(--bg-2));
  border-radius: var(--radius);
}

/* ------------------------------------------- a player's own character card */

.hero-card {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  border-left: 3px solid var(--accent);
  margin-top: 12px;
}
.hero-main { display: flex; flex-direction: column; gap: 4px; min-width: 0; flex: 1 1 260px; }
.hero-name { font-family: var(--serif); font-size: 21px; font-weight: 700; color: var(--accent-2); }
.hero-stats { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; font-variant-numeric: tabular-nums; }
.hero-stats b { font-size: 17px; }
.hero-bar { height: 6px; max-width: 360px; background: var(--bg-4); border-radius: 3px; overflow: hidden; }
.hero-bar .fill { display: block; height: 100%; background: var(--green); }
.hero-bar .fill.hurt { background: var(--accent); }
.hero-bar .fill.out { background: var(--red); }
.hero-actions { gap: 6px; }
.about-campaign > summary { cursor: pointer; font-weight: 600; }
.about-campaign[open] > summary { margin-bottom: 12px; }
.about-campaign .card { margin-top: 12px; }
.camp-notes { white-space: pre-line; margin: 0 0 8px; }

/* ------------------------------------------------ invite links on the page */

.invite-block { margin-top: 12px; }
.invite-list { margin-top: 12px; display: flex; flex-direction: column; gap: 6px; }
.add-existing { margin-top: 12px; }
.add-existing > summary { cursor: pointer; }

/* ------------------------------------------------------- home: your sheets */

.campaign-card { display: flex; flex-direction: column; padding: 0; cursor: default; }
.campaign-card:hover { background: var(--bg-2); }
.cc-open, .cc-sheet {
  background: none;
  border: 0;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.cc-open { padding: 12px 16px; }
.cc-open:hover { background: var(--bg-3); }
.cc-sheet {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  padding: 8px 16px;
  border-top: 1px solid var(--line);
  font-size: 13px;
}
.cc-sheet:hover { background: var(--bg-3); }
.cc-go { color: var(--accent-2); white-space: nowrap; font-weight: 600; }
.check-line { display: flex; align-items: center; gap: 7px; margin-top: 8px; font-size: 12.5px; }

/* ---------------------------------------------------- the DM's roster rows

   One row per combatant (party.js rosterRow); the row opens into its card. */

.roster { display: flex; flex-direction: column; margin-top: 6px; }
.ros-row {
  display: grid;
  grid-template-columns: minmax(170px, 1.4fr) minmax(110px, 1fr) 44px 44px 56px minmax(0, 1.2fr) auto;
  gap: 12px;
  align-items: center;
  padding: 6px 6px;
  border-bottom: 1px solid var(--line);
  border-left: 3px solid transparent;
}
.ros-row.pc { border-left-color: color-mix(in srgb, var(--green) 55%, transparent); }
.ros-row.npc { border-left-color: color-mix(in srgb, var(--red) 55%, transparent); }
.ros-row.summon { border-left-color: color-mix(in srgb, var(--blue) 55%, transparent); }
.ros-row.acting { background: color-mix(in srgb, var(--accent) 12%, transparent); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 50%, transparent); }
.ros-row.open { background: var(--bg-3); }
.ros-face {
  flex: none; width: 30px; height: 30px; border-radius: 50%; overflow: hidden;
  display: grid; place-items: center; background: var(--bg-4); border: 1px solid var(--line-2);
  font: 700 13px/1 var(--serif); color: var(--ink-2);
}
.ros-face img { width: 100%; height: 100%; object-fit: cover; }
.ros-name { display: flex; flex-direction: column; min-width: 0; line-height: 1.25; }
.ros-name b, .ros-name .tiny { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ros-who { display: flex; align-items: center; gap: 2px; min-width: 0; }
.ros-open {
  display: flex; align-items: center; gap: 8px; min-width: 0; flex: 1;
  background: none; border: 0; padding: 2px 4px; border-radius: var(--radius);
  color: inherit; font: inherit; text-align: left; cursor: pointer;
}
.ros-open:hover { background: var(--bg-4); }
.ros-open:hover b { color: var(--accent-2); }
.ros-caret {
  flex: none; width: 24px; height: 30px; display: grid; place-items: center;
  background: none; border: 0; border-radius: var(--radius); padding: 0;
  color: var(--ink-3); font-size: 12px; cursor: pointer;
}
.ros-caret:hover { background: var(--bg-4); color: var(--ink); }
.ros-hp { display: flex; flex-direction: column; gap: 3px; font-variant-numeric: tabular-nums; }
.ros-hpn b { font-size: 14px; }
.ros-num { display: flex; flex-direction: column; align-items: center; line-height: 1.1; font-variant-numeric: tabular-nums; }
.ros-num b { font-size: 14px; }
.ros-num span { font-size: 9px; letter-spacing: .08em; text-transform: uppercase; color: var(--ink-3); }
.ros-init input { width: 46px; text-align: center; padding: 2px; font-size: 13px; }
.ros-conds { display: flex; flex-wrap: wrap; gap: 4px; min-width: 0; }
.ros-card { margin: 6px 0 12px 20px; }
.view-set { display: inline-flex; gap: 2px; margin-right: 4px; }
.view-set .btn.on { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); font-weight: 600; }


@media (max-width: 900px) {
  .ros-row { grid-template-columns: minmax(0, 1fr) 76px 38px 48px auto; gap: 8px; }
  .ros-row .ros-num[title="Passive perception"], .ros-conds { display: none; }
}
@media (max-width: 600px) {
  .ros-row { grid-template-columns: minmax(0, 1fr) 70px 38px; }
  .ros-init { display: none; }
  .ros-dmg { grid-column: 1 / -1; }
}
/* with the order already on each row, the rail over the roster keeps only its
   round and buttons when it stacks above it */
@media (max-width: 1100px) {
  .party-card:has(.roster) .party-layout > .turn-rail .trk-list { display: none; }
  .party-card:has(.roster) .party-layout > .turn-rail { max-width: none; }
}

/* several of one kind as one row, opening into each (party.js grouped) */
.ros-group .ros-name b { color: var(--ink); }
.ros-members { margin-left: 24px; border-left: 2px solid var(--line); }

/* who has the campaign open (server.js onlineIn) */
.presence {
  display: inline-block; width: 8px; height: 8px; border-radius: 50%;
  background: var(--bg-4); border: 1px solid var(--line-2); margin-right: 5px; vertical-align: 1px;
}
.presence.on { background: var(--green); border-color: var(--green); box-shadow: 0 0 0 2px color-mix(in srgb, var(--green) 30%, transparent); }
.ros-face { position: relative; overflow: visible; }
.ros-face img { border-radius: 50%; }
.ros-face .presence { position: absolute; right: -2px; bottom: -2px; margin: 0; border: 2px solid var(--bg-2); width: 11px; height: 11px; }

/* ------------------------------------------------------------ main tabs
   The way round a campaign: Table, Battle map, your sheet. Small capitals in a
   row of pills, the current one lit - always one click from anywhere. */
.main-nav {
  display: flex;
  align-items: center;
  gap: 4px;
  min-width: 0;
  overflow-x: auto;
  scrollbar-width: none;
}
.main-nav::-webkit-scrollbar { display: none; }

.nav-tab {
  display: inline-flex;
  align-items: center;
  height: 30px;
  padding: 0 12px;
  border: 1px solid transparent;
  border-radius: 4px;
  color: var(--ink-2);
  font-family: var(--sans);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .06em;
  text-transform: uppercase;
  text-decoration: none;
  white-space: nowrap;
  transition: color .18s ease, background .18s ease, border-color .18s ease;
}
.nav-tab:hover { color: var(--ink); background: var(--bg-3); }
.nav-tab.active {
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  border-color: color-mix(in srgb, var(--accent) 45%, transparent);
}
.nav-tab:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
