/* ---------- board + log side-by-side on wide screens ---------- */
.gamecols { display: flex; flex-direction: column; gap: 12px; }
.colmain { min-width: 0; flex: 1; }
.colside { min-width: 0; }
@media (min-width: 1300px) {
  /* only the in-game board+log view earns the extra width; the lobby, log
     screens and admin stay at the readable 980px cap from above so their
     forms and cards don't stretch across the whole window. a generous cap
     only kicks in on ultrawide so the log doesn't reach absurd line lengths */
  main:has(.gamecols) { max-width: 2200px; }
  .gamecols { flex-direction: row; align-items: flex-start; }
  /* split the row proportionally so the board always stays a touch wider
     than the log (≈55/45) instead of the log overtaking it once the window
     is wide enough to hit the max cap */
  .colmain { flex: 1.25 1 0; min-width: 0; }
  .colside { flex: 1 1 0; min-width: 340px; position: sticky;
             top: calc(12px + env(safe-area-inset-top)); }
  .colside #digest { max-height: calc(100vh - 90px); }
}

/* ---------- phase ruler: turn chip + sliding step marker ---------- */
/* the 12 steps of engine/turn.py's STEP_TABLE laid out as a ruler; a pill
   slides to the current step. Phase hues echo the colour-coded log:
   beginning=accent (turn banners), mains=good (your window to act),
   combat=sorcery (damage), ending=plan. */
/* The ruler PINS while everything else scrolls: reading another player's
   board is exactly when "whose turn, which step" matters most, and it's the
   only piece of chrome that answers that. --rulerh (set from renderPhaseBar)
   is its measured height, so the ticker and announcer below can stack under
   it instead of docking at the same 4px and overlapping. */
#phasebar {
  display: flex; gap: 10px; align-items: stretch;
  background: var(--panel); border: 1px solid var(--border);
  border-radius: 10px; padding: 7px 10px; margin-bottom: 8px;
  position: sticky; top: env(safe-area-inset-top); z-index: 6;
  box-shadow: 0 4px 14px rgba(8,10,14,.45);
}
#phasebar[hidden] { display: none; }
.phturn { flex: 0 0 auto; display: flex; flex-direction: column;
  justify-content: center; border-right: 1px solid var(--border2);
  padding-right: 10px; min-width: 62px; }
.phtnum { font-size: 15px; font-weight: 800; line-height: 1.15;
  color: var(--warn); font-variant-numeric: tabular-nums; white-space: nowrap; }
.phturn.pulse .phtnum { animation: phpulse .7s ease-out; }
@keyframes phpulse { 0% { transform: scale(1.25); filter: brightness(1.6); } }
.phtwho { font-size: 11px; font-weight: 650; max-width: 110px; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.phruler { position: relative; flex: 1; min-width: 0; display: flex; gap: 7px; }
.phgroup { display: flex; flex-direction: column; min-width: 0; }
.phgroup + .phgroup { border-left: 1px solid var(--border2); padding-left: 7px; }
.phsteps { display: flex; flex: 1; align-items: stretch; }
.phstep { position: relative; z-index: 1; flex: 1; min-width: 0; display: flex;
  align-items: center; justify-content: center; text-align: center;
  font-size: 10.5px; line-height: 1.15; color: var(--faint);
  padding: 2px 3px; white-space: nowrap; }
.phstep.done { color: var(--dim); }
.phstep.cur { color: #14161c; font-weight: 750; }
.phglabel { text-align: center; font-size: 9px; text-transform: uppercase;
  letter-spacing: .07em; color: var(--faint); margin-top: 2px;
  white-space: nowrap; }
.phgroup.cur .phglabel { font-weight: 700; }
.phgroup.cur.ph-beginning .phglabel { color: var(--accent); }
.phgroup.cur.ph-precombat_main .phglabel,
.phgroup.cur.ph-postcombat_main .phglabel { color: var(--good); }
.phgroup.cur.ph-combat .phglabel { color: var(--sorcery); }
.phgroup.cur.ph-ending .phglabel { color: var(--plan); }
#phmarker { position: absolute; z-index: 0; border-radius: 6px;
  background: var(--warn); box-shadow: 0 0 9px rgba(224,175,104,.3);
  transition: left .35s cubic-bezier(.3,.7,.3,1), top .35s,
              width .35s cubic-bezier(.3,.7,.3,1), height .35s,
              background .35s, box-shadow .35s; }
#phmarker.ph-beginning { background: var(--accent);
  box-shadow: 0 0 9px rgba(122,162,247,.35); }
#phmarker.ph-precombat_main, #phmarker.ph-postcombat_main {
  background: var(--good); box-shadow: 0 0 9px rgba(158,206,106,.35); }
#phmarker.ph-combat { background: var(--sorcery);
  box-shadow: 0 0 9px rgba(204,123,111,.4); }
#phmarker.ph-ending { background: var(--plan);
  box-shadow: 0 0 9px rgba(187,154,247,.35); }
.phstep .la { display: none; }
@media (max-width: 720px) {
  #phasebar { gap: 7px; padding: 6px 8px; }
  .phturn { min-width: 0; padding-right: 7px; }
  .phtwho { max-width: 64px; }
  .phstep .lf { display: none; }
  .phstep .la { display: inline; }
  .phstep { font-size: 9.5px; padding: 2px 1px; }
  .phruler { gap: 4px; }
  .phgroup + .phgroup { padding-left: 4px; }
  .phglabel { letter-spacing: .02em; max-width: 100%;
              overflow: hidden; text-overflow: ellipsis; }
}
/* seat identity: each row's left edge + commander-name pips carry the
   commander's colour identity (mono = pip hue, multi = gold, none = grey) */
.cbident { margin-left: 5px; vertical-align: 1px; white-space: nowrap; }
.cbident .pip { width: 10px; height: 10px; line-height: 10px; font-size: 0;
  margin-right: 2px; box-shadow: 0 1px 2px rgba(0,0,0,.6); }

/* ---------- players / board ---------- */
.seatrowg {
  position: relative; display: flex; gap: 10px; align-items: stretch;
  background: var(--panel); border: 1px solid var(--border); border-radius: 10px;
  padding: 8px 10px; margin-bottom: 8px;
}
/* commander portrait panel: the seat's identity — art with name/life/counts
   overlaid; tap inspects the commander. Speech bubbles anchor over it. */
.cmdrbox {
  position: relative; flex: 0 0 112px; min-height: 118px; max-height: 200px;
  border-radius: 8px;   /* stretch caps out so a tall hand row can't zoom the art */
  border: 1px solid var(--border2); overflow: hidden; cursor: pointer;
  background: linear-gradient(160deg, #2a3042, #1a1e29) center/cover no-repeat;
}
.cmdrbox::after {           /* legibility scrim over the art */
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(8,10,14,.62) 0, rgba(8,10,14,.1) 34%,
              rgba(8,10,14,.08) 55%, rgba(8,10,14,.78) 100%);
}
.cmdrbox > * { position: relative; z-index: 1; }
.cmdrbox .cbname { font-size: 11.5px; font-weight: 700; line-height: 1.2;
  padding: 5px 36px 0 7px; text-shadow: 0 1px 3px #000; overflow-wrap: anywhere; }
.cmdrbox .cbtag { position: absolute; top: 4px; right: 4px; font-size: 9px;
  font-weight: 700; text-transform: uppercase; letter-spacing: .06em;
  padding: 1px 6px; border-radius: 8px; background: rgba(14,16,22,.8);
  color: var(--accent); }
.cmdrbox .cbtag.turn { background: var(--warn); color: #14161c; }
/* library-peek badge: shown only to a seat that can currently look at the
   top of their own library any time (Mystic Forge, Radar Sense, ...) — tap
   to inspect that card, same modal a permanent tap opens. Opposite corner
   from .cbtag so the two never collide. */
.cmdrbox .cbpeek { position: absolute; top: 4px; left: 4px; width: 18px; height: 18px;
  display: flex; align-items: center; justify-content: center; font-size: 12px;
  border-radius: 5px; background: rgba(14,16,22,.8); color: var(--accent);
  border: none; padding: 0; cursor: pointer; line-height: 1; }
.cmdrbox .cbpeek:hover { background: var(--accent); color: #14161c; }
.cmdrbox .cbbottom { position: absolute; left: 7px; right: 7px; bottom: 5px; }
.cmdrbox .cblife { display: block; font-size: 22px; font-weight: 800; line-height: 1;
  font-variant-numeric: tabular-nums;
  text-shadow: 0 1px 4px #000, 0 0 12px rgba(0,0,0,.7); }
.cmdrbox .cblife.low { color: var(--bad); }
.cmdrbox .cbcounts { display: block; font-size: 9.5px; color: #cfd3dd;
  text-shadow: 0 1px 2px #000; margin-top: 2px; }
.seatrowg.dead .cmdrbox { filter: grayscale(1); }
.zones { flex: 1; min-width: 0; }
/* table-talk speech bubble: floats up from the portrait's shoulder,
   stays until tapped away */
.bubble {
  position: absolute; top: -12px; left: 84px; z-index: 4;
  max-width: min(300px, 75%); cursor: pointer;
  background: #2c2440; border: 1px solid var(--plan); color: var(--text);
  border-radius: 12px; border-bottom-left-radius: 2px; padding: 6px 24px 6px 11px;
  font-size: 12.5px; font-style: italic; line-height: 1.35;
  box-shadow: 0 4px 14px rgba(0,0,0,.5);
  animation: bubblein .25s ease-out;
  transition: opacity .3s;
}
.bubble .bx { position: absolute; top: 4px; right: 7px; font-size: 10px;
              font-style: normal; color: var(--faint); }
.bubble:hover .bx { color: var(--text); }
.bubble.fading { opacity: 0; }
@keyframes bubblein { from { opacity: 0; transform: translateY(6px); } }
.seatrowg.you { border-color: #3d5a99; }
.seatrowg.active-turn { box-shadow: inset 3px 0 0 var(--warn); }
.seatrowg.dead { opacity: .5; }
.counts { color: var(--faint); font-size: 12px; }
.cmddmg { color: var(--warn); font-size: 11.5px; margin-bottom: 4px; }
.perms { display: flex; flex-wrap: wrap; gap: 5px; }
.perms + .perms { margin-top: 6px; }   /* lane break: creatures / other / lands */
.tile {
  position: relative; background: var(--panel2); border: 1px solid var(--border2);
  border-left: 3px solid var(--faint); border-radius: 6px; padding: 3px 7px 4px;
  font-size: 12px; line-height: 1.25; cursor: pointer; max-width: 170px;
  transition: transform .15s;
}
.tile .nm { font-weight: 600; overflow: hidden; text-overflow: ellipsis;
            white-space: nowrap; display: block; }
.tile .meta { color: var(--dim); font-size: 10.5px; display: flex; gap: 5px;
              flex-wrap: wrap; }
.tile .pt { color: var(--text); font-weight: 700; }
.tile .tax { color: var(--warn, #d9a441); font-weight: 600; white-space: nowrap; }
.tile.tapped { transform: rotate(6deg); opacity: .62; }
.tile.attacking { border-color: var(--bad); background: #2e2230; }
/* auto-tap payment preview: hovering a castable row lights the sources the
   engine will tap to pay for it */
.tile.willtap { outline: 2px solid var(--accent); box-shadow: 0 0 10px var(--accent);
                opacity: 1; }
.payhint { display: inline-flex; align-items: center; gap: 3px; margin-left: 2px;
           font-size: 10px; color: var(--dim); white-space: nowrap; flex: none;
           background: none; border: 1px solid var(--border2); border-radius: 6px;
           padding: 3px 7px; cursor: pointer; font-family: inherit;
           min-height: 26px; }
.payhint:hover { border-color: var(--accent); color: var(--text); }
/* ISS-manamanual-web-1: the row control that opens the payment box used to be a
   bare pip strip — nothing on it said "mana", so the manual-control path had to
   be DISCOVERED. It now leads with the word, at a real tap size; the pips (and
   the tap-to-open behaviour) are unchanged behind it. */
.payhint .phword { font-weight: 700; font-size: 10.5px; letter-spacing: .02em;
                   color: var(--text); }
.payhint:hover .phword { color: var(--accent); }
/* the payment box (openPayPanel): the manual mana editor (ISS-manamanual-web-1) */
.paybody { padding: 10px 12px; }
.payrow { display: flex; justify-content: space-between; align-items: center;
          gap: 10px; padding: 3px 0; font-size: 13px; flex-wrap: wrap; }
/* flex:1 1 0 + min-width:0 is what actually lets the NAME be the thing that
   ellipsises on a narrow phone. Without it the row refuses to shrink below its
   content and the source list grows a horizontal scrollbar (seen at 220px). */
.paysrc { overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
          flex: 1 1 0; min-width: 0; }
.paytap { flex: none; }
/* the seat's live pool, big and FIRST: deliberate sequencing is "tap something,
   read what floated, decide what to tap next", so that read has to be instant */
.paypool { display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
           background: rgba(10,12,16,.55); border: 1px solid rgba(255,255,255,.16);
           border-radius: 8px; padding: 7px 10px; }
.paypool .plbl { font-size: 10px; text-transform: uppercase; letter-spacing: .08em;
                 color: rgba(255,255,255,.62); }
.paypool .pip { width: 19px; height: 19px; line-height: 19px; font-size: 12px;
                margin-right: 2px; }
.paypool .payempty { font-size: 12.5px; font-style: italic;
                     color: rgba(255,255,255,.6); }
/* EVERY source, scrollable — a 30-land board must not push "Cast it" off screen */
.paylist { max-height: 34vh; overflow-y: auto; overflow-x: hidden; }
.payrow .payadds { flex: 0 0 auto; margin-left: auto; }
.payrow .paymeta { color: rgba(255,255,255,.6); font-size: 10.5px; }
.payrow .paycost { color: var(--warn); font-size: 10.5px; white-space: nowrap; }
/* a source auto-tap already picked stays in the full list (it IS one of your
   sources) but says so, so the two sections can't read as contradicting */
.payrow.planned .paysrc { color: var(--accent); }
.payrow .payautochip { color: var(--accent); font-size: 9.5px; font-weight: 700;
                       text-transform: uppercase; letter-spacing: .06em; }
/* a source you cannot tap right now is DISABLED WITH ITS REASON, never hidden:
   "where did my Signet go?" is the question a filtered list creates */
.payrow.blocked { opacity: .62; }
.payrow.blocked .paywhy { color: var(--warn); font-size: 10.5px;
                          white-space: nowrap; overflow: hidden;
                          text-overflow: ellipsis; }
.paytap[disabled] { cursor: not-allowed; opacity: .55; }
/* ISS-triage-10071237-1-1: the card view's quick tap buttons (reveal.js) reuse
   this same "disabled with its reason" convention, so the reason has to read
   outside a .payrow too, and a blocked .quick button must LOOK dead — without
   these it rendered at full accent brightness and silently ignored the tap,
   with the reason only in a `title` that a phone never shows. */
.quickrow > .paywhy { color: var(--warn); font-size: 10.5px; }
button.quick[disabled] { cursor: not-allowed; opacity: .55; }
/* the autopilot switch, IN the box: the player is already looking at the mana
   they want to control, so turning auto-tap off must not cost a trip to settings */
.payauto { display: flex; align-items: center; justify-content: space-between;
           gap: 8px; margin-top: 10px; background: rgba(10,12,16,.55);
           border: 1px solid rgba(255,255,255,.16); border-radius: 8px;
           padding: 6px 10px; font-size: 12.5px; }
.payauto .payautoname { font-weight: 600; }
.payauto button.on { border-color: var(--good); color: var(--good); }
.payauto button.off { border-color: var(--warn); color: var(--warn); }
.tile.t-land { border-left-color: var(--land); }
.tile.t-creature { border-left-color: var(--creature); }
.tile.t-artifact { border-left-color: var(--artifact); }
.tile.t-enchantment { border-left-color: var(--enchantment); }
.tile.t-planeswalker, .tile.t-battle { border-left-color: var(--planeswalker); }
.tile.t-instant { border-left-color: var(--instant); }
.tile.t-sorcery { border-left-color: var(--sorcery); }
.tile .badge { position: absolute; top: -6px; right: -5px; background: var(--warn);
               color: #14161c; border-radius: 8px; font-size: 9.5px; font-weight: 700;
               padding: 0 5px; }
.tile .cmdr { background: var(--plan); }
/* equipment/auras stack under the permanent they're attached to */
.permgroup { display: flex; flex-direction: column; max-width: 170px; }
.permgroup .tile { max-width: none; }
.permgroup .tile.att { margin: -1px 0 0 12px; padding: 2px 7px 3px;
                       font-size: 11px; opacity: .92; }
.tile.att .nm { font-weight: 500; }
.tile.att .nm::before { content: "↳ "; color: var(--faint); }
.hand { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
.hand .tile { background: var(--panel3); padding: 5px 8px; max-width: 200px; }
.zlabel { font-size: 10.5px; text-transform: uppercase; letter-spacing: .08em;
          color: var(--faint); margin: 7px 0 3px; }
.specbanner { background: var(--panel3); border: 1px solid var(--plan);
              border-radius: 8px; padding: 8px 12px; margin-bottom: 10px;
              font-size: 13px; color: var(--fg); }
.notes { margin: 7px 0 3px; padding: 6px 8px; border-radius: 6px;
         background: var(--panel3); border-left: 3px solid var(--plan);
         font-size: 12.5px; font-style: italic; color: var(--fg); }
.notes .zlabel { margin: 0 0 2px; color: var(--plan); font-style: normal; }
.mana { white-space: nowrap; }
.pip { display: inline-block; width: 14px; height: 14px; border-radius: 50%;
       font-size: 9.5px; font-weight: 700; text-align: center; line-height: 14px;
       color: #1b1e27; margin-right: 1px; vertical-align: -2px; background: #c8c3bb; }
.pip.W { background: #f5f0dc; } .pip.U { background: #9ec7e8; }
.pip.B { background: #a89bb0; } .pip.R { background: #e8a294; }
.pip.G { background: #a5c99a; } .pip.C, .pip.n { background: #c4c8cf; }

/* ---------- event ticker + board-change cues ---------- */
/* one event at a time, a readable beat each, sticky so it's visible from
   anywhere on the board; the permanent log below fills in sync with it */
#ticker { position: sticky; top: calc(var(--rulerh, 46px) + 4px); z-index: 4;
          display: flex;
          align-items: center; gap: 8px; margin-bottom: 8px; cursor: pointer; }
#ticker .tline {
  flex: 1; min-width: 0; background: var(--panel2);
  border: 1px solid var(--border2); border-left: 3px solid var(--accent);
  border-radius: 8px; padding: 6px 10px; font-size: 12.5px; line-height: 1.35;
  box-shadow: 0 4px 14px rgba(0,0,0,.45); animation: tickin .3s ease-out;
}
#ticker .tmore { flex: 0 0 auto; font-size: 10.5px; color: var(--faint);
                 font-variant-numeric: tabular-nums; }
@keyframes tickin { from { opacity: 0; transform: translateY(5px); } }
/* ---------- announcer banner: the table's own voice (🎙️) ---------- */
/* deliberately louder than the ticker — the booth calls the big moments;
   gold = canned base call, blue = the LLM color-commentary line */
#announcer { position: sticky; top: calc(var(--rulerh, 46px) + 4px);
  z-index: 5; display: none;
  margin: 0 auto 8px; padding: 7px 14px; max-width: 94%; width: fit-content;
  background: linear-gradient(180deg, #2c2410, #1a1508);
  border: 1px solid #b8912f; border-radius: 10px; cursor: pointer;
  color: #f4d879; font-weight: 700; font-size: 13px; line-height: 1.35;
  box-shadow: 0 4px 16px rgba(0,0,0,.5); animation: annpop .25s ease-out; }
#announcer.on { display: block; }
#announcer.color { border-color: #5f9fd4; color: #cfe9ff;
  background: linear-gradient(180deg, #14243a, #0b1626); }
#announcer .amic { margin-right: 6px; }
@keyframes annpop { from { opacity: 0; transform: translateY(-8px); } }
.tile.arrived { animation: popin .4s ease-out; }
@keyframes popin { from { opacity: 0; transform: scale(.6); } }
.cblife.up { animation: lifeup 1.1s ease-out; }
.cblife.dn { animation: lifedn 1.1s ease-out; }
@keyframes lifeup { 0% { color: var(--good); transform: scale(1.3); } }
@keyframes lifedn { 0% { color: var(--bad); transform: scale(1.3); } }
/* damage / counter floating fx (dmgfx.js) — a small "+N"/"-N" badge that
   floats up off a permanent's tile and fades, same spirit as the card-play
   reveal but for the number that just changed on the battlefield. Position
   (left/top) is set inline per-spawn; the animation itself lives on the
   element's Web Animations API call, this just sets its look. */
.dmgfx { position: fixed; transform: translate(-50%, -50%); z-index: 30;
  pointer-events: none; font-weight: 800; font-size: 13px;
  text-shadow: 0 1px 3px rgba(0,0,0,.7), 0 0 6px rgba(0,0,0,.5);
  white-space: nowrap; }
.dmgfx-hit { color: var(--bad); }      /* damage marked increased */
.dmgfx-heal { color: var(--good); }    /* damage marked decreased (cleanup, regen) */
.dmgfx-ctrup { color: var(--accent); } /* a counter was added */
.dmgfx-ctrdn { color: var(--faint); }  /* a counter was removed */
/* the seat the table is waiting on: pulsing ring on their portrait. This ring
   IS the whole waiting indicator (ISS-wt70a2b-3) — "Bob is declaring
   blockers…" used to hold the bottom tray open across the board on a decision
   that wasn't yours, saying what the ring already says. */
.seatrowg.deciding .cmdrbox { box-shadow: 0 0 0 2px var(--accent);
                              animation: decpulse 1.6s ease-in-out infinite; }
@keyframes decpulse { 50% { box-shadow: 0 0 0 2px rgba(122,162,247,.2); } }
/* a NEW decision arriving flashes the prompt bar — "it's you now" */
#prompt-bar.attn { animation: barflash .8s ease-out; }
@keyframes barflash { 0% { box-shadow: 0 -6px 26px rgba(122,162,247,.5);
                           border-top-color: var(--accent); } }

/* ---------- stack / log / text view ---------- */
#stackbox { background: #251f33; border: 1px solid #4a3f66; border-radius: 10px;
            padding: 8px 12px; margin-bottom: 8px; }
#stackbox .zlabel { color: var(--plan); margin-top: 0; }
#stackbox .sitem { font-size: 12.5px; padding: 2px 0; }
#stackbox .sitem:first-of-type { font-weight: 600; }
#stackbox .sitem[data-iid] { cursor: pointer; }
#stackbox .sitem[data-iid]:hover { color: var(--accent); }
pre {
  background: var(--panel); border: 1px solid var(--border); border-radius: 8px;
  padding: 10px; overflow-x: auto; font: 12px/1.5 ui-monospace, SFMono-Regular,
  Menlo, Consolas, monospace; white-space: pre-wrap; margin: 0;
}
#digest { max-height: 34vh; overflow-y: auto; line-height: 1.55; }
#logbody { line-height: 1.55; }
/* colour-coded game log — shared by the live in-game log (#digest) and the
   admin/history full-record dump (#logbody, via showLogScreen -> appendLog).
   Each line is a .ll block tagged by event kind (classified client-side in
   logClass(), keyed off gamelog.render_events' wording). Base lines sit dim
   so the meaningful beats — casts, combat, life swings — carry the eye.
   Leading indent is preserved via the <pre>'s inherited white-space, so the
   2-/4-space hierarchy still reads. */
.logview .ll { display: block; color: var(--dim); padding: 1px 0;
              border-left: 2px solid transparent; padding-left: 7px;
              margin-left: -9px; }
.logview .ll-turn {
  color: var(--accent); font-weight: 650; margin-top: 9px;
  padding-top: 7px; border-top: 1px solid var(--border); letter-spacing: .01em;
}
.logview .ll-turn:first-child { margin-top: 0; padding-top: 0; border-top: 0; }
.logview .ll-cast     { color: var(--instant); border-left-color: var(--instant); }
.logview .ll-land     { color: var(--land); }
.logview .ll-activate { color: var(--artifact); }
.logview .ll-combat   { color: var(--sorcery); border-left-color: var(--sorcery); }
.logview .ll-good     { color: var(--good); }
.logview .ll-bad      { color: var(--bad); }
.logview .ll-loss     { color: var(--bad); font-weight: 650;
                       border-left-color: var(--bad); }
.logview .ll-remove   { color: var(--faint); }
.logview .ll-trigger  { color: var(--enchantment); }
.logview .ll-plan     { color: var(--plan); }
.logview .ll-warn     { color: var(--warn); border-left-color: var(--warn); }
.logview .ll-announce { color: var(--warn); font-style: italic; }
.logview .ll-speech   { color: var(--dim); font-style: italic; }
.logview .ll-note     { color: var(--plan); font-style: italic; opacity: .82; }
details { margin-top: 10px; }
details summary { cursor: pointer; color: var(--dim); font-size: 12px;
                  text-transform: uppercase; letter-spacing: .08em; user-select: none; }
section h2, .h2 {
  font-size: 12px; text-transform: uppercase; letter-spacing: .08em;
  color: var(--dim); margin: 14px 0 6px;
}

/* ---------- prompt bar ---------- */
#prompt-bar {
  position: fixed; left: 0; right: 0; bottom: 0; background: var(--panel);
  border-top: 1px solid var(--border);
  padding: 10px 14px calc(10px + env(safe-area-inset-bottom));
  max-height: 62vh; overflow-y: auto; z-index: 6;
  -webkit-overflow-scrolling: touch;
}
#prompt-bar.plan { border-top: 2px solid var(--plan); background: #1c1826; }
/* baseline-ish alignment, not center: the ask can wrap to two lines on a phone
   and the pill should sit with the FIRST line, not drift to the block's middle */
#pbhead { display: flex; align-items: flex-start; gap: 10px; margin-bottom: 8px; }
#pbsummary { flex: 1; min-width: 0; color: var(--warn); font-size: 13px;
             white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
             display: none; }
/* the floating-mana readout in the tray head: pips of whatever is sitting in
   your pool right now, only present when the pool is non-empty */
#pbpool { flex: 0 0 auto; margin-left: auto; display: inline-flex; gap: 3px;
          align-items: center; padding: 2px 8px; border-radius: 999px;
          background: var(--panel3); border: 1px solid var(--border2);
          font-size: 11px; color: var(--dim); white-space: nowrap; cursor: help; }
#pbpool .plbl { letter-spacing: .04em; text-transform: uppercase; }
#pbpool + #pbtoggle { margin-left: 8px; }
#pbtoggle { margin-left: auto; flex: 0 0 auto; min-height: 0; padding: 2px 12px;
            font-size: 11.5px; color: var(--dim); border-radius: 999px;
            line-height: 1.5; }
#prompt-bar.min > :not(#pbhead) { display: none !important; }
#prompt-bar.min #pbsummary { display: block; }
#prompt-bar.min #ask { display: none; }   /* #pbsummary owns the slot when minimized */
#prompt-bar.min #pbhead { margin-bottom: 0; cursor: pointer; align-items: center; }
#prompt-bar.min { overflow-y: hidden; }
#planbanner { display: none; color: var(--plan); font-size: 11.5px; font-weight: 700;
              text-transform: uppercase; letter-spacing: .1em; margin-bottom: 6px; }
#prompt-bar.plan #planbanner { display: block; }
/* The ask is the tray's heading — the one line telling you what the game wants.
   It used to render at body size in regular weight, so amber was doing all the
   work of marking it as the prompt and it read as a caution strip rather than a
   title. Size and weight carry the hierarchy now, which also lets the amber sit
   at a calmer contrast instead of straining to be noticed (ISS-wt1-1). */
#ask { flex: 1; min-width: 0; font-size: 14.5px; font-weight: 600;
       line-height: 1.35; letter-spacing: .005em; color: var(--warn);
       text-wrap: pretty; }
#perror { color: var(--bad); font-size: 13px; margin-bottom: 8px; display: none; }
#pnote { color: var(--good); font-size: 12.5px; margin-bottom: 8px; display: none;
         white-space: pre-wrap; font-family: ui-monospace, Consolas, monospace; }
#planreport { display: none; margin-bottom: 10px; max-height: 36vh; overflow-y: auto;
  white-space: normal; font: inherit; }
#plansteps { display: none; color: var(--plan); font-size: 12px; margin-bottom: 6px; }
/* visual SimReport (ISS-982): life-delta strip, entered/died chips, step ✓/✗ */
#planreport .simwarn { border: 1px solid var(--bad); background: #2e1e26;
  color: var(--bad); border-radius: 8px; padding: 7px 9px; margin-bottom: 8px;
  font-size: 13px; font-weight: 600; }
#planreport .lifestrip { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; }
#planreport .lifechip { border: 1px solid var(--border2); border-radius: 999px;
  padding: 3px 10px; font-size: 12px; font-variant-numeric: tabular-nums; }
#planreport .lifechip.you { border-color: var(--accent); }
#planreport .lifechip.down { color: var(--bad); border-color: var(--bad); }
#planreport .lifechip.up { color: var(--good); border-color: var(--good); }
#planreport .simrow { display: flex; flex-wrap: wrap; gap: 5px; align-items: center;
  margin-bottom: 6px; }
#planreport .simlabel { color: var(--dim); font-size: 11px; text-transform: uppercase;
  letter-spacing: .04em; margin-right: 2px; }
#planreport .simchip { border: 1px solid var(--border2); border-radius: 6px;
  padding: 2px 8px; font-size: 12px; }
#planreport .simchip.in { color: var(--good); border-color: var(--good); }
#planreport .simchip.out { color: var(--bad); border-color: var(--bad); }
#planreport .simchip.cnt { color: var(--plan); border-color: var(--plan); }
#planreport .simchip.warn2 { color: var(--warn); border-color: var(--warn); }
#planreport .simsteps { margin: 6px 0; }
#planreport .simstep { display: flex; gap: 7px; font-size: 12.5px; padding: 1px 0; }
#planreport .simstep .mark { flex: 0 0 auto; font-weight: 700; }
#planreport .simstep.ok .mark { color: var(--good); }
#planreport .simstep.bad { color: var(--dim); }
#planreport .simstep.bad .mark { color: var(--bad); }
#planreport .simstop { color: var(--warn); font-size: 12px; margin: 4px 0; }
#planreport .simfoot { color: var(--faint); font-size: 11px; margin-top: 8px;
  border-top: 1px solid var(--border); padding-top: 6px; }
/* sandbox target tap-picker (ISS-981) */
.tgtpick { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; }
button.tgtchip { flex: 0 0 auto; font-size: 13px; padding: 7px 11px; }
button.tgtchip.sel { border-color: var(--accent); background: #253048;
  color: var(--accent); }
/* multi-world compare panel (ISS-503) — self-contained styles */
#plancompare { display: none; margin-bottom: 10px; }
#plancompare .cmpgrid { display: flex; gap: 8px; overflow-x: auto; }
#plancompare .cmpcol { flex: 1 0 46%; min-width: 160px; border: 1px solid var(--line);
                       border-radius: 8px; padding: 8px; font-size: 12px; }
#plancompare .cmpname { font-weight: 700; color: var(--plan); margin-bottom: 4px; }
#plancompare .cmpv { margin: 2px 0; }
#plancompare .cmpfail { color: var(--danger, #c33); }
/* type-to-filter box for long pick menus (ISS-triage-9089-1) */
/* one row: quick answers keep their natural width, the filter takes the slack.
   240px basis means a narrow phone wraps it under the buttons instead of
   squeezing it to a stub (ISS-wt1-1). */
#pbcontrols { display: flex; gap: 8px; align-items: center; flex-wrap: wrap;
              margin-bottom: 10px; }
#pbcontrols:empty, #pbcontrols.bare { margin-bottom: 0; }
#pbcontrols > .quickrow { margin-bottom: 0; flex: 0 1 auto; }
#pbcontrols > .mfilter { flex: 1 1 240px; margin-bottom: 0; }
.mfilter { display: flex; gap: 6px; align-items: center; margin-bottom: 8px; }
/* `display: flex` above is an AUTHOR rule and beats the UA stylesheet's
   `[hidden] { display: none }`, so `mfilterrow.hidden = true` never actually
   hid anything — the 🔍 box rendered on EVERY decision, including a two-button
   mulligan with no menu to filter (ISS-wt1-2). Same trap renderOwnerGroups
   documents for `.menu`; spell the hidden case out. */
.mfilter[hidden] { display: none; }
.mfilter input {
  flex: 1; background: var(--bg); color: var(--text); border: 1px solid var(--border);
  border-radius: 8px; padding: 8px 10px; font-size: 13px; min-width: 0;
}
.mfico { font-size: 14px; opacity: .7; }
.menu { display: flex; flex-direction: column; gap: 6px; margin-bottom: 10px; }
/* wrap the attack-target chips onto a second line under the attacker button
   instead of crushing the button into a tall glyph-per-line column: on a
   narrow (mobile) row a multiplayer/planeswalker attacker carries several
   fixed-width seatchips, and without wrap + a real button min-width the
   flex:1 button collapses to min-width:0 and stacks its card name vertically,
   ballooning every row (ISS-57-1). */
.menurow { display: flex; gap: 6px; align-items: stretch; flex-wrap: wrap; }
.menurow > button:first-child { flex: 1 1 60%; min-width: min(220px, 60%); overflow-wrap: anywhere; }
/* "any combination of colors": a colour taken more than once shows its running
   count on the row, so a three-mana split reads back as W ×2 / U ×1 (ISS-triage-f0c6-1) */
.menurow > button[data-count]::after { content: " ×" attr(data-count); opacity: .85; }
/* cross-player pick menu: whose-permanent filter bar + per-owner section
   headings (ISS-triage-a196-1) */
.ownerbar { display: flex; flex-wrap: wrap; gap: 6px; }
button.ownerchip { flex: 0 0 auto; font-size: 13px; padding: 6px 10px;
  text-align: center; min-height: 32px; }
button.ownerchip.sel { border-color: var(--accent); background: #253048;
  color: var(--accent); }
button.ownerchip.mine { font-weight: 700; }
.ownerhead { font-size: 12px; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: var(--faint); margin: 4px 0 -2px; }
/* board-state disambiguation badge — shown on menu rows where two candidates
   share a name (same-named tokens, legend copies, etc.) so the player can
   tell them apart without relying on the raw [iid] number
   (ISS-triage-08041311-1) */
.disambig { display: block; font-size: 11px; font-style: italic;
  opacity: 0.75; margin-top: 3px; }
button {
  background: var(--panel2); color: var(--text); border: 1px solid var(--border);
  border-radius: 8px; padding: 10px 12px; font-size: 14px; text-align: left;
  cursor: pointer; touch-action: manipulation; font-family: inherit;
  min-height: 40px;
}
button:active { background: #2e3345; }
button:focus-visible, input:focus-visible, select:focus-visible,
summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
button.sel { border-color: var(--accent); background: #253048; }
button.quick { display: inline-block; text-align: center; font-weight: 600; }
button.primary { background: var(--accent); color: #0e1016; border: none; font-weight: 700; }
button.planbtn { background: #2c2440; border-color: #4a3f66; color: var(--plan); }
button.danger { color: var(--bad); }
/* two-line "auto/pass" label keeps the button as narrow as the old "mute";
   .on = this option is being auto-passed (glows so the state is visible at a
   glance, and taps back off to stop) */
button.mutebtn { flex: 0 0 auto; color: var(--faint); font-size: 11px;
                 line-height: 1.02; text-align: center; padding: 3px 8px; }
button.mutebtn.on { color: var(--accent); border-color: var(--accent);
                    background: #253048; box-shadow: 0 0 7px -1px var(--accent); }
/* card-level auto-pass toggle in the inspect modal (ISS-triage-08091027-1):
   active (ON) state mirrors .mutebtn.on but at normal button size */
button.cardautopasson { color: var(--accent); border-color: var(--accent);
                        background: #253048; box-shadow: 0 0 7px -1px var(--accent); }
/* per-option "read this card" button — pops the same inspect modal a card tile
   does, so a target/cast/trigger option can be read without hunting the board
   (and a card that's left the hand stays readable). One tap = view, never picks. */
button.viewbtn { flex: 0 0 auto; color: var(--dim); font-size: 16px; line-height: 1;
                 padding: 4px 10px; }
button.viewbtn:hover { color: var(--text); }
/* the same button on the ask line, naming the card that asked the question */
button.viewbtn.asksrc { font-size: 12px; padding: 2px 8px; margin-left: 7px;
                        border: 1px solid var(--border); border-radius: 12px;
                        color: var(--text); vertical-align: middle; }
/* attack-target chips: one per living opponent on each attacker row */
button.seatchip { flex: 0 0 auto; color: var(--dim); font-size: 12px;
                  padding: 4px 9px; white-space: nowrap; max-width: 110px;
                  overflow: hidden; text-overflow: ellipsis; }
button.seatchip.sel { border-color: var(--bad); color: var(--bad); background: #2e2230; }
button.seatchip.forbidden { opacity: 0.35; text-decoration: line-through;
                            cursor: not-allowed; }
button.seatchip.pwchip { color: var(--planeswalker); max-width: 150px; }
button.seatchip.pwchip.sel { border-color: var(--planeswalker);
                             color: var(--planeswalker); background: #2e2a20; }
/* blocking board: one lane per attacker, blocker chips assigned into it */
#blockboard { display: flex; flex-direction: column; gap: 8px; margin-bottom: 10px; }
#blockboard .dim { color: var(--dim); }
.btotal { font-size: 13.5px; }
.btotal .lethal { color: var(--bad); font-weight: 700; }
/* The attacking mirror of .btotal: while BLOCKING you get "you'd take 14",
   while ATTACKING you got nothing but a row of chips to count by hand. This
   is the same readout from the other side (ISS-wt-73-07e80bbc-1). */
#atktotals { margin-bottom: 9px; font-size: 13.5px; display: flex;
             flex-direction: column; gap: 5px; }
#atktotals[hidden] { display: none; }
#atktotals .atkgrand { font-weight: 600; }
#atktotals .atkrows { display: flex; flex-wrap: wrap; gap: 6px; }
/* NOT nowrap and NOT a 999px pill: a long planeswalker name ("↳ Jace, the Mind
   Sculptor 6 power · would REMOVE it if unblocked") ran straight off the right
   edge of the tray on a phone. The chip wraps its own text instead, so the
   squarer radius is what keeps a two-line chip looking deliberate. */
#atktotals .atkchip { border: 1px solid var(--border2); border-radius: 12px;
  padding: 3px 10px; font-size: 12.5px; font-variant-numeric: tabular-nums;
  max-width: 100%; }
#atktotals .atkchip.pw { color: var(--planeswalker);
                         border-color: var(--planeswalker); }
#atktotals .atkchip.lethal { color: var(--bad); border-color: var(--bad);
                             background: #2e2230; font-weight: 600; }
#atktotals .dim { color: var(--dim); font-weight: 400; }
#atktotals .atktaxline { font-size: 12.5px; color: var(--dim); }
#atktotals .atktaxline.taxwarn { color: var(--bad); font-weight: 600; }
/* every lane is tappable — with nothing picked a tap ARMS it (ISS-triage-c8dc-1) */
.blane { border: 1px solid var(--border); border-left: 3px solid var(--bad);
         border-radius: 10px; padding: 8px 10px; background: var(--panel2);
         cursor: pointer; }
.blane.cantake, .blane.armed { border-color: var(--accent); }
.blane.cantake:active, .blane.armed { background: #253048; }
.blane.illegal { opacity: .4; }
.bhead { display: flex; gap: 7px; align-items: baseline; flex-wrap: wrap; }
.bhead .nm { font-weight: 600; }
.bhead .pt, .bchip .pt { font-weight: 700; }
.bhead .kws, .bchip .kws { color: var(--dim); font-size: 11.5px; }
.bdmg { margin-left: auto; color: var(--bad); font-size: 12.5px; white-space: nowrap; }
.bdmg.held { color: var(--good, #7dc47d); }
.mwarn { flex-basis: 100%; color: var(--dim); font-size: 11.5px; }
.mwarn.bad { color: var(--bad); font-weight: 600; }
.bchips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 7px; }
/* ISS-993: chip + its sibling view button share one pill-shaped wrapper */
.bchipwrap { display: inline-flex; align-items: center; }
.bchipwrap .viewbtn { padding: 3px 7px; font-size: 13px; }
button.bchip { border: 1px solid var(--border); border-left: 3px solid var(--creature);
               border-radius: 16px; background: var(--panel3); padding: 5px 11px;
               font-size: 12.5px; min-height: 0; }
button.bchip.sel { border-color: var(--accent); background: #253048; }
button.bchip.illegal { opacity: .4; }   /* can't block the armed attacker */
.bpend { color: var(--bad); }           /* half-made block: Confirm will refuse */
button.bchip.inlane::after { content: " ✕"; color: var(--faint); }
.bhint { color: var(--accent); font-size: 11.5px; margin-top: 6px; }
.btraylab { color: var(--dim); font-size: 11.5px; margin-top: 2px; }
.btray { display: flex; flex-wrap: wrap; gap: 6px; }
.quickrow { display: flex; gap: 8px; margin-bottom: 10px; flex-wrap: wrap; }
.composer { display: flex; gap: 8px; }
.composer input {
  flex: 1; background: var(--bg); color: var(--text); border: 1px solid var(--border);
  border-radius: 8px; padding: 10px 12px; font: 14px ui-monospace, Consolas, monospace;
  min-width: 0;
}
/* auto-pass exceptions: one collapsible group per decision kind, full width */
.mutegroups { flex-basis: 100%; display: flex; flex-wrap: wrap; gap: 6px; }
.mutegroup { border: 1px solid var(--border); border-radius: 10px;
             background: var(--panel2); padding: 4px 9px; }
.mutegroup > summary { cursor: pointer; user-select: none; color: var(--dim);
             font-size: 11.5px; list-style: none; display: flex; gap: 6px;
             align-items: center; }
.mutegroup > summary::-webkit-details-marker { display: none; }
.mutegroup > summary::before { content: "▸"; color: var(--faint); font-size: 10px; }
.mutegroup[open] > summary::before { content: "▾"; }
.mutecount { color: var(--faint); background: var(--panel3); border-radius: 10px;
             padding: 0 6px; font-size: 10.5px; }
.mutelist { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
.mutechip { background: var(--panel3); border: 1px solid var(--border);
            border-radius: 12px; padding: 3px 10px; font-size: 11.5px; cursor: pointer;
            color: var(--dim); }
.mutechip:hover { color: var(--bad); border-color: var(--bad); }
#bugbtn { min-height: 0; padding: 3px 9px; font-size: 13px; border-radius: 12px;
          color: var(--dim); }
/* in-game settings cluster: one small icon toggle per option, plus a distinct
   ❓ that opens the full-label panel. settingsSpec() drives the icons AND the
   panel from one place, so they can't drift. */
.seticons { display: inline-flex; gap: 1px; align-items: center; }
.seticons[hidden] { display: none; }   /* explicit display above would beat [hidden] otherwise — icons only in-game */
.seticon { min-height: 0; padding: 3px 6px; font-size: 15px; line-height: 1;
           border-radius: 12px; color: var(--dim); }
.seticon.off { opacity: 0.38; }
#setexpand { min-height: 0; padding: 2px 8px; font-size: 13px; font-weight: 800;
             line-height: 1.25; border-radius: 12px; color: var(--text);
             background: var(--panel3); border: 1px solid var(--border2); }
#setexpand:hover { border-color: var(--accent); }
#setexpand.open { background: var(--accent); color: #000; border-color: var(--accent); }
#setmenu {
  position: absolute; top: calc(100% + 4px); right: 10px; z-index: 30;
  width: min(360px, calc(100vw - 20px));
  background: var(--panel2); border: 1px solid var(--border2);
  border-radius: 10px; box-shadow: 0 10px 30px rgba(0,0,0,.55);
  padding: 6px; max-height: 70vh; overflow-y: auto;
}
#setmenu .smrow { display: flex; gap: 10px; align-items: flex-start;
  padding: 8px 10px; border-radius: 8px; cursor: pointer; font-size: 13px; }
#setmenu .smrow:hover { background: var(--panel3); }
#setmenu .smicon { font-size: 15px; line-height: 1.3; width: 18px; text-align: center; }
#setmenu .smtext { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
#setmenu .smname { color: var(--text); }
#setmenu .smhint { color: var(--faint); font-size: 11.5px; line-height: 1.35; }
#setmenu .smtoggle { margin-left: auto; flex: 0 0 auto; align-self: center;
                     width: 15px; height: 15px; accent-color: var(--accent); cursor: pointer; }
#setmenu .smhead { font-size: 10.5px; text-transform: uppercase;
  letter-spacing: .08em; color: var(--faint); padding: 8px 10px 2px; }
#bugtext { width: 100%; box-sizing: border-box; background: var(--bg);
           color: var(--text); border: 1px solid var(--border); border-radius: 8px;
           padding: 10px 12px; font-size: 14px; font-family: inherit;
           resize: vertical; }

/* ---------- inspect modal: framed like a card ---------- */
.custombadge{font:700 10px/1 system-ui;letter-spacing:.08em;text-transform:uppercase;
  color:#f0d9ff;background:#4d2469;border:1px solid #a66bca;border-radius:999px;
  padding:3px 6px;white-space:nowrap}
#modal { position: fixed; inset: 0; background: rgba(8,10,14,.72); z-index: 20;
         display: none; align-items: center; justify-content: center; padding: 18px; }
#modal.open { display: flex; }
/* The card frame is a reusable LOOK (.cardframe), not the modal itself: the
   inspect modal wears it, and so does the battlefield entry reveal, which
   shows the same frame briefly over the tile a permanent just landed on. */
.cardframe {
  border-radius: 18px; padding: 10px; border: 1px solid #000;
  box-shadow: 0 12px 40px rgba(0,0,0,.6);
  background: linear-gradient(160deg, #5a616c, #363b44);   /* colorless default */
}
#modalcard { max-width: 400px; width: 100%; max-height: 86vh; overflow-y: auto; }
/* entry reveal: the same frame, card-sized, over the tile a permanent just
   landed on — then it falls back INTO that tile. A board tile is a name in a
   box; this is the beat that says the box IS a card. Compact type and no
   live section: it's a flourish, not the inspector. */
/* These MUST out-specify the `.cardframe .part` rules below (same 0-2-0 as a
   bare `.reveal .part`, and those come later in the sheet, so they would win
   on order): with .ctext falling back to the modal's 30vh cap, a wordy card
   rendered as a 503px tower instead of a card. Capping the one variable-height
   part keeps every reveal at ~196x295 — real card proportions. */
/* the animated element is the WRAPPER: it carries the card plus the caster's
   commander portrait tucked behind the card's top-right corner */
.revwrap { position: fixed; z-index: 15; width: 196px; pointer-events: none;
           will-change: transform, opacity; }
.revcmdr { position: absolute; right: -30px; top: -30px; z-index: 0;
           width: 86px; height: 86px; border-radius: 50%; object-fit: cover;
           border: 2px solid var(--border2); background: #0a0c10;
           box-shadow: 0 6px 18px rgba(0,0,0,.6); }
.revcmdrx { display: flex; align-items: center; justify-content: center;
            font-weight: 800; font-size: 30px; color: #14161c; }
.cardframe.reveal { position: relative; z-index: 1; width: 196px; padding: 7px;
          border-radius: 12px; }
.cardframe.reveal .cbar { font-size: 11.5px; padding: 4px 7px; border-radius: 6px; }
.cardframe.reveal .ctype { font-size: 9.5px; margin-top: 4px; }
.cardframe.reveal .cart { margin-top: 4px; border-radius: 4px; }
.cardframe.reveal .ctext { font-size: 9.5px; line-height: 1.4; padding: 5px 7px;
                 margin-top: 4px; height: 84px; overflow: hidden; }
.cardframe.reveal .cfoot { margin-top: 4px; }
.cardframe.reveal .cartist { font-size: 8.5px; }
.cardframe.reveal .cpt { font-size: 12px; padding: 1px 7px; }
/* tokens and anything missing from the art cache keep the card's shape */
.cardframe.reveal .cartnone { display: block; width: 100%; aspect-ratio: 626/457;
  margin-top: 4px; border-radius: 4px; border: 1px solid rgba(0,0,0,.6);
  background: repeating-linear-gradient(135deg, rgba(255,255,255,.05) 0 8px,
              rgba(255,255,255,.02) 8px 16px), #0a0c10; }
/* frame color by card color(s) — muted, dark-theme takes on the real frames */
.cardframe.c-W { background: linear-gradient(160deg, #918a6b, #625d49); }
.cardframe.c-U { background: linear-gradient(160deg, #2f4f80, #1d3152); }
.cardframe.c-B { background: linear-gradient(160deg, #4a4152, #262029); }
.cardframe.c-R { background: linear-gradient(160deg, #83392e, #4f231c); }
.cardframe.c-G { background: linear-gradient(160deg, #3e6039, #233a20); }
.cardframe.c-gold { background: linear-gradient(160deg, #90783f, #5e4e28); }
.cardframe.c-land { background: linear-gradient(160deg, #71604a, #483c2d); }
.cardframe .cbar {
  display: flex; justify-content: space-between; gap: 8px; align-items: center;
  background: rgba(10,12,16,.55); border: 1px solid rgba(255,255,255,.16);
  border-radius: 8px; padding: 6px 10px; font-weight: 700; font-size: 15px;
}
.cardframe .cbar .mana { flex: 0 0 auto; }
.cardframe .ctype { font-size: 12px; font-weight: 600; margin-top: 6px; }
.cardframe .cart {
  display: block; width: 100%; aspect-ratio: 626/457; object-fit: cover;
  border-radius: 5px; margin-top: 6px; border: 1px solid rgba(0,0,0,.6);
  background: #0a0c10;
}
.cardframe .ctext {
  background: rgba(10,12,16,.55); border: 1px solid rgba(255,255,255,.16);
  border-radius: 8px; padding: 8px 10px; margin-top: 6px; font-size: 13px;
  line-height: 1.55; max-height: 30vh; overflow-y: auto;
}
.cardframe .clive { margin-top: 6px; font-size: 12.5px; }
.cardframe .clive .livehead { font-size: 10px; text-transform: uppercase;
                              letter-spacing: .08em; color: rgba(255,255,255,.62);
                              margin-bottom: 3px; }
.cardframe .clive .lrow { margin-top: 2px; }
.cardframe .clive ul { margin: 2px 0 0; padding-left: 18px; }
.cardframe .clive .ilink { color: var(--accent); cursor: pointer;
                           text-decoration: underline dotted; }
.cardframe .clive .idim { color: rgba(255,255,255,.5); font-style: italic; }
.cardframe .cfoot { display: flex; justify-content: space-between;
                    align-items: center; margin-top: 6px; padding: 0 2px;
                    min-height: 18px; }
.cardframe .cartist { font-size: 10.5px; color: rgba(255,255,255,.78);
                      font-style: italic; }
.cardframe .cpt {
  background: rgba(10,12,16,.8); border: 1px solid rgba(255,255,255,.25);
  border-radius: 8px; padding: 2px 14px; font-weight: 800; font-size: 15px;
}
.cardframe .tl { color: var(--dim); font-size: 12.5px; }  /* error fallback */
/* player-details panel (tap a portrait): resources + commander damage */
.cardframe.pcard { background: linear-gradient(160deg, #3a4150, #23272f); }
.cardframe .pstats {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(70px, 1fr));
  gap: 6px; margin-top: 6px;
}
.cardframe .pstat {
  background: rgba(10,12,16,.55); border: 1px solid rgba(255,255,255,.16);
  border-radius: 8px; padding: 6px 8px; text-align: center;
}
.cardframe .pstat .k { display: block; font-size: 9.5px; text-transform: uppercase;
  letter-spacing: .07em; color: rgba(255,255,255,.6); }
.cardframe .pstat .v { display: block; font-size: 19px; font-weight: 800;
  line-height: 1.15; }
.cardframe .pstat .v.low { color: var(--bad); }
.cardframe .plabel { font-size: 10px; text-transform: uppercase;
  letter-spacing: .08em; color: rgba(255,255,255,.62); margin: 10px 0 3px; }
.cardframe .cdrow { display: flex; justify-content: space-between; gap: 8px;
  font-size: 13px; padding: 4px 10px; border-radius: 8px;
  background: rgba(10,12,16,.55); border: 1px solid rgba(255,255,255,.16);
  margin-top: 4px; }
.cardframe .cdrow .cdv { font-weight: 800; color: var(--warn); flex: 0 0 auto; }
.cardframe .cdrow.lethal .cdv { color: var(--bad); }
.cardframe .pnote { color: rgba(255,255,255,.6); font-size: 12px; margin-top: 4px; }
.cardframe .cmdrlink { color: var(--accent); cursor: pointer;
  text-decoration: underline dotted; }
/* graveyard viewer (tap a portrait): public zone, tiles tap through to inspect;
   capped + scrollable so a deep pile never blows out the modal */
.cardframe .gyard { max-height: 208px; overflow-y: auto; }
/* table-talk history (tap a portrait): every quip this seat has said, scrollable */
.cardframe .ttlog { max-height: 168px; overflow-y: auto; display: flex;
  flex-direction: column; gap: 4px; margin-top: 4px; }
.cardframe .ttline { font-size: 13px; font-style: italic; padding: 4px 10px;
  border-radius: 8px; background: rgba(10,12,16,.55);
  border: 1px solid rgba(255,255,255,.14); color: rgba(255,255,255,.9); }
/* a line whose quip has a known trigger: dotted hint + the action revealed on
   hover (↳ …), so you can see what the comment was reacting to */
.cardframe .ttline.hasctx { cursor: help; }
.cardframe .ttctx { display: none; margin-top: 3px; font-style: normal;
  font-size: 11px; color: var(--accent); }
.cardframe .ttline.hasctx:hover { border-color: rgba(120,150,220,.5); }
.cardframe .ttline.hasctx:hover .ttctx { display: block; }
