/* Sticky Phrases game styles, shared by every page that plays a game
   (the curator/playtest page game-set-tester-findk.html and the site).
   The game's markup is in game-core.js; the page's own extras carry
   their own styles. */

/* ---- Theme ---------------------------------------------------------
   The game's font, here so a design experiment changes it in one place:
   Tailwind's font-sans (tailwind-config.js), the chain graph's words and
   the graph's text measuring (textWidth in game-core.js) all read it. */
:root { --sp-font: Inter, sans-serif; }

html { overflow-y: scroll; }

/* ---- Per-game background art ---------------------------------
   A fixed layer behind everything, washed out by a white veil so
   it sets a mood without competing with the board. The UI panels
   stay fully opaque, so the art is visible only in the gutters
   around them. Sepia + reduced saturation pushes the late-19thC
   plates toward the "old-timey techno-optimism" register rather
   than looking like a stock photo. */
#bg-layer {
    position: fixed;
    inset: 0;
    z-index: -2;
    background-size: cover;   /* replaced by explicit px in sizeBackground() */
    background-position: center;
    background-repeat: no-repeat;
    filter: sepia(0.10) saturate(0.9);
    opacity: 0;
    transition: opacity 0.6s ease;
}
#bg-layer.loaded { opacity: 1; }
/* Game-change reveal (playtest): the board and results panes vanish
   instantly, the new art shows on its own for a moment, then the
   panes fade in over it. See hidePanes / revealPanesAfterHold. */
.pane-fade { transition: opacity 0.8s ease; }
.pane-fade.pane-hidden { opacity: 0; transition: opacity 0.2s ease; pointer-events: none; }
/* Holds the OUTGOING image during a crossfade: same placement as
   #bg-layer, stacked just above it, faded to transparent. */
#bg-old {
    position: fixed;
    inset: 0;
    z-index: -2;
    background-position: center;
    background-repeat: no-repeat;
    filter: sepia(0.10) saturate(0.9);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.6s ease;
}
/* Fills the side bars when a wide window letterboxes the art (see
   sizeBackground). Modes via ?fill=: paper (default) is a flat
   manila; blur is the same image, covering and heavily blurred;
   none leaves the page white. Scaled up so the blur's soft edges
   fall off-screen. */
#bg-fill {
    position: fixed;
    inset: 0;
    z-index: -3;
    background-size: cover;
    background-position: center;
    opacity: 0;
    transition: opacity 0.6s ease;
}
#bg-fill.loaded { opacity: 1; }
#bg-fill.blur  { filter: blur(40px) sepia(0.10) saturate(0.8) brightness(1.05); transform: scale(1.15); }
#bg-fill.paper { background-image: none !important; background-color: #efe6d6; }
#bg-veil {
    position: fixed;
    inset: 0;
    z-index: -1;
    background: rgba(255, 255, 255, 0);   /* default: no veil; ?veil= to try one */
}
/* The page background would otherwise paint over the art. */
body { background-color: transparent !important; }
/* Shared surface for the instructions bar, board and results panels.
   White rather than a tint: it keeps attention on the background art
   instead of competing with it. The warm border is the only nod to
   the plates' manila tones. Opaque so the art never bleeds through
   anything being read or played. */
.panel-paper {
    background-color: #ffffff !important;
    border-color: #ddd0ba !important;
}

/* Panels already carry opaque Tailwind backgrounds (bg-white,
   bg-gray-50, bg-gray-100), so the art shows only in the gutters
   between them. Nothing to override here -- forcing a colour would
   quietly change the existing palette. */
.word-button {
    transition: all 0.1s ease;
    box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.word-button:hover:not(:disabled):not(.off) {
    transform: translateY(-2px);
    box-shadow: 0 6px 10px rgba(0, 0, 0, 0.15);
}
/* no background transition: when .filling comes off, the segment's own
   background must turn green at once, under the green ::after, or it
   fades in from gray and flickers pale */
.progress-seg { width: 0.95rem; height: 0.95rem; border-radius: 3px; background: #e5e7eb; }   /* rounded squares (were 1.25 x 0.85rem) */
.progress-seg.found { background: #22b33a; }
/* Filling a segment (?fill=grow|ring|burst|flip, default burst):
   grow  green swells from the centre out to the edges
   ring  grow, then a green ring ripples outward
   burst grow with a pop
   flip  the segment flips over to green (Wordle tiles) */
.progress-seg { position: relative; }
.progress-seg.filling { background: #e5e7eb; }
.progress-seg.filling::after { content: ''; position: absolute; inset: 0; background: #22b33a;
    animation: segGrow 0.5s cubic-bezier(.2, .8, .3, 1.2) forwards; }
@keyframes segGrow { from { transform: scale(0); border-radius: 50%; } to { transform: scale(1); border-radius: 3px; } }
.progress-seg.ring { animation: segRing 0.8s ease-out 0.3s; }
@keyframes segRing { from { box-shadow: 0 0 0 0 rgba(34, 179, 58, 0.7); } to { box-shadow: 0 0 0 10px rgba(34, 179, 58, 0); } }
.progress-seg.pop { animation: segPop 0.6s ease-out; }
@keyframes segPop { 40% { transform: scale(1.45); } }
.progress-seg.flip { animation: segFlip 0.5s ease-in-out; }
@keyframes segFlip { 0%, 49% { transform: rotateX(0); background: #e5e7eb; }
                     50% { transform: rotateX(90deg); background: #22b33a; } 100% { transform: rotateX(0); } }
/* Winning (?win=glow|hop|wave|confetti|hop-confetti, default hop): the bar holds,
   all green, for WIN_HOLD_MS before the result replaces it.
   glow      the segments glow and pulse
   hop       a hop runs along the segments, no glow
   wave      hop, then the segments glow
   confetti  wave, plus confetti from the bar
   hop-confetti  hop and confetti, no glow
   ?bursts=1|2 (default 2): confetti bursts */
#sticky-progress.won .progress-seg { box-shadow: 0 0 8px 1px rgba(34, 179, 58, 0.75); transition: box-shadow 0.4s ease; }
#sticky-progress.won-pulse .progress-seg { animation: segGlow 0.9s ease-in-out 2; }
@keyframes segGlow { 50% { box-shadow: 0 0 14px 4px rgba(34, 179, 58, 0.9); } }
.progress-seg.hop { animation: segHop 0.7s ease-out; }
@keyframes segHop { 35% { transform: translateY(-8px) scale(1.15); } 70% { transform: translateY(1px); } }
/* The guess flying from the entry box to its segment or miss dot
   (?fly=dot a coloured dot, the default; ?fly=1 the phrase as a
   chip; ?fly=0 off). */
.fly-chip { position: fixed; z-index: 60; pointer-events: none; white-space: nowrap; color: #fff;
    font-weight: 600; font-size: 1rem; padding: 0.15rem 0.6rem; border-radius: 9999px;
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2); will-change: transform, opacity; }
.fly-chip.dot { width: 0.9rem; height: 0.9rem; padding: 0; }
/* A free guess (almost, off-corpus) says so in the entry box, for
   hope rather than a scolding wobble (?close=pop|rise|toast, default
   rise). It never blocks: the next word tapped replaces it.
   pop    appears at once, a touch large, settles, holds, fades
   rise   fades in, holds, drifts up as it fades
   toast  an amber pill just above the entry box (Connections'
          "One away..."), fades in, holds, fades */
.close-msg { position: absolute; left: 50%; top: 50%; color: #d97706;
    /* the STICKY PHRASES / MISSES label style (text-sm bold uppercase tracking-wide), in amber */
    font-size: 0.875rem; line-height: 1.25rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.025em;
    white-space: nowrap; pointer-events: none; transform: translate(-50%, -50%); }
.close-msg.pop { animation: closePop 1.6s ease-out forwards; }
@keyframes closePop { 0% { transform: translate(-50%, -50%) scale(1.3); opacity: 1; }
                      12% { transform: translate(-50%, -50%) scale(1); } 70% { opacity: 1; }
                      100% { transform: translate(-50%, -50%) scale(1); opacity: 0; } }
.close-msg.rise { animation: closeRise 1.6s ease-out forwards; }
@keyframes closeRise { 0% { transform: translate(-50%, -30%); opacity: 0; } 12% { transform: translate(-50%, -50%); opacity: 1; }
                       60% { transform: translate(-50%, -50%); opacity: 1; }
                       100% { transform: translate(-50%, -110%); opacity: 0; } }
.close-msg.toast { top: auto; bottom: calc(100% + 0.5rem); transform: translateX(-50%); color: #fff; background: #d97706;
    font-size: 1rem; padding: 0.3rem 0.9rem; border-radius: 9999px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18);
    animation: closeToast 1.8s ease-out forwards; }
@keyframes closeToast { 0% { opacity: 0; } 10%, 75% { opacity: 1; } 100% { opacity: 0; } }
/* Split controls (CONTROLS): shuffle in the board's bottom-left (away from Delete)
   corner, its bottom on the lowest a nudged word can reach (placeWords sets its top; spotBoxes keeps that row clear
   of it), Delete as an icon inside the phrase box, Enter alone below.
   flow-root keeps the board's margins inside the wrapper, so
   offsetTop measures from the wrapper's top. */
.controls-split #board-wrap { display: flow-root; position: relative; }
/* flow-root stops the board's bottom margin collapsing into the panel's
   0.75rem spacing, so: 12px here + 12px spacing + 8px above the phrase
   box = 32px from the lowest word to the box (SHUFFLE_DROP puts
   shuffle 16px lower than that) */
.controls-split #word-buttons-container { margin-bottom: 12px !important; }
/* and 16px + 12px spacing + the status slot's 4px = 32px from the
   status row to the highest a nudged word reaches (the board's top) */
.controls-split #word-buttons-container { margin-top: 16px !important; }
.shuffle-corner #shuffle-btn { position: absolute; left: 0; width: 2.5rem; height: 2.5rem;
    display: flex; align-items: center; justify-content: center; border-radius: 9999px; color: #4b5563;
    background: rgba(255, 255, 255, 0.9); border: 1px solid #d1d5db; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
    transition: background-color 0.15s ease, transform 0.3s ease; }
.shuffle-corner #shuffle-btn:hover { background: #f3f4f6; }
.controls-split #shuffle-btn:active { transform: rotate(90deg); }
.shuffle-corner #shuffle-btn.hidden { display: none; }
.shuffle-corner #shuffle-btn svg { width: 20px; height: 20px; }
/* Phrase box, Enter and Give up narrower than the panel: 22rem fits the
   longest pair in a saved game (28 characters, "sympathetically
   comprehended") beside Delete, and keeps Delete near the words. */
.entry-narrow #entry-wrap, .entry-narrow #enter-row, .entry-narrow #give-up-row {
    max-width: 22rem; margin-left: auto; margin-right: auto; }
/* ?shuffle=bar: a square-ish button left of Enter, Enter's height */
.shuffle-bar #shuffle-btn { width: 3.25rem; flex: none; display: flex; align-items: center; justify-content: center;
    border-radius: 0.5rem; color: #4b5563; background: #f3f4f6; border: 1px solid #d1d5db; }
.shuffle-bar #shuffle-btn:hover { background: #e5e7eb; }
.shuffle-bar #shuffle-btn.hidden { display: none; }
.shuffle-bar #shuffle-btn svg { width: 22px; height: 22px; }
/* ?box= phrase box styles (the markup's classes are the old outline) */
.box-none #current-bigram { border-color: transparent; box-shadow: none; background: transparent; }
.box-fill #current-bigram { border-color: transparent; box-shadow: none; background: #f3f4f6; }
.box-line #current-bigram { border-width: 0 0 2px; border-color: #d1d5db; border-radius: 0; box-shadow: none; background: transparent; }
.controls-split #current-bigram { padding-left: 3rem; padding-right: 3rem; gap: 0.1rem; }
.controls-split #delete-btn { position: absolute; right: 0.35rem; top: 50%; transform: translateY(-50%);
    width: 2.4rem; height: 2.4rem; display: flex; align-items: center; justify-content: center;
    border-radius: 0.5rem; color: #4b5563; transition: opacity 0.15s ease, background-color 0.15s ease; }
.controls-split #delete-btn:hover:not(:disabled) { background: #f3f4f6; }
.controls-split #delete-btn:disabled { opacity: 0; pointer-events: none; }
/* about a word space apart in all (gap + padding), so the two read as one phrase */
.entry-word { padding: 0 0.12rem; border-radius: 0.375rem; line-height: 1.4; }
.entry-word:hover { background: #eff6ff; }
/* Full games (useSummary): no instruction heading during play */
.no-heading #status-slot { display: none; }
/* Summary card: while open it replaces the board and everything under
   it; folded (Hide) it's a title bar over the finished board. */
#summary { display: none; }
.summary-open #summary { display: block; animation: calloutIn 0.4s ease-out; padding: 0.5rem 0.5rem 0.25rem; }   /* room inside the panel's own */
@media (min-width: 640px) { .summary-open #summary { padding: 0.75rem 1rem 0.5rem; } }
/* The summary stands alone in the board pane (no Hide/Show since
   2026-11-05): the empty heading slot and the board go too, so the
   result line sits at the top. */
.summary-open :is(#board-wrap, #entry-wrap, #enter-row, #status-bottom, #history-wrap, #give-up-row, #status-slot, #word-buttons-container) { display: none !important; }
.summary-open .panel-paper > :has(> #summary) { margin-top: 0 !important; padding-top: 0 !important; }   /* the board's spacing, not the summary's */
.summary-open :is(#entry-wrap, #enter-row, #give-up-row, #next-game-row, #share-box) { display: none !important; }
.summary-open #chain-graph-box:not(:has(#chain-graph)) { display: none !important; }   /* its graph is in the summary */
.sum-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 0.75rem; }
.sum-section { margin-top: 1rem; }
.sum-label { font-size: 0.875rem; font-weight: 700; color: #6b7280; text-transform: uppercase; letter-spacing: 0.025em; }
.sum-chips { display: flex; flex-wrap: wrap; gap: 0.35rem; margin-top: 0.4rem; }
.sum-note { font-size: 0.75rem; color: #6b7280; margin-top: 0.3rem; }
.sum-none { color: #6b7280; }
.sum-words { color: #374151; }
.hchip.unfound { background: transparent; color: #4b5563; border: 1px dashed #9ca3af; }
.sum-share { display: flex; align-items: center; justify-content: space-between; gap: 0.75rem; margin-top: 1.25rem; }
.sum-next { display: block; width: 100%; margin-top: 1.25rem; padding: 0.7rem; border-radius: 0.5rem;
    background: #2563eb; color: #fff; font-weight: 700; }
.sum-next:hover { background: #1d4ed8; }
/* Tap Sticky Phrases or Misses (toggleStatusList): that list, alphabetical, in a popover under the row */
.status-col { cursor: pointer; border-radius: 0.5rem; padding: 0.15rem 0.5rem 0.35rem; transition: background-color 0.15s ease;
    -webkit-tap-highlight-color: transparent; }
.status-col:hover, .status-col[aria-expanded="true"] { background: #f3f4f6; }
.status-col:focus-visible { outline: 2px solid #2563eb; outline-offset: 1px; }
#status-list { position: absolute; left: 0; right: 0; top: calc(100% + 0.4rem); z-index: 45; padding: 0.6rem 0.75rem 0.75rem;
    background: #fff; border: 1px solid #e5e7eb; border-radius: 0.75rem; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
    max-height: 50vh; overflow-y: auto; text-align: left; animation: calloutIn 0.2s ease-out; }
#status-list.hidden { display: none; }
/* ?status=bottom: the status row under Enter, above the history */
#status-bottom { margin-top: 1.25rem; }
.status-bottom #game-status-display { opacity: 1 !important; pointer-events: auto; }
.status-bottom.guided #status-bottom { display: none; }
/* Playtest header bar (all sizes): logo, mode menu, ? for How to play.
   The instructions stay hidden until ? opens them. */
#top-bar { display: none; }
.playtest #top-bar { display: flex; }
.curator-bar #top-bar { display: flex; justify-content: flex-end; }
.curator-bar :is(#top-logo, #top-mode-wrap, #top-help-btn) { display: none; }
.playtest #mode-toggle-row { display: none; }
.playtest #instructions-container { display: none; }
.playtest.show-help #instructions-container { display: block; }
#instructions-content :is(.ins-head, .term) { font-weight: 700; }
/* Opened by ?, the pane needs no title: its close chevron sits in the corner,
   and the text has room round it */
.playtest #instructions-container > .panel-paper { position: relative; padding: 1rem 1.25rem; }
@media (min-width: 640px) { .playtest #instructions-container > .panel-paper { padding: 1.25rem 1.75rem; } }
.playtest #ins-title { display: none; }
.playtest #ins-head { position: absolute; top: 0.6rem; right: 0.6rem; }
.playtest #instructions-content { margin-top: 0; padding-right: 1.5rem; }
.top-btn { display: inline-flex; align-items: center; gap: 0.25rem; padding: 0.35rem 0.6rem; border-radius: 0.5rem;
    font-size: 0.875rem; font-weight: 600; color: #374151; background: #f3f4f6; }
.top-btn:hover { background: #e5e7eb; }
.top-icon { width: 2rem; height: 2rem; justify-content: center; padding: 0; border-radius: 9999px; font-weight: 700; }
#top-mode-menu { position: absolute; right: 0; top: calc(100% + 0.35rem); z-index: 50; width: 15rem; padding: 0.35rem;
    background: #fff; border: 1px solid #e5e7eb; border-radius: 0.75rem; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15); }
#top-mode-menu button { display: block; width: 100%; text-align: left; padding: 0.5rem 0.65rem; border-radius: 0.5rem; }
#top-mode-menu button:hover { background: #f3f4f6; }
#top-mode-menu button[aria-checked="true"] { background: #eff6ff; }
#top-mode-menu b { display: block; font-size: 0.9rem; color: #1f2937; }
#top-mode-menu span { display: block; font-size: 0.8125rem; color: #6b7280; }
@media (min-width: 641px) { #top-mode-menu span { font-size: 0.875rem; } }
#top-settings-menu { position: absolute; right: 0; top: calc(100% + 0.35rem); z-index: 50; width: 17rem; padding: 0.35rem;
    background: #fff; border: 1px solid #e5e7eb; border-radius: 0.75rem; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15); }
.set-row { display: flex; align-items: center; justify-content: space-between; gap: 0.75rem; padding: 0.5rem 0.65rem;
    border-radius: 0.5rem; cursor: pointer; }
.set-row:hover { background: #f3f4f6; }
.set-row b { display: block; font-size: 0.9rem; color: #1f2937; }
.set-row b + span { display: block; font-size: 0.75rem; color: #6b7280; line-height: 1.3; }
.set-row input { flex: none; width: 1.1rem; height: 1.1rem; accent-color: #2563eb; cursor: pointer; }
/* Background images off (setting): no art, a plain page. */
html.no-bg :is(#bg-layer, #bg-old, #bg-fill, #bg-veil) { display: none !important; }
html.no-bg { background: #f9fafb; }
/* ---- Dark mode (setting) ---------------------------
   Overrides by class: the page is Tailwind (no dark: variants) plus
   the rules above. Greens, ambers and reds keep their hue, lifted
   for a dark ground. Bright art is dimmed (--bg-dim, set per image by
   game-core.js's bgDimFor; since 2026-11-11), dark art left as it is. */
html.dark { color-scheme: dark; background: #111827; }
html.dark :is(#bg-layer, #bg-old) { filter: sepia(0.10) saturate(0.9) brightness(var(--bg-dim, 1)); }
html.dark #bg-fill.blur { filter: blur(40px) sepia(0.10) saturate(0.8) brightness(calc(1.05 * var(--bg-dim, 1))); }
html.dark #bg-fill.paper { background-color: #1c1917; }
html.dark .panel-paper { background-color: #1f2937 !important; border-color: #4b5563 !important; }
html.dark :is(.bg-white, .bg-gray-50):not(body) { background-color: #1f2937 !important; }
html.dark .bg-gray-100 { background-color: #374151 !important; }
html.dark .bg-gray-200 { background-color: #4b5563 !important; }
html.dark .hover\:bg-gray-300:hover, html.dark .hover\:bg-gray-200:hover { background-color: #6b7280 !important; }
html.dark :is(.text-gray-900, .text-gray-800) { color: #f3f4f6 !important; }
html.dark .text-gray-700 { color: #e5e7eb !important; }
html.dark .text-gray-600 { color: #d1d5db !important; }
html.dark .text-gray-500 { color: #9ca3af !important; }
html.dark :is(.border-gray-200, .border-gray-300, .border) { border-color: #4b5563 !important; }
html.dark .word-button { background-color: #374151 !important; color: #f3f4f6 !important; border-color: #6b7280 !important;
    box-shadow: 0 4px 6px rgba(0, 0, 0, 0.4); }
html.dark .word-button:hover:not(.off) { background-color: #1e3a8a !important; }
html.dark #word-buttons-container .word-button.off:hover { background-color: #374151 !important; }
html.dark .word-button.drag-from { background: #1e3a8a !important; border-color: #60a5fa !important; }
html.dark .word-button.drag-to { background: #2563eb !important; }
html.dark #current-bigram { background-color: #111827; border-color: #4b5563; color: #f3f4f6; }
html.dark .entry-word:hover { background: #1e3a8a; }
html.dark :is(.top-btn, .shuffle-bar #shuffle-btn) { background: #374151; color: #e5e7eb; border-color: #4b5563; }
html.dark .top-btn:hover, html.dark .shuffle-bar #shuffle-btn:hover { background: #4b5563; }
html.dark .shuffle-corner #shuffle-btn { background: rgba(55, 65, 81, 0.9); color: #e5e7eb; border-color: #6b7280; }
html.dark .controls-split #delete-btn { color: #d1d5db; }
html.dark .controls-split #delete-btn:hover:not(:disabled) { background: #374151; }
html.dark :is(#top-mode-menu, #top-settings-menu, #status-list) { background: #1f2937; border-color: #4b5563; }
html.dark :is(#top-mode-menu button:hover, .set-row:hover) { background: #374151; }
html.dark #top-mode-menu button[aria-checked="true"] { background: #1e3a8a; }
html.dark :is(#top-mode-menu b, .set-row b) { color: #f3f4f6; }
html.dark :is(#top-mode-menu span, .set-row b + span) { color: #9ca3af; }
html.dark :is(.status-col:hover, .status-col[aria-expanded="true"]) { background: #374151; }
html.dark :is(.progress-seg, .progress-seg.filling) { background: #4b5563; }
html.dark .progress-seg.found { background: #22b33a; }
html.dark .miss-dot { background: #d1d5db; }
html.dark .miss-dot.used { background: #4b5563; }
html.dark .miss-dot.shrinking::after { background: #d1d5db; }
html.dark .sum-label, html.dark .sum-note, html.dark .sum-none { color: #9ca3af; }
html.dark .sum-words { color: #e5e7eb; }
html.dark .stickiness-sticky { color: #4ade80; }
html.dark .stickiness-none, html.dark .text-red-800 { color: #f87171 !important; }
html.dark .history-tint .hchip.sticky { background: #14532d; color: #bbf7d0; border-color: #166534; }
html.dark .history-tint .hchip.almost { background: #ab6c00; color: #fff; border-color: #c98a1a; }
html.dark .history-tint .hchip.miss   { background: #374151; color: #d1d5db; border-color: #4b5563; }
html.dark .hchip.unfound { color: #d1d5db; border-color: #6b7280; }
html.dark .bg-stickiness-sticky { background-color: #14532d; }
html.dark .bg-stickiness-almost { background-color: #ab6c00; }   /* amber: #451a03 read as red, like a miss */
html.dark .stickiness-almost { color: #fef3c7; }
html.dark .bg-stickiness-none { background-color: #7f1d1d; }
html.dark #chain-graph .node rect { fill: #1f2937; stroke: #34d399; }
html.dark #chain-graph .node text { fill: #f3f4f6; }
html.dark #chain-graph .edge { stroke: #34d399; }
html.dark #chain-graph .chev { fill: #34d399; }
html.dark #chain-graph .node.ghost rect { fill: #111827; stroke: #4b5563; }
html.dark #chain-graph .node.ghost text { fill: #9ca3af; }
html.dark .board-graph #chain-graph .node.ghost rect { fill: #374151; stroke: #6b7280; }
html.dark .board-graph #chain-graph .node.ghost text { fill: #f3f4f6; }
html.dark .board-graph #chain-graph .node.drag-from rect { fill: #1e3a8a; stroke: #60a5fa; }
html.dark :is(.board-graph #chain-hint, #summary #chain-hint) { color: #d1d5db; }
/* Logos (brand/export, 144x26): the dark one in dark mode. 28px tall on
   a phone, 54px on larger screens. */
.top-logo-img { height: 28px; width: auto; }
/* the header's mode button: Hunt or Chain on a phone, the full name on larger screens */
.mode-short { display: none; }
@media (max-width: 640px) { .mode-full { display: none; } .mode-short { display: inline; } }
@media (min-width: 641px) {
    .top-logo-img { height: 54px; }
    /* the mode menu, gear and ? grow with the logo */
    .top-btn { font-size: 1rem; padding: 0.45rem 0.8rem; }
    .top-icon { width: 2.5rem; height: 2.5rem; padding: 0; font-size: 1.1rem; }
    #top-settings-btn svg { width: 22px; height: 22px; }
}
#top-logo-dark, html.dark #top-logo-img { display: none; }
html.dark #top-logo-dark { display: block; }
/* History chips: newest at top-left. Shape echoes the status row:
   sticky and almost are rounded rectangles (like the segments), a
   miss is a pill (like the dots); a screen-reader word says which. */
#history-wrap { display: none; }
.hchip { display: inline-flex; align-items: center; padding: 0.1rem 0.5rem; border-radius: 0.375rem;
    font-size: 1rem; line-height: 1.5rem; white-space: nowrap; border: 1px solid transparent; }
.hchip.miss { border-radius: 9999px; padding: 0.1rem 0.65rem; }
.history-tint .hchip.sticky { background: #dcfce7; color: #166534; border-color: #86efac; }
.history-tint .hchip.almost { background: #fffbeb; color: #92400e; border-color: #fcd34d; }
.history-tint .hchip.miss   { background: #f3f4f6; color: #4b5563; border-color: #e5e7eb; }
.history-solid .hchip.sticky { background: #16a34a; color: #fff; }
.history-solid .hchip.almost { background: #f59e0b; color: #fff; }
.history-solid .hchip.miss   { background: #9ca3af; color: #fff; }
#history .hchip:first-child { animation: hchipIn 0.35s ease-out; }   /* a new guess slides in; lists that open whole don't */
@keyframes hchipIn { from { opacity: 0; transform: translateX(-6px); } }
/* The panes' boxes are spaced by a gap, not space-y margins: the
   hidden chain box (.hidden, display none) still pushed the results
   box down 1rem, so its top sat below the board's. */
#right-pane { display: flex; flex-direction: column; gap: 1rem; }
/* Playtest, two columns (lg): the board keeps its width and the
   Phrases entered pane is narrower, the pair centred together, so
   more of the art shows. The curator keeps equal columns (its rows
   carry much more). */
@media (min-width: 1024px) {
    .playtest #game-container > .grid { grid-template-columns: minmax(0, 31.5rem) minmax(0, 19rem); justify-content: center; }
    .playtest #top-bar { max-width: 51.5rem; margin-left: auto; margin-right: auto; }   /* spans the two panes */
}
#right-pane > * { margin-top: 0 !important; margin-bottom: 0 !important; }
@media (max-width: 640px) {
    .playtest #word-buttons-container .word-button { font-size: 18px; line-height: 1.625rem; }   /* 16px tried first */
    .playtest #current-bigram { font-size: 18px; font-weight: 600; }   /* as the word boxes */
    .playtest #call-to-action { font-size: 1.125rem; }
    .playtest #history-wrap { display: block; margin-top: 0.5rem; }
    .playtest #history-wrap:has(#history:empty) { display: none; }
    .playtest #history { display: flex; flex-wrap: wrap; gap: 0.35rem; margin-top: 0.4rem; }   /* the panel grows with it */
    .playtest #submitted-list-container { display: none; }
    .playtest.summary-open #right-results-box { display: none; }   /* the summary has it all */
    .playtest #right-results-box:not(:has(#correct-list-reveal:not(.hidden), #almost-list-reveal:not(.hidden))) { display: none; }
}
/* Tutorial callouts (showCallout): label + connecting line, fading in and out. */
.callout > * { position: absolute; z-index: 40; pointer-events: none; animation: calloutIn 0.35s ease-out; }
.callout-line { width: 2px; background: #2563eb; }   /* 'left' callouts set a 2px height instead */
.callout-label { max-width: 15rem; padding: 0.3rem 0.65rem; border-radius: 0.5rem; background: #2563eb; color: #fff;
    font-size: 0.85rem; font-weight: 600; line-height: 1.25; text-align: center; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18); }
.callout.gone > * { opacity: 0; transition: opacity 0.4s ease; }
.callout-target { box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.35) !important; border-radius: 0.5rem; transition: box-shadow 0.3s ease; }
@keyframes calloutIn { from { opacity: 0; } }
/* Guided tutorial: each new line of the script fades in. */
#call-to-action.guide-change { animation: fadeInGuide 0.35s ease-out; }
@keyframes fadeInGuide { from { opacity: 0.15; } }
@media (prefers-reduced-motion: reduce) {
    .progress-seg, .progress-seg::after, .miss-dot::after { animation: none !important; }
}
.miss-dot { width: 1rem; height: 1rem; border-radius: 9999px; background: #6b7280; transition: background-color 0.3s ease; }
.miss-dot.used { background: #e5e7eb; }
/* A miss: the dark dot swells a touch, then shrinks to its centre
   and is gone, leaving the pale empty slot. */
.miss-dot { position: relative; }
.miss-dot.shrinking::after { content: ''; position: absolute; inset: 0; border-radius: 9999px; background: #6b7280;
    animation: dotShrink 0.6s ease-in forwards; }
@keyframes dotShrink { 0% { transform: scale(1); } 25% { transform: scale(1.2); } 100% { transform: scale(0); } }
#status-slot .slot-layer { grid-area: 1 / 1; transition: opacity 0.4s ease; }
#status-slot .slot-layer.faded { opacity: 0; pointer-events: none; }
#word-buttons-container { margin: 1.75rem 0 2rem !important; }
#word-buttons-container .word-button { position: absolute; white-space: nowrap; }
/* Same shadow as Delete/Enter in CSS, but on a pale button the same
   shadow reads as bigger: soften it so the three look matched. */
#shuffle-btn { box-shadow: 0 2px 3px rgba(0, 0, 0, 0.06); }
#shuffle-btn:hover:not(:disabled) { box-shadow: 0 4px 6px rgba(0, 0, 0, 0.09); }
.word-button:disabled, #word-buttons-container .word-button.off {
    opacity: 0.3;
    cursor: not-allowed;
    box-shadow: none;
}
#word-buttons-container .word-button.off:hover { background-color: #fff; }
/* Drag a phrase (initWordDrag): a touch on a word draws (no page
   scroll, no text selection); a touch between words still scrolls. */
.drag-board #word-buttons-container { touch-action: manipulation; user-select: none; -webkit-user-select: none; -webkit-touch-callout: none; }
.drag-board #word-buttons-container .word-button { touch-action: none; -webkit-tap-highlight-color: transparent; }
.word-button.drag-from { opacity: 1 !important; background: #dbeafe !important; border-color: #2563eb !important; }
.word-button.drag-to { opacity: 1 !important; background: #2563eb !important; border-color: #2563eb !important; color: #fff !important; }
/* ?board=graph: the chain graph stands in for the word buttons */
.board-graph #word-buttons-container { position: absolute !important; visibility: hidden; pointer-events: none; }
.board-graph #shuffle-btn, .board-graph #chain-graph-box { display: none !important; }
.board-graph #chain-graph { position: relative; margin: 0.5rem 0 0.25rem; touch-action: manipulation; user-select: none;
    -webkit-user-select: none; -webkit-touch-callout: none; }
/* SVG shapes can't set touch-action reliably, so each node has an
   invisible HTML box over it (G.hits) that blocks scrolling there */
.node-hit { position: absolute; z-index: 2; touch-action: none; -webkit-tap-highlight-color: transparent; }
.board-graph #chain-graph .node text { font-size: 18px; }
.board-graph #chain-graph .node { cursor: pointer; transition: opacity 0.15s ease; }
.board-graph #chain-graph .node.ghost rect { fill: #fff; stroke: #d1d5db; stroke-width: 1.5; stroke-dasharray: none; }
.board-graph #chain-graph .node.ghost text { fill: #1f2937; }
.board-graph #chain-graph .node.off { opacity: 0.3; cursor: default; }
.board-graph #chain-graph .node.drag-from { opacity: 1; }
.board-graph #chain-graph .node.drag-from rect { fill: #dbeafe; stroke: #2563eb; }
.board-graph #chain-graph .node.drag-to { opacity: 1; }
.board-graph #chain-graph .node.drag-to rect { fill: #2563eb; stroke: #2563eb; }
.board-graph #chain-graph .node.drag-to text { fill: #fff; }
.board-graph #chain-hint, #summary #chain-hint { font-size: 1rem; color: #4b5563; margin-top: 0.25rem; }
#summary #chain-hint { font-size: 0.875rem; }
.board-graph #board-wrap #chain-hint { margin-bottom: -0.5rem; }   /* sits close over the phrase box */
.sum-graph #chain-graph { margin: 0.5rem 0 0; }
/* the phrase in the box (syncPending): dotted green; on Enter solid, or red / amber and fading */
#chain-graph .edge.pending { stroke-dasharray: 0.1 7; stroke-width: 3.5; }
#chain-graph :is(.edge, .chev) { transition: opacity 0.15s ease; }
#chain-graph svg.focus-pair :is(.edge, .chev):not(.pending) { opacity: 0.3; }
#chain-graph .edge.pending.miss { stroke: #dc2626; animation: pendingOut 1.2s ease-in forwards; }
#chain-graph .chev.pending.miss { fill: #dc2626; animation: pendingOut 1.2s ease-in forwards; }
#chain-graph .edge.pending.almost { stroke: #f59e0b; animation: pendingOut 1.2s ease-in forwards; }
#chain-graph .chev.pending.almost { fill: #f59e0b; animation: pendingOut 1.2s ease-in forwards; }
@keyframes pendingOut { 0%, 45% { opacity: 1; } 100% { opacity: 0; } }
.drag-line { position: absolute; left: 0; top: 0; overflow: visible; pointer-events: none; z-index: 5; }

/* Stickiness Colors */
.stickiness-sticky { color: #065f46; font-weight: bold; }
.stickiness-almost { color: #d97706; font-weight: bold; }
.stickiness-none { color: #991b1b; font-weight: bold; }
.stickiness-offcorpus { color: #1e40af; font-weight: bold; }


/* Custom Background Colors */
.bg-stickiness-sticky { background-color: #87F5A8; }
.bg-stickiness-almost { background-color: #fffbeb; }
.bg-stickiness-none { background-color: #F7BABA; }
.bg-stickiness-offcorpus { background-color: #dbeafe; }

.submitted-list-transition { transition: opacity 0.5s ease-out; }
.fade-out-submitted { opacity: 0.6; }
.fade-in-answers {
    opacity: 0;
    animation: fadeIn 0.5s forwards;
    animation-delay: 0.1s; 
}
@keyframes fadeIn { to { opacity: 1; } }

@keyframes floatUp {
    0% { transform: translateY(-50%); opacity: 1; }
    100% { transform: translateY(-80px); opacity: 0; }
}
.float-up-feedback {
    position: absolute; 
    top: 50%; 
    right: 0.75rem; 
    transform: translateY(-30%); 
    animation: floatUp 0.5s ease-out forwards; 
    pointer-events: none; 
    font-size: 1.25rem; 
    z-index: 50;
}

/* ---- Chain mode ----------------------------------------------- */
/* Tabs sitting on the game panel: the selected one is the panel's own
   white and covers the panel's top border, so it reads as part of it. */
#mode-toggle-row { position: relative; z-index: 1; }
.mode-toggle button {
    transition: background-color 0.15s ease, color 0.15s ease;
    background: #f3f4f6; color: #6b7280;
    border: 1px solid #e5e7eb; border-bottom: none; border-radius: 0.5rem 0.5rem 0 0;
}
.mode-toggle button:hover { color: #374151; }
.mode-toggle button.active { background: #fff; color: #1f2937; border-color: #ddd0ba; position: relative; top: 1px; }
/* Each mode is named by a sticky phrase, shown in full. On narrow
   screens only the selected tab shows its full name, the other its
   short word, and switching animates the widths. (Until 2026-10-28
   that collapsed form was used everywhere, with hover expanding
   both; it looked odd on desktop. To bring it back, move the two
   collapsed rules out of the media query and restore
   @media (hover: hover) { .mode-toggle:hover .mode-extra { max-width: 6em; opacity: 1; } }) */
.mode-toggle .mode-extra {
    display: inline-block; vertical-align: bottom; white-space: nowrap; overflow: hidden;
    max-width: 6em; opacity: 1;
    transition: max-width 0.3s ease, opacity 0.25s ease;
}
@media (max-width: 420px) {
    .mode-toggle .mode-extra { max-width: 0; opacity: 0; }
    .mode-toggle button.active .mode-extra { max-width: 6em; opacity: 1; }
}
/* Board words already in the chain. */
.word-chained {
    border-color: #059669 !important;
    box-shadow: 0 0 0 2px #a7f3d0, 0 4px 6px rgba(0, 0, 0, 0.1);
}
#chain-graph svg { display: block; width: 100%; height: auto; touch-action: manipulation; }
#chain-graph .edge { fill: none; stroke: #059669; stroke-width: 3; stroke-linecap: round; }
#chain-graph .edge.missed { stroke: #9ca3af; stroke-width: 2; stroke-dasharray: 5 5; }
/* New phrase draws itself in; the path carries pathLength=100 while
   this runs, so the dash maths ignores the edge's real length. */
#chain-graph .edge.fresh { stroke-dasharray: 100; animation: edgeDraw 0.7s ease-out forwards; }
@keyframes edgeDraw { from { stroke-dashoffset: 100; } to { stroke-dashoffset: 0; } }
/* Its arrowhead waits for the line to reach the target, then pops in. */
#chain-graph .chev.fresh { opacity: 0; animation: chevIn 0.15s ease-out 0.6s forwards; }
@keyframes chevIn { to { opacity: 1; } }
#chain-graph .chev { fill: #059669; }
#chain-graph .chev.missed { fill: #9ca3af; }
#chain-graph .edge-hit { fill: none; stroke: transparent; stroke-width: 16; cursor: default; }
#chain-graph .node rect { fill: #fff; stroke: #059669; stroke-width: 2; }
#chain-graph .node text { fill: #1f2937; font-weight: 600; font-size: 15px; font-family: var(--sp-font); }
#chain-graph .node.ghost rect { fill: #f9fafb; stroke: #d1d5db; stroke-width: 1.5; stroke-dasharray: 4 3; }
#chain-graph .node.ghost text { fill: #9ca3af; }
#chain-graph .node.missed rect { stroke: #9ca3af; stroke-dasharray: 4 3; }
#chain-graph .tray-label { fill: #9ca3af; font-size: 11px; font-family: var(--sp-font);
                           letter-spacing: 0.06em; text-transform: uppercase; }
