/* Build-a-Bot. Burke Bot's visual language, verbatim - same tokens, same three
   faces, same card language - because this is a companion page and should feel
   like one. Everything is sized in rem so the whole page scales with the window
   from one root clamp.

   Generated from the approved mockup; edit here, not there. */
/* Burke Bot's own tokens and both its themes, verbatim - tournament green, the
   club vinyl board.

   DARK ON EVERY LOAD. There is deliberately no prefers-color-scheme branch and
   nothing read from storage at boot, so a fresh visit is always dark whatever
   the operating system or the last visit preferred. The toggle then stamps
   data-theme="light" for the rest of that session. Because the default needs no
   script, there is no flash of the wrong theme before it settles. */
:root{
  --bg:#162117; --surface:#1e2b1f; --surface-2:#26362a; --border:#3a4f3c;
  --ink:#edf1e4; --ink-2:#aab8a5; --accent:#8fc57f; --accent-rgb:143,197,127;
  --accent-dim:#527a4b; --q-good:#709f65; --sq-light:#e9ecd6; --sq-dark:#527a4b;
  --warn:#d9a441;
  color-scheme:dark;
}
:root[data-theme="light"]{
  --bg:#f0f2e4; --surface:#fafbf2; --surface-2:#e7ecd8; --border:#c6d1b2;
  --ink:#1e2a1d; --ink-2:#55684f; --accent:#3f5e3a; --accent-rgb:63,94,58;
  --q-good:#486c42; --warn:#8a5e12;
  color-scheme:light;
}

*{box-sizing:border-box}
/* Burke Bot's own scaling rule, verbatim: on a desktop the type tracks the
   window so the whole page keeps one set of proportions, and because every
   size on this page is in rem, everything grows and shrinks together. A
   1440x900 laptop is the reference - 900 * 0.01778 = 16px, the browser
   default - so that view is unchanged. Phones are left out on purpose. */
@media (min-width:861px){
  html{font-size:clamp(13px, min(1.78vh, 1.36vw), 24px)}
}
body{
  margin:0; background:var(--bg); color:var(--ink);
  font-family:"IBM Plex Mono",ui-monospace,SFMono-Regular,Menlo,monospace;
  font-size:0.94rem; line-height:1.6;
  padding:0 1.25rem 4.5rem;
}
.wrap{max-width:52rem; margin:0 auto}


/* The header is a GRID, the way Burke Bot's is, rather than a wordmark with the
   buttons absolutely positioned over it. Absolute positioning needs a fixed top
   offset, and a fixed pixel offset inside a page whose type scales with the
   window means the buttons drift away from the title as it grows. In a grid the
   two are siblings in one row and stay tied at every size. */
header{
  display:grid; grid-template-columns:1fr auto;
  align-items:center; column-gap:1.2rem;
  padding-block:2.75rem .5rem;
}
.wordmark{grid-column:1; grid-row:1}
.from{grid-column:1 / -1; grid-row:2}
.wordmark{
  font-family:"Alfa Slab One",Georgia,serif; font-weight:400;
  /* no bottom margin: the grid row owns the spacing, and a margin here shifts
     the wordmark's box centre below the buttons' so they stop lining up */
  font-size:clamp(1.9rem,4.6vw,2.6rem); margin:0; letter-spacing:.01em;
  text-wrap:balance;
}
.from{
  margin:.9rem 0 0; font-size:.86rem; color:var(--ink-2);
}
.from a{color:var(--accent); text-decoration:none; border-bottom:1px solid transparent}
.from a:hover{border-bottom-color:var(--accent)}
.header-actions{
  grid-column:2; grid-row:1; justify-self:end; align-self:center;
  display:flex; align-items:center; gap:.5rem;
}
.icon-btn{
  width:2.1rem; height:2.1rem; padding:0; display:flex; align-items:center;
  justify-content:center; border:1px solid var(--border); border-radius:8px;
  background:var(--surface); color:var(--ink-2); cursor:pointer;
  transition:border-color .15s,color .15s;
}
.icon-btn:hover{border-color:var(--accent); color:var(--ink)}
/* Sound only exists where there is something to hear. Setup, building and the
   result page play nothing, so the control is hidden there rather than sitting
   in the corner doing nothing; the shared page has a live board, so it keeps
   it. Hidden, not removed - the same button is the one the board uses. */
#sound-toggle{display:none}
body[data-route="shared"] #sound-toggle{display:flex}
.icon-btn svg{width:1rem; height:1rem}
/* Only the board view can be shared, so the control only exists there. It is
   first in DOM order, which puts it at the left of the right-aligned group on a
   desktop and at the far left once the row switches to space-between. */
.share-bot{display:none}
body[data-route="shared"] .share-bot{
  display:inline-flex; align-items:center; gap:.4rem;
  font:inherit; font-size:.82rem;
  padding:0 .7rem; height:2.1rem;
  border:1px solid var(--border); border-radius:8px;
  background:var(--surface); color:var(--ink-2); cursor:pointer;
  transition:border-color .15s, color .15s, background .15s;
}
body[data-route="shared"] .share-bot:hover{border-color:var(--accent); color:var(--ink)}
body[data-route="shared"] .share-bot.done{
  color:var(--accent); border-color:var(--accent);
  background:rgba(var(--accent-rgb),.15); background-clip:padding-box;
  transition:none;
}
.share-bot svg{flex:none; width:.9rem; height:.9rem}
.share-bot.done svg polyline{
  stroke-dasharray:100; stroke-dashoffset:0;
  animation:tick-write 260ms cubic-bezier(.65,0,.35,1);
}
@media (max-width:640px){
  /* Share goes hard left, the two utilities stay together on the right.
     space-between would distribute all THREE evenly and leave the sound button
     stranded mid-row, so instead the row packs from the left and a single auto
     margin on the first icon pushes that pair - and only that pair - right. */
  body[data-route="shared"] .header-actions{
    grid-column:1 / -1; justify-self:stretch; justify-content:flex-start;
  }
  body[data-route="shared"] .share-bot + .icon-btn{margin-left:auto}
}

.card{
  background:var(--surface); border:1px solid var(--border); border-radius:12px;
  padding:1.05rem 1.15rem; margin-top:1.15rem;
}
.card h2{
  font-family:Fraunces,Georgia,serif; font-weight:600; font-size:1.06rem;
  margin:0 0 .15rem;
}
.card .sub{color:var(--ink-2); font-size:.86rem; margin:0 0 1rem}

label.lbl{
  display:block; font-size:.72rem; letter-spacing:.13em; text-transform:uppercase;
  color:var(--ink-2); margin-bottom:.4rem;
}
/* Setup centres its controls; only each section's own heading and description
   stay left, so the eye still has a fixed edge to read down. */
#setup .card > *:not(h2):not(.sub):not(.facts){text-align:center}
#setup .toggles{justify-content:center}
/* The assurances sit ABOVE the button: read the promises, then act on them.
   Both are centred as blocks while the list keeps its own left-aligned text, so
   the ticks form a straight column instead of ragging around the centre. */
#setup .actions{
  display:flex; flex-direction:column; align-items:center;
  gap:1.15rem; margin-top:2.3rem;   /* clear of the tick row, not just the track */
}
#setup .slider-head{justify-content:flex-start}
#setup .readout{text-align:left}
#setup .acct .lbl{text-align:center}
#setup .field{justify-content:center}
#setup .field input{text-align:center; flex:0 1 auto; width:auto; max-width:100%}
.accounts{display:grid; grid-template-columns:1fr 1fr; gap:.85rem; max-width:34rem; margin:0 auto}
.acct{
  background:var(--surface-2); border:1px solid var(--border);
  border-radius:10px; padding:.7rem .75rem;
}
.acct .lbl{color:var(--accent); margin-bottom:.45rem}
@media (max-width:560px){ .accounts{grid-template-columns:1fr} }
.field{
  display:flex; align-items:center; gap:.5rem; background:var(--bg);
  border:1px solid var(--border); border-radius:7px; padding:.45rem .65rem;
}
.field:focus-within{border-color:var(--accent)}
.field input{
  flex:1; min-width:0; border:0; background:none; color:var(--ink);
  font:inherit; font-size:.94rem; outline:none;
}
.field input::placeholder{color:var(--ink-2); opacity:.62}
.field .ok{color:var(--accent); font-size:.8rem; white-space:nowrap}

.speeds{display:flex; flex-wrap:wrap; justify-content:center; gap:.55rem; margin-top:.3rem}
.speed{
  width:10.5rem; display:flex; align-items:center; gap:.55rem; cursor:pointer;
  border:1px solid var(--border); border-radius:8px; padding:.5rem .65rem;
  background:var(--bg); transition:border-color .15s,background .15s;
}
.speed:hover{border-color:var(--accent-dim)}
.speed input{accent-color:var(--accent); width:1rem; height:1rem; margin:0; cursor:pointer}
.speed .nm{font-size:.9rem}
.speed .ct{margin-left:auto; color:var(--ink-2); font-size:.8rem; font-variant-numeric:tabular-nums}
.speed.off .nm,.speed.off .ct{opacity:.55}

.toggles{display:flex; flex-wrap:wrap; gap:1.1rem; margin-top:1.1rem}
.tg{display:flex; align-items:center; gap:.5rem; cursor:pointer; font-size:.9rem}
.tg input{accent-color:var(--accent); width:1rem; height:1rem; margin:0; cursor:pointer}

/* the slider: two numbers that move together */
.slider-head{display:flex; align-items:baseline; justify-content:space-between; gap:1rem; flex-wrap:wrap}
.readout{font-size:.95rem; font-variant-numeric:tabular-nums}
.readout b{color:var(--ink); font-weight:500}
.readout .sep{color:var(--ink-2); margin:0 .45rem}
.readout .q{font-weight:500}
.q.good{color:var(--q-good)}
.q.best{color:var(--accent)}
.q.ok{color:var(--ink)}
.q.rough{color:var(--warn)}
.q.bad{color:#d98c73}
input[type=range]{
  width:100%; accent-color:var(--accent); margin:.85rem 0 .3rem; cursor:pointer;
}
.ticks{display:flex; justify-content:space-between; font-size:.72rem; color:var(--ink-2)}

.btn{
  font:inherit; font-size:1.06rem; padding:.55rem 1.05rem; border-radius:8px;
  border:1px solid var(--border); background:var(--surface); color:var(--ink);
  cursor:pointer; transition:border-color .15s,background .15s,transform .1s;
}
.btn:hover{border-color:var(--accent); background:var(--surface-2); transform:translateY(-1px)}
.btn.primary{border-color:var(--accent); color:var(--accent)}
/* A disabled action must not look like an available one. Without this the
   "Build my bot!" button kept its accent border and hover lift while being
   genuinely unclickable, which reads as a broken page rather than a blocked one. */
.btn:disabled{
  opacity:.45; cursor:not-allowed;
  border-color:var(--border); color:var(--ink-2);
}
.btn:disabled:hover{
  background:var(--surface); border-color:var(--border);
  color:var(--ink-2); transform:none;
}

.actions{margin-top:1.2rem; display:flex; gap:2.2rem; align-items:center; flex-wrap:wrap}
.assure{
  list-style:none; margin:0; padding:0; text-align:left;
  font-size:.74rem; line-height:1.5; color:var(--ink-2);
}
/* Baseline-aligned flex rather than an absolutely positioned glyph with a
   guessed offset: the marker and the text then sit on one baseline by
   construction, at any size, in any font. Wrapped text hangs under the text
   rather than under the tick, which is what a checklist should do. */
.assure li{display:flex; align-items:baseline; gap:.42rem}
.assure li::before{
  content:"\2713"; flex:none; width:.72rem; text-align:center;
  color:var(--accent); font-size:1.106rem; line-height:1;
  /* the tick's ink floats above the baseline; drop it by its own descent so
     its foot lands ON the baseline, level with the capitals beside it */
  transform:translateY(.093em);
}

/* What the archive scan found, as two statements rather than one paragraph:
   one thing gained, one thing lost. Same tick as the assurances above, and the
   same cross the failed build step uses, so a reader who has seen either one
   already knows which way each line points. Left-aligned against the card's
   heading while the chips and toggles above stay centred. */
.facts{
  list-style:none; margin:.35rem 0 0; padding:0; text-align:left;
  font-size:.8rem; line-height:1.55; color:var(--ink-2);
}
.facts li{display:flex; align-items:baseline; gap:.45rem}
/* The row's text is ONE flex item. Without this wrapper a <b> inside it becomes
   a flex item of its own and the sentence lays out as columns - which looked
   fine on a desktop because it fitted on one line, and fell apart on a phone. */
.facts li > .t{flex:1 1 auto; min-width:0}
.facts li + li{margin-top:.2rem}
/* Sized so each glyph's INK is the height of a capital letter beside it -
   measured, not guessed, and the two need different sizes because the cross
   carries less ink per em than the tick. */
.facts li::before{flex:none; width:.8rem; text-align:center; line-height:1; transform:translateY(.093em)}
.facts li.yes::before{content:"\2713"; color:var(--accent); font-size:1.195rem}
.facts li.no::before{content:"\00d7"; color:var(--warn); font-size:1.316rem}
/* Work still happening, not a fact yet. The same ring the build page's running
   step uses, down to the keyframe, so "this is going" looks identical wherever
   it appears. The global reduced-motion rule stops the spin and leaves the
   accent edge, which still reads as in-progress against a tick. */
.facts li.busy::before{
  /* one cap-height tall, so it occupies exactly the band the capitals do. As a
     flex item with no text its baseline is its bottom edge, so baseline
     alignment puts its foot on the text's baseline and no nudge is needed. */
  content:""; width:.56rem; height:.56rem;
  /* Baseline alignment puts a circle on the CAPITAL band, and beside a line of
     mostly lowercase text that reads a pixel high, so it wants nudging down.
     NOT with transform: step-spin animates transform, so a static translate on
     the same element does not stack with it - it is the FROM value the rotation
     interpolates away from, which makes the wheel bob up and down instead of
     spinning. Relative positioning moves it without touching the property the
     animation owns. */
  position:relative; top:.1em;
  /* And the glyph rule's translate must be cancelled, not inherited: it is the
     same property step-spin animates, so leaving it in place is what made the
     wheel bob instead of turn. none = identity, which is the base the rotation
     is meant to start from - the build page's wheel works precisely because
     nothing sets a transform on it. */
  transform:none;
  border:2px solid var(--border); border-top-color:var(--accent);
  border-radius:50%; animation:step-spin .8s linear infinite;
}
.facts b{color:var(--ink)}

/* the shared-link landing page: Burke Bot's board column, nothing else.
   The eval gutter is carried over so the player rows, the board and the resign
   button all line up on the same left edge the board has. */
/* On the shared route the page's own wordmark goes, but the sound and theme
   controls stay - they are utilities, not branding, and the page renders in
   both themes. The header collapses to just enough room to hold them. */
body[data-route="shared"] .wordmark,
body[data-route="shared"] .from{display:none}
/* On the shared route the cards are narrower than the page and centred, so the
   header has to take the same width or the theme button's right edge sits out
   past the boxes beneath it. The footer already borrows --shared-card for the
   same reason; this puts all three on one pair of edges. */
body[data-route="shared"] header{
  padding-block:1rem 0;
  width:min(var(--shared-card), 100%); margin-left:auto; margin-right:auto;
}
body[data-route="shared"] .shared-top{padding-block:.2rem 1.1rem}
.from-shared{margin:.3rem 0 0; font-size:.86rem; color:var(--ink-2)}
.from-shared a{color:var(--accent); text-decoration:none; border-bottom:1px solid transparent}
.from-shared a:hover{border-bottom-color:var(--accent)}
/* No percentage lives in these custom properties on purpose: a percentage
   inside one resolves against whichever element reads it, so the board row and
   the resign row were each computing a different width from the same variable.
   The CARD caps the width once; everything inside simply fills it. */
/* 41rem is the width at which the "Same machinery as Burke Bot..." line fits
   on one line; measured, not guessed. The footer borrows it so its rule and
   its copyright line land on the cards' own two edges. */
:root{--shared-card:41rem}
#shared{
  --eval-w:3.85rem; --eval-bar-x:2.41rem;
  --colmax:calc(var(--shared-card) - 2.3rem);
}
#shared .card{
  width:min(var(--shared-card), 100%);
  margin-left:auto; margin-right:auto;
}
body[data-route="shared"] footer{
  width:min(var(--shared-card), 100%); margin-left:auto; margin-right:auto;
}
.play-card{padding-bottom:1.2rem}

/* The board, its player rows, the eval gutter and the resign button all share
   one width and one centred axis, so the whole column reads as a single object. */
#shared .player,
#shared .board-wrap,
#shared .controls-row{width:100%}
.player{
  display:flex; align-items:center; gap:.55rem;
  padding-left:var(--eval-w); min-height:2.15rem;
}
.player:first-of-type{margin-bottom:.5rem}
/* Burke Bot's treatment, so the same two drawings read the same on both boards:
   the artwork fills the tile, and there is no border - both avatars set it to
   their own background anyway, so it only shrank the drawing by a pixel a side. */
.avatar{
  flex:none; width:2rem; height:2rem; border-radius:6px; overflow:hidden;
  display:flex; align-items:center; justify-content:center;
  background:var(--sq-dark); border:0; color:var(--ink-2);
}
.avatar svg{width:100%; height:100%; display:block; shape-rendering:geometricPrecision}
.avatar.bot{
  background:var(--sq-dark); border-color:var(--sq-dark);
  color:var(--sq-light); font-size:1.3rem; line-height:1;
}
.pname{font-size:.92rem; font-weight:500; color:var(--ink); white-space:nowrap}
.caps{
  margin-left:.35rem; color:var(--ink-2); font-size:1.2rem; line-height:1;
  /* IBM Plex Mono has no chess glyphs, so these need an explicit fallback or
     they land in whatever the system picks and render as blobs */
  font-family:"Apple Symbols","Segoe UI Symbol","Noto Sans Symbols 2",serif;
  letter-spacing:-.07em;
}

.board-wrap{position:relative; padding-left:var(--eval-w)}
.eval-num{
  position:absolute; left:-.42rem; width:2.52rem; top:50%; transform:translateY(-50%);
  text-align:right; font-size:.82rem; font-weight:700; color:var(--ink);
  font-variant-numeric:tabular-nums;
}
.eval-bar{
  position:absolute; left:var(--eval-bar-x); top:0; bottom:0; width:.94rem;
  border-radius:5px; overflow:hidden; background:#23261f; border:1px solid var(--border);
}
.eval-fill{position:absolute; left:0; right:0; bottom:0; height:54%; background:var(--sq-light)}
.board{
  width:100%; aspect-ratio:1; display:grid;
  grid-template-columns:repeat(8,1fr); grid-template-rows:repeat(8,1fr);
  border:1px solid var(--border); border-radius:8px; overflow:hidden;
}
.sq{display:flex; align-items:center; justify-content:center; font-size:min(2rem,5vw); line-height:1}
.sq.l{background:var(--sq-light)} .sq.d{background:var(--sq-dark)}
.sq.w{color:#f8f8f2; text-shadow:0 0 1px rgba(0,0,0,.65)}
.sq.b{color:#22271f}
:root[data-theme="light"] .board-overlay{background:rgba(240,242,228,.76)}
.board-overlay{
  position:absolute; inset:0 0 0 var(--eval-w);
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  gap:.9rem; background:rgba(22,33,23,.72); border-radius:8px;
}
.ov-kicker{
  margin:0; font-size:.72rem; letter-spacing:.16em; text-transform:uppercase; color:var(--ink);
}
.ov-buttons{display:flex; gap:.7rem; flex-wrap:wrap; justify-content:center}
.ov-title{
  font-family:Fraunces,Georgia,serif; font-weight:600; font-size:1.4rem;
  margin:0; color:var(--ink);
}
.ov-line{margin:0; font-size:.9rem; color:var(--ink-2); text-align:center; max-width:18rem}
@media (max-width:520px){
  /* the overlay buttons were spanning the whole board, same as on Burke Bot */
  .board-overlay .btn{font-size:.82rem; padding:.45rem .6rem}
  .ov-buttons{gap:.5rem}
}
.controls-row{
  display:flex; justify-content:flex-end; padding-left:var(--eval-w); margin-top:.9rem;
}
.promo{margin-top:1.15rem}
.shared-top{text-align:center}
.card .sub b{color:var(--ink); font-weight:500}

/* the shared-link landing page */
.eyebrow{
  font-size:.7rem; letter-spacing:.14em; text-transform:uppercase;
  color:var(--accent); margin:0 0 .35rem;
}
.promo{border-color:var(--accent-dim)}
.promo h2{font-family:"Alfa Slab One",Georgia,serif; font-weight:400; font-size:1.35rem}

/* building */
/* The recap and the line under the heading are the two pieces of running
   commentary on these pages, so they take one size - and it is the checklist's
   size, because a step that has finished should not be shouting over the line
   explaining what is happening. */
#building .recap, #result .recap{font-size:.88rem}
#building .card > .sub, #result .card > .sub{font-size:.88rem}
.recap{
  /* Flowing text, not a flex row. As a flex container each fact was an item and
     the separators were items too, so a wrap could strand a lone dot at the end
     of a line. Text wrapping plus a non-breaking space before each dot keeps
     every separator attached to the phrase it follows. */
  font-size:.86rem; color:var(--ink-2); line-height:1.7;
  padding-bottom:.9rem;
  border-bottom:1px solid var(--border); margin-bottom:1rem;
}
.recap .sep{opacity:.55}
.recap b{color:var(--ink); font-weight:500}
/* an unstyled <a> renders in the browser's blue, which is the one colour on the
   page that belongs to no theme. No underline at all - not on hover either;
   brightening to full ink is the hover signal, the way the icon buttons do it. */
.recap a{color:var(--accent); text-decoration:none}
.recap a:hover{color:var(--ink)}
/* One gap governs the three things stacked under the heading - bar, checklist,
   Cancel - so the two spaces are equal by construction rather than by two
   numbers that happen to match today. It is in rem, and the root font on this
   page is clamped to the window, so both gaps grow and shrink together with it.
   The progress bar's own transition is what makes the width changes glide
   instead of stepping. */
#building .card{--build-gap:1.5rem}
.bar{
  height:7px; border-radius:4px; background:var(--surface-2); overflow:hidden;
  margin:.9rem 0 0;
}
.bar span{
  display:block; height:100%; background:var(--accent); border-radius:4px; width:62%;
  /* the clock repaints every 50ms; this smooths the gaps between repaints and,
     more importantly, makes the jump to 100% on completion read as an arrival */
  transition:width .18s linear;
}
/* Only present when it has something to say - an always-there empty row would
   put an invisible fourth gap into a stack whose spacing is meant to be even. */
.prog{display:flex; justify-content:center; font-size:.84rem; color:var(--ink-2); margin-top:var(--build-gap)}
.prog[hidden]{display:none}

/* The step list. It only ever grows: the step in flight is the last row, with
   the bar above it, and finished steps keep their place with a tick. Nothing
   is removed and nothing is relabelled, so the reader can see how far in they
   are without having watched the whole time. */
.steps{list-style:none; margin:var(--build-gap) 0 0; padding:0; display:flex; flex-direction:column; gap:.55rem}
.step{display:flex; align-items:flex-start; gap:.6rem; font-size:.88rem; line-height:1.45}
.step .tick{position:relative; flex:none; width:1.05rem; height:1.05rem; margin-top:.1rem}
.step .what{color:var(--ink)}
.step .detail{color:var(--ink-2); font-variant-numeric:tabular-nums}
/* the detail follows the name on the same line where there is room, and drops
   under it on a phone rather than squeezing the name */
.step .detail:not(:empty)::before{content:"\00a0\00b7\00a0"}
@media (max-width:430px){
  .step{flex-wrap:wrap}
  .step .detail{flex-basis:100%; margin-left:1.65rem}
  .step .detail:not(:empty)::before{content:none}
}

/* in flight: a ring with one accent edge, spinning where motion is allowed.
   The global reduced-motion rule stops the spin; the accent edge still reads
   as "this one is going" against the plain ticks above it. */
.step.running .tick::after{
  content:""; position:absolute; inset:.06rem;
  border:2px solid var(--border); border-top-color:var(--accent);
  border-radius:50%; animation:step-spin .8s linear infinite;
}
@keyframes step-spin{to{transform:rotate(360deg)}}

/* done: the name recedes and the tick carries the state */
.step.done .what{color:var(--ink-2)}
.step.done .tick::after{
  content:""; position:absolute; left:.36rem; top:.1rem;
  width:.26rem; height:.58rem;
  border:solid var(--accent); border-width:0 2px 2px 0;
  transform:rotate(45deg);
}

.step.failed .tick::after{
  content:"\00d7"; position:absolute; inset:0;
  display:flex; align-items:center; justify-content:center;
  color:var(--warn); font-size:1.1rem; line-height:1;
}
.step.failed .what{color:var(--warn)}

/* Cancel sits centred under the checklist, the same distance below it as the
   checklist sits below the bar. */
.build-actions{display:flex; justify-content:center; margin-top:var(--build-gap)}

/* the PGN drop zone. A dashed box reads as "put something here" without
   needing a label to say so, and the whole box is the target - a button-sized
   hit area would make dragging a file harder than clicking one. */
.drop{
  margin-top:1rem; padding:1rem .9rem;
  border:1px dashed var(--border); border-radius:10px;
  background:var(--bg); text-align:center;
  display:flex; flex-direction:column; align-items:center; gap:.7rem;
  transition:border-color .15s, background .15s;
}
.drop{cursor:pointer}
.drop:hover{border-color:var(--accent-dim)}
/* one sentence per line: what the situation is, then what to do about it */
.drop-line span{display:block}
#pgn-btn{display:none}

/* A touch device cannot drag a file from anywhere, so the dashed "drop here"
   box is describing something impossible. Keyed on the pointer rather than on
   the width because that is the actual question - a narrow desktop window can
   still drag, and a touch laptop still cannot. The box stays tappable either
   way, so even where this query does not match the feature is reachable. */
@media (hover:none) and (pointer:coarse){
  .drop{border-style:solid; cursor:default}
  .drop:hover{border-color:var(--border)}
  #pgn-btn{display:inline-flex}
}
.drop.over{border-color:var(--accent); border-style:solid; background:var(--surface-2)}
/* a dragged file must not land on a child and cancel the parent's dragleave */
.drop.over *{pointer-events:none}
.drop-line{margin:0; font-size:.84rem; color:var(--ink-2); line-height:1.5; max-width:30rem}
.drop .btn{font-size:.9rem; padding:.45rem .95rem}
.drop .note{margin:0}
/* The upload confirmation. This is the moment a file the player went and
   fetched turns into games, and it used to say so in the same small grey type
   as every other footnote on the card. It now reads as an outcome: a tinted
   panel, the count at display size, and the caveats demoted underneath. The
   accent tint is the same one the copy button flashes on success, so "that
   worked" looks the same here as it does there. */
.upload-result{
  display:flex; flex-direction:column; align-items:center; gap:.1rem;
  margin:.85rem auto 0; padding:.6rem 1rem; width:fit-content; max-width:100%;
  border:1px solid var(--accent); border-radius:10px;
  background:rgba(var(--accent-rgb),.12); background-clip:padding-box;
}
.upload-result .head{
  display:flex; align-items:baseline; gap:.45rem;
  font-size:1.02rem; color:var(--ink); line-height:1.35;
}
.upload-result .head::before{
  content:"\2713"; flex:none; color:var(--accent);
  font-size:1.52rem; line-height:1; transform:translateY(.093em);
}
.upload-result .head b{font-weight:500; color:var(--accent); font-size:1.22rem}
.upload-result .why{font-size:.78rem; color:var(--ink-2); line-height:1.45; text-align:center}

/* Nothing came in. Same shape so the page does not jump, opposite reading: the
   warning ink and a cross, because a green tick over "0 games added" would be
   celebrating a failure. */
.upload-result.none{border-color:var(--warn); background:rgba(217,164,65,.1)}
.upload-result.none .head::before{content:"\00d7"; color:var(--warn); font-size:1.67rem}
.upload-result.none .head b{color:var(--warn)}

/* who the games were read as, with the other names in the file one click away */
.who-row{margin:.45rem 0 0; line-height:1.9}
.who-row b{color:var(--ink)}
.who-row .who{
  font:inherit; font-size:.78rem; margin-left:.3rem;
  padding:.15rem .5rem; border:1px solid var(--border); border-radius:6px;
  background:var(--surface); color:var(--ink-2); cursor:pointer;
  transition:border-color .15s, color .15s;
}
.who-row .who:hover{border-color:var(--accent); color:var(--ink)}
.who-row .who.more{border-style:dashed}
/* a guess rather than a reading: the lead is worded differently and sits in the
   warning ink, so the buttons beside it read as something to act on */
.who-row.guess{color:var(--warn)}
.who-row.guess b{color:var(--warn)}
/* a long list has to stay inside the card rather than push it wider */
.who-row{max-height:11rem; overflow-y:auto}

/* result */
.stats{
  display:grid; grid-template-columns:repeat(3,1fr); gap:.9rem;
  margin:.2rem 0 1.15rem;
}
@media (max-width:560px){ .stats{grid-template-columns:1fr} }
.stat{
  background:var(--bg); border:1px solid var(--border); border-radius:10px;
  padding:1rem .9rem; text-align:center;
  display:flex; flex-direction:column; justify-content:center; gap:.15rem;
  min-height:5.6rem;
}
.stat .k{font-size:.7rem; letter-spacing:.12em; text-transform:uppercase; color:var(--ink-2)}
/* a name rather than a figure, so it takes a readable size instead of the
   display size the two numeric tiles use */
.stat .v.text{font-size:1.32rem; line-height:1.25}
.stat .v{
  font-family:Fraunces,Georgia,serif; font-weight:600; font-size:1.65rem;
  line-height:1.15; margin-top:.2rem; font-variant-numeric:tabular-nums;
}
.stat .n{font-size:.76rem; color:var(--ink-2); margin-top:.15rem}
/* One shared height drives both, because aspect-ratio cannot feed back into a
   flex item's main size - the width is resolved from content before the cross
   axis stretches, so the button would come out tall and thin. */
.linkrow{display:flex; gap:.5rem; align-items:stretch; --h:2.7rem}
.linkbox{
  flex:1; min-width:0; height:var(--h);
  display:flex; align-items:center; justify-content:center;
  background:var(--bg); border:1px solid var(--border);
  border-radius:8px; padding:0 .7rem; font-size:.82rem; color:var(--ink-2);
  overflow:hidden; white-space:nowrap;
}
.linkbox span{overflow:hidden; text-overflow:ellipsis}
/* Burke Bot's copy control, carried over exactly - the wash and ring sit on the
   button's existing perimeter, and the tick draws itself on from the short tail. */
.copy-btn{
  flex:none;
  width:var(--h,1.86rem); height:var(--h,1.86rem); padding:0;
  display:flex; align-items:center; justify-content:center;
  border:1px solid var(--border); border-radius:7px;
  background:none; color:var(--ink-2); cursor:pointer;
  transition:color .15s, border-color .15s, background .15s;
}
.copy-btn svg{flex:none; width:1.04rem; height:1.04rem}
.copy-btn:hover{color:var(--ink); border-color:var(--border); background:var(--surface-2)}
.copy-btn.done svg{width:1.26rem; height:1.26rem}
.copy-btn.done{
  color:var(--accent);
  background:rgba(var(--accent-rgb),.15); background-clip:padding-box;
  border-color:var(--accent);
  transition:none;
}
@keyframes tick-write{from{stroke-dashoffset:100}}
.copy-btn.done svg polyline{
  stroke-dasharray:100; stroke-dashoffset:0;
  animation:tick-write 260ms cubic-bezier(.65,0,.35,1);
}
@media (prefers-reduced-motion:reduce){ .copy-btn.done svg polyline{animation:none} }
footer{margin-top:2.4rem; padding-top:1.1rem; border-top:1px solid var(--border);
  color:var(--ink-2); font-size:.82rem}
footer a{color:var(--accent); text-decoration:none}
[hidden]{display:none !important}
@media (prefers-reduced-motion:reduce){ *{transition:none !important; animation:none !important} }
:focus-visible{outline:2px solid var(--accent); outline-offset:2px}

/* ---- pieces the live page needs that the mockup did not ---- */

/* Boot veil: hold the page until fonts and the board have settled so it fades
   in as one unit instead of reflowing in front of the reader. build-a-bot.js lifts
   it; index.html carries a 3s fallback so it can never stick. */
html.booting body{opacity:0}
body{transition:opacity .18s ease}
@media (prefers-reduced-motion:reduce){ body{transition:none} }

/* one-line messages under a card's controls - lookup failures, the mobile cap */
.note{
  margin:.9rem 0 0; font-size:.8rem; color:var(--ink-2); text-align:center;
}
.note.bad{color:#d98c73}
.note.warn{color:var(--warn)}

.foot-inner{color:var(--ink-2)}

/* "Play Burke Bot" is an anchor, not a button, so it needs the button's box */
a.btn{display:inline-block; text-decoration:none}
