/* Conquest of Fates — game client.
   Visual language matches the marketing site: Orbitron display, cyan #08F5FF accent,
   deep space background. */

@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@500;700;900&family=Azeret+Mono:wght@300;400;600&display=swap');

:root {
  --cyan: #08f5ff;
  --gold: #f5c451;
  --bg: #05070f;
  --panel: rgba(10, 18, 34, 0.72);
  --edge: rgba(8, 245, 255, 0.22);
  --text: #dbe8f5;
  --dim: #7d93ab;
  --danger: #ff5d6c;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  min-height: 100vh;
  font-family: 'Azeret Mono', ui-monospace, monospace;
  color: var(--text);
  background:
    radial-gradient(1200px 700px at 20% -10%, #10233f 0%, transparent 60%),
    radial-gradient(900px 600px at 90% 110%, #1a1030 0%, transparent 60%),
    var(--bg);
}

.hidden { display: none !important; }

/* ---------------------------------------------------------------- topbar */
.topbar {
  display: flex; align-items: center; justify-content: space-between;
  padding: .7rem 1.2rem;
  border-bottom: 1px solid var(--edge);
  background: rgba(4, 8, 16, .7);
  backdrop-filter: blur(8px);
  position: sticky; top: 0; z-index: 20;
}
.brand {
  font-family: Orbitron, sans-serif; font-weight: 900; letter-spacing: .14em;
  font-size: .95rem; color: #fff; text-shadow: 0 0 14px rgba(8,245,255,.55);
}
.brand span { color: var(--cyan); }
.session { font-size: .78rem; color: var(--dim); letter-spacing: .1em; }
.session b { color: var(--cyan); letter-spacing: .22em; }

/* ---------------------------------------------------------------- lobby */
.lobby { max-width: 640px; margin: 14vh auto; padding: 0 1.5rem; text-align: center; }
.lobby h1 {
  font-family: Orbitron, sans-serif; font-weight: 700; font-size: 2rem; margin: 0 0 .4rem;
  color: #fff; text-shadow: 0 0 22px rgba(8,245,255,.4);
}
.lobby .sub { color: var(--dim); margin: 0 0 2.2rem; }
.lobby-actions { display: flex; flex-direction: column; align-items: center; gap: 1rem; }
.or { color: var(--dim); font-size: .75rem; letter-spacing: .2em; }
.join { display: flex; gap: .5rem; }

button {
  font-family: Orbitron, sans-serif; font-weight: 700; letter-spacing: .1em;
  font-size: .8rem; padding: .8rem 1.5rem; border-radius: 3px; cursor: pointer;
  border: 1px solid var(--edge); background: transparent; color: var(--text);
  transition: background .15s, box-shadow .15s, transform .1s;
}
button:hover { background: rgba(8,245,255,.1); box-shadow: 0 0 18px rgba(8,245,255,.25); }
button:active { transform: translateY(1px); }
button.primary { background: rgba(8,245,255,.14); border-color: var(--cyan); color: #eaffff; }
button.ghost { color: var(--dim); }
button:disabled { opacity: .4; cursor: not-allowed; }

input {
  font-family: Orbitron, sans-serif; font-size: 1rem; letter-spacing: .3em; text-align: center;
  width: 8rem; padding: .8rem .6rem; text-transform: uppercase;
  background: rgba(0,0,0,.45); border: 1px solid var(--edge); border-radius: 3px; color: #fff;
}
input:focus { outline: none; border-color: var(--cyan); box-shadow: 0 0 14px rgba(8,245,255,.3); }
.note { color: var(--dim); font-size: .8rem; margin-top: 1.5rem; min-height: 1.2em; }
.note.err { color: var(--danger); }
.starters { display: flex; flex-wrap: wrap; gap: .4rem; justify-content: center; }
.starter {
  font-size: .64rem; padding: .5rem .8rem; letter-spacing: .06em;
  font-family: 'Azeret Mono', monospace; text-transform: none;
}
.starter.on { background: rgba(8,245,255,.18); border-color: var(--cyan); color: #eaffff; }
.or.small { font-size: .6rem; margin: .3rem 0; }
.startrow { display: flex; align-items: center; gap: 1rem; margin-top: 1.4rem; flex-wrap: wrap; justify-content: center; }
.decklink {
  width: min(560px, 100%); text-align: left; letter-spacing: normal;
  font-family: 'Azeret Mono', monospace; font-size: .72rem; padding: .6rem .7rem;
  text-transform: none;
}
.field { display: flex; flex-direction: column; gap: .35rem; align-items: center; width: 100%; }
.field label { font-size: .62rem; letter-spacing: .18em; color: var(--dim); }
.waiting { margin-top: 2rem; font-size: .8rem; color: var(--dim); line-height: 1.8; }
.waiting .seat { color: var(--text); }
.waiting .seat.ready { color: var(--cyan); }
.bigcode {
  font-family: Orbitron, sans-serif; font-weight: 900; font-size: 2.2rem; letter-spacing: .3em;
  color: var(--cyan); cursor: pointer; user-select: all; display: inline-block; padding: .2rem .4rem;
  border: 1px dashed transparent; border-radius: 4px;
}
.bigcode:hover { border-color: var(--cyan); background: rgba(8,245,255,.08); }
.session b { cursor: pointer; }
.session b:hover { text-decoration: underline; }
.copied { color: var(--cyan); font-size: .68rem; margin-left: .5rem; }

/* ---------------------------------------------------------------- table */
/* Log lives in a VERTICAL RIGHT RAIL so both mats fit on screen without scrolling. */
.table {
  display: grid; gap: .5rem;
  padding: .6rem .8rem 0;
  grid-template-areas: "score log" "foe log" "mid log" "you log";
  grid-template-columns: minmax(0, 1fr) 250px;
  grid-template-rows: auto auto auto 1fr;
  height: calc(100vh - 46px - 104px);      /* topbar + hand tray */
  align-items: start;
  box-sizing: border-box;
}
.scoreboard {
  grid-area: score; display: grid; grid-template-columns: 1fr auto 1fr;
  align-items: center; gap: 1rem;
  border: 1px solid var(--edge); border-radius: 4px; padding: .4rem 1.1rem; background: var(--panel);
}
.score { display: flex; flex-direction: column; }
.score.foe { text-align: right; }
.score .lbl { font-size: .58rem; letter-spacing: .22em; color: var(--dim); }
.score .cq {
  font-family: Orbitron, sans-serif; font-weight: 900; font-size: 1.5rem; line-height: 1;
  color: var(--gold); text-shadow: 0 0 16px rgba(245,196,81,.4);
}
.phase { text-align: center; }
.phase > div:first-child {
  font-family: Orbitron, sans-serif; font-weight: 700; font-size: .74rem;
  letter-spacing: .16em; color: var(--cyan);
}
.phase .battle { font-size: .62rem; color: var(--dim); letter-spacing: .1em; }

/* ---- the play mat ------------------------------------------------------
   FIXED, CONSISTENT SIZING. Every Location and zone is exactly one card:
   poker proportions 2.5 x 3.5in => 5:7. The ONLY exception is Extra Locations
   and the Upload Zone, which are the SAME WIDTH but taller.
   Canonical grid: content/how-to-play/playmat-layout-reference.md               */
:root {
  /* one card. Height is derived so 3 rows x 2 mats always fit the viewport. */
  --card-h: clamp(52px, calc((100vh - 390px) / 6), 92px);
  --card-w: calc(var(--card-h) * 5 / 7);
  --gap: 5px;
}

.mat-wrap { display: flex; flex-direction: column; gap: .2rem; align-items: center; }
.foe-wrap { grid-area: foe; }
.you-wrap { grid-area: you; }
.mat-tag { font-size: .58rem; letter-spacing: .22em; color: var(--dim); }
.you-tag { color: var(--cyan); }

/* Upload Zone sits OUTSIDE the mat, to the left: out of play, still in the game. */
.side { display: flex; align-items: flex-start; gap: var(--gap); }

.mat {
  display: grid; gap: var(--gap);
  grid-template-columns: repeat(6, var(--card-w));   /* every column is one card wide */
  grid-template-rows: repeat(3, var(--card-h));      /* every row is one card tall    */
  grid-template-areas:
    "cel mid mid mid mid void"
    "ext mid mid mid mid deck"
    "ext mid mid mid mid disc";
  border: 1px solid var(--edge); border-radius: 5px; padding: var(--gap); background: var(--panel);
}
.you-mat { border-color: rgba(8,245,255,.45); }
/* Both mats create stacking contexts (the opponent's is rotated), so a child can never
   paint above the sibling mat. Lift the whole mat while reading a card. */
.mat-wrap { position: relative; z-index: 1; }
.mat-wrap:hover { z-index: 50; }
.side { position: relative; }
.foe-mat { transform: rotate(180deg); }
.foe-mat .card, .foe-mat .zlabel, .foe-mat .locnum, .foe-mat .zcount { transform: rotate(180deg); }

/* every corner zone is exactly one card */
.mcell {
  grid-area: var(--ga);
  width: var(--card-w); height: var(--card-h);
  border: 1px solid rgba(125,147,171,.25); border-radius: 3px;
  background: rgba(255,255,255,.015);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: .1rem; position: relative; padding: 2px; overflow: hidden;
}
/* A hovered card must be able to grow OUT of its zone box. `overflow:hidden` keeps the
   stacked piles tidy, but it also clipped the hover-zoom to the size of the cell — the
   Celestial enlarged inside a one-card box and stayed unreadable. Lift the clip only while
   a card in that cell is hovered, and raise the cell above its neighbours. */
@media (hover: hover) and (pointer: fine) {
  .mcell:has(.card.zoomable:hover) { overflow: visible; z-index: 320; }
}
.celestial { --ga: cel; }
.void      { --ga: void; }
.deck      { --ga: deck; }
.disc      { --ga: disc; }
/* Extra: SAME WIDTH, taller — it spans both battle rows. */
.extra {
  --ga: ext; width: var(--card-w);
  height: calc(var(--card-h) * 2 + var(--gap));
  justify-content: flex-start; overflow: visible;
}

.zlabel {
  font-size: .42rem; letter-spacing: .08em; color: var(--dim); text-align: center;
  text-transform: uppercase; line-height: 1.15; flex: 0 0 auto;
}
/* Only the Deck shows a number, over a face-down card back. */
.deck .zcount {
  position: absolute; bottom: 2px; left: 0; right: 0; text-align: center; z-index: 3;
  font-family: Orbitron, sans-serif; font-weight: 900; font-size: .8rem; color: #fff;
  text-shadow: 0 1px 4px #000, 0 0 8px #000;
}
.mcell.clickable { cursor: pointer; }
.mcell.clickable:hover { border-color: var(--cyan); box-shadow: 0 0 14px rgba(8,245,255,.3); }
.mcell .zlabel { position: absolute; top: 2px; left: 0; right: 0; z-index: 3;
  text-shadow: 0 1px 3px #000; }
.mcell .card { position: absolute; inset: 0; }

/* ---- stacked zones (Extra Locations, Upload) ---------------------------
   Cards overlap so only the TOP 15% of each shows — enough for the title bar.
   Hover lifts one out to full size. Unlimited capacity stays readable.        */
.stack { position: relative; width: 100%; flex: 1 1 auto; min-height: 0; }
.stack .card {
  position: absolute; left: 0; width: 100%; height: var(--card-h);
  top: calc(var(--i) * var(--card-h) * 0.15);
  z-index: calc(var(--i) + 1);
  transition: transform .12s ease, box-shadow .12s ease;
}
.stack .card:hover {
  transform: scale(var(--zoom, 5.2)) translateY(-4%); z-index: 300;
  box-shadow: 0 12px 40px rgba(0,0,0,.9), 0 0 26px rgba(8,245,255,.4);
  border-color: var(--cyan);
}
.foe-mat .stack .card:hover, .foe-upload .stack .card:hover { transform: rotate(180deg) scale(var(--zoom, 5.2)); }

.upload {
  width: var(--card-w);
  height: calc(var(--card-h) * 3 + var(--gap) * 2);
  border: 1px dashed rgba(245,196,81,.45); border-radius: 3px;
  background: rgba(245,196,81,.04);
  display: flex; flex-direction: column; align-items: center;
  padding: 2px; position: relative; overflow: visible;
}
.upload .zlabel { color: var(--gold); }
.upload-cq { font-size: .5rem; color: var(--gold); font-family: Orbitron, sans-serif; }

.midzone {
  grid-area: mid; display: flex; flex-direction: column; justify-content: center;
  gap: var(--gap);
}
.locs { display: flex; gap: var(--gap); justify-content: center; }
/* Every Location is exactly one card — identical width AND height everywhere.
   3 rear cards centre under the gaps of the 4 forward cards automatically. */
.loc {
  width: var(--card-w); height: var(--card-h); flex: 0 0 auto;
  border: 1px dashed rgba(125,147,171,.28); border-radius: 3px;
  display: flex; align-items: center; justify-content: center; position: relative;
}
.loc .locnum {
  position: absolute; top: 2px; left: 3px; font-size: .48rem; color: var(--dim);
  opacity: .55; z-index: 2;
}
.loc.droppable { border-color: var(--cyan); border-style: solid; background: rgba(8,245,255,.06); }
.loc.dragover  { background: rgba(8,245,255,.22); box-shadow: inset 0 0 18px rgba(8,245,255,.4); }
.loc.staged    { border-color: var(--gold); border-style: solid; }
.loc.flipnext  { border-color: var(--gold); border-style: solid; box-shadow: 0 0 18px rgba(245,196,81,.5); }

.midline {
  grid-area: mid; align-self: center; justify-self: center;
  font-size: .6rem; color: var(--dim); letter-spacing: .1em; text-align: center;
  border-top: 1px solid rgba(8,245,255,.15); border-bottom: 1px solid rgba(8,245,255,.15);
  padding: .2rem 1.2rem; width: 100%;
}
#upload-strip b { color: var(--gold); }

/* ---- cards -------------------------------------------------------------
   The art carries name, CQ and abilities, so there is no caption. A card always
   fills its slot exactly; slots are fixed-size, so cards are uniform everywhere. */
.card {
  width: 100%; height: 100%; border-radius: 3px; position: relative; overflow: hidden;
  border: 1px solid var(--edge); background: linear-gradient(160deg, #0d1c33, #060c18);
  box-shadow: 0 0 10px rgba(8,245,255,.12);
}
.card img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* Badge only when current CQ differs from the printed value (buffed/debuffed) —
   the one thing the art cannot show. */
.card .cqmod {
  position: absolute; top: 2px; right: 2px; z-index: 3;
  font-family: Orbitron, sans-serif; font-weight: 900; font-size: .58rem;
  padding: 0 .18rem; border-radius: 2px; background: rgba(2,6,14,.9);
  color: var(--gold); border: 1px solid var(--gold);
}
.card.noart { display: flex; align-items: center; justify-content: center; padding: 2px; }
.card.noart .fallback {
  font-family: Orbitron, sans-serif; font-size: .42rem; line-height: 1.15;
  color: #eaf6ff; text-align: center;
}
/* the real printed card back (images/final_cardback.png) */
.card.facedown {
  background: url('/cardback.png') center/cover no-repeat, #060c18;
  border-color: rgba(8,245,255,.28); box-shadow: none;
}
/* YOUR OWN face-down units: known to you [GR §6], but must not read as deployed. */
.card.own-facedown { border-style: dashed; border-color: rgba(125,147,171,.55); box-shadow: none; }
.card.own-facedown img { opacity: .42; filter: grayscale(.5); }
.card.own-facedown::after {
  content: 'FACE-DOWN'; position: absolute; top: 50%; left: 0; right: 0;
  transform: translateY(-50%); text-align: center;
  font-family: Orbitron, sans-serif; font-size: .34rem; letter-spacing: .12em;
  color: #cfe6f5; text-shadow: 0 1px 3px #000; pointer-events: none;
}
.card.manual { border-color: var(--gold); border-style: dashed; }
.card.draggable { cursor: grab; }
.card.dragging { opacity: .35; }
.card.sel { outline: 2px solid var(--gold); box-shadow: 0 0 16px rgba(245,196,81,.5); }
/* The one unit a per-target question is about. Cyan, so it never reads as "selected". */
.card.subject { outline: 2px dashed var(--cyan); box-shadow: 0 0 18px rgba(8,245,255,.55); }

/* Hover to read the real card. */
.card.zoomable { transition: transform .12s ease, box-shadow .12s ease; }
body.dragging .card { pointer-events: auto; }
/*
 * ⚠️ `body.dragging @media (...) { … }` USED TO LIVE HERE AND IT KILLED HOVER-ZOOM ON EVERY
 * DESKTOP. A prelude cannot mix a selector with an at-rule: the whole qualified rule is an
 * invalid selector, so the parser discarded the block AND ITS CONTENTS — which is where the
 * real `.card.zoomable:hover { transform: scale(…) }` had been moved. Nothing errored, no
 * rule was missing from the file, and the touch override below still worked, so the file
 * read as if the gating had succeeded. To gate rules on a media query, open the @media
 * FIRST and put the selectors inside it, as below.
 */
@media (hover: hover) and (pointer: fine) {
  .card.zoomable:hover {
    /* big enough to actually read the card text, not just recognise the art */
    transform: scale(var(--zoom, 5.2)); z-index: 300;
    box-shadow: 0 14px 50px rgba(0,0,0,.9), 0 0 30px rgba(8,245,255,.4);
    border-color: var(--cyan);
  }
  /* Never let a zoom preview eat a drop: no hover effects at all while dragging. */
  body.dragging .card.zoomable:hover { transform: none; box-shadow: none; z-index: auto; }
}
.hand .card.zoomable { transform-origin: bottom center; }
.you-mat .card.zoomable, .you-wrap .card.zoomable { transform-origin: center bottom; }
@media (hover: hover) and (pointer: fine) {
.foe-mat .card.zoomable:hover, .foe-upload .card.zoomable:hover {
  transform: rotate(180deg) scale(var(--zoom, 5.2));
}
}


/* ---- right rail: decisions + log ---------------------------------------
   Every prompt lives here so nothing ever covers the mats or the hand.        */
/* The rail must be BOUNDED by the table's height, not by its own content.
   `.table` sets align-items:start, which sizes grid items to their content — so the log
   grew down the page forever instead of scrolling inside a fixed box. Stretching the rail
   to the row, and letting the log be the only thing that scrolls, keeps it on screen. */
.rail {
  grid-area: log; display: flex; flex-direction: column; gap: .4rem;
  align-self: stretch; min-height: 0; max-height: 100%; overflow: hidden;
}

.decision {
  border: 1px solid var(--cyan); border-radius: 4px; padding: .7rem .8rem;
  background: rgba(8,245,255,.07); flex: 0 0 auto;
}
.decision.hidden { display: none; }
.decision.lightspeed { border-color: var(--gold); background: rgba(245,196,81,.08); }
.decision-tag {
  font-family: Orbitron, sans-serif; font-size: .56rem; letter-spacing: .2em;
  color: var(--cyan); margin-bottom: .45rem;
}
.decision.lightspeed .decision-tag { color: var(--gold); }
.decision-prompt { margin: 0 0 .7rem; font-size: .72rem; line-height: 1.5; color: #eaf6ff; }
.decision-actions { display: flex; flex-wrap: wrap; gap: .4rem; }
.decision-actions button { padding: .45rem .8rem; font-size: .64rem; flex: 1 1 auto; }

.waiting-note {
  border: 1px dashed rgba(125,147,171,.45); border-radius: 4px; padding: .55rem .7rem;
  font-size: .66rem; color: var(--dim); text-align: center; flex: 0 0 auto;
}
.waiting-note.hidden { display: none; }

/* dice */
.dice-wrap { display: flex; align-items: center; gap: .7rem; margin-bottom: .7rem; }
.die {
  width: 52px; height: 52px; border-radius: 8px; border: 1px solid var(--cyan);
  background: #071726; display: flex; align-items: center; justify-content: center;
  font-family: Orbitron, sans-serif; font-weight: 900; font-size: 1.5rem; color: var(--gold);
}
.die.rolling { animation: tumble .09s linear infinite; }
@keyframes tumble {
  0%   { transform: rotate(0deg)   scale(1);    }
  25%  { transform: rotate(-14deg) scale(1.06); }
  50%  { transform: rotate(10deg)  scale(.96);  }
  75%  { transform: rotate(-6deg)  scale(1.04); }
  100% { transform: rotate(0deg)   scale(1);    }
}
@media (prefers-reduced-motion: reduce) { .die.rolling { animation: none; opacity: .6; } }
.dice-them { font-size: .66rem; color: var(--dim); line-height: 1.5; }

.log-rail { display: none; }
.log-label { font-size: .58rem; letter-spacing: .22em; color: var(--dim); margin-bottom: .3rem; }
/* Transparent to the rail's flex layout: the log used to be a direct child of .rail and
   relies on flex:1/min-height:0 to scroll rather than expand. The wrapper (added so the
   whole thing can become a bottom sheet on mobile) has to pass that through. */
.log-drawer {
  display: flex; flex-direction: column; gap: .3rem;
  flex: 1 1 auto; min-height: 0;
}

.log {
  list-style: none; margin: 0; padding: .5rem;
  /* flex:1 + min-height:0 is what actually lets a flex child scroll instead of expanding */
  flex: 1 1 auto; min-height: 0; overflow-y: auto;
  border: 1px solid var(--edge); border-radius: 4px; background: var(--panel);
  font-size: .62rem; line-height: 1.45; color: var(--dim);
}
.log li { padding: .12rem 0; border-bottom: 1px solid rgba(125,147,171,.08); }
/* newest entry is now FIRST, so highlight that one */
.log li:first-child { color: var(--text); }
.log { scrollbar-width: thin; }

/* ---- fixed bottom: action bar + hand ----------------------------------- */
.actionbar {
  position: fixed; left: 0; right: 0; bottom: 104px; z-index: 26;
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  border-top: 1px solid var(--cyan); border-bottom: 1px solid var(--cyan);
  padding: .5rem 1.2rem; background: rgba(6,20,32,.97); font-size: .74rem;
}
.actionbar-actions { display: flex; gap: .5rem; flex: 0 0 auto; }
.actionbar button { padding: .45rem 1rem; font-size: .66rem; }

.hand-wrap.dropready { box-shadow: inset 0 3px 0 var(--cyan); background: rgba(8,30,44,.98); }
/* While reading a card the tray must rise ABOVE the action bar, or the zoomed card
   is clipped by it. Also allow the card to escape the tray's bounds. */
.hand-wrap:hover { z-index: 40; }
.hand { overflow: visible; }
.hand-wrap {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 24; height: 100px;
  overflow: visible;
  padding: .4rem 1.2rem; background: rgba(4,10,20,.97);
  border-top: 1px solid var(--edge); backdrop-filter: blur(8px);
}
.hand-label { font-size: .58rem; letter-spacing: .22em; color: var(--dim); margin-bottom: .3rem; }
.hand-label .hint { color: var(--cyan); letter-spacing: .05em; text-transform: none; margin-left: .6rem; }
.hand { display: flex; gap: .35rem; flex-wrap: nowrap; justify-content: center; }
.hand .card { flex: 0 0 auto; height: 74px; width: calc(74px * 5 / 7); }

/* topbar extras */
.topright { display: flex; align-items: center; gap: 1.2rem; }
.toggle { display: none; align-items: center; gap: .4rem; font-size: .66rem; color: var(--dim); cursor: pointer; }
.toggle.on { display: flex; }
.toggle input { width: auto; padding: 0; }

.pileview {
  position: fixed; inset: 0; z-index: 400; display: flex; align-items: center;
  justify-content: center; background: rgba(2,5,12,.88); backdrop-filter: blur(5px);
}
.pileview-box {
  width: min(560px, calc(100% - 3rem)); max-height: 78vh; display: flex; flex-direction: column;
  border: 1px solid var(--cyan); border-radius: 5px; background: #060d1b;
  box-shadow: 0 0 50px rgba(8,245,255,.25);
}
.pileview-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: .9rem 1.1rem; border-bottom: 1px solid var(--edge);
  font-family: Orbitron, sans-serif; font-size: .72rem; letter-spacing: .18em; color: var(--cyan);
}
.pileview-grid {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: .6rem;
  padding: 1rem; overflow-y: auto;
}
.pileview-grid .card { width: 100%; height: auto; aspect-ratio: 5 / 7; position: relative; }
.pileview-empty { padding: 2rem; text-align: center; color: var(--dim); font-size: .8rem; }

/* ---------------------------------------------------------------- choice */
.choice {
  position: fixed; inset: 0; z-index: 40; display: flex; align-items: center; justify-content: center;
  background: rgba(2,5,12,.82); backdrop-filter: blur(4px);
}
.choice-card {
  max-width: 560px; width: calc(100% - 2.5rem); padding: 1.6rem;
  max-height: 92vh; overflow-y: auto;
  border: 1px solid var(--cyan); border-radius: 5px; background: #060d1b;
  box-shadow: 0 0 50px rgba(8,245,255,.28);
}
.choice-tag {
  font-family: Orbitron, sans-serif; font-size: .62rem; letter-spacing: .28em; color: var(--cyan);
  margin-bottom: .7rem;
}
.choice-tag.lightspeed { color: var(--gold); }
.choice-prompt { margin: 0 0 1.1rem; font-size: .9rem; line-height: 1.55; color: #eaf6ff; }
.choice-options { display: flex; flex-wrap: wrap; gap: .45rem; margin-bottom: 1.2rem; }
/* Selection cards are bigger than a board card: you are reading them to decide. */
.choice-options .card { width: 104px; height: 146px; cursor: pointer; }
.choice-options .card.pickable:hover { transform: translateY(-3px); }
/* The selected state must be unmistakable — a thin border alone reads as "maybe". */
.choice-options .card.sel {
  border-color: var(--gold);
  outline: 3px solid var(--gold);
  box-shadow: 0 0 22px rgba(245,196,81,.6);
  transform: translateY(-5px);
}
.choice-options .card.sel::after {
  content: '✓'; position: absolute; top: 3px; right: 5px;
  font-size: .95rem; font-weight: 700; color: #1a1206;
  background: var(--gold); border-radius: 50%;
  width: 1.15rem; height: 1.15rem; display: grid; place-items: center;
}
/* At the cap, the cards you cannot add are visibly out of reach rather than silently inert. */
.choice-options .card.maxed { opacity: .4; cursor: not-allowed; }
.choice-options .card.maxed:hover { transform: none; }
.choice-count {
  width: 100%; font-family: Orbitron, sans-serif; font-size: .6rem;
  letter-spacing: .16em; color: var(--dim); padding-top: .5rem;
}
.choice-actions { display: flex; justify-content: flex-end; gap: .6rem; }
.choice-actions button[disabled] { opacity: .38; cursor: not-allowed; }

/* ---------------------------------------------------------------- responsive
   The rail must stay on the RIGHT and stay scrollable at narrow desktop widths —
   dropping it below the mats made the log unreachable at ~960px (half of 1920). */

/* narrow desktop / large tablet: keep the rail, just make it narrower */
@media (max-width: 1180px) {
  .table { grid-template-columns: minmax(0, 1fr) 210px; }
  .rail { font-size: .95em; }
  .decision-prompt { font-size: .68rem; }
  .decision-actions button { flex: 1 1 100%; }
}
@media (max-width: 980px) {
  .table { grid-template-columns: minmax(0, 1fr) 180px; padding: .5rem .5rem 0; }
  .log { font-size: .58rem; }
  .decision { padding: .5rem .55rem; }
}

/* phones / very narrow: the rail becomes a COLLAPSIBLE drawer over the board */
@media (max-width: 760px) {
  .table {
    grid-template-areas: "score" "foe" "mid" "you";
    grid-template-columns: minmax(0, 1fr);
    height: auto; padding-bottom: 8px;
  }
  /*
   * ⭐ THE REQUIRED ACTION IS NEVER HIDDEN.
   *
   * The whole rail — decision box AND log — used to be a closed drawer opened by the LOG
   * button, so on a phone the dice-roll buttons were simply off-screen and a new game
   * could not be started at all. What is tucked away now is the LOG, which is reference
   * material; the prompt you must answer is pinned to the top of the screen.
   */
  .rail {
    position: fixed; top: 46px; left: 0; right: 0; bottom: auto; width: auto;
    z-index: 60; padding: .45rem .5rem; gap: .4rem;
    background: rgba(4, 10, 20, .97); border-bottom: 1px solid var(--cyan);
    transform: none; max-height: 60vh; overflow-y: auto;
  }
  /* The log is the drawer: hidden until the LOG button is pressed. See `.log-drawer`
     further down for the open state — it leaves the rail and becomes a bottom sheet. */
  .rail .log-drawer { display: none; }
  /* With nothing to answer the panel must not sit there as an empty bar. */
  .rail:not(.open):has(.decision.hidden):has(.waiting-note.hidden) {
    background: none; border-bottom: 0; padding: 0; pointer-events: none;
  }
  .decision { font-size: .95em; }
  .decision-prompt { font-size: .74rem; }
  /* The drawer handle is shown by a LATER media block — see `.rail-toggle` below.
     Showing it here did nothing: the base `display: none` rule is further down the file
     and, at equal specificity, source order wins over being inside a media query. */
  .mat { max-width: 100%; }
  --card-h: clamp(44px, 11vw, 70px);
}
@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; }
  .rail { transition: none !important; }
}

/* Drawer handle — hidden on desktop (the log is simply the bottom of the rail there),
   shown by the phone breakpoint FURTHER DOWN. It must be later in the file than this
   rule, not earlier: at equal specificity source order beats a media query. */
.rail-toggle {
  display: none; position: fixed; right: 10px; z-index: 70;
  align-items: center; gap: .35rem;
  padding: .5rem .8rem; font-size: .62rem;
  background: rgba(6, 20, 32, .96); border: 1px solid var(--cyan); border-radius: 999px;
  font-family: Orbitron, sans-serif; letter-spacing: .1em; color: var(--cyan); cursor: pointer;
  box-shadow: 0 2px 12px rgba(0,0,0,.6);
}
.rail-toggle .dot {
  width: 7px; height: 7px; border-radius: 50%; background: var(--gold);
  box-shadow: 0 0 8px var(--gold);
}
.rail-toggle .dot.hidden { display: none; }

/* ---------------------------------------------------------------- game over
   The engine stops asking for decisions once it has a winner, so without an explicit
   result panel the table simply went quiet and read as a freeze. */
.gameover {
  position: fixed; inset: 0; display: grid; place-items: center;
  background: rgba(2,6,14,.82); backdrop-filter: blur(3px); z-index: 60;
}
.gameover.hidden { display: none; }
.gameover .go-card {
  background: linear-gradient(160deg, #0b1930, #060c18);
  border: 1px solid var(--cyan); border-radius: 6px;
  padding: 2rem 2.6rem; text-align: center; box-shadow: 0 0 40px rgba(8,245,255,.25);
}
.gameover.won .go-card { border-color: var(--gold); box-shadow: 0 0 46px rgba(245,196,81,.35); }
.gameover.lost .go-card { border-color: #6b3350; box-shadow: 0 0 30px rgba(160,60,90,.25); }
.go-tag {
  font-family: Orbitron, sans-serif; font-size: 1.5rem; letter-spacing: .3em;
  color: var(--cyan); margin-bottom: 1rem;
}
.gameover.won .go-tag { color: var(--gold); }
.gameover.lost .go-tag { color: #e0709a; }
.go-score { font-family: Orbitron, sans-serif; font-size: 2.6rem; color: #eaf6ff; }
.go-score i { opacity: .4; margin: 0 .6rem; font-style: normal; }
.go-sub { color: var(--dim); font-size: .75rem; margin: .5rem 0 1.5rem; letter-spacing: .12em; }

/* Build stamp: lets a cached client be spotted instantly rather than debugged. */
.buildstamp { font-size: .58rem; letter-spacing: .1em; color: var(--dim); opacity: .75; }

/* ------------------------------------------------------------- asking the player
   The game halts until a prompt is answered, so an un-noticed prompt reads as a freeze.
   Every decision box pulses; reduced-motion users get a static highlight instead. */
@keyframes ask-pulse {
  0%, 100% { border-color: var(--edge);  box-shadow: 0 0 0 rgba(8,245,255,0); }
  50%      { border-color: var(--cyan);  box-shadow: 0 0 22px rgba(8,245,255,.45); }
}
.decision.asking { animation: ask-pulse 1.8s ease-in-out infinite; }
.decision.asking.lightspeed { animation-name: ask-pulse-gold; }
@keyframes ask-pulse-gold {
  0%, 100% { border-color: var(--edge); box-shadow: 0 0 0 rgba(245,196,81,0); }
  50%      { border-color: var(--gold); box-shadow: 0 0 24px rgba(245,196,81,.5); }
}

/* ---------------------------------------------------------- Lightspeed notice
   Deliberately NOT a full-screen overlay: the player has to be able to look at the card
   their opponent just revealed while deciding whether to react to it. */
.ls-notice {
  position: fixed; left: 50%; transform: translateX(-50%);
  bottom: calc(104px + 1rem); z-index: 55;
  max-width: min(640px, 94vw);
  border: 1px solid var(--gold); border-radius: 5px;
  background: linear-gradient(160deg, rgba(26,18,6,.97), rgba(10,8,4,.97));
  box-shadow: 0 0 34px rgba(245,196,81,.35);
  animation: ls-pulse 1.4s ease-in-out infinite;
}
@keyframes ls-pulse {
  0%, 100% { box-shadow: 0 0 14px rgba(245,196,81,.25); border-color: rgba(245,196,81,.55); }
  50%      { box-shadow: 0 0 38px rgba(245,196,81,.6);  border-color: var(--gold); }
}
.ls-notice-body {
  display: flex; align-items: center; gap: 1.2rem; padding: .85rem 1.1rem; flex-wrap: wrap;
}
/* --- drag handle: the prompt floats over the board, so it must be movable ------
   The player needs to read the board and their hand BEFORE answering, and wherever this
   box sits by default it covers something. `touch-action: none` here (and only here) keeps
   the browser from stealing the gesture as a scroll. */
.ls-notice-grip {
  display: flex; align-items: center; justify-content: center; gap: .5rem;
  padding: .3rem 0 .1rem; cursor: grab; touch-action: none;
  border-bottom: 1px solid rgba(245,196,81,.2);
}
.ls-notice.dragging .ls-notice-grip { cursor: grabbing; }
.grip-bars {
  width: 34px; height: 3px; border-radius: 3px; background: rgba(245,196,81,.5);
  box-shadow: 0 5px 0 rgba(245,196,81,.5);
  margin-bottom: 5px;
}
.grip-hint {
  font-family: Orbitron, sans-serif; font-size: .5rem; letter-spacing: .14em;
  color: rgba(245,196,81,.55); text-transform: uppercase;
}
/* While being dragged the pulse is a distraction and the box should track the finger
   exactly — no transition, no transform offset. */
.ls-notice.dragging { animation: none; transition: none; opacity: .96; }
.ls-notice.moved { transform: none; }
.ls-notice-text { display: flex; flex-direction: column; gap: .15rem; }
.ls-notice-text b {
  font-family: Orbitron, sans-serif; font-size: .78rem; letter-spacing: .1em; color: var(--gold);
}
.ls-notice-text span { font-size: .76rem; color: var(--text); }
.ls-notice-text em { font-size: .7rem; color: var(--dim); font-style: normal; }
.ls-notice-actions { display: flex; gap: .5rem; margin-left: auto; }
.ls-notice-actions button { padding: .6rem 1.1rem; font-size: .72rem; }

/* --------------------------------------------- "what you are reacting to" panel */
.choice-split { display: flex; gap: 1.2rem; align-items: flex-start; }
.choice-main { flex: 1 1 auto; min-width: 0; }
.reacting {
  flex: 0 0 190px; border-left: 1px solid var(--edge); padding-left: 1.1rem;
  display: flex; flex-direction: column; gap: .45rem;
}
.reacting.hidden { display: none; }
.reacting-tag {
  font-family: Orbitron, sans-serif; font-size: .56rem; letter-spacing: .16em; color: var(--gold);
}
.reacting-card { width: 130px; height: 182px; }
.reacting-card .card { width: 100%; height: 100%; }
.reacting-name { font-family: Orbitron, sans-serif; font-size: .74rem; color: #eaf6ff; }
.reacting-meta { font-size: .6rem; letter-spacing: .1em; color: var(--dim); }
.reacting-ability {
  font-size: .68rem; line-height: 1.5; color: var(--text);
  background: rgba(0,0,0,.35); border: 1px solid var(--edge); border-radius: 3px;
  padding: .5rem .6rem; max-height: 190px; overflow-y: auto;
}

@media (prefers-reduced-motion: reduce) {
  .decision.asking, .ls-notice { animation: none; }
  .decision.asking { border-color: var(--cyan); box-shadow: 0 0 18px rgba(8,245,255,.35); }
  .ls-notice { border-color: var(--gold); box-shadow: 0 0 26px rgba(245,196,81,.45); }
}

@media (max-width: 760px) {
  .choice-split { flex-direction: column; }
  .reacting { flex: 1 1 auto; border-left: 0; border-top: 1px solid var(--edge); padding: .8rem 0 0; }
  .reacting-card { width: 104px; height: 146px; }
  .ls-notice { bottom: .6rem; }
  .grip-hint { font-size: .48rem; }
  .ls-notice-body { gap: .7rem; }
  .ls-notice-actions { margin-left: 0; width: 100%; }
  .ls-notice-actions button { flex: 1; }
}

/* With the "what you are reacting to" panel alongside, the modal needs room for two
   columns. Ordinary card choices keep the narrower single-column width. */
.choice-card:has(.reacting:not(.hidden)) { max-width: 860px; }
.reacting-ability { max-height: 220px; }

/* ------------------------------------------------------------- card details view
   Full-page overlay: art large enough to read, and every word also present as real text
   beside it so it can be selected, zoomed by the browser, or read aloud. */
.cardview {
  position: fixed; inset: 0;
  /* ABOVE the zone popups (.pileview is 400) and the selection modal (40). The details
     window is always opened FROM one of those, so it has to sit on top of them — and
     closing it must leave the window underneath still open. */
  z-index: 500;
  display: grid; place-items: center; padding: 2rem 1.2rem;
  background: rgba(2, 6, 14, .9); backdrop-filter: blur(4px);
  overflow-y: auto;
}
.cardview.hidden { display: none; }
.cardview:focus { outline: none; }
.cv-box {
  position: relative; display: flex; gap: 1.8rem; align-items: flex-start;
  max-width: 900px; width: 100%;
  background: linear-gradient(160deg, #0b1930, #060c18);
  border: 1px solid var(--cyan); border-radius: 6px;
  box-shadow: 0 0 60px rgba(8,245,255,.3);
  padding: 1.8rem;
}
.cv-close {
  position: absolute; top: .6rem; right: .6rem;
  width: 2.2rem; height: 2.2rem; padding: 0; line-height: 1;
  font-size: .9rem; border-radius: 50%;
}
.cv-art {
  flex: 0 0 300px; border-radius: 5px; overflow: hidden;
  border: 1px solid var(--edge); background: #04070e;
  display: grid; place-items: center; min-height: 200px;
}
.cv-art img { width: 100%; height: auto; display: block; }
.cv-info { flex: 1 1 auto; min-width: 0; }
.cv-name {
  font-family: Orbitron, sans-serif; font-size: 1.5rem; margin: 0 0 .4rem; color: #fff;
  text-shadow: 0 0 18px rgba(8,245,255,.4);
}
.cv-stats { font-size: .76rem; color: var(--cyan); letter-spacing: .06em; margin-bottom: .7rem; }
.cv-chips { display: flex; flex-wrap: wrap; gap: .35rem; margin-bottom: 1rem; }
.cv-chip {
  font-family: Orbitron, sans-serif; font-size: .56rem; letter-spacing: .12em;
  padding: .28rem .55rem; border: 1px solid var(--edge); border-radius: 999px; color: var(--dim);
}
.cv-chip.hot { border-color: var(--gold); color: var(--gold); }
.cv-h {
  font-family: Orbitron, sans-serif; font-size: .58rem; letter-spacing: .2em;
  color: var(--dim); margin: 1rem 0 .35rem; text-transform: uppercase;
}
/* A readonly textarea, not a <p>: selectable, focusable, reachable by read-aloud tools,
   and it scrolls instead of clipping long rules text. */
.cv-ability {
  width: 100%; resize: vertical; font-family: 'Azeret Mono', monospace;
  font-size: .86rem; line-height: 1.6; color: #eaf6ff;
  background: rgba(0,0,0,.4); border: 1px solid var(--edge); border-radius: 4px;
  padding: .7rem .8rem;
}
.cv-ability:focus { outline: none; border-color: var(--cyan); box-shadow: 0 0 14px rgba(8,245,255,.3); }
.cv-live, .cv-engine { font-size: .78rem; line-height: 1.55; color: var(--text); }
.cv-engine { color: var(--dim); }
.cv-hint { font-size: .64rem; color: var(--dim); margin: 1.2rem 0 0; }

/* A focused card must be obvious — this is the keyboard route into the details view. */
.card:focus-visible {
  outline: 2px solid var(--cyan); outline-offset: 2px;
  box-shadow: 0 0 16px rgba(8,245,255,.5);
}

@media (max-width: 760px) {
  .cv-box { flex-direction: column; gap: 1rem; padding: 1.2rem; }
  .cv-art { flex: 0 0 auto; width: min(260px, 70vw); margin: 0 auto; }
  .cv-name { font-size: 1.2rem; }
}

/* ------------------------------------------------ opponent's hand, as card backs
   How many cards the opponent holds is public and constantly relevant; a number in the
   corner is easy to miss. Backs overlap so a big hand stays compact. */
.foe-hand {
  display: flex; justify-content: center; align-items: flex-start;
  height: calc(var(--card-h) * .34); margin-bottom: .1rem; pointer-events: none;
}
.foe-hand .card {
  width: calc(var(--card-w) * .42); height: calc(var(--card-h) * .42);
  margin-left: calc(var(--card-w) * -.22);
  box-shadow: 0 2px 6px rgba(0,0,0,.6);
}
.foe-hand .card:first-child { margin-left: 0; }

/* The Extra Locations cell is double-clickable when it holds anything. */
.mcell.extra.clickable { cursor: zoom-in; }
.mcell.extra.clickable:hover { border-color: var(--cyan); }
.mcell.extra:focus-visible { outline: 2px solid var(--cyan); outline-offset: 2px; }

@media (max-width: 760px) {
  .foe-hand { height: calc(var(--card-h) * .3); }
}


/* ------------------------------------------- no hover-zoom inside the popup windows
   The zone popups and the selection modal already show cards at a readable size, and a
   card that leaps out from under the cursor makes a grid of them harder to scan, not
   easier. Double-click still opens the full details view. */
.pileview-grid .card.zoomable:hover,
.choice-options .card.zoomable:hover,
.cv-art .card.zoomable:hover,
.reacting .card.zoomable:hover {
  transform: none;
  z-index: auto;
}
.pileview-grid .card.zoomable, .choice-options .card.zoomable { cursor: pointer; }
/* Keep the selection lift on picked cards — that is state, not magnification. */
.choice-options .card.sel:hover { transform: translateY(-5px); }

/* ------------------------------------------------------- game over: dismissible
   The end of a game is exactly when a player wants to study the final board, so the
   result panel closes and leaves a badge that brings it back. */
.gameover .go-card { position: relative; }
.go-code {
  font-size: .62rem; letter-spacing: .08em; color: var(--dim); margin-bottom: 1.2rem;
}
.go-code b { color: var(--cyan); letter-spacing: .18em; }
.go-actions { display: flex; gap: .6rem; justify-content: center; flex-wrap: wrap; }

.go-badge {
  position: fixed; top: 56px; right: 12px; z-index: 58;
  font-family: Orbitron, sans-serif; font-size: .6rem; letter-spacing: .14em;
  padding: .5rem .9rem; border-radius: 999px;
  border: 1px solid var(--cyan); background: rgba(4,10,20,.92); color: var(--cyan);
  box-shadow: 0 0 18px rgba(8,245,255,.25); cursor: pointer;
}
.go-badge.won  { border-color: var(--gold); color: var(--gold); box-shadow: 0 0 18px rgba(245,196,81,.3); }
.go-badge.lost { border-color: #6b3350; color: #e0709a; }
.go-badge:hover { background: rgba(8,245,255,.12); }

@media (max-width: 760px) {
  .go-badge { top: auto; bottom: .6rem; right: .6rem; }
}

/* ------------------------------------------------------------ board targeting
   Choosing an opponent's card happens ON the battlefield: position is part of the
   decision, and a face-down unit must stay a card back while you pick it. */
body.targeting .card.targetable {
  cursor: crosshair;
  outline: 2px dashed var(--cyan);
  outline-offset: 2px;
  animation: target-pulse 1.6s ease-in-out infinite;
}
@keyframes target-pulse {
  0%, 100% { box-shadow: 0 0 6px rgba(8,245,255,.35); }
  50%      { box-shadow: 0 0 20px rgba(8,245,255,.75); }
}
body.targeting .card.targetable.sel {
  outline: 3px solid var(--gold); animation: none;
  box-shadow: 0 0 24px rgba(245,196,81,.65);
}
body.targeting .card.targetable.sel::after {
  content: '✓'; position: absolute; top: 2px; right: 3px;
  width: 1rem; height: 1rem; border-radius: 50%; display: grid; place-items: center;
  background: var(--gold); color: #1a1206; font-size: .7rem; font-weight: 700;
}
/* Everything that is NOT a legal target recedes, so the eye goes to the choices. */
body.targeting .card:not(.targetable) { opacity: .45; }
body.targeting .card.targetable:focus-visible { outline-color: #fff; }
.decision.targeting { border-color: var(--cyan); }

@media (prefers-reduced-motion: reduce) {
  body.targeting .card.targetable { animation: none; box-shadow: 0 0 14px rgba(8,245,255,.5); }
}

/* The Celestial sits in a single-card cell, so its zoom needs the same escape as the
   Locations, and an origin that keeps it on screen. */
.mcell.celestial .card.zoomable { transform-origin: center center; }
.foe-mat .mcell.celestial .card.zoomable:hover { transform-origin: center top; }

/* "Battle X of Y" — Y is live, so a Celestial adding a Battle is visible immediately. */
.phase .battle b { color: var(--cyan); font-weight: 700; }

/* ------------------------------------------------------- mobile topbar
   On a phone the brand, build stamp, auto-flip toggle and session chip were all crammed
   onto one row and ran together. Give them two clear lines and real spacing. */
@media (max-width: 760px) {
  .topbar {
    flex-direction: column; align-items: stretch; gap: .35rem;
    padding: .5rem .7rem;
  }
  .brand { font-size: .8rem; letter-spacing: .1em; text-align: center; }
  .topright {
    display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
    gap: .5rem .9rem;
  }
  .buildstamp { order: 3; width: 100%; text-align: center; font-size: .52rem; }
  .toggle { display: inline-flex; align-items: center; gap: .35rem; font-size: .66rem; }
  .toggle input { width: 1.1rem; height: 1.1rem; }   /* a real tap target */
  .session { font-size: .64rem; letter-spacing: .06em; line-height: 1.5; text-align: center; }
  .session b { letter-spacing: .18em; }

  /* Measured from the real topbar (see syncRailOffset), because its height depends on the
     deck name, the device font and how the lines wrap — a fixed value clears it on one
     phone and covers the session chip on another. The fallback matches a 3-line bar. */
  .rail { top: var(--rail-top, 124px); }
  /* .rail-toggle is NOT offset by --rail-top — it sits in the very top-right corner,
     above the topbar. See the mobile game log block at the end of this file. */
  /* Collapsed sits a little lower, and the board is pushed down to make room, so the pill
     covers nothing at all — the scoreboard was still underneath it otherwise. */
  .rail.collapsed { top: calc(var(--rail-top, 124px) + 4px); }
  body.rail-collapsed .table { padding-top: 42px; }
  /* keep the board clear of the pinned panel */
  .table { padding-top: 4px; }
}

/* ------------------------------------------------------------- touch dragging
   HTML5 drag-and-drop never fires from a finger, so touch has its own path (app.js).
   `touch-action: none` on a draggable card stops the browser claiming the gesture as a
   scroll before the drag is recognised — without it the page moves and the card does not. */
.card.draggable { touch-action: none; }

/* The card follows the finger as a floating copy, because a finger covers the original. */
.touch-ghost {
  position: fixed; z-index: 400; pointer-events: none;
  opacity: .92; transform: scale(1.06);
  box-shadow: 0 8px 24px rgba(0,0,0,.65), 0 0 20px rgba(8,245,255,.35);
  transition: none;
}
/* Bigger drop targets for fingers while a touch drag is in progress. */
body.dragging .loc[data-drop] { outline: 1px dashed rgba(8,245,255,.5); outline-offset: 1px; }

/* ------------------------------------------- minimising the pinned mobile prompt
   The panel is pinned over the board so a required action can never be missed — which
   also means it covers the scoreboard. It collapses to a pulsing pill that still says
   something is waiting; hiding it outright would bring back the "the game looks frozen"
   failure it exists to prevent. */
.rail-min { display: none; }

@media (max-width: 760px) {
  .decision { position: relative; padding-right: 2.4rem; }
  .rail-min {
    display: block; position: absolute; top: .3rem; right: .3rem;
    width: 1.9rem; height: 1.9rem; padding: 0; line-height: 1;
    font-size: .8rem; border-radius: 4px;
    border: 1px solid var(--edge); background: rgba(0,0,0,.35); color: var(--cyan);
  }

  /* collapsed: a compact tappable pill, top-right, clear of the topbar */
  .rail.collapsed {
    left: auto; right: .4rem; width: auto; max-width: 62vw;
    background: none; border-bottom: 0; padding: 0; overflow: visible;
  }
  .rail.collapsed .decision {
    padding: .4rem 2.2rem .4rem .7rem;
    border-radius: 999px; background: rgba(4,10,20,.97);
    animation: ask-pulse 1.8s ease-in-out infinite;
  }
  .rail.collapsed .decision-prompt,
  .rail.collapsed .decision-actions,
  .rail.collapsed .dice-wrap,
  .rail.collapsed .waiting-note,
  .rail.collapsed .log-drawer,
  .rail.collapsed .log-label,
  .rail.collapsed .log { display: none; }
  .rail.collapsed .decision-tag {
    margin: 0; font-size: .58rem; white-space: nowrap;
    overflow: hidden; text-overflow: ellipsis;
  }
  .rail.collapsed .rail-min { top: 50%; transform: translateY(-50%); border: 0; background: none; }
  /* nothing pending: no pill at all */
  .rail.collapsed:has(.decision.hidden) { display: none; }
}

/* ------------------------------------------------- touch: no stuck hover zoom
   A touch browser keeps :hover on the last element tapped until something else is tapped.
   With the zoom escaping its cell that left a card blown up over the battlefield after
   every discard, and you had to tap elsewhere to clear it. On touch the card details view
   (double-tap) is the way to read a card; hover magnification is a mouse affordance. */
@media (hover: none), (pointer: coarse) {
  .card.zoomable:hover { transform: none !important; z-index: auto !important; box-shadow: none; }
  .mcell:has(.card.zoomable:hover) { overflow: hidden; z-index: auto; }
  .choice-options .card.pickable:hover { transform: none; }
}

/* ------------------------------------------------- mobile: scrollable hand
   Ten cards do not fit across a phone, and the ends were simply cut off. The hand scrolls
   sideways instead.

   ⚠️ THE GESTURE CONFLICT: `touch-action: none` on a draggable card (needed so a drag is
   not stolen by the browser as a scroll) also kills scrolling when the finger starts ON a
   card — which is nearly everywhere in a full hand. `pan-x` hands HORIZONTAL swipes back to
   the browser to scroll the hand, while VERTICAL movement is left for the drag handler,
   which is the direction you pull a card out of your hand anyway. */
@media (max-width: 760px) {
  .hand {
    overflow-x: auto; overflow-y: hidden;
    justify-content: safe center;
    scroll-padding: 0 .6rem;
    -webkit-overflow-scrolling: touch;
    padding-bottom: .25rem;
  }
  .hand::-webkit-scrollbar { height: 3px; }
  .hand::-webkit-scrollbar-thumb { background: rgba(8,245,255,.45); border-radius: 3px; }
  .hand { scrollbar-width: thin; scrollbar-color: rgba(8,245,255,.45) transparent; }
  .hand .card.draggable { touch-action: pan-x; }
  /* a staged card sits on the board, where there is nothing to scroll */
  .loc .card.draggable { touch-action: none; }
  .hand-wrap { height: 112px; }
}

/* =============================================================== mobile game log
 * The log is reference material, so on a phone it stays out of the way and is opened
 * from a pinned LOG button.
 *
 * ⚠️ This block must stay AFTER the base `.rail-toggle { display: none }` rule. The
 * original mobile rule sat BEFORE it, so at equal specificity source order won and the
 * button never rendered at all — the log was simply unreachable on a phone.
 *
 * The button is pinned bottom-right above the hand tray, and the log opens as a bottom
 * sheet from the same corner. The top of the screen belongs to the prompt panel and its
 * minimise button; a handle up there overlapped them.
 */
.log-close { display: none; }

@media (max-width: 760px) {
  /* Very top-right corner. The topbar centres its own content there, so that corner is
     the one piece of screen nothing else claims — it covers no part of the board, no
     prompt, and no card. z-index clears the sticky topbar (20) and the rail (60). */
  .rail-toggle {
    display: flex; top: 6px; right: 8px; bottom: auto;
    padding: .4rem .6rem; font-size: .56rem;
  }
  .rail-toggle[aria-expanded="true"] { background: var(--cyan); color: #04121c; }

  /* Open: a bottom sheet. The button is at the top and the sheet is at the bottom, which
     is deliberate — the top of the screen is where the prompt panel is pinned, and the
     log must never cover the thing you have to answer. The button turns solid while the
     sheet is open so the two still read as one control. */
  .rail.open .log-drawer {
    display: flex; flex-direction: column;
    position: fixed; z-index: 69;
    left: .4rem; right: .4rem;
    bottom: calc(var(--tray-h, 112px) + .5rem);
    max-height: min(46vh, 340px);
    background: rgba(4, 10, 20, .98);
    border: 1px solid var(--cyan); border-radius: 6px;
    box-shadow: 0 6px 28px rgba(0,0,0,.7);
  }
  .rail.open .log-drawer .log-label {
    display: flex; align-items: center; justify-content: space-between;
    margin: 0; padding: .5rem .6rem .4rem; border-bottom: 1px solid var(--edge);
  }
  .rail.open .log-drawer .log {
    display: block; flex: 1 1 auto; min-height: 0;
    max-height: none; overflow-y: auto; padding: .5rem .6rem .6rem; margin: 0;
  }
  .log-close {
    display: block; width: 1.8rem; height: 1.8rem; padding: 0; line-height: 1;
    font-size: .7rem; border-radius: 4px;
    border: 1px solid var(--edge); background: rgba(0,0,0,.35); color: var(--cyan);
  }
  /* The sheet is its own surface — the rail's top-pinned panel styling must not leak in
     while it is open and the prompt is not showing. */
  .rail.open:has(.decision.hidden):has(.waiting-note.hidden) {
    background: none; border-bottom: 0; padding: 0;
  }
}

/* ------------------------------------------- mobile: two options sit SIDE BY SIDE
 * Stacked full-width buttons put the two answers directly above one another, a few
 * millimetres apart — a thumb that lands slightly high hits the wrong one, and these
 * answers are not undoable. Side by side, the two live on opposite halves of the screen,
 * so a miss lands on nothing rather than on the other choice.
 *
 * `:has(button:nth-child(2):last-child)` = EXACTLY two buttons. Three or more keep
 * wrapping full-width: splitting those into slivers would make each target smaller, which
 * is the problem this rule exists to solve.
 *
 * Must stay after the max-width:1180px `flex: 1 1 100%` rule that forces the stacking.
 */
@media (max-width: 760px) {
  .decision-actions:has(button:nth-child(2):last-child) {
    flex-wrap: nowrap; gap: .75rem;
  }
  .decision-actions:has(button:nth-child(2):last-child) button {
    flex: 1 1 0; min-width: 0;
    padding: .75rem .4rem;          /* a taller target, since it is now narrower */
    white-space: normal; line-height: 1.25;
  }
}

/* --------------------------------------------------- log: cause, then consequence
   Each entry reads "<battle> <the card that did it> <what happened>". The source card is
   the part a player scans for when asking "what put this in my discard pile", so it is
   the part that gets the colour. */
.log-battle {
  color: rgba(125,147,171,.55); margin-right: .4rem;
  font-variant-numeric: tabular-nums;
}
.log-src {
  color: var(--cyan); font-weight: 600; margin-right: .35rem;
}
.log-src::after { content: ' ·'; color: rgba(125,147,171,.5); font-weight: 400; }

/* ------------------------------------------------ deck import: live feedback
   The field takes a share LINK or a bare share CODE. Saying which one it recognised
   before the game starts turns "it didn't work" into "that isn't a deck code". */
.hint-line {
  min-height: 1rem; margin-top: .3rem;
  font-size: .58rem; letter-spacing: .04em; color: var(--dim);
}
.hint-line.ok { color: var(--cyan); }
.hint-line.bad { color: #e0709a; }
.or.small em { font-style: normal; color: var(--cyan); }
