/* Shared styles for index.html, watch.html and replay.html. Anything that
   touches the board, clocks, player rows or move list lives here so the
   three pages can't drift out of sync with each other. */

body { background: #f3f4f6; color: #1f2937; font-family: 'Segoe UI', sans-serif; }
.highlight-last { box-shadow: inset 0 0 4px 4px #ffd700 !important; }

/* MOBILE TOUCH (2026-07-12): pan-x/pan-y on the page allows scrolling but
   blocks pinch-zoom at the touch-action level (iOS Safari ignores the
   viewport user-scalable flag; it does honor touch-action). Element rules
   below override this where needed. */
html, body { touch-action: pan-x pan-y; }

/* FIX FOR MOBILE SCROLLING: the LIVE board owns its touches completely
   (piece drags must never scroll the page)... */
#board { touch-action: none; }
/* ...but the viewer pages (replay.html / watch.html, body.viewer) have no
   piece dragging, and a full-width board that eats touches leaves a phone
   stuck scrolled-down with no way back up (2026-07-12, Will's iPhone) —
   there, touches on the board pan the page vertically. */
body.viewer #board { touch-action: pan-y; }

.timer-box { font-family: monospace; font-size: 0.95rem; font-weight: 800; background: #111827; color: #f3f4f6; padding: 3px 10px; border-radius: 6px; border: 2px solid #374151; min-width: 58px; text-align: center; }
@media (min-width: 768px) { .timer-box { font-size: 1.1rem; min-width: 68px; } }
.timer-low { color: #ef4444; border-color: #ef4444; animation: pulse 1s infinite; }

/* Moves-left badge: sized to match .timer-box so it reads as a
   companion to the clock rather than a separate widget. */
.moves-badge { font-family: monospace; font-size: 0.95rem; font-weight: 800; background: #ecfdf5; color: #047857; padding: 3px 10px; border-radius: 6px; border: 2px solid #a7f3d0; min-width: 40px; text-align: center; }
@media (min-width: 768px) { .moves-badge { font-size: 1.1rem; min-width: 46px; } }

/* TROLL FACE TRANSPARENCY FIX */
/* Sized to the full height of the moves-left label + badge/clock stack
   (2026-07-12, Will: "bigger, to match the bottom margin of the moves
   left and game clock boxes") — the game-screen top row is items-end, so
   the face's bottom edge sits flush with the boxes' bottoms. */
.player-img { width: 44px; height: 44px; border-radius: 6px; border: 2px solid #e5e7eb; object-fit: cover; transition: all 0.5s; background: #ffffff; display: block; flex-shrink: 0; }
@media (min-width: 768px) { .player-img { width: 52px; height: 52px; } }

/* (the observer troll's sizing lives INSIDE watch.html as an #watch-troll
   style block — a stale cached copy of this file once shrank it to the
   .player-img fallback, 2026-07-12) */

/* BOARD SIZING: full-width on phones/portrait (unchanged); on desktop
   the board grows with the window but is capped by whichever is
   smaller — available width, or available viewport height minus the
   surrounding chrome — so a large monitor gets a bigger board instead
   of being stuck at a small fixed size, and nothing pushes the page
   into a vertical scroll. --board-chrome (2026-07-18, Will's cut-off
   nav report) is that page's real vertical chrome: each viewer page
   sets its own in its style block (replay 205px, watch 240px — taller
   transport row + bands/troll); the 180px fallback keeps the game
   screen exactly as before, including under a stale cached page. */
.board-frame { width: 100%; }
@media (min-width: 768px) {
    .board-frame { width: min(100%, calc(100vh - var(--board-chrome, 180px)), 760px); }
}

/* (the old #video-overlay full-screen end-video wash was removed
   2026-07-11 — the troll clip is now a contained card inside #screen-end,
   styled inline with Tailwind classes in index.html) */
@keyframes pulse { 0% { opacity: 1; } 50% { opacity: 0.7; } 100% { opacity: 1; } }
.masked-link { cursor: pointer; }

/* (the end-screen "Play Again" pulse lives INSIDE index.html as a style
   block — page-specific features ship atomically with their page so a
   stale cached copy of this file can't strand them, 2026-07-12) */

/* CHALLENGE SLIDER (drives the move-count headline on the menu) */
/* touch-action none: thumb drags belong to the slider, never the page pan */
.elo-slider { -webkit-appearance: none; width: 100%; height: 8px; border-radius: 5px; background: #e5e7eb; outline: none; touch-action: none; }
.elo-slider::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 22px; height: 22px; border-radius: 50%; background: #10b981; cursor: pointer; border: 3px solid white; box-shadow: 0 1px 3px rgba(0,0,0,0.3); }
.elo-slider::-moz-range-thumb { width: 22px; height: 22px; border-radius: 50%; background: #10b981; cursor: pointer; border: 3px solid white; box-shadow: 0 1px 3px rgba(0,0,0,0.3); }

/* EVAL BAR (watch.html live spectating, replay.html scrubbing) — a vertical
   chess.com-style bar, White's share filling from whichever end White is
   playing from: .flipped = black on bottom of the board, so White fills
   from the top. */
/* No height rule ON PURPOSE (2026-07-18, Will's vanished-bar report):
   the bar sits in a `flex items-stretch` row beside the aspect-square
   board, so stretch gives it the board's height for free. The old JS
   (syncEvalBarHeight) measured the board and pinned an inline height —
   a feedback loop that ratcheted on window shrink until the row
   overflowed and flex crushed the 20px track into an invisible sliver.
   flex-shrink: 0 makes the track uncrushable regardless. */
.eval-bar-track { position: relative; width: 22px; flex-shrink: 0; border-radius: 6px; overflow: hidden; background: #1f2937; border: 2px solid #111827; }
.eval-bar-fill { position: absolute; left: 0; right: 0; bottom: 0; background: #f3f4f6; transition: height 0.4s ease; }
.eval-bar-track.flipped .eval-bar-fill { bottom: auto; top: 0; }

/* PLAYBACK CONTROLS (replay.html scrub row; watch.html browse row too
   since 2026-07-18) */
.replay-btn { display: flex; align-items: center; justify-content: center; width: 36px; height: 36px; border-radius: 8px; background: #fff; border: 1px solid #e5e7eb; color: #374151; font-weight: 700; transition: background 0.15s; }
.replay-btn:hover:not(:disabled) { background: #f3f4f6; }
.replay-btn:disabled { opacity: 0.35; cursor: default; }
/* Transport steps (first/back/forward/last) ride ON TOP of .replay-btn:
   chess.com/lichess-size targets (2026-07-18, Will: 36px squares were
   too small). flex-grow to a cap keeps one centered cluster that
   shrinks into four full-height thumb buttons on phones. */
.replay-btn-step { flex: 1 1 0; max-width: 104px; height: 52px; font-size: 26px; line-height: 1; }

/* ==== PLAYER FLARE COINS (2026-07-24) =========================
   Shared by index.html, replay.html and watch.html — see
   docs/FLARE_PLAN.md. Page-specific chrome (the home shelf, the
   collection grid, the end-of-game burst) stays in index.html. */
/* ---- FLARE (2026-07-24, Will) — docs/FLARE_PLAN.md -------------
   Every badge is a CSS COIN with the artwork layered on top and
   hidden onerror. That is deliberate: the whole system ships and
   looks finished before a single image exists, and a half-
   delivered art drop degrades one badge at a time instead of
   leaving the page full of broken-image boxes. */
.flare-coin { position: relative; display: inline-flex; align-items: center;
              justify-content: center; flex-shrink: 0; overflow: hidden;
              border-radius: 50%; border: 2px solid; font-weight: 900;
              line-height: 1; letter-spacing: -0.02em; cursor: default;
              background-clip: padding-box; }
.flare-coin img { position: absolute; inset: 0; width: 100%; height: 100%;
                  object-fit: cover; }
/* a badge earned by ONE game is an <a> to that game's replay
   (2026-07-24). It must still look like a badge, not a link: no
   underline, no link colour — just a pointer and a small lift, so the
   affordance is discovered on hover without changing the strip's look. */
a.flare-coin.fc-link { cursor: pointer; text-decoration: none;
                       transition: transform .12s ease, filter .12s ease; }
a.flare-coin.fc-link:hover { transform: translateY(-2px) scale(1.06);
                             filter: brightness(1.08); z-index: 2; }
a.flare-coin.fc-link:focus-visible { outline: 2px solid #16a34a;
                                     outline-offset: 2px; }
/* REAL ART TAKES OVER (2026-07-24). The delivered badges are
   transparent PNGs that carry their own rim, shading and shape —
   and several are SHIELDS, not discs. So once art loads the
   fallback coin must get completely out of the way: no disc
   background, no border, no circular clip (which would saw the
   corners off a shield), and contain rather than cover so the
   whole badge fits. */
.flare-coin.fc-art { background: none; border-color: transparent;
                     border-radius: 0; overflow: visible; }
.flare-coin.fc-art img { object-fit: contain; }
/* The six tier badges carry a blank banner across their LOWER
   third, so their overlaid number belongs there, not centred over
   the emblem. --fc-banner is measured from the delivered art. */
.flare-coin.fc-art.fc-banner .fc-txt {
    position: absolute; left: 0; right: 0;
    top: var(--fc-banner, 81%); transform: translateY(-50%);
    /* text-align is REQUIRED here, not decoration: left:0 + right:0
       blockifies the span to the full padding box, and the parent's
       justify-content:center does not position an abspos child whose
       left and right are both non-auto — so without this the numeral
       falls to text-align:start and paints against the coin's left rim
       instead of on the banner cartouche. */
    text-align: center;
    font-size: 0.56em; text-shadow: none; }
/* Number ink measured off the delivered art (mean banner
   luminance): wood 57 and bronze 81 are dark bands and need
   light digits; silver 191, diamond 201, gold 161 and obsidian
   168 are bright bands and need dark ones. */
.flare-coin.fc-art.fc-wood     .fc-txt { color: #f6ead6; }
.flare-coin.fc-art.fc-bronze   .fc-txt { color: #ffeedd; }
.flare-coin.fc-art.fc-silver   .fc-txt { color: #23282f; }
.flare-coin.fc-art.fc-gold     .fc-txt { color: #3a2705; }
.flare-coin.fc-art.fc-diamond  .fc-txt { color: #1f2a33; }
.flare-coin.fc-art.fc-obsidian .fc-txt { color: #3a2705; }
/* centre-overlay badges: the duel shield's blank face is bright
   gold (199), so dark digits; the level frames are dark (52-64)
   so they keep light digits with a shadow to lift them */
.flare-coin.fc-art.fc-duel .fc-txt,
.flare-coin.fc-art.fc-duel .fc-sub { color: #3a2705;
    text-shadow: 0 1px 0 rgba(255,255,255,.45); }
.flare-coin.fc-art.fc-rapid .fc-txt,
.flare-coin.fc-art.fc-blitz5 .fc-txt,
.flare-coin.fc-art.fc-blitz .fc-txt { color: #fff6dd;
    text-shadow: 0 1px 2px rgba(0,0,0,.75); }
/* at 20px the banner is ~4px tall — a number there is mush, and
   the tooltip already carries the exact count */
.flare-coin.fc-art.fc-banner.fc-mini .fc-txt { display: none; }
.flare-coin .fc-txt { position: relative; z-index: 1; }
.flare-coin.fc-strike .fc-txt { text-decoration: line-through;
                                text-decoration-thickness: 2px; }
/* metal tiers. The top-left sheen is what keeps a coin from reading
   as a flat disc while it is still standing in for real artwork. */
.flare-coin { background-image: linear-gradient(145deg,
                rgba(255,255,255,.28), rgba(255,255,255,0) 45%,
                rgba(0,0,0,.22)); }
.fc-wood     { background-color: #6b4f2a; border-color: #a9814a; color: #f6ead6; }
.fc-bronze   { background-color: #9a6229; border-color: #e0954a; color: #fff1de; }
.fc-silver   { background-color: #8b95a5; border-color: #e2e8f0; color: #ffffff; }
.fc-gold     { background-color: #b8891a; border-color: #f7d774; color: #fffbea; }
.fc-diamond  { background-color: #3183a8; border-color: #bdeeff; color: #f2fdff; }
.fc-obsidian { background-color: #1b1b22; border-color: #f0c75e; color: #f7d774; }
/* families */
/* background-COLOR, never the shorthand: the shorthand would wipe
   the shared sheen gradient set on .flare-coin above */
.fc-rapid { background-color: #1d5c43; border-color: #6ee7b7; color: #d1fae5; }
/* the 5-min clock sits between the 10-min green and the 3-min blue */
.fc-blitz5{ background-color: #1f5d5c; border-color: #5eead4; color: #ccfbf1; }
.fc-blitz { background-color: #27406a; border-color: #93c5fd; color: #dbeafe; }
.fc-ava   { background-color: #7c4a06; border-color: #fcd34d; color: #fef3c7; }
.fc-feat  { background-color: #4a2a5c; border-color: #d8b4fe; color: #f3e8ff; }
.fc-pure  { background-color: #333a46; border-color: #9ca3af; color: #e5e7eb; }
.fc-streak{ background-color: #7c2d12; border-color: #fb923c; color: #ffedd5; }
/* the duel record is the crown jewel: a shield, not a coin */
.fc-duel { border-radius: 26% 26% 42% 42% / 18% 18% 60% 60%;
           background-color: #3d3552; border-color: #f0c75e; color: #f7d774;
           flex-direction: column; gap: 0; }
.fc-duel .fc-sub { font-size: 0.5em; font-weight: 800; opacity: .85;
                   letter-spacing: .08em; }
/* sizes */
.fc-mini  { width: 20px; height: 20px; font-size: 8.5px; border-width: 1.5px; }
.fc-mini.fc-duel  { width: 22px; height: 25px; font-size: 9px; }
.fc-shelf { width: 38px; height: 38px; font-size: 14px; }
.fc-shelf.fc-duel { width: 50px; height: 56px; font-size: 19px; }
.fc-grid  { width: 46px; height: 46px; font-size: 16px; }
.fc-big   { width: 132px; height: 132px; font-size: 46px; border-width: 4px; }
.fc-big.fc-duel   { width: 150px; height: 168px; font-size: 54px; }
.fc-locked { background: #f3f4f6 !important; border-color: #e5e7eb !important;
             color: #d1d5db !important; }

/* ==== STACKED BANDS (2026-07-30) — docs/FLARE_V2_PLAN.md ==========
   A band won more than once grows offset PLATES behind it (1, 2, 3),
   and at five a ribbon appears whose metal steps at 10 and 25. No
   digits anywhere: the count is in the tooltip and in the expand.
   Both plates and ribbon are hidden until .fc-art proves the artwork
   loaded — the CSS fallback coin is a flat disc with nothing to stack,
   and an unclipped ribbon on a border-radius:50% box would be sawn in
   half by its own overflow:hidden. */
.flare-coin .plate, .flare-coin .fc-rib { display: none; }
.flare-coin.fc-art.fc-stack .plate,
.flare-coin.fc-art.fc-stack .fc-rib { display: block; }
.flare-coin.fc-stack .plate { position: absolute; inset: 0; width: 100%;
    height: 100%; object-fit: contain; z-index: 0; pointer-events: none; }
/* p1 is the nearest plate. Painted before the face in DOM order, so the
   face (z-index auto) still covers them without needing a z-index. */
.flare-coin.fc-stack .plate.p1 { transform: translate(3px, 3px); opacity: .50; }
.flare-coin.fc-stack .plate.p2 { transform: translate(6px, 6px); opacity: .30; }
.flare-coin.fc-stack .plate.p3 { transform: translate(9px, 9px); opacity: .18; }
.flare-coin.fc-stack .fc-rib { position: absolute; left: 50%; bottom: -8px;
    transform: translateX(-50%); z-index: 3; width: 22px; height: 11px;
    background: linear-gradient(180deg, #d08a45, #9a6229);
    clip-path: polygon(0 0, 100% 0, 100% 100%, 50% 66%, 0 100%); }
.flare-coin.fc-stack .fc-rib.r-silver {
    background: linear-gradient(180deg, #cfd6e0, #8b95a5); }
.flare-coin.fc-stack .fc-rib.r-gold {
    background: linear-gradient(180deg, #f0c75e, #b8891a); }
/* the ribbon hangs 8px past the box, so any container that puts a
   caption under a coin must reserve it or the count lands on the label */
.fc-grid.fc-stack, .fc-shelf.fc-stack { margin-bottom: 10px; }

/* ==== DUEL HUD (2026-07-30) ======================================
   Duel only, in the slot Avalanche already uses (label / badge / bar).
   ONE status line, not two: keeping "Surviving · move" as well would
   state the fact the number inside the shield already states, and cost
   another 14px of board push (measured 65px vs 79px, against today's
   41px). */
#duel-hud { display: none; flex-direction: column; align-items: center;
            gap: 2px; width: 88px; }
#duel-hud.on { display: flex; }
#duel-status { font-size: 8px; text-transform: uppercase; font-weight: 800;
    color: #9ca3af; letter-spacing: .04em; white-space: nowrap; line-height: 1.3; }
#duel-status.warm { color: #b45309; }
#duel-status.good { color: #047857; }
#duel-shield { width: 38px; height: 43px; font-size: 15px; }
@media (min-width: 768px) { #duel-shield { width: 44px; height: 49px; font-size: 17px; } }
/* below the flare floor the shield is UNMINTED: the server only banks
   duel_best >= 5, so promising the badge before then would be a lie */
#duel-shield.unminted img { filter: grayscale(1) brightness(1.25); opacity: .34; }
#duel-shield.unminted .fc-txt { color: #9ca3af !important; text-shadow: none !important; }
#duel-track { position: relative; width: 56px; height: 4px; border-radius: 3px;
              background: #e5e7eb; margin-top: 3px; }
#duel-fill { height: 100%; border-radius: 3px; background: #a9814a;
             transition: width .35s ease; }
#duel-fill.m-bronze  { background: #c07a2e; }
#duel-fill.m-silver  { background: #94a3b8; }
#duel-fill.m-gold    { background: #d4a017; }
#duel-fill.m-diamond { background: #3183a8; }
#duel-fill.m-obsidian{ background: #4b4b57; }
#duel-pb { position: absolute; top: -3px; width: 2px; height: 10px;
           border-radius: 1px; background: #111827; opacity: .55; }
#duel-pb.hit { background: #f0c75e; opacity: 1; box-shadow: 0 0 5px #f0c75e; }
/* the machine's own lowest projection, shown ONLY once you are past it
   (see index.html duelSync) — so it can never be bad news */
#duel-guess { position: absolute; top: -6px; width: 0; height: 0;
    border-left: 5px solid #16a34a; border-top: 3px solid transparent;
    border-bottom: 3px solid transparent; display: none; }
#duel-guess.on { display: block; }
@keyframes duel-mark-pop { 0% { transform: scale(1); }
    32% { transform: scale(1.16); } 100% { transform: scale(1); } }
.duel-pop { animation: duel-mark-pop .55s ease-out; }
