/* SYVT for the web — the free version of the Android game, skinned to match
   it. The game has two sides, and both are here: the grown-ups' Light and
   the children's Space and Day are playable, which is the app's own free
   set; Dark, Night, Aquarium, Princess Castle and Dinosaurs, and levels 6 to
   10, are SYVT+ on Android.

   Everything is drawn in the app's design space, 420 x 746.67 design px,
   scaled to fit the viewport. --s is design px per CSS px and is written by
   the script from the measured field, so the drawing and the simulation are
   one scale model. Every length below is therefore the number the app was
   drawn at. */

@font-face {
  font-family: "Manrope";
  src: url("manrope.woff2") format("woff2");
  font-weight: 200 800;
  font-display: optional;
}
@font-face {
  font-family: "Fredoka";
  src: url("fredoka.woff2") format("woff2");
  font-weight: 300 700;
  font-display: optional;
}

/* ------------------------------------------------------------------ tokens
   Three worlds, one set of names. Everything that differs between them is a
   custom property; everything that does not is written once.

   `data-theme` is the world; `data-audience` is the side of the game it
   belongs to. The face follows the SIDE rather than the world — Manrope for
   the grown-ups, Fredoka for the children — so the few rules that have to
   know which face is set key on the side, and go on working when a world is
   added to one. */

:root {
  --s: calc(min(100vw, calc(100dvh * 9 / 16)) / 420);
  --radius: calc(14 * var(--s));
  --floor-h: calc(46 * var(--s));
  --ceiling-h: calc(56 * var(--s));
}

:root[data-theme="light"] {
  --font: "Manrope", system-ui, -apple-system, "Segoe UI", sans-serif;
  --cap: 0.72em;                         /* Manrope's capital height */
  --page: radial-gradient(62% 48% at 50% 42%, #eef3f4 0%, #e1e8ea 58%, #d6dee0 100%);
  --field: radial-gradient(128% 86% at 50% -12%, #ffffff 0%, #f3f7f7 46%, #e9eff0 100%);
  --field-shadow:
    0 calc(48 * var(--s)) calc(100 * var(--s)) calc(-34 * var(--s)) rgba(16, 40, 44, .35),
    0 0 calc(90 * var(--s)) calc(-32 * var(--s)) rgba(45, 212, 191, .18),
    0 0 0 1px rgba(16, 40, 44, .10);

  --scrim: rgba(236, 242, 243, .62);
  --scrim-blur: 10px;
  --panel-face: linear-gradient(180deg, rgba(255, 255, 255, .78) 0%, rgba(244, 248, 248, .86) 100%);
  --panel-shadow:
    0 calc(44 * var(--s)) calc(96 * var(--s)) calc(-32 * var(--s)) rgba(16, 40, 44, .35),
    inset 0 0 0 1px rgba(16, 40, 44, .10),
    inset 0 1px 0 rgba(255, 255, 255, .90);
  --panel-blur: blur(40px) saturate(1.25);

  --brand-a: #10282c;
  --brand-b: #1c8f87;
  --title: #10282c;
  --over-title: #c43a52;
  --msg: rgba(16, 40, 44, .72);
  --legend-text: rgba(16, 40, 44, .70);
  --ring: #1c8f87;
  --pick-label: rgba(16, 40, 44, .68);
  --pick-label-on: #0b4a40;

  --lang-track: rgba(16, 40, 44, .05);
  --lang-track-border: 1px solid rgba(16, 40, 44, .10);
  --lang-on: rgba(45, 212, 191, .22);
  --lang-on-ring: inset 0 0 0 1px rgba(28, 143, 135, .5);
  --lang-on-text: #0b4a40;
  --lang-text: rgba(16, 40, 44, .68);

  --start-fill: linear-gradient(180deg, #7df0dd 0%, #2bc9b3 100%);
  --start-shadow:
    0 calc(16 * var(--s)) calc(34 * var(--s)) calc(-14 * var(--s)) rgba(45, 212, 191, .55),
    inset 0 1px 0 rgba(255, 255, 255, .55);
  --start-text: #032220;

  --quiet-fill: rgba(16, 40, 44, .05);
  --quiet-border: 1px solid rgba(16, 40, 44, .10);
  --quiet-text: rgba(16, 40, 44, .68);

  --hud-kicker: rgba(16, 40, 44, .72);
  --hud-score: #10282c;
  --hud-bump: #0fa38c;
  --hud-score-shadow: 0 calc(2 * var(--s)) calc(18 * var(--s)) rgba(16, 40, 44, .18);
  --chip-radius: calc(12 * var(--s));
  --chip-fill: rgba(16, 40, 44, .05);
  --chip-border: 1px solid rgba(16, 40, 44, .12);
  --chip-shadow: none;
  --chip-text: rgba(16, 40, 44, .72);
  --bar-track: rgba(16, 40, 44, .12);
  --bar-fill: linear-gradient(90deg, #2dd4bf, #1c8f87);

  --legend-chip-radius: calc(10 * var(--s));
  --legend-left: #7a1526;
  --legend-right: #0b4a40;
  --legend-left-tint: 229, 86, 110;
  --legend-right-tint: 31, 181, 158;

  --danger-wash: linear-gradient(180deg, rgba(251, 113, 133, .36) 0%, rgba(244, 63, 94, .10) 52%, rgba(244, 63, 94, 0) 100%);
  --danger-line: linear-gradient(90deg, rgba(251, 113, 133, .9) 0%, rgba(251, 113, 133, .35) 30%, rgba(251, 113, 133, .35) 70%, rgba(251, 113, 133, .9) 100%);
  --danger-glow: 0 0 calc(12 * var(--s)) rgba(251, 113, 133, .5);

  --plate-text: #10282c;
  --plate-shadow: 0 0 calc(28 * var(--s)) rgba(45, 212, 191, .35);
  --plate-rule: rgba(16, 40, 44, .45);
  --flash: rgba(251, 113, 133, .38);
  --pause-scrim: rgba(236, 242, 243, .6);
  --pause-blur: 8px;
  --pop-color: #0fa38c;
  --pop-shadow: 0 0 calc(18 * var(--s)) rgba(45, 212, 191, .45), 0 calc(2 * var(--s)) calc(10 * var(--s)) rgba(255, 255, 255, .9);
  --settle-ring: rgba(16, 40, 44, .495);
  --grain-vignette: inset 0 0 calc(150 * var(--s)) calc(26 * var(--s)) rgba(16, 40, 44, .16);
}

/* The children's Space: a friendly cartoon galaxy. White capsules fall on to
   a squishy moon under a violet sky. */
:root[data-theme="space"] {
  --font: "Fredoka", system-ui, -apple-system, "Segoe UI", sans-serif;
  --cap: 0.71em;
  --page: radial-gradient(70% 55% at 50% 40%, #1a1546 0%, #110e2e 100%);
  --field: linear-gradient(180deg, #4a3aa8 0%, #3a2c86 35%, #261f64 72%, #171340 100%);
  --field-shadow:
    0 calc(40 * var(--s)) calc(90 * var(--s)) calc(-30 * var(--s)) rgba(0, 0, 0, .75),
    0 0 calc(80 * var(--s)) calc(-28 * var(--s)) rgba(90, 79, 214, .45),
    0 0 0 1px rgba(255, 255, 255, .08);

  --scrim: rgba(11, 8, 36, .66);
  --scrim-blur: 6px;
  --panel-face: #f6f4ff;
  --panel-shadow:
    0 calc(8 * var(--s)) 0 rgba(11, 8, 36, .5),
    0 calc(28 * var(--s)) calc(56 * var(--s)) calc(-20 * var(--s)) rgba(11, 8, 36, .6),
    inset 0 0 0 calc(4 * var(--s)) #5a4fd6,
    inset 0 calc(-4 * var(--s)) 0 #ded9ff;
  --panel-blur: none;

  --brand-a: #7c6cf0;
  --brand-b: #4a3aa8;
  --title: #241e5c;
  --over-title: #241e5c;
  --msg: #4a4580;
  --legend-text: #4a4580;
  --ring: #5a4fd6;
  --pick-label: #4a4580;
  --pick-label-on: #241e5c;

  --lang-track: #e6e3ff;
  --lang-track-border: 1px solid rgba(90, 79, 214, .35);
  --lang-on: #ffd166;
  --lang-on-ring: none;
  --lang-on-text: #241e5c;
  --lang-text: #4a4580;

  --start-fill: linear-gradient(180deg, #ffe08a 0%, #ffd166 100%);
  --start-shadow:
    0 calc(6 * var(--s)) 0 #d9a62e,
    0 calc(14 * var(--s)) calc(24 * var(--s)) calc(-8 * var(--s)) rgba(11, 8, 36, .35),
    inset 0 1px 0 rgba(255, 255, 255, .5);
  --start-text: #241e5c;

  --quiet-fill: #e6e3ff;
  --quiet-border: 1px solid rgba(90, 79, 214, .35);
  --quiet-text: #4a4580;

  --hud-kicker: rgba(201, 194, 255, .9);
  --hud-score: #ffffff;
  --hud-bump: #ffd166;
  /* the score has no pill of its own, so a dense dark halo is its body under
     the red wash */
  --hud-score-shadow:
    0 calc(2 * var(--s)) 0 rgba(23, 19, 64, .7),
    0 0 calc(6 * var(--s)) rgba(23, 19, 64, .9),
    0 0 calc(14 * var(--s)) rgba(127, 211, 255, .55);
  --chip-radius: calc(15 * var(--s));
  /* a mission badge: a dark body that holds under the wash, with a gold
     stripe along its left end */
  --chip-fill: linear-gradient(90deg, #ffd166 0%, #ffd166 9%, rgba(23, 19, 64, .72) 9%, rgba(23, 19, 64, .72) 100%);
  --chip-border: 1.5px solid rgba(255, 255, 255, .45);
  --chip-shadow: 0 calc(2 * var(--s)) 0 rgba(23, 19, 64, .35);
  --chip-text: #ffffff;
  --bar-track: rgba(255, 255, 255, .22);
  --bar-fill: linear-gradient(90deg, #ffd166, #bfefff);

  --legend-chip-radius: calc(14 * var(--s));
  --legend-left: #8e1a16;
  --legend-right: #0c4a24;
  --legend-left-tint: 142, 26, 22;
  --legend-right-tint: 12, 74, 36;

  --danger-wash: linear-gradient(180deg, rgba(255, 90, 90, .45) 0%, rgba(255, 90, 90, .14) 55%, rgba(255, 90, 90, 0) 100%);
  --danger-line: linear-gradient(90deg, rgba(255, 97, 97, 1) 0%, rgba(255, 97, 97, .55) 30%, rgba(255, 97, 97, .55) 70%, rgba(255, 97, 97, 1) 100%);
  --danger-glow: 0 0 calc(12 * var(--s)) rgba(255, 97, 97, .75);

  --plate-text: #ffffff;
  --plate-shadow: 0 calc(2 * var(--s)) calc(6 * var(--s)) rgba(23, 19, 64, .75), 0 0 calc(16 * var(--s)) rgba(255, 209, 102, .8);
  --plate-rule: #ffd166;
  --flash: rgba(255, 77, 93, .32);
  --pause-scrim: rgba(23, 19, 64, .68);
  --pause-blur: 3px;
  --pop-color: #ffd166;
  --pop-shadow:
    1.5px 0 0 #241e5c, -1.5px 0 0 #241e5c, 0 1.5px 0 #241e5c, 0 -1.5px 0 #241e5c,
    0 0 calc(12 * var(--s)) rgba(255, 209, 102, .7);
  --settle-ring: rgba(255, 255, 255, .495);
  --grain-vignette: none;
}

/* The children's Day: a sunny meadow, cream picnic cards on bright grass. */
:root[data-theme="day"] {
  --font: "Fredoka", system-ui, -apple-system, "Segoe UI", sans-serif;
  --cap: 0.71em;
  --page: radial-gradient(70% 55% at 50% 40%, #3fa2e8 0%, #2b8fd8 55%, #1b6fb8 100%);
  --field: linear-gradient(180deg, #5fb8f5 0%, #8ed3ff 45%, #cfefff 78%, #eaf7ff 100%);
  --field-shadow:
    0 calc(26 * var(--s)) calc(60 * var(--s)) calc(-20 * var(--s)) rgba(12, 60, 100, .6),
    0 0 0 1.5px rgba(255, 255, 255, .35);

  --scrim: rgba(43, 143, 216, .55);
  --scrim-blur: 10px;
  --panel-face: #fffdf6;
  --panel-shadow:
    0 0 0 calc(5 * var(--s)) rgba(255, 255, 255, .45),
    0 calc(8 * var(--s)) 0 rgba(31, 127, 69, .35),
    0 calc(30 * var(--s)) calc(56 * var(--s)) calc(-14 * var(--s)) rgba(12, 60, 100, .45),
    inset 0 0 0 calc(4 * var(--s)) #2e9e58;
  --panel-blur: none;

  --brand-a: #2b8fd8;
  --brand-b: #1f7f45;
  --title: #1b3a2e;
  --over-title: #1b3a2e;
  --msg: #3e5a4e;
  --legend-text: #3e5a4e;
  --ring: #fb7185;
  --pick-label: #3e5a4e;
  --pick-label-on: #1b3a2e;

  --lang-track: #eaf7ea;
  --lang-track-border: 1.5px solid rgba(46, 158, 88, .5);
  /* gold already means "held / selected" here — the drag ring and the level
     bar are both this colour — and ink reads on it and on the cream face
     alike, which is why the picker's active label takes it too */
  --lang-on: #ffc93c;
  --lang-on-ring: none;
  --lang-on-text: #1b3a2e;
  --lang-text: #1b3a2e;

  --start-fill: linear-gradient(180deg, #ff9dae 0%, #fb7185 100%);
  --start-shadow:
    0 calc(6 * var(--s)) 0 #c94d65,
    0 calc(16 * var(--s)) calc(26 * var(--s)) calc(-8 * var(--s)) rgba(27, 58, 46, .3),
    inset 0 calc(4 * var(--s)) 0 rgba(255, 255, 255, .35);
  --start-text: #4a0f1e;

  --quiet-fill: #eaf7ea;
  --quiet-border: 1.5px solid rgba(46, 158, 88, .5);
  --quiet-text: #1b3a2e;

  --hud-kicker: rgba(27, 58, 46, .8);
  --hud-score: #1b3a2e;
  --hud-bump: #f07a2e;
  --hud-score-shadow: 0 0 calc(8 * var(--s)) rgba(255, 255, 255, .85);
  --chip-radius: 999px;
  --chip-fill: rgba(255, 255, 255, .88);
  --chip-border: 1.5px solid rgba(46, 158, 88, .7);
  --chip-shadow: 0 calc(2 * var(--s)) 0 rgba(31, 127, 69, .25);
  --chip-text: #1b3a2e;
  --bar-track: rgba(27, 58, 46, .18);
  --bar-fill: linear-gradient(90deg, #ffc93c, #ffe08a);

  --legend-chip-radius: calc(14 * var(--s));
  /* dark tints: a mid-luminance coral and green sink into the grass */
  --legend-left: #7a1530;
  --legend-right: #0b4a40;
  --legend-left-tint: 122, 21, 48;
  --legend-right-tint: 11, 74, 64;

  --danger-wash: linear-gradient(180deg, rgba(255, 77, 94, .42) 0%, rgba(255, 77, 94, .12) 52%, rgba(255, 77, 94, 0) 100%);
  --danger-line: linear-gradient(90deg, rgba(255, 77, 94, .95) 0%, rgba(255, 77, 94, .5) 30%, rgba(255, 77, 94, .5) 70%, rgba(255, 77, 94, .95) 100%);
  --danger-glow: 0 0 calc(12 * var(--s)) rgba(255, 77, 94, .7);

  --plate-text: #1b3a2e;
  --plate-shadow: 0 0 calc(10 * var(--s)) rgba(255, 255, 255, .95), 0 0 calc(3 * var(--s)) rgba(255, 255, 255, .9);
  --plate-rule: rgba(31, 127, 69, .8);
  --flash: rgba(255, 77, 94, .32);
  --pause-scrim: rgba(142, 211, 255, .66);
  --pause-blur: 6px;
  --pop-color: #ff9f43;
  --pop-shadow:
    1.5px 0 0 #1b3a2e, -1.5px 0 0 #1b3a2e, 0 1.5px 0 #1b3a2e, 0 -1.5px 0 #1b3a2e,
    0 0 calc(10 * var(--s)) rgba(255, 159, 67, .6);
  --settle-ring: rgba(255, 255, 255, .495);
  --grain-vignette: none;
}

/* ------------------------------------------------------------------ frame */

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

html, body {
  margin: 0;
  height: 100%;
  overflow: hidden;
  overscroll-behavior: none;
  touch-action: none;
}

body {
  font-family: var(--font);
  background: var(--page);
  color: var(--title);
  user-select: none;
  -webkit-user-select: none;
  display: flex;
  align-items: center;
  justify-content: center;
}

#game {
  position: relative;
  width: min(100vw, calc(100dvh * 9 / 16));
  height: min(100dvh, calc(100vw * 16 / 9));
  overflow: hidden;
  isolation: isolate;
  border-radius: calc(22 * var(--s));
  background: var(--field);
  box-shadow: var(--field-shadow);
}

#sceneBack, #sceneFront {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}
#sceneBack { z-index: 0; }
#sceneFront { z-index: 1; }   /* the ground paints over the pile */

#field { position: absolute; inset: 0; touch-action: none; }
#field.shake { animation: shake .3s cubic-bezier(.36, .07, .19, .97); }
@keyframes shake {
  0%, 100% { transform: translateX(0); }
  25% { transform: translateX(calc(-4 * var(--s))); }
  55% { transform: translateX(calc(3 * var(--s))); }
  85% { transform: translateX(calc(-1 * var(--s))); }
}

/* Light's static scenery is three CSS layers and a grain; the children's
   worlds paint their own on the canvas and hide these. Keyed on the side
   rather than on each world, so a world added to the children never arrives
   wearing the grown-ups' aurora. */
#aurora, #dots, #ceiling, #grain { pointer-events: none; position: absolute; }
:root[data-audience="kids"] #aurora,
:root[data-audience="kids"] #dots,
:root[data-audience="kids"] #ceiling,
:root[data-audience="kids"] #grain { display: none; }

#aurora {
  inset: -28%;
  z-index: 0;
  background:
    radial-gradient(36% 34% at 26% 22%, rgba(45, 212, 191, .22), rgba(45, 212, 191, 0) 70%),
    radial-gradient(40% 38% at 76% 66%, rgba(120, 170, 230, .18), rgba(120, 170, 230, 0) 70%),
    radial-gradient(30% 26% at 62% 92%, rgba(251, 113, 133, .10), rgba(251, 113, 133, 0) 70%);
  animation: drift 30s ease-in-out infinite alternate;
}
@keyframes drift {
  from { transform: translate3d(-3%, -2%, 0) scale(1); }
  to   { transform: translate3d(3%, 2%, 0) scale(1.08); }
}

#dots {
  inset: 0;
  z-index: 0;
  background-image: radial-gradient(circle at calc(1 * var(--s)) calc(1 * var(--s)),
    rgba(16, 40, 44, .075) calc(1.3 * var(--s)), rgba(16, 40, 44, 0) calc(1.4 * var(--s)));
  background-size: calc(27 * var(--s)) calc(27 * var(--s));
  -webkit-mask-image: linear-gradient(180deg, #000 0%, rgba(0, 0, 0, .55) 46%, transparent 74%);
  mask-image: linear-gradient(180deg, #000 0%, rgba(0, 0, 0, .55) 46%, transparent 74%);
}

/* the line the stack must not reach */
#ceiling {
  left: 0; right: 0; top: var(--ceiling-h);
  height: 1px;
  z-index: 0;
  background: linear-gradient(90deg,
    rgba(16, 40, 44, .204) 0%, rgba(16, 40, 44, .037) 26%,
    rgba(16, 40, 44, .037) 74%, rgba(16, 40, 44, .204) 100%);
}

#grain {
  inset: 0;
  z-index: 4;
  box-shadow: var(--grain-vignette);
}

/* ------------------------------------------------------------------ tiles */

.block {
  position: absolute;
  top: 0; left: 0;
  padding: calc(12 * var(--s)) calc(18 * var(--s));
  border-radius: var(--radius);
  font-family: var(--font);
  font-size: calc(21 * var(--s));
  font-weight: 600;
  letter-spacing: calc(.21 * var(--s));
  white-space: nowrap;
  touch-action: none;
  cursor: grab;
  will-change: transform, opacity;
  transition: border-color .22s ease, box-shadow .22s ease, color .22s ease;
}
.block.dragging { cursor: grabbing; }
.block.locked { cursor: default; }

/* one-shot rim flash the moment a tile settles. It lives on a pseudo-element
   because the tile's own transform belongs to the physics loop and must never
   be animated by CSS. */
.block::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0;
}
.block.locked::after { animation: settle .5s ease-out; }
@keyframes settle {
  0%   { opacity: 1; box-shadow: inset 0 0 0 calc(1.5 * var(--s)) var(--settle-ring); }
  100% { opacity: 0; box-shadow: inset 0 0 0 calc(1.5 * var(--s)) var(--settle-ring); }
}

/* sums fall as a disc, not a brick: a different shape and a different family
   from the words, so you know which call you are about to make before you
   have read it */
.block.eq {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 0;
  border-radius: 50%;
  font-size: calc(19 * var(--s));
  line-height: 1.12;
  letter-spacing: calc(.38 * var(--s));
  font-variant-numeric: tabular-nums;
}

:root[data-theme="light"] .block {
  background: linear-gradient(158deg, #ffffff 0%, #f4f8f8 100%);
  border: 1px solid rgba(16, 40, 44, .16);
  color: #10282c;
  box-shadow:
    0 calc(16 * var(--s)) calc(30 * var(--s)) calc(-16 * var(--s)) rgba(16, 40, 44, .28),
    0 calc(2 * var(--s)) calc(6 * var(--s)) rgba(16, 40, 44, .12),
    inset 0 1px 0 rgba(255, 255, 255, .9),
    inset 0 calc(-2 * var(--s)) calc(6 * var(--s)) rgba(16, 40, 44, .06);
}
:root[data-theme="light"] .block.dragging {
  border-color: rgba(28, 143, 135, .6);
  box-shadow:
    0 calc(22 * var(--s)) calc(40 * var(--s)) calc(-18 * var(--s)) rgba(16, 40, 44, .32),
    0 0 calc(26 * var(--s)) calc(-6 * var(--s)) rgba(45, 212, 191, .35),
    inset 0 1px 0 rgba(255, 255, 255, .9);
}
:root[data-theme="light"] .block.ok {
  background: linear-gradient(158deg, #d7f8ef 0%, #bfefe2 100%);
  border-color: rgba(31, 181, 158, .8);
  color: #0b4a40;
  box-shadow:
    0 calc(16 * var(--s)) calc(30 * var(--s)) calc(-16 * var(--s)) rgba(16, 40, 44, .25),
    0 0 calc(24 * var(--s)) calc(-6 * var(--s)) rgba(45, 212, 191, .35),
    inset 0 1px 0 rgba(255, 255, 255, .7);
}
:root[data-theme="light"] .block.mis {
  background: linear-gradient(158deg, #ffd6dc 0%, #fbaab8 100%);
  border-color: rgba(240, 84, 111, .8);
  color: #7a1526;
  box-shadow:
    0 calc(16 * var(--s)) calc(30 * var(--s)) calc(-16 * var(--s)) rgba(16, 40, 44, .25),
    0 0 calc(24 * var(--s)) calc(-6 * var(--s)) rgba(251, 113, 133, .35),
    inset 0 1px 0 rgba(255, 255, 255, .7);
}
:root[data-theme="light"] .block.eq:not(.locked) {
  background: radial-gradient(120% 120% at 34% 24%, #ffffff 0%, #e9e4ff 72%);
  border-color: rgba(120, 110, 210, .45);
  color: #3e2a7a;
  box-shadow:
    0 calc(16 * var(--s)) calc(30 * var(--s)) calc(-16 * var(--s)) rgba(16, 40, 44, .28),
    0 calc(2 * var(--s)) calc(6 * var(--s)) rgba(16, 40, 44, .12),
    0 0 calc(24 * var(--s)) calc(-6 * var(--s)) rgba(129, 140, 248, .3),
    inset 0 1px 0 rgba(255, 255, 255, .9);
}

/* Space: a white capsule with a violet rim, a hard 4px drop and a faint cyan
   halo so the white pops off the violet sky. A settled tile keeps the drop
   and a small shadow; a pile of forty must stay cheap. */
:root[data-theme="space"] .block {
  background: linear-gradient(180deg, #ffffff 0%, #ffffff 62%, #efedff 100%);
  border: calc(2.5 * var(--s)) solid #5a4fd6;
  color: #241e5c;
  box-shadow:
    0 calc(4 * var(--s)) 0 rgba(23, 19, 64, .45),
    0 calc(12 * var(--s)) calc(20 * var(--s)) rgba(11, 8, 36, .5),
    0 0 calc(10 * var(--s)) rgba(127, 211, 255, .18);
}
:root[data-theme="space"] .block.dragging {
  border-color: #ffd166;
  box-shadow:
    0 calc(6 * var(--s)) 0 rgba(23, 19, 64, .45),
    0 calc(14 * var(--s)) calc(22 * var(--s)) rgba(11, 8, 36, .45),
    0 0 calc(16 * var(--s)) rgba(255, 209, 102, .5);
}
:root[data-theme="space"] .block.ok {
  background: linear-gradient(180deg, #aef3bf 0%, #a6f0b8 50%, #93e6ab 100%);
  border-color: #2fa85a;
  color: #0c4a24;
  box-shadow:
    0 calc(4 * var(--s)) 0 rgba(23, 19, 64, .45),
    0 calc(6 * var(--s)) calc(8 * var(--s)) rgba(11, 8, 36, .35);
}
:root[data-theme="space"] .block.mis {
  background: linear-gradient(180deg, #d03d39 0%, #cc3a36 50%, #be332f 100%);
  border-color: #8e1a16;
  color: #fff7f3;
  box-shadow:
    0 calc(4 * var(--s)) 0 rgba(23, 19, 64, .45),
    0 calc(6 * var(--s)) calc(8 * var(--s)) rgba(11, 8, 36, .35);
}
/* a sum falls as a little moon: a different shape AND a different family */
:root[data-theme="space"] .block.eq:not(.locked) {
  background: radial-gradient(78% 78% at 36% 28%, #fff8cf 0%, #ffe58a 62%, #e8c25a 100%);
  border-color: #c89a2e;
  color: #4a3600;
  box-shadow:
    0 calc(4 * var(--s)) 0 rgba(23, 19, 64, .45),
    0 calc(12 * var(--s)) calc(20 * var(--s)) rgba(11, 8, 36, .5),
    0 0 calc(10 * var(--s)) rgba(255, 233, 168, .25);
}

/* Day: a cream picnic card with a leaf-green rim. Every look keeps the same
   three shadow slots — a hard lift, a soft glow, an inset gloss — so the
   verdict transitions interpolate shadow by shadow. */
:root[data-theme="day"] .block {
  background: linear-gradient(180deg, #fffdf5 0%, #fff0d2 100%);
  border: calc(2.5 * var(--s)) solid #2e9e58;
  color: #1b3a2e;
  box-shadow:
    0 calc(4 * var(--s)) 0 rgba(31, 127, 69, .35),
    0 calc(10 * var(--s)) calc(18 * var(--s)) rgba(27, 58, 46, .22),
    inset 0 calc(5 * var(--s)) 1px rgba(255, 255, 255, .6);
}
:root[data-theme="day"] .block.dragging {
  border-color: #ffc93c;
  box-shadow:
    0 calc(6 * var(--s)) 0 rgba(31, 127, 69, .35),
    0 0 calc(14 * var(--s)) rgba(255, 201, 60, .55),
    inset 0 calc(5 * var(--s)) 1px rgba(255, 255, 255, .6);
}
:root[data-theme="day"] .block.ok {
  background: linear-gradient(180deg, #9ef0d9 0%, #7ee3c8 100%);
  border-color: #1c8f87;
  color: #0b4a40;
  box-shadow:
    0 calc(4 * var(--s)) 0 rgba(31, 127, 69, .3),
    0 0 calc(8 * var(--s)) rgba(45, 212, 191, .4),
    inset 0 calc(5 * var(--s)) 1px rgba(255, 255, 255, .5);
}
/* the top stop IS the 4.5:1 colour; the card only gets darker below it */
:root[data-theme="day"] .block.mis {
  background: linear-gradient(180deg, #d2304f 0%, #c22a46 100%);
  border-color: #7a1530;
  color: #ffffff;
  box-shadow:
    0 calc(4 * var(--s)) 0 rgba(27, 58, 46, .35),
    0 0 calc(8 * var(--s)) rgba(251, 113, 133, .4),
    inset 0 calc(5 * var(--s)) 1px rgba(255, 255, 255, .3);
}
/* a sum falls as a little sun */
:root[data-theme="day"] .block.eq:not(.locked) {
  background: radial-gradient(80% 80% at 36% 30%, #fff8d6 0%, #ffe27a 55%, #ffc93c 100%);
  border-color: #f07a2e;
  color: #4a3600;
  box-shadow:
    0 calc(4 * var(--s)) 0 rgba(31, 127, 69, .35),
    0 0 calc(18 * var(--s)) rgba(255, 209, 102, .5),
    inset 0 calc(5 * var(--s)) 1px rgba(255, 255, 255, .7);
}

/* landing burst and the "+1" */
.puff {
  position: absolute;
  width: calc(62 * var(--s)); height: calc(62 * var(--s));
  margin: calc(-31 * var(--s)) 0 0 calc(-31 * var(--s));
  border-radius: 50%;
  pointer-events: none;
  z-index: 2;
  border: calc(1.5 * var(--s)) solid var(--puff-ring, rgba(28, 143, 135, .6));
  animation: puff .6s cubic-bezier(.2, .75, .3, 1) forwards;
}
:root[data-theme="day"] .puff { --puff-ring: rgba(255, 201, 60, .95); }
:root[data-theme="space"] .puff { --puff-ring: rgba(255, 233, 168, .9); }
@keyframes puff {
  0%   { transform: scale(.22); opacity: .85; }
  100% { transform: scale(1.4); opacity: 0; }
}
.score-pop {
  position: absolute;
  pointer-events: none;
  font-family: var(--font);
  font-size: calc(22 * var(--s));
  font-weight: 800;
  letter-spacing: calc(-.31 * var(--s));
  font-variant-numeric: tabular-nums;
  color: var(--pop-color);
  text-shadow: var(--pop-shadow);
  animation: floatUp .78s cubic-bezier(.2, .8, .3, 1) forwards;
}
@keyframes floatUp {
  0%   { transform: translateY(0) scale(.7); opacity: 0; }
  20%  { transform: translateY(calc(-10 * var(--s))) scale(1.12); opacity: 1; }
  100% { transform: translateY(calc(-58 * var(--s))) scale(1); opacity: 0; }
}

/* -------------------------------------------------------------------- HUD */

#hud {
  position: absolute;
  top: calc(13 * var(--s));
  left: calc(18 * var(--s));
  right: calc(18 * var(--s));
  z-index: 10;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  pointer-events: none;
}
#scoreBlock { display: flex; flex-direction: column; gap: calc(3 * var(--s)); }
#scoreKicker {
  font-size: calc(9 * var(--s));
  line-height: 1;
  font-weight: 700;
  letter-spacing: calc(1.71 * var(--s));
  color: var(--hud-kicker);
}
#score {
  font-size: calc(30 * var(--s));
  line-height: .92;
  font-weight: 800;
  letter-spacing: calc(-.51 * var(--s));
  font-variant-numeric: tabular-nums;
  color: var(--hud-score);
  text-shadow: var(--hud-score-shadow);
  transform-origin: left center;
}
#score.bump, #scoreBonus.bump { animation: bump .34s cubic-bezier(.2, .8, .3, 1); }

/* The free game stops counting at 150. What comes after runs in a counter of
   its own, hung off a hairline so the two can never be read as one number:
   150 is what compares with another player, the +X is not. */
#scoreRow { display: flex; align-items: baseline; gap: calc(7 * var(--s)); }
#scoreBonus {
  padding-left: calc(7 * var(--s));
  border-left: 1px solid color-mix(in srgb, var(--hud-score) 30%, transparent);
  font-size: calc(15 * var(--s));
  line-height: .92;
  font-weight: 800;
  letter-spacing: calc(-.26 * var(--s));
  font-variant-numeric: tabular-nums;
  color: var(--hud-score);
  text-shadow: var(--hud-score-shadow);
  transform-origin: left center;
}
#scoreBonus[hidden] { display: none; }
@keyframes bump {
  0%   { transform: scale(1); }
  35%  { transform: scale(1.26); color: var(--hud-bump); }
  100% { transform: scale(1); }
}

/* The pause chip and the voice chip share the middle of the rail, so the HUD
   keeps its three columns and the score and level chips do not move. */
#hudMid {
  display: flex;
  align-items: flex-start;
  gap: calc(14 * var(--s));
  pointer-events: none;
}

#pauseBtn, #voiceBtn {
  pointer-events: auto;
  width: calc(38 * var(--s));
  height: calc(38 * var(--s));
  padding: 0;
  border-radius: calc(19 * var(--s));
  background: var(--chip-fill);
  border: var(--chip-border);
  box-shadow: var(--chip-shadow);
  cursor: pointer;
  display: none;
  align-items: center;
  justify-content: center;
}
#game[data-phase="playing"] #pauseBtn,
#game[data-phase="playing"] #voiceBtn { display: flex; }
#pauseBtn svg, #voiceBtn svg { width: calc(38 * var(--s)); height: calc(38 * var(--s)); }
#pauseBtn path {
  fill: none;
  stroke: var(--chip-text);
  stroke-width: 2.6;
  stroke-linecap: round;
}

/* One glyph or the other, never both: at 38 px on a moving field silhouette
   is all a player gets, and "cone plus waves" against "cone plus bar" is two
   shapes at a glance where all three together is one busy one. */
#voiceBtn .muted { display: none; }
#voiceBtn[aria-pressed="false"] .spoken { display: none; }
#voiceBtn[aria-pressed="false"] .muted { display: inline; }
#voiceBtn .cone { fill: var(--chip-text); }
#voiceBtn .wave, #voiceBtn .bar {
  fill: none;
  stroke: var(--chip-text);
  /* 2.4 rather than the pause bars' 2.6: a filled cone already carries more
     ink than two uprights. */
  stroke-width: 2.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* What a control keeps of itself when it cannot be used: switched on with
   nothing to fetch from, or past the levels the free game reads. Harder than
   the locked world card's .7, because that card also wears a padlock and
   there is no room for one here. It still takes taps either way — a player
   whose train is in a tunnel must be able to switch it off, and a player past
   level 2 gets the offer. */
#voiceBtn.dim { opacity: .5; }

#levelChip {
  display: flex;
  flex-direction: column;
  gap: calc(7 * var(--s));
  padding: calc(8 * var(--s)) calc(13 * var(--s)) calc(9 * var(--s));
  border-radius: var(--chip-radius);
  background: var(--chip-fill);
  border: var(--chip-border);
  box-shadow: var(--chip-shadow);
}
#levelLabel {
  font-size: calc(10 * var(--s));
  font-weight: 700;
  line-height: 1;
  letter-spacing: calc(1.6 * var(--s));
  color: var(--chip-text);
  white-space: nowrap;
}
#levelBar {
  height: calc(2 * var(--s));
  border-radius: 999px;
  background: var(--bar-track);
  overflow: hidden;
}
#levelFill {
  display: block;
  height: 100%;
  width: 100%;
  transform: scaleX(0);
  transform-origin: left center;
  background: var(--bar-fill);
  transition: transform .35s cubic-bezier(.2, .8, .3, 1);
}

/* ----------------------------------------------------------- swipe legend */

#legend {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: var(--floor-h);
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 calc(12 * var(--s));
  pointer-events: none;
  transition: opacity .7s ease;
}
#game[data-phase="playing"] #legend,
#game[data-phase="paused"] #legend { opacity: .9; }
#legend .side {
  display: flex;
  align-items: center;
  gap: calc(9 * var(--s));
  max-width: calc(192 * var(--s));
}
#legend .word {
  font-size: calc(9.5 * var(--s));
  font-weight: 700;
  line-height: 1.35;
  letter-spacing: calc(1.14 * var(--s));
  text-transform: uppercase;
}
#legend .left .word { color: var(--legend-left); text-align: left; }
#legend .right .word { color: var(--legend-right); text-align: right; }
#legend .chip {
  flex: none;
  width: calc(28 * var(--s));
  height: calc(28 * var(--s));
  border-radius: var(--legend-chip-radius);
  display: flex;
  align-items: center;
  justify-content: center;
}
#legend .chip.blank { background: none; border: none; }
#legend .left .chip:not(.blank) {
  background: linear-gradient(158deg, rgba(var(--legend-left-tint), .16), rgba(var(--legend-left-tint), .05));
  border: 1px solid rgba(var(--legend-left-tint), .26);
  animation: nudgeL 2.8s ease-in-out infinite;
}
#legend .right .chip:not(.blank) {
  background: linear-gradient(158deg, rgba(var(--legend-right-tint), .16), rgba(var(--legend-right-tint), .05));
  border: 1px solid rgba(var(--legend-right-tint), .26);
  animation: nudgeR 2.8s ease-in-out infinite;
}
#legend .chip svg { width: calc(15 * var(--s)); height: calc(15 * var(--s)); }
#legend .chip path { fill: none; stroke-width: 2.6; stroke-linecap: round; stroke-linejoin: round; }
#legend .left .chip path { stroke: rgb(var(--legend-left-tint)); }
#legend .right .chip path { stroke: rgb(var(--legend-right-tint)); }
@keyframes nudgeL { 0%, 74%, 100% { transform: translateX(0); } 86% { transform: translateX(calc(-3 * var(--s))); } }
@keyframes nudgeR { 0%, 74%, 100% { transform: translateX(0); } 86% { transform: translateX(calc(3 * var(--s))); } }

/* ------------------------------------------------- danger, plate, flash */

#danger {
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 40%;
  z-index: 3;
  pointer-events: none;
  opacity: 0;
  background: var(--danger-wash);
  transition: opacity .25s ease;
}
#dangerLine {
  position: absolute;
  left: 0; right: 0; top: var(--ceiling-h);
  height: 1px;
  background: var(--danger-line);
  box-shadow: var(--danger-glow);
}
#danger.crit { animation: dangerPulse .9s ease-in-out infinite; }
@keyframes dangerPulse { 0%, 100% { filter: brightness(1); } 50% { filter: brightness(1.55); } }

#levelUp {
  position: absolute;
  top: 36%; left: 0; right: 0;
  z-index: 40;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: calc(16 * var(--s));
  font-size: calc(15 * var(--s));
  font-weight: 700;
  letter-spacing: calc(5.1 * var(--s));
  text-transform: uppercase;
  color: var(--plate-text);
  text-shadow: var(--plate-shadow);
  opacity: 0;
  pointer-events: none;
}
#levelUp .rule { width: calc(52 * var(--s)); height: 1px; background: linear-gradient(90deg, transparent, var(--plate-rule)); }
#levelUp .rule:last-child { transform: scaleX(-1); }
#levelUp.show { animation: levelUp 1.4s ease-out; }
@keyframes levelUp {
  0%   { opacity: 0; transform: translateY(calc(12 * var(--s))) scale(.94); }
  18%  { opacity: 1; transform: translateY(0) scale(1); }
  76%  { opacity: 1; transform: translateY(0) scale(1); }
  100% { opacity: 0; transform: translateY(calc(-12 * var(--s))) scale(1.03); }
}

#flash {
  position: absolute;
  inset: 0;
  z-index: 50;
  pointer-events: none;
  opacity: 0;
  box-shadow: inset 0 0 calc(90 * var(--s)) calc(14 * var(--s)) var(--flash);
}
#flash.show { animation: flashRed .34s ease-out; }
@keyframes flashRed { 0% { opacity: 0; } 25% { opacity: 1; } 100% { opacity: 0; } }

/* ------------------------------------------------------------- the pills */

.pill {
  width: calc(236 * var(--s));
  min-height: calc(44 * var(--s));
  padding: calc(11 * var(--s)) calc(20 * var(--s));
  border-radius: 999px;
  font-family: var(--font);
  font-size: calc(15 * var(--s));
  line-height: 1;
  cursor: pointer;
  border: none;
  text-align: center;
  text-decoration: none;
  display: flex;
  align-items: center;
  justify-content: center;
}
.pill.primary {
  font-weight: 800;
  letter-spacing: calc(1.05 * var(--s));
  text-transform: uppercase;
  background: var(--start-fill);
  color: var(--start-text);
  box-shadow: var(--start-shadow);
}
.pill.secondary {
  font-weight: 700;
  background: var(--quiet-fill);
  border: var(--quiet-border);
  color: var(--quiet-text);
}

/* ------------------------------------------------------------ pause veil */

#pauseVeil {
  position: absolute;
  inset: 0;
  z-index: 95;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: calc(10 * var(--s));
  background: var(--pause-scrim);
  backdrop-filter: blur(var(--pause-blur));
  -webkit-backdrop-filter: blur(var(--pause-blur));
}
#pauseVeil[hidden] { display: none; }
#pauseTitle {
  margin: 0 0 calc(12 * var(--s));
  font-size: calc(15 * var(--s));
  font-weight: 700;
  letter-spacing: calc(4.5 * var(--s));
  color: var(--title);
}

/* ---------------------------------------------------------- start panel */

#overlay {
  position: absolute;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: calc(18 * var(--s));
  background: var(--scrim);
  backdrop-filter: blur(var(--scrim-blur));
  -webkit-backdrop-filter: blur(var(--scrim-blur));
}
#overlay.hidden { display: none; }

#panel {
  position: relative;
  width: calc(372 * var(--s));
  max-height: 100%;
  /* `clip`, not `hidden`: SYVT flies in from beyond the panel's left edge
     and loops out over the letters, and hidden on one axis would make the
     panel scrollable sideways to follow him. clip draws nothing outside and
     scrolls nowhere, which is what the app's own scroll view does to him,
     and it leaves the vertical scrolling below untouched. */
  overflow-x: clip;
  overflow-y: auto;
  scrollbar-width: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding: calc(31 * var(--s)) 0 calc(5 * var(--s));
  border-radius: calc(28 * var(--s));
  background: var(--panel-face);
  box-shadow: var(--panel-shadow);
  backdrop-filter: var(--panel-blur);
  -webkit-backdrop-filter: var(--panel-blur);
}
#panel::-webkit-scrollbar { display: none; }

/* ------------------------------------------------------------- the mascot

   SYVT beside the name, on the children's side only. The app's _Brand: he
   is 72 design px tall, his slot in the row is twelve px shorter so the
   game-over panel still fits the field, and he is drawn sixteen px left of
   it, which is the air between him and the S. The lockup keeps the slot, so
   the name does not move as he flies.

   His canvas is far wider and taller than he is - the flight envelope, 396
   by 130 design px with his slot's top-left at (0, 0) inside it - because a
   canvas draws nothing outside itself and he leaves his slot in every
   direction. The numbers are measured from his own ink; mascot.js says how.
   It takes no pointer events, so the name and the tagline under it are
   untouched and the panel decides whether a tap was his. */

#brandRow {
  display: flex;
  align-items: center;
  gap: calc(6 * var(--s));
}
#mascotSlot {
  display: none;
  position: relative;
  flex: none;
  /* 72 x 120 / 110: his height, and the width his box gives it */
  width: calc(78.5455 * var(--s));
  height: calc(60 * var(--s));
}
:root[data-audience="kids"] #mascotSlot { display: block; }
/* The envelope, and no pointer events: it reaches over the name and down
   into the tagline, so the panel hears the taps and the script decides
   whether one of them was his. */
#syvtMascot {
  position: absolute;
  left: calc(-160 * var(--s));
  top: calc(-34 * var(--s));
  width: calc(396 * var(--s));
  height: calc(130 * var(--s));
  pointer-events: none;
}

/* the lockup: S, the funnel as the Y, VT. The funnel is a placeholder, not a
   glyph, so it carries no tracking of its own; the margins are the tracking
   the S already has, measured until the ink gaps match the one inside VT. */
#brand {
  display: inline-flex;
  align-items: baseline;
  /* 38 is the app's reference size and --s the design-space scale, but the
     wordmark alone gets a floor under it: the brand's minimum is a 24px cap
     height, and 34px is where that lands (x .72 Manrope, x .71 Fredoka). The
     scale model would put it at 20.9px on a 320px phone, where the card
     knocked out of the Y starts to close. This is the only place --s is
     departed from, and the lockup is the only thing it moves: the panel is
     calc(372 * var(--s)) and the floored lockup still sits inside it. */
  font-size: max(34px, calc(38 * var(--s)));
  font-weight: 800;
  line-height: 1;
  letter-spacing: .22em;
  /* the final T still gets a trailing letter-space that draws nothing, so the
     lockup is padded by the same amount on the left to sit centred */
  padding-left: .22em;
  background: linear-gradient(180deg, var(--brand-a) 0%, var(--brand-b) 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
/* The Y's two margins are MEASURED, not BRAND.md's 0.092em / 0.312em.

   Those two numbers come from the Flutter render, where the placeholder is
   given a trailing letter-space; CSS does not add one after an atomic inline,
   and an inline SVG is one, so they do not carry over. Built from them here
   the three ink gaps come out 115 / 105 / 70 at a 300px em - the Y floating in
   half again the air the letters get. The app's own renders have all three
   equal: 25 / 26 / 25 px off store/play/screenshot-1-start.png, which is
   _Brand, and 25 / 25 / 25 off the Play feature graphic. That is also what
   BRAND.md says it was after, "measured from the render until the gaps either
   side were even".

   So these are solved, per face, until each side of the Y matches the gap
   inside VT. Computed against the shipped fonts the three gaps come to
   .2370/.2353/.2350 em in Manrope and .2489/.2494/.2494 in Fredoka - equal to
   within .002 em, which is as equal as two side bearings and a tracking value
   can be made. What lands on screen moves by up to about .02 em on top of
   that, because the letter-space and the margins round to device pixels
   independently; at a 300px em all three measure 70px in Manrope and 77px in
   Fredoka, and at body sizes they differ by a pixel either way. The second
   pair below is the Fredoka solution, and it is keyed on the side of the
   game rather than on one world, because the face is.

   The left one is negative because it is undoing part of the letter-space CSS
   puts after the S and the app's layout never had. Re-derive with the harness
   in the commit message if the face or the tracking changes.
*/
/* The Y is sized from the cap height and is 96/88 as wide as it is tall; it
   stands on the baseline, and the drawn shape fills its box exactly, so there
   is no drop to correct. */
#brand .y {
  height: var(--cap);
  width: calc(var(--cap) * 96 / 88);
  margin-left: -.058em;    /* Manrope */
  margin-right: .1953em;
}
:root[data-audience="kids"] #brand .y {
  margin-left: -.018em;    /* Fredoka sets the S and the V differently */
  margin-right: .1987em;
}

/* The Y takes the letters' own gradient and never a colour of its own.
   background-clip: text cannot reach an inline SVG child, so the same two
   stops are repeated here in the Y's user space, mapped over the lockup's 1em
   line box rather than over the Y's own box - an objectBoundingBox gradient
   would refit the stops to the cap height and the funnel would come out darker
   at its foot than the letters beside it.

   Two gradients, because y1/y2 fall out of the font's ascent, descent and cap
   factor, and gradient coordinates are not CSS geometry properties, so one
   parameterised gradient is not possible. The stops are attributes in the
   markup as well, so a stylesheet that never arrives leaves the Y in Light's
   colours rather than in black. */
#brand .y .stop-a { stop-color: var(--brand-a); }
#brand .y .stop-b { stop-color: var(--brand-b); }
#brand .y .glyph { fill: url(#brandY-manrope); }
:root[data-audience="kids"] #brand .y .glyph { fill: url(#brandY-fredoka); }

/* Fredoka draws its S with more ink than its V and its T, so the name looks
   like it starts with a bold letter; 620 evens it. Its weight axis also stops
   at 700, which is why the rest asks for 800 and gets 700 rather than a step
   below it. Both axes every time: the wght default is 300, and a wdth left
   unwritten inherits whatever the instance carries. Manrope's S has not been
   measured, so the grown-ups' lockup keeps font-weight: 800 for all of it. */
:root[data-audience="kids"] #brand .lead { font-variation-settings: 'wdth' 100, 'wght' 620; }
:root[data-audience="kids"] #brand .tail { font-variation-settings: 'wdth' 100, 'wght' 800; }

/* These two sit directly under the lockup row, and the loop's return leg
   brings SYVT down across them. In the app they are later children of the
   panel's Column, so they paint over him; here his canvas is positioned and
   would otherwise paint over them. Lifting the text is the fix, not sinking
   the canvas: his round over the letters has to pass IN FRONT of the name,
   which a negative z-index on the canvas would undo. */
#tagline {
  position: relative;
  z-index: 1;
  margin: calc(16 * var(--s)) 0 0;
  width: calc(306 * var(--s));
  font-size: calc(14.5 * var(--s));
  font-weight: 500;
  line-height: 1.62;
  color: var(--msg);
}
#tagline[hidden] { display: none; }

#overTitle {
  position: relative;
  z-index: 1;
  margin: calc(22 * var(--s)) 0 0;
  font-size: calc(19 * var(--s));
  font-weight: 700;
  letter-spacing: calc(3.42 * var(--s));
  color: var(--over-title);
}
#overTitle[hidden] { display: none; }

#overStats {
  margin-top: calc(16 * var(--s));
  display: flex;
  gap: calc(36 * var(--s));
  justify-content: center;
}
#overStats[hidden] { display: none; }
#overStats .stat { display: flex; flex-direction: column; align-items: center; gap: calc(6 * var(--s)); }
/* an author display outranks the user agent's [hidden], so BEST needs its
   own rule or a first-ever game over shows a record of 0 */
#overStats .stat[hidden] { display: none; }
#overStats .val { display: flex; align-items: baseline; gap: calc(7 * var(--s)); }
#overStats em {
  font-style: normal;
  padding-left: calc(7 * var(--s));
  border-left: 1px solid color-mix(in srgb, var(--hud-score) 30%, transparent);
  font-size: calc(16 * var(--s));
  font-weight: 800;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  color: var(--hud-score);
}
#overStats em[hidden] { display: none; }

/* the "i" rides inside the counter as an inline box, so vertical-align does
   the sum the flex baseline could not: its middle on the number's middle */
#scoreInfo {
  display: inline-block;
  position: relative;
  vertical-align: middle;
  margin: 0 0 calc(1.5 * var(--s)) calc(5 * var(--s));
  padding: 0;
  width: calc(16 * var(--s));
  height: calc(16 * var(--s));
  border: none;
  background: none;
  cursor: pointer;
}
/* the target a thumb needs, without a box that would move anything */
#scoreInfo::after { content: ""; position: absolute; inset: calc(-8 * var(--s)); }
#scoreInfo svg { display: block; width: 100%; height: 100%; }
#scoreInfo circle, #scoreInfo path {
  fill: none;
  stroke: var(--legend-text);
  stroke-width: 1.333;
  stroke-linecap: round;
}
#scoreInfo .dot { fill: var(--legend-text); stroke: none; }
#overStats b {
  font-size: calc(34 * var(--s));
  font-weight: 800;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  color: var(--hud-score);
}
#overStats i {
  font-style: normal;
  font-size: calc(9 * var(--s));
  font-weight: 700;
  line-height: 1.62;
  letter-spacing: calc(1.71 * var(--s));
  color: var(--legend-text);
}

/* ------------------------------------------------------- the two sides

   The app has two sides and a player is on one of them: a child, or a
   grown-up. On a tablet that is a setting, one per player, kept in the
   parents' corner; here there are no players, so it is a switch on the start
   panel and the page remembers which way it was left.

   The pills carry `aria-pressed` rather than `aria-current`: this is a
   two-state control being toggled, not a choice among a list, and the picker
   below it redraws to the side that wins. */

#audienceRow {
  margin-top: calc(18 * var(--s));
  width: calc(226 * var(--s));
  height: calc(40 * var(--s));
  padding: calc(4 * var(--s));
  display: flex;
  gap: calc(4 * var(--s));
  border-radius: 999px;
  background: var(--lang-track);
  border: var(--lang-track-border);
}
.aud {
  flex: 1;
  height: calc(32 * var(--s));
  border: none;
  border-radius: 999px;
  background: transparent;
  font-family: var(--font);
  font-size: calc(12.5 * var(--s));
  font-weight: 600;
  line-height: 1;
  white-space: nowrap;
  color: var(--lang-text);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: calc(6 * var(--s));
  transition: background .18s ease, color .18s ease, box-shadow .18s ease;
}
.aud[aria-pressed="true"] {
  background: var(--lang-on);
  color: var(--lang-on-text);
  box-shadow: var(--lang-on-ring);
}
/* A face for each side, so the switch reads before the words do: a child and
   a grown-up as the same two strokes at two heights. They are decoration —
   the label beside them is what is read out. */
.aud svg { width: calc(15 * var(--s)); height: calc(15 * var(--s)); flex: none; }
.aud svg circle { fill: currentColor; }
.aud svg path { fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; }

/* ------------------------------------------------------- theme picker */

#themePicker {
  margin-top: calc(14 * var(--s));
  width: calc(226 * var(--s));
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  column-gap: calc(8 * var(--s));
  row-gap: calc(10 * var(--s));
}
.world {
  width: calc(70 * var(--s));
  padding: 0;
  border: none;
  background: none;
  font-family: var(--font);
  cursor: pointer;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: calc(6 * var(--s));
}
.world .thumb {
  position: relative;
  width: calc(70 * var(--s));
  height: calc(52 * var(--s));
  border-radius: calc(12 * var(--s));
  overflow: hidden;
  box-shadow: 0 calc(6 * var(--s)) calc(14 * var(--s)) calc(-4 * var(--s)) rgba(0, 0, 0, .3);
  transition: box-shadow .18s ease;
}
.world[aria-current="true"] .thumb { box-shadow: 0 calc(6 * var(--s)) calc(14 * var(--s)) calc(-4 * var(--s)) rgba(0, 0, 0, .5); }
.world .thumb canvas { display: block; width: 100%; height: 100%; }
.world.locked .thumb canvas { opacity: .7; }
/* the ring is drawn over the thumbnail, or a full-bleed preview hides it */
.world .thumb::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  border: 1px solid rgba(0, 0, 0, 0);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--ring) 25%, transparent);
  transition: box-shadow .18s ease;
}
.world[aria-current="true"] .thumb::after { box-shadow: inset 0 0 0 calc(3 * var(--s)) var(--ring); }
/* The mark on everything SYVT+ holds back. Its own two colours, not the
   theme's: it used to be a dark disc carrying var(--pick-label), and that
   label is dark in both worlds the page is ever themed as — so the badge
   was charcoal on near-black, hardest to see on exactly the cards it is
   there to mark, and the Night and Space thumbnails under it are darker
   still. A badge lands on whatever happens to be beneath it and cannot
   take its contrast from any of it: it brings its own. Pale disc, dark
   ink, and a shadow so it keeps its edge on the bright artwork too.

   Not pure white on pure black: a hard pair reads as a sticker, and these
   sit on pictures. It stays an overlay — absolute, never in the flow — so
   a locked card and a free one are exactly the same size. */
.world .lock {
  position: absolute;
  top: calc(4 * var(--s));
  right: calc(4 * var(--s));
  width: calc(18 * var(--s));
  height: calc(18 * var(--s));
  border-radius: 50%;
  background: rgba(247, 249, 250, .95);
  box-shadow: 0 calc(1 * var(--s)) calc(3 * var(--s)) rgba(0, 0, 0, .45);
  display: flex;
  align-items: center;
  justify-content: center;
}
/* inset by 22% of the disc on every side: 18 - 2 * (18 * .22) */
.world .lock svg { width: calc(10.08 * var(--s)); height: calc(10.08 * var(--s)); }
.world .lock path { fill: none; stroke: rgb(28, 30, 36); stroke-width: 2.6; stroke-linecap: round; stroke-linejoin: round; }
.world .lock rect { fill: rgb(28, 30, 36); }
.world .name {
  height: calc(26 * var(--s));
  font-size: calc(10 * var(--s));
  font-weight: 700;
  letter-spacing: calc(.6 * var(--s));
  line-height: 1.2;
  color: var(--pick-label);
  transition: color .18s ease;
}
.world[aria-current="true"] .name { color: var(--pick-label-on); }

/* -------------------------------------------------- language row, start */

#langRow {
  margin-top: calc(14 * var(--s));
  width: calc(300 * var(--s));
  height: calc(44 * var(--s));
  padding: calc(5 * var(--s));
  display: flex;
  gap: calc(4 * var(--s));
  border-radius: 999px;
  background: var(--lang-track);
  border: var(--lang-track-border);
}
.lang {
  width: calc(94 * var(--s));
  height: calc(34 * var(--s));
  border: none;
  border-radius: 999px;
  background: transparent;
  font-family: var(--font);
  font-size: calc(13 * var(--s));
  font-weight: 600;
  line-height: 1;
  white-space: nowrap;
  color: var(--lang-text);
  cursor: pointer;
  transition: background .18s ease, color .18s ease, box-shadow .18s ease;
}
.lang.active {
  background: var(--lang-on);
  color: var(--lang-on-text);
  box-shadow: var(--lang-on-ring);
}

#startBtn {
  margin-top: calc(18 * var(--s));
  width: calc(236 * var(--s));
  height: calc(52 * var(--s));
  border: none;
  border-radius: 999px;
  font-family: var(--font);
  font-size: calc(15 * var(--s));
  font-weight: 800;
  letter-spacing: calc(1.05 * var(--s));
  line-height: 1;
  text-transform: uppercase;
  white-space: nowrap;
  cursor: pointer;
  background: var(--start-fill);
  color: var(--start-text);
  box-shadow: var(--start-shadow);
}

#legendText {
  margin: calc(16 * var(--s)) 0 0;
  width: calc(314 * var(--s));
  font-size: calc(11.5 * var(--s));
  font-weight: 500;
  line-height: 1.75;
  color: var(--legend-text);
  white-space: pre-line;
}

/* The link to the Play page, flush under the legend; the 44 px row is its
   tap target. */
#storeLink {
  margin: 0;
  min-height: calc(44 * var(--s));
  padding: 0 calc(16 * var(--s));
  display: flex;
  align-items: center;
  justify-content: center;
  gap: calc(7 * var(--s));
  font-size: calc(11.5 * var(--s));
  font-weight: 700;
  letter-spacing: calc(.3 * var(--s));
  color: var(--legend-text);
  text-decoration: none;
}
#storeLink svg { width: calc(14 * var(--s)); height: calc(14 * var(--s)); flex: none; }
#storeLink path { fill: var(--ring); }

/* ----------------------------------------------------------- the offer */

#modal {
  position: absolute;
  inset: 0;
  z-index: 120;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: calc(24 * var(--s));
  background: var(--scrim);
  backdrop-filter: blur(var(--scrim-blur));
  -webkit-backdrop-filter: blur(var(--scrim-blur));
  animation: fadeIn .2s ease-out;
}
#modal[hidden] { display: none; }
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
#modalCard {
  width: 100%;
  max-width: calc(340 * var(--s));
  padding: calc(22 * var(--s));
  border-radius: calc(18 * var(--s));
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: calc(10 * var(--s));
  text-align: center;
  background: var(--panel-face);
  box-shadow: var(--panel-shadow);
  backdrop-filter: var(--panel-blur);
  -webkit-backdrop-filter: var(--panel-blur);
}
#modalTitle {
  margin: 0;
  font-size: calc(19 * var(--s));
  font-weight: 800;
  line-height: 1.2;
  color: var(--over-title);
}
#modalBody {
  margin: 0 0 calc(10 * var(--s));
  font-size: calc(14 * var(--s));
  font-weight: 500;
  line-height: 1.5;
  color: var(--msg);
}
#modalCard .pill { width: 100%; }
/* .pill carries its own display, which outranks the user agent's [hidden] */
#modalCard .pill[hidden] { display: none; }

/* -------------------------------------------------------------- motion */

:focus-visible { outline: calc(2 * var(--s)) solid var(--ring); outline-offset: calc(3 * var(--s)); }

@media (prefers-reduced-motion: reduce) {
  /* the sideways jolt of the field on a wrong call is the one effect that
     can make a motion-sensitive player ill; the tile turning red says it */
  #aurora, #legend .chip, #score, #scoreBonus, #levelUp, #flash, #field.shake, .puff, .score-pop, #modal { animation: none !important; }
  #danger.crit { animation: none; }
  .block.locked::after { animation: none; }
}
