/* Brimbob shared styles: the base, the one button system, tags and fields, the wordmark, Bob and the
   beaker. The game loads it before app.css and the static pages before pages.css, so the pages never
   download the game screens' styles. Tokens live in tokens.css. No inline styles anywhere (CSP style-src 'self'). */

/* ---------------- base ---------------- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; overflow-x: clip; }
body {
  margin: 0;
  min-height: 100vh;
  min-height: 100dvh;
  background: var(--bg);
  color: var(--ink);
  font: 400 16px/1.45 var(--font-ui);
  -webkit-font-smoothing: antialiased;
  -webkit-tap-highlight-color: transparent;
  overflow-x: clip;
}
[hidden] { display: none !important; }
h1, h2, h3, p, ol, ul, dl, dd, figure, blockquote { margin: 0; }
ol, ul { padding: 0; list-style: none; }
button, input, textarea, select { font: inherit; color: inherit; }
button { cursor: pointer; touch-action: manipulation; }
button:disabled, [aria-disabled="true"] { cursor: default; }
a { color: inherit; }
svg { flex: none; }
img { max-width: 100%; height: auto; }
.ic { width: 22px; height: 22px; }
code { font-family: ui-monospace, SFMono-Regular, Consolas, monospace; font-size: 13px; }

:focus { outline: none; }
:focus-visible { outline: 3px solid var(--focus); outline-offset: 3px; border-radius: 10px; }
h1:focus-visible, h2:focus-visible, section:focus-visible, main:focus-visible { outline: none; }

.sr-only, .offscreen {
  position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.skip {
  position: absolute; left: 12px; top: -60px; z-index: 50; padding: 10px 16px; border-radius: 12px;
  min-height: 48px; display: inline-flex; align-items: center;
  background: var(--ink); color: var(--bg); font-weight: 600; text-decoration: none;
}
.skip:focus { top: calc(8px + env(safe-area-inset-top)); }
.muted { color: var(--muted); }
.small { font-size: 14px; }
.center { text-align: center; }
.nowrap { white-space: nowrap; }
.noscript { padding: 40px 16px; text-align: center; font-family: var(--font-display); font-size: 22px; }

/* liquid colour hooks */
.liq-violet { --liq: var(--liq-violet); }
.liq-teal { --liq: var(--liq-teal); }
.liq-sky { --liq: var(--liq-sky); }
.liq-coral { --liq: var(--liq-coral); }
.liq-mint { --liq: var(--liq-mint); }
.liq-indigo { --liq: var(--liq-indigo); }
.liq-green { --liq: var(--liq-green); }

/* ---------------- buttons ---------------- */
/* One button system for the game and the static pages. A button is a physical key: a solid bottom
   "lip" that collapses when pressed (a fast press) and springs back on release. Variants only set the
   custom properties below; sizes set height, type and icon size. Every size keeps a 48x48 tap target.

     <button class="btn btn-primary btn-lg">        primary   brand yellow, ink label, deep-yellow lip
     <a class="btn btn-secondary" href="faq">       secondary surface, strong edge + lip
     <button class="btn btn-ghost btn-sm">          ghost     transparent, soft edge, no lip
     <button class="btn btn-ink btn-block">         ink       the STOP key
     <button class="btn btn-danger">                danger    destructive
     <button class="icon-btn" aria-label="…">       icon only, 48x48 round
     disabled / aria-disabled="true"  flat, muted, no lip
     aria-busy="true"                 spinner before the label, not clickable  */
.btn {
  --btn-bg: var(--surface);
  --btn-bg-hover: var(--btn-bg);
  --btn-fg: var(--ink);
  --btn-edge: transparent;
  --btn-lip: transparent;
  --btn-extra: 0 0 0 0 transparent;   /* extra shadow layer (e.g. the sticky Next button's fade) */
  --lip: 4px;
  --btn-h: 48px;
  --btn-px: 20px;
  --btn-fs: 17px;
  --btn-ic: 20px;
  --btn-gap: 8px;
  position: relative;
  display: inline-flex; align-items: center; justify-content: center; gap: var(--btn-gap);
  min-height: var(--btn-h); min-width: 48px; padding: 4px var(--btn-px); margin-bottom: var(--lip);
  border: 2px solid var(--btn-edge); border-radius: var(--r-pill);
  background: var(--btn-bg); color: var(--btn-fg);
  box-shadow: 0 var(--lip) 0 var(--btn-lip), var(--btn-extra);
  font: 600 var(--btn-fs)/1.15 var(--font-display); letter-spacing: .01em;
  text-decoration: none; text-align: center; vertical-align: middle;
  cursor: pointer; touch-action: manipulation; -webkit-user-select: none; user-select: none;
  transition: transform .34s var(--ease-spring), box-shadow .34s var(--ease-spring), background-color .15s, color .15s, border-color .15s;
}
.btn .ic { width: var(--btn-ic); height: var(--btn-ic); flex: none; }
.btn:focus-visible { outline: 3px solid var(--focus); outline-offset: 3px; border-radius: var(--r-pill); }

.btn-primary { --btn-bg: var(--brand); --btn-bg-hover: var(--brand-hover); --btn-fg: var(--on-brand); --btn-lip: var(--brand-deep); }
.btn-secondary { --btn-bg: var(--surface); --btn-bg-hover: var(--surface-2); --btn-edge: var(--edge-strong); --btn-lip: var(--edge-strong); --lip: 3px; }
.btn-ghost { --btn-bg: transparent; --btn-bg-hover: var(--tint-hover); --btn-edge: var(--edge-soft); --lip: 0px; }
.btn-ink { --btn-bg: var(--ink); --btn-fg: var(--bg); --btn-lip: var(--ink-lip); }
.btn-danger { --btn-bg: var(--danger-fill); --btn-fg: #fff; --btn-lip: var(--danger-lip); }

.btn-sm { --btn-fs: 15px; --btn-px: 16px; --btn-ic: 18px; --btn-gap: 6px; --lip: 3px; }
.btn-ghost.btn-sm { --lip: 0px; }
.btn-lg { --btn-h: 56px; --btn-fs: 19px; --btn-px: 24px; --btn-ic: 22px; --btn-gap: 10px; --lip: 5px; }
.btn-xl { --btn-h: 60px; --btn-fs: 20px; --btn-px: 28px; --btn-ic: 22px; --btn-gap: 10px; --lip: 5px; }
.btn-block { display: flex; width: 100%; }

@media (hover: hover) and (pointer: fine) {
  .btn:hover:not(:disabled):not([aria-disabled="true"]):not([aria-busy="true"]) {
    background-color: var(--btn-bg-hover);
    transform: translateY(-2px);
    box-shadow: 0 calc(var(--lip) + 2px) 0 var(--btn-lip), var(--btn-extra);
  }
}
/* Pressed: the key travels down by its lip. A fast press, then the spring above brings it back. */
.btn:active:not(:disabled):not([aria-disabled="true"]):not([aria-busy="true"]) {
  transform: translateY(var(--lip));
  box-shadow: 0 0 0 var(--btn-lip), var(--btn-extra);
  transition-duration: .06s; transition-timing-function: ease-out;
}
.btn-ghost:active:not(:disabled):not([aria-disabled="true"]):not([aria-busy="true"]) { transform: scale(.97); }

.btn:disabled, .btn[aria-disabled="true"] {
  --btn-bg: var(--surface-3); --btn-bg-hover: var(--surface-3); --btn-fg: var(--muted);
  --btn-edge: transparent; --btn-lip: transparent; cursor: default;
}
/* Busy: a spinner takes the icon's place, the label stays (so the width never jumps). */
.btn[aria-busy="true"] { cursor: progress; }
.btn[aria-busy="true"] .ic { display: none; }
.btn[aria-busy="true"]::before {
  content: ""; flex: none; width: calc(var(--btn-ic) - 2px); height: calc(var(--btn-ic) - 2px);
  border-radius: 50%; border: 3px solid currentColor; border-right-color: transparent;
  animation: spin .75s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

.icon-btn {
  display: inline-grid; place-items: center; flex: none; width: 48px; height: 48px; padding: 0;
  border: 0; border-radius: 50%; background: transparent; color: var(--ink);
  cursor: pointer; touch-action: manipulation;
  transition: transform .3s var(--ease-spring), background-color .15s, color .15s;
}
.icon-btn .ic { width: 24px; height: 24px; }
.icon-btn-sm .ic { width: 22px; height: 22px; }
.icon-btn:focus-visible { outline: 3px solid var(--focus); outline-offset: 0; border-radius: 50%; }
.icon-btn[aria-pressed="true"] { background: var(--surface-3); }
@media (hover: hover) and (pointer: fine) {
  .icon-btn:hover { background: var(--surface-3); }
}
.icon-btn:active { transform: scale(.88); transition-duration: .06s; transition-timing-function: ease-out; }

/* Text links and text buttons: still a 48x48 target (a short label such as "FAQ" is centred in it). */
.link {
  display: inline-flex; align-items: center; justify-content: center; min-height: 48px; min-width: 48px; padding: 0 6px;
  border: 0; background: none; color: var(--muted); cursor: pointer;
  font: 600 14px var(--font-ui); text-decoration: underline; text-decoration-thickness: 1.5px; text-underline-offset: 4px;
  transition: color .15s, text-decoration-color .15s;
}
.link:hover { color: var(--ink); text-decoration-color: var(--brand-deep); }
.link:focus-visible { outline-offset: -2px; border-radius: 8px; }

/* ---------------- tags, fields ---------------- */

.tag {
  display: inline-flex; align-items: center; gap: 4px; padding: 2px 9px; border-radius: var(--r-pill);
  background: var(--surface-3); color: var(--ink); font: 700 13px/1.5 var(--font-ui); letter-spacing: .02em; vertical-align: middle;
}
.tag .ic { width: 15px; height: 15px; }
.tag-archive { background: var(--brand); color: var(--on-brand); }
.tag-bot { background: #9AA7BA; color: #13223A; text-transform: lowercase; }

.pill {
  display: inline-flex; align-items: center; gap: 6px; min-height: 34px; padding: 4px 12px;
  border-radius: var(--r-pill); background: var(--surface); border: 1.5px solid var(--line);
  font: 600 14px var(--font-ui); color: var(--ink);
}
.pill .ic { width: 18px; height: 18px; }
.pill-streak .ic { color: var(--liq-sky); }
.pill-freeze .ic { color: var(--liq-sky); }
.pill-gold { border-color: var(--gold); background: var(--gold-wash, color-mix(in srgb, var(--gold) 18%, var(--surface))); color: var(--ink); min-height: 28px; padding: 2px 10px; font-size: 13px; }

.field { display: grid; gap: 6px; margin: 12px 0; }
.field-label { font: 600 14px var(--font-ui); }
.field-hint { font-size: 13px; color: var(--muted); }
.field input, .field textarea, textarea.code {
  width: 100%; min-height: 48px; padding: 10px 14px; border-radius: 12px;
  border: 2px solid var(--field-line); background: var(--bg); color: var(--ink);
}
.copy-fallback { margin-top: 10px; }
textarea.code { font: 13px/1.4 ui-monospace, Consolas, monospace; resize: vertical; word-break: break-all; }
.field input:focus-visible, .field textarea:focus-visible { outline: 3px solid var(--focus); outline-offset: 1px; border-color: transparent; }
.field input[aria-invalid="true"] { border-color: var(--danger); }

/* ---------------- wordmark (game top bar, page header and footer) ---------------- */
.logo-bob { width: 44px; height: 33px; }
.logo-word { font: 700 25px/1 var(--font-display); letter-spacing: -.01em; }

/* ---------------- Bob ---------------- */
.bob { overflow: visible; }
.bob .bob-eye-open, .bob .bob-wing { display: inline; }
.bob .bob-eye-wide, .bob .bob-eye-wink, .bob .bob-sweat, .bob .bob-lines, .bob .bob-wing-up { display: none; }
.bob[data-state="nervous"] .bob-eye-open { display: none; }
.bob[data-state="nervous"] .bob-eye-wide, .bob[data-state="nervous"] .bob-sweat { display: inline; }
.bob[data-state="wink"] .bob-eye-open { display: none; }
.bob[data-state="wink"] .bob-eye-wink { display: inline; }
.bob[data-state="balance"] .bob-wing, .bob[data-state="surf"] .bob-wing { display: none; }
.bob[data-state="balance"] .bob-wing-up, .bob[data-state="surf"] .bob-wing-up, .bob[data-state="surf"] .bob-lines { display: inline; }

.bob-float { animation: bobFloat 2.4s ease-in-out infinite; }
.bob-tilt { transform-box: fill-box; transform-origin: 50% 90%; }
/* Idle Bob blinks now and then (one eye, scaleY): the cheapest sign of life. */
.bob .bob-eye-open { transform-box: fill-box; transform-origin: 50% 55%; }
.bob[data-state="idle"] .bob-eye-open { animation: blink 5.2s ease-in-out infinite; }
.logo-bob .bob-eye-open { animation-delay: 1.7s; }
@keyframes blink { 0%, 93%, 100% { transform: scaleY(1); } 95.5% { transform: scaleY(.12); } }
.bob[data-state="nervous"] .bob-tilt { animation: bobNervous .7s ease-in-out infinite; }
.bob[data-state="balance"] .bob-tilt { animation: bobBalance 1.5s ease-in-out infinite; }
.bob[data-state="wink"] .bob-tilt { animation: bobWink .6s var(--ease-pop) 1; }
.bob[data-state="surf"] .bob-float { animation: none; }
.bob .bob-sweat { animation: sweat 1.2s ease-in infinite; transform-box: fill-box; }
@keyframes bobFloat { 0%, 100% { transform: translateY(-6px); } 50% { transform: translateY(6px); } }
@keyframes bobNervous { 0%, 100% { transform: rotate(-3deg); } 50% { transform: rotate(3deg); } }
@keyframes bobBalance { 0%, 100% { transform: rotate(-10deg); } 50% { transform: rotate(10deg); } }
@keyframes bobWink { 0% { transform: scale(.9); } 60% { transform: scale(1.08); } 100% { transform: scale(1); } }
@keyframes sweat { 0% { transform: translateY(-2px); opacity: 0; } 30% { opacity: 1; } 100% { transform: translateY(10px); opacity: 0; } }

/* ---------------- beaker ---------------- */
.beaker { display: block; overflow: visible; }
.bk-back { fill: var(--glass); }
.bk-outline, .bk-brim { stroke: var(--glass-stroke); }
.bk-tick { stroke: var(--grad-ink); opacity: 1; }
/* Scale labels keep >= 4.5:1 over any liquid: solid ink plus a halo in the glass colour. */
.bk-grad { fill: var(--ink); fill-opacity: .82; opacity: 1; font-family: var(--font-ui); font-weight: 600; paint-order: stroke; stroke: var(--glass); stroke-width: 3px; stroke-linejoin: round; }
.bk-shine { stroke: var(--glass-shine); opacity: 1; }
.bk-tag-bg { fill: var(--ink); }
.bk-tag-k, .bk-tag-v { fill: var(--bg); font-family: var(--font-display); font-weight: 700; letter-spacing: .04em; }
.bk-tag-v { letter-spacing: 0; }
.bk-wave-front { animation: waveA 2.6s linear infinite; }
.bk-wave-back { animation: waveB 3.6s linear infinite; }
.bk-static .bk-wave, .bk-mini .bk-wave-back { animation: none; }
.bk-mini .bk-wave-front { animation-duration: 4s; }
@keyframes waveA { from { transform: translateX(0); } to { transform: translateX(40px); } }
@keyframes waveB { from { transform: translateX(0); } to { transform: translateX(-40px); } }

/* Pour stream: grows down from the top, then its tail drops into the glass (shrinks toward the surface). */
.bk-stream { opacity: 0; transform-box: fill-box; transform-origin: 50% 0; }
.bk-stream.is-on { opacity: .95; animation: streamIn .28s cubic-bezier(.5, 0, .9, .6) both; }
.bk-stream.is-off { opacity: .95; transform-origin: 50% 100%; animation: streamOut .2s ease-in both; }
@keyframes streamIn { from { transform: scaleY(0); } to { transform: scaleY(1); } }
@keyframes streamOut { from { transform: scaleY(1); opacity: .95; } to { transform: scaleY(0); opacity: .6; } }
/* Where the stream lands: a ripple ring on the surface, and fizz rising inside the liquid. */
.bk-ripple { opacity: 0; transform-box: fill-box; transform-origin: 50% 50%; }
.beaker.is-ripple .bk-ripple { animation: ripple .55s var(--ease-out) both; }
@keyframes ripple { from { transform: scale(.2, .3); opacity: .85; } to { transform: scale(1.7, 1); opacity: 0; } }
.bk-fizz circle { opacity: 0; transform-box: fill-box; }
.beaker.is-fizz .bk-fizz circle { animation: fizz 1s ease-out both; }
.bk-fizz circle:nth-child(2) { animation-delay: .08s; }
.bk-fizz circle:nth-child(3) { animation-delay: .16s; }
.bk-fizz circle:nth-child(4) { animation-delay: .05s; }
.bk-fizz circle:nth-child(5) { animation-delay: .22s; }
.bk-fizz circle:nth-child(6) { animation-delay: .12s; }
@keyframes fizz { 0% { transform: translateY(0); opacity: 0; } 25% { opacity: .75; } 100% { transform: translateY(-46px); opacity: 0; } }

.beaker[data-phase="tense"] .bk-brim, .beaker[data-phase="brimful"] .bk-brim { animation: brimPulse 1.2s ease-in-out infinite; }
@keyframes brimPulse { 0%, 100% { stroke: var(--glass-stroke); stroke-width: 5.5; } 50% { stroke: var(--gold); stroke-width: 8.5; } }
.beaker[data-phase="surface"] .bk-brim { stroke: var(--gold); }
.beaker.is-ended .bk-brim { animation: none; }

.bk-dome { transform-box: fill-box; transform-origin: 50% 100%; }
.bk-dome.is-wobble { animation: domeWobble 1.1s ease-out; }
@keyframes domeWobble {
  0% { transform: scaleY(.2); } 36% { transform: scaleY(1.28); } 52% { transform: scaleY(.84); }
  68% { transform: scaleY(1.12); } 84% { transform: scaleY(.95); } 100% { transform: scaleY(1); }
}

.bk-drips, .bk-puddle { display: none; }
.beaker.is-spilled .bk-drips, .beaker.is-spilled .bk-puddle { display: inline; }
.bk-drip { stroke-dasharray: 1 1; stroke-dashoffset: 1; animation: drip .75s ease-in .1s forwards; }
.bk-puddle { transform-box: fill-box; transform-origin: 50% 50%; animation: puddle .8s var(--ease-out) .35s both; }
.beaker.is-static .bk-drip { animation: none; stroke-dashoffset: 0; }
.beaker.is-static .bk-puddle { animation: none; }
@keyframes drip { to { stroke-dashoffset: 0; } }
@keyframes puddle { from { transform: scaleX(0); opacity: 0; } to { transform: scaleX(1); opacity: .9; } }
.bk-drop { animation: splash .7s cubic-bezier(.2, .7, .4, 1) forwards; }
@keyframes splash {
  0% { transform: translate(0, 0) scale(1); opacity: 1; }
  45% { transform: translate(var(--dx), var(--dy)) scale(1); opacity: 1; }
  100% { transform: translate(calc(var(--dx) * 1.5), var(--fall)) scale(.6); opacity: 0; }
}

.bk-spark { opacity: 0; transform-box: fill-box; transform-origin: 50% 50%; }
.beaker.is-sparkle .bk-spark { animation: spark 1s var(--ease-out) both; }
.bk-sparks g:nth-child(3n+1) .bk-spark { animation-delay: 0ms; }
.bk-sparks g:nth-child(3n+2) .bk-spark { animation-delay: 120ms; }
.bk-sparks g:nth-child(3n) .bk-spark { animation-delay: 240ms; }
@keyframes spark { 0% { opacity: 0; transform: scale(0) rotate(0); } 40% { opacity: 1; transform: scale(1.25) rotate(45deg); } 100% { opacity: 0; transform: scale(.4) rotate(90deg); } }
.bk-shimmer { opacity: 0; }
.beaker.is-shimmer .bk-shimmer { animation: shimmer .9s ease-in-out; }
@keyframes shimmer { 0% { transform: translateX(0); opacity: 0; } 15% { opacity: 1; } 85% { opacity: 1; } 100% { transform: translateX(330px); opacity: 0; } }

.is-shaking { animation: shake .25s linear; }
@keyframes shake { 0%, 100% { transform: translateX(0); } 20% { transform: translateX(-6px); } 40% { transform: translateX(6px); } 60% { transform: translateX(-5px); } 80% { transform: translateX(4px); } }

/* ---------------- small phones ---------------- */
@media (max-width: 360px) { :root { --gutter: 12px; } }

/* ---------------- reduced motion (system or the settings override) ---------------- */
/* The game's own selectors for this live at the end of app.css. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: 1ms !important; animation-delay: 0ms !important; animation-iteration-count: 1 !important; transition-duration: 150ms !important; transition-property: opacity, background-color, color, border-color !important; }
  .bob-float, .bob-tilt, .bk-wave, .bk-drop, .bob .bob-sweat, .bob .bob-eye-open, .bk-fizz circle, .bk-ripple, .is-shaking { animation: none !important; }
}
:root[data-motion="reduce"] *, :root[data-motion="reduce"] *::before, :root[data-motion="reduce"] *::after {
  animation-duration: 1ms !important; animation-delay: 0ms !important; animation-iteration-count: 1 !important;
  transition-duration: 150ms !important; transition-property: opacity, background-color, color, border-color !important;
}
:root[data-motion="reduce"] .bob-float, :root[data-motion="reduce"] .bob-tilt, :root[data-motion="reduce"] .bk-wave,
:root[data-motion="reduce"] .bk-drop, :root[data-motion="reduce"] .bob .bob-sweat, :root[data-motion="reduce"] .is-shaking,
:root[data-motion="reduce"] .bob .bob-eye-open, :root[data-motion="reduce"] .bk-fizz circle, :root[data-motion="reduce"] .bk-ripple { animation: none !important; }
