/* BOBBLE — core UI. Colours and font families are overridden at runtime from
   src/config.js (see UI.applyTheme); the values below are fallbacks. */

@property --veil {
  syntax: '<number>';
  inherits: true;
  initial-value: 1;
}

:root {
  --c-black: #050505;
  --c-white: #f4f4f0;
  --c-mint: #cfe6d2;
  --c-sky: #8cc8ea;
  --c-accent: #57b947;
  --c-neon: #39ff88;
  --c-orange: #ff8a3d;
  --c-pink: #ff4fd8;
  --c-violet: #7b5cff;

  --f-logo: 'Bagel Fat One', 'Arial Black', sans-serif;
  --f-block: 'Bungee', 'Arial Black', sans-serif;
  --f-ui: 'Fredoka', system-ui, sans-serif;
  --f-mono: 'JetBrains Mono', ui-monospace, 'Cascadia Mono', monospace;

  --bar-h: 56px;
  --gutter: clamp(16px, 3vw, 44px);
  --ease-out: cubic-bezier(.16, 1, .3, 1);
  --ease-in-out: cubic-bezier(.65, 0, .35, 1);

  color-scheme: dark;
}

*,
*::before,
*::after { box-sizing: border-box; }

html,
body {
  margin: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
  overscroll-behavior: none;
  background: #000;
  color: var(--c-white);
}

body {
  font-family: var(--f-ui);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  touch-action: none;
  -webkit-tap-highlight-color: transparent;
  user-select: none;
  -webkit-user-select: none;
}

button { font: inherit; color: inherit; }

:focus-visible { outline: 1px solid #fff; outline-offset: 3px; }

.gl {
  position: fixed;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  outline: none;
}

.gl:focus,
.gl:focus-visible { outline: none; }

/* ------------------------------------------------------------------ loader */

.loader {
  --vis: 1;
  --veil: .94;
  position: fixed;
  inset: 0;
  z-index: 30;
  display: grid;
  place-items: center;
  padding: var(--gutter);
  background: rgb(0 0 0 / calc(var(--veil) * var(--vis)));
  transition: --veil 1.4s ease;
  pointer-events: none;
}

.loader.is-gone { visibility: hidden; }

/* Fades out as the visitor scrolls on (--vis, set per frame): opacity and
   transform only, on its own layer, so the drop-shadow stack of the wordmark
   is rasterised once instead of on every frame of the fade. */
.loader__brand {
  font-size: clamp(36px, 4.7vw, 84px);
  opacity: var(--vis);
  transform: translateY(calc((1 - var(--vis)) * -3vh)) scale(calc(.92 + .08 * var(--vis)));
  will-change: opacity, transform;
}

.loader__inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  opacity: 0;
  transform: translateY(12px);
  transition: opacity 1s ease, transform 1.4s var(--ease-out);
}

/* shown once the wordmark / edition fonts are in, or after a second (ui.js showBrand) */
.brand-ready .loader__inner,
.fonts-ready .loader__inner,
.load-failed .loader__inner { opacity: 1; transform: none; }

/* The logo (src/core/logo.js): the head on its spring and the word, white on
   the dark loader; it nods and the letters hop while the scenes load
   (css/logo-live.css, drawn by src/core/loaderLogo.js). */
.loader__logo {
  width: 5.6em;
  margin: 0;
  font-size: 1em; /* (an h1) */
}

/* Packing tape sealing the box as the scenes load (--seal 0..1, --tape: the
   branded print drawn by ui.js). A dashed guide shows where it will run. */
.seal {
  position: relative;
  width: 4.4em;
  height: .36em;
  margin-top: .34em;
  rotate: -1.4deg;
}

.seal__track {
  position: absolute;
  inset: 0;
  border: 1px dashed rgb(255 255 255 / .34);
  border-radius: 2px;
}

.seal__fill {
  position: absolute;
  inset: 0 auto 0 0;
  width: calc(var(--seal, 0) * 100%);
  background: var(--tape, none) left center / auto 100% repeat-x, var(--c-accent);
  /* torn zigzag ends */
  clip-path: polygon(0 0, 100% 0, calc(100% - .09em) 20%, 100% 40%, calc(100% - .09em) 60%, 100% 80%, calc(100% - .09em) 100%,
    0 100%, .09em 80%, 0 60%, .09em 40%, 0 20%);
  transition: width .7s var(--ease-out);
}

/* glossy film */
.seal__fill::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(rgb(255 255 255 / .34), rgb(255 255 255 / .06) 42%, transparent 55%, rgb(0 0 0 / .12));
}

/* "#001  UNBOXING_ 64%" — the collector's edition label */
.edition {
  display: flex;
  align-items: center;
  gap: .55em;
  margin: .5em 0 0;
  font: 400 max(12px, .22em)/1 var(--f-block);
  letter-spacing: .07em;
  text-transform: uppercase;
  white-space: nowrap;
  color: #f1f1ea;
}

.edition__no {
  padding: .34em .5em .28em;
  border: 2px solid #050505;
  border-radius: .32em;
  background: var(--c-accent);
  box-shadow: 0 0 0 2px #fff;
  color: #07150a;
  rotate: -4deg;
}

.edition__no:empty { display: none; }

.edition__caret {
  align-self: flex-end;
  width: .55em;
  height: .14em;
  margin: 0 0 .05em -.4em;
  background: var(--c-accent);
  animation: blink 1.05s steps(1) infinite;
}

/* hangs off the right end so the label stays centred when it fades */
.edition { position: relative; }

.edition__pct {
  position: absolute;
  left: 100%;
  margin-left: .7em;
  font: 400 .92em/1 var(--f-mono);
  letter-spacing: .02em;
  color: #8f8f88;
  transition: opacity .6s ease;
}

.loader.is-open .edition__pct { opacity: 0; }

@keyframes blink { 50% { opacity: 0; } }

/* ------------------------------------------------------------- scroll hint */

/* Bottom centre: the intro keeps its box clear of this spot and of the centred
   story lines (right third in landscape, above the lines in portrait). */
.scroll-btn {
  position: fixed;
  left: 50%;
  bottom: calc(env(safe-area-inset-bottom) + 34px);
  z-index: 25;
  display: grid;
  place-items: center;
  width: 78px;
  height: 78px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: rgb(0 0 0 / .22);
  cursor: pointer;
  opacity: 0;
  visibility: hidden;
  translate: -50% 10px;
  transition: opacity .8s ease, translate .9s var(--ease-out), bottom .8s var(--ease-out), visibility 0s .8s;
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
}

.scroll-btn.is-visible {
  opacity: 1;
  visibility: visible;
  translate: -50% 0;
  transition: opacity .8s ease, translate .9s var(--ease-out), bottom .8s var(--ease-out);
}

.has-bar .scroll-btn { bottom: calc(var(--bar-h) + env(safe-area-inset-bottom) + 26px); }

.scroll-btn__ring {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  rotate: -90deg;
  overflow: visible;
}

.scroll-btn__track { fill: none; stroke: rgb(255 255 255 / .26); stroke-width: 1; }
.scroll-btn__fill { fill: none; stroke: #fff; stroke-width: 1.6; stroke-linecap: round; }

.scroll-btn__label {
  margin-top: -6px;
  font: 400 8.5px/1 var(--f-block);
  letter-spacing: .22em;
  text-indent: .22em;
  text-transform: uppercase;
  color: #fff;
}

.scroll-btn__tick {
  position: absolute;
  left: 50%;
  top: 56%;
  width: 1px;
  height: 11px;
  margin-left: -.5px;
  background: linear-gradient(#fff, transparent);
  animation: tick 1.8s var(--ease-in-out) infinite;
}

@keyframes tick {
  0% { transform: translateY(-3px) scaleY(.2); opacity: 0; }
  35% { opacity: 1; }
  100% { transform: translateY(8px) scaleY(1); opacity: 0; }
}

.scroll-btn:hover .scroll-btn__track { stroke: rgb(255 255 255 / .5); }

/* ---------------------------------------------------------------- overlays */

.overlays {
  position: fixed;
  inset: 0 0 var(--bar-h);
  z-index: 10;
  pointer-events: none;
}

.ov {
  position: absolute;
  inset: 0;
  opacity: 0;
  visibility: hidden;
  transition: opacity .7s ease, visibility 0s .7s;
}

/* Painted once behind the loader at 1 % (UI.warmCaptions), so an overlay's
   first show never costs a long frame while its chapter is on screen. */
.ov.is-warm { opacity: .01; visibility: visible; transition: none; }
.ov-cta.is-warm :is(.ov-cta__lead, .ov-cta__hl, .ov-cta__actions, .ov-cta__ca, .ov-cta__legal) { opacity: 1; transition: none; }

.ov.is-active {
  opacity: 1;
  visibility: visible;
  transition: opacity .7s ease;
}

/* intro story lines */
.ov-lines {
  display: grid;
  place-items: center;
  padding: 0 var(--gutter);
}

/* soft darkening behind the text, whatever the scene does; --scrim (overlays.js)
   follows the lines so it never dims the scene once they have faded out */
.ov-lines::before {
  content: '';
  position: absolute;
  inset: 12% 8%;
  z-index: -1;
  background: radial-gradient(closest-side, rgb(0 0 0 / .55), rgb(0 0 0 / .28) 55%, transparent);
  opacity: var(--scrim, 0);
}

.ov-lines__list {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .95em;
  max-width: 34em;
  margin: 0;
  padding: 0;
  list-style: none;
  text-align: center;
  font: 400 clamp(15px, 1.6vw, 25px)/1.38 var(--f-ui);
  letter-spacing: .005em;
  text-wrap: balance;
}

/* Lines fade and drift in (opacity and transform only). No blur-in: an
   animated filter: blur(12px) -> none on each line stalled the first frames
   of scrolling on the intro (lab/pace.html, intro wheeled at 0.3 units/s:
   7 frames over 20 ms up to 97 ms while the first line came in; none without
   the filter). */
.ov-lines__item {
  opacity: 0;
  transform: translateY(.7em);
  text-shadow: 0 1px 18px rgb(0 0 0 / .7);
  transition: opacity 1.1s var(--ease-out), transform 1.3s var(--ease-out);
}

/* (filter: none, never blur(0): a lingering blur(0) keeps a line on a filter
   layer the compositor redraws every frame the overlay fades) */
.ov-lines__item.is-on { opacity: 1; filter: none; transform: none; }
.ov-lines__item.is-on.is-dim { opacity: .34; }

/* The call to unbox is a sticker in the wordmark's language: brand-green
   letters, black outline (a stroked copy behind), white die-cut rim with a
   soft shadow (a wider white stroked copy behind that). It is stuck on (fade
   and a small scale settle) rather than blurred in: it appears just before
   the intro -> unboxing slide starts, and an animated blur over the big
   sticker stalled the first frames of that slide by 40-130 ms. */
.ov-lines__item--em {
  position: relative;
  isolation: isolate;
  margin-top: .5em;
  font: 400 1.7em/1.02 var(--f-logo);
  letter-spacing: .005em;
  text-transform: uppercase;
  color: var(--c-accent);
  text-shadow: none;
  rotate: -2deg;
  filter: none;
  transform: translateY(.25em) scale(1.14);
  transition: opacity .5s var(--ease-out), transform .75s cubic-bezier(.22, 1.25, .36, 1);
}

.ov-lines__item--em::before,
.ov-lines__item--em::after {
  content: attr(data-text);
  position: absolute;
  inset: 0;
}

.ov-lines__item--em::before {
  z-index: -1;
  color: #050505;
  -webkit-text-stroke: .17em #050505;
}

.ov-lines__item--em::after {
  z-index: -2;
  color: #fff;
  -webkit-text-stroke: .29em #fff;
  text-shadow: 0 .12em .2em rgb(0 0 0 / .55);
}

/* manifesto column */
.ov-manifesto {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  padding: 0 clamp(20px, 6.5vw, 120px);
}

.ov-manifesto__box {
  position: relative;
  width: min(31em, 44vw);
  padding: 1.6em 0 1.6em 1.8em;
  font: 400 clamp(12.5px, 1.08vw, 16.5px)/1.6 var(--f-mono);
}

.ov-manifesto__box::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 1px;
  background: linear-gradient(transparent, var(--neon-55) 18%, var(--neon-55) 82%, transparent);
}

.ov-manifesto { --neon-55: color-mix(in srgb, var(--c-neon) 55%, transparent); }

/* wide layout: a soft pool of shade behind the column keeps the lines readable
   whatever the scene does behind them (the narrow layout has its own scrim) */
@media (min-width: 901px) {
  .ov-manifesto__box::after {
    content: '';
    position: absolute;
    inset: -14% -16% -14% -12%;
    z-index: -1;
    background: radial-gradient(closest-side, rgb(0 0 0 / .32), rgb(0 0 0 / .14) 62%, transparent);
    pointer-events: none;
  }
}

.ov-manifesto__label {
  margin: 0 0 1.5em;
  font-size: .78em;
  letter-spacing: .24em;
  text-transform: uppercase;
  color: var(--c-neon);
  opacity: .85;
}

.ov-manifesto__label::before { content: '// '; }

.ov-manifesto__list {
  margin: 0;
  padding: 0;
  list-style: none;
}

.ov-manifesto__item {
  display: grid;
  grid-template-columns: 2.7em 1fr;
  min-height: 1.6em;
  margin: 0 0 .75em;
  color: #eef4ef;
  text-shadow: 0 0 14px rgb(0 0 0 / .9), 0 0 2px rgb(0 0 0 / .8);
  opacity: 0;
  transition: opacity .25s ease;
}

.ov-manifesto__item.is-on { opacity: 1; }
.ov-manifesto__num { color: var(--c-neon); opacity: .6; font-size: .86em; padding-top: .1em; }

.caret {
  display: inline-block;
  width: .58em;
  height: 1.12em;
  margin-left: .12em;
  vertical-align: -.2em;
  background: var(--c-neon);
  box-shadow: 0 0 10px color-mix(in srgb, var(--c-neon) 60%, transparent);
  animation: blink 1s steps(1) infinite;
}

/* final call to action */
.ov-cta {
  display: grid;
  place-items: center;
  padding: 0 var(--gutter);
  text-align: center;
}

.ov-cta__inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  transform: translateY(-5vh);
}

/* Sticker headline: white letters with a thick black outline and a white
   die-cut rim with a soft shadow (stroked copies behind: black, then a wider
   white one), the highlight a brand-green foil sticker. Strokes paint once;
   a drop-shadow filter stack on the title, and a blur-in under it, cost
   50-100 ms frames as the finale faded in. The halves are stuck on (fade and
   a small scale settle). */
.ov-cta__title {
  margin: 0;
  font: 400 clamp(34px, 5.3vw, 92px)/1.04 var(--f-logo);
  letter-spacing: .004em;
  text-transform: uppercase;
  color: #fff;
}

.ov-cta__lead,
.ov-cta__hl {
  position: relative;
  display: inline-block;
  isolation: isolate;
  white-space: nowrap;
  opacity: 0;
  transform: translateY(.2em) rotate(var(--r, 0deg)) scale(1.1);
  transition: opacity .6s var(--ease-out), transform .85s cubic-bezier(.22, 1.25, .36, 1);
}

.ov-cta__lead::before,
.ov-cta__hl::before,
.ov-cta__lead::after,
.ov-cta__hl::after {
  content: attr(data-text);
  position: absolute;
  inset: 0;
}

.ov-cta__lead::before,
.ov-cta__hl::before {
  z-index: -1;
  color: #050505;
  -webkit-text-stroke: .16em #050505;
}

.ov-cta__lead::after,
.ov-cta__hl::after {
  z-index: -2;
  color: #fff;
  -webkit-text-stroke: .25em #fff;
  text-shadow: 0 .1em .16em rgb(0 0 0 / .55);
}

.ov-cta__hl {
  --r: -2.5deg;
  margin-left: .06em;
  color: var(--c-accent);
  transition-delay: .16s;
}

/* holo-foil sheen sweeping over the green letters (a copy of them, see
   overlays.js), a few times: each sweep frame repaints the letters */
.ov-cta__foil {
  position: absolute;
  inset: 0;
  color: transparent;
  background: linear-gradient(105deg, transparent 36%, rgb(236 255 214 / .9) 46%, rgb(255 255 255 / .95) 50%, rgb(190 255 150 / .8) 54%, transparent 64%)
    0 0 / 300% 100% no-repeat;
  -webkit-background-clip: text;
  background-clip: text;
  animation: foil 4.8s var(--ease-in-out) 1.2s 3;
  pointer-events: none;
}

@keyframes foil {
  0% { background-position: 110% 0; }
  55%, 100% { background-position: -10% 0; }
}

.ov-cta__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 16px 26px;
  margin-top: clamp(22px, 4.2vh, 40px);
  opacity: 0;
  transform: translateY(.8em);
  transition: opacity 1s var(--ease-out) .42s, transform 1.2s var(--ease-out) .42s;
}

.ov-cta__actions a { text-decoration: none; pointer-events: auto; }
.ov-cta__actions a[hidden] { display: none; }
.ov-cta__actions a[aria-disabled='true'] { cursor: default; }

/* Primary: a price tag off the blister card — punched hang hole, black
   outline, white rim, stuck on at an angle; a "soon" sticker while inert. */
.ov-cta__buy {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: .6em;
  padding: .58em 1.15em .52em .7em;
  border: 3px solid #050505;
  border-radius: .42em .9em .9em .42em;
  background: linear-gradient(#7ad866, var(--c-accent) 55%, #4aa83b);
  /* white rim and a hard sticker shadow: a large blurred shadow on the
     rotated tag was a slow first paint as the finale appeared */
  box-shadow: 0 0 0 3px #fff, 0 6px 0 3px rgb(0 0 0 / .34);
  font: 400 clamp(18px, 1.55vw, 26px)/1 var(--f-logo);
  letter-spacing: .01em;
  color: #081606;
  rotate: -2.5deg;
  transition: rotate .35s var(--ease-out), translate .35s var(--ease-out);
}

.ov-cta__hole {
  flex: none;
  width: .5em;
  height: .5em;
  border-radius: 50%;
  background: #050505;
  box-shadow: inset 0 .08em .1em rgb(255 255 255 / .25), 0 0 0 .1em rgb(0 0 0 / .18);
}

.ov-cta__soon {
  position: absolute;
  top: -.95em;
  right: -1.1em;
  padding: .45em .6em .38em;
  border: 2px solid #050505;
  border-radius: 999px;
  background: #fff;
  font: 400 .46em/1 var(--f-block);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: #050505;
  rotate: 9deg;
}

.ov-cta__soon[hidden] { display: none; }

.ov-cta__buy:not([aria-disabled='true']):hover,
.ov-cta__buy:not([aria-disabled='true']):focus-visible { rotate: -1deg; translate: 0 -3px; }

/* Secondary: a capsule pill with the mascot's head. */
.ov-cta__join {
  display: inline-flex;
  align-items: center;
  gap: .55em;
  padding: .45em 1.05em .42em .45em;
  border: 2px solid rgb(255 255 255 / .9);
  border-radius: 999px;
  /* a plain dark tint, no backdrop blur: fading in a blur over the live
     canvas cost a 60-80 ms frame as the finale's buttons appeared */
  background: rgb(0 0 0 / .52);
  font: 400 clamp(12.5px, 1vw, 15px)/1 var(--f-block);
  letter-spacing: .06em;
  text-transform: uppercase;
  color: #fff;
  transition: background-color .3s ease;
}

.ov-cta__join:not([aria-disabled='true']):hover,
.ov-cta__join:not([aria-disabled='true']):focus-visible { background: rgb(255 255 255 / .16); }

.ov-cta__icon { width: 2.1em; height: auto; flex: none; }

/* Copy-contract chip (only once links.contract is set). */
.ov-cta__ca {
  display: inline-flex;
  align-items: center;
  gap: .7em;
  margin-top: 18px;
  padding: .55em .8em;
  border: 1px dashed rgb(255 255 255 / .5);
  border-radius: 6px;
  background: rgb(0 0 0 / .5);
  font: 500 12px/1 var(--f-mono);
  color: #eef6ee;
  cursor: copy;
  pointer-events: auto;
  opacity: 0;
  transition: opacity 1s var(--ease-out) .6s, border-color .2s ease;
}

.ov-cta__ca[hidden] { display: none; }
.ov-cta__ca-label { font-family: var(--f-block); letter-spacing: .08em; color: var(--c-accent); }
.ov-cta__ca.is-copied { border-color: var(--c-accent); }

.ov-cta__ca-copy {
  position: relative;
  width: 11px;
  height: 11px;
  border: 1.5px solid currentColor;
  border-radius: 2px;
  opacity: .7;
}

.ov-cta__ca-copy::after {
  content: '';
  position: absolute;
  left: 2px;
  top: -5px;
  width: 9px;
  height: 9px;
  border: 1.5px solid currentColor;
  border-width: 1.5px 1.5px 0 0;
  border-radius: 0 2px 0 0;
}

/* Phones only (see the 640px block): the footer's fine print and copyright. */
.ov-cta__legal {
  display: none;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  max-width: 30em;
  margin: 28px 0 0;
  font: 500 8.5px/1.35 var(--f-mono);
  letter-spacing: .08em;
  text-transform: uppercase;
  text-wrap: balance;
  color: rgb(236 236 230 / .6);
  text-shadow: 0 1px 8px rgb(0 0 0 / .85);
  opacity: 0;
  transition: opacity 1s var(--ease-out) .7s;
}

.ov-cta.is-on .ov-cta__lead,
.ov-cta.is-on .ov-cta__hl { opacity: 1; transform: rotate(var(--r, 0deg)); }
.ov-cta.is-on .ov-cta__actions { opacity: 1; transform: none; }
.ov-cta.is-on .ov-cta__ca,
.ov-cta.is-on .ov-cta__legal { opacity: 1; }
.ov-cta:not(.is-on) .ov-cta__actions a,
.ov-cta:not(.is-on) .ov-cta__ca { pointer-events: none; }

/* --------------------------------------------------------------- subtitles */

.subs {
  position: fixed;
  left: 50%;
  bottom: calc(var(--bar-h) + env(safe-area-inset-bottom) + 22px);
  z-index: 15;
  width: min(92vw, 660px);
  height: 0;
  translate: -50% 0;
  pointer-events: none;
}

/* Price-tag stickers: white card, green stub with a punched hole and a
   perforation, black outline, each stuck on at its own small angle (--tilt). */
.subs__item {
  position: absolute;
  left: 50%;
  bottom: 0;
  width: max-content;
  max-width: 100%;
  padding: .56em .95em .48em 2.05em;
  border: 2px solid #050505;
  border-radius: 7px;
  background: linear-gradient(90deg, var(--c-accent) 0 1.3em, #f6f6ef 1.3em);
  font: 400 12.5px/1.3 var(--f-block);
  letter-spacing: .035em;
  text-align: center;
  text-wrap: balance;
  color: #0b0b0b;
  opacity: 0;
  translate: -50% 5px;
  rotate: calc(var(--tilt, -1deg) + 3deg);
  transition: opacity .45s ease, translate .6s var(--ease-out), rotate .7s var(--ease-out);
  /* A hard sticker shadow on its own layer: a soft (blurred) box-shadow on the
     rotated tag cost a 70-110 ms frame each time a new caption was painted. */
  box-shadow: 0 3px 0 rgb(0 0 0 / .42);
  will-change: transform, opacity;
}

.subs__item::before {
  content: '';
  position: absolute;
  left: .42em;
  top: 50%;
  width: .46em;
  height: .46em;
  margin-top: -.23em;
  border-radius: 50%;
  background: #050505;
}

.subs__item::after {
  content: '';
  position: absolute;
  left: 1.3em;
  top: 3px;
  bottom: 3px;
  border-left: 1.5px dashed rgb(0 0 0 / .45);
}

/* Painted once behind the loader, all but invisible (UI.warmCaptions): the
   first caption's set-up cost then never lands on a frame in motion. */
.subs__item.is-warm { opacity: .01; transition: none; }

.subs__item.is-on { opacity: 1; translate: -50% 0; rotate: var(--tilt, -1deg); }

/* ------------------------------------------------------------------ footer */

.bar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 20;
  display: grid;
  grid-template-columns: auto auto 1fr;
  align-items: center;
  column-gap: 20px;
  height: calc(var(--bar-h) + env(safe-area-inset-bottom));
  padding: 0 var(--gutter) env(safe-area-inset-bottom);
  background: #000;
  transform: translateY(101%);
  transition: transform .9s var(--ease-out);
}

.bar.is-visible { transform: none; }

/* Staircase minimap (src/core/stairs.js sets the box size and step positions).
   The SVG is drawn 1:1 in CSS px; transparent buttons sit on top of it. */
.stairs {
  position: relative;
  justify-self: start;
  margin-left: -5px;
}

/* On touch screens a tap anywhere around the staircase picks the nearest step
   (stairs.js); this pad extends the nav's hit area beyond the tiny drawing. */
.stairs.is-coarse::before {
  content: '';
  position: absolute;
  inset: -12px -12px -10px -10px;
}

.stairs__svg {
  position: absolute;
  inset: 0;
  overflow: visible;
  pointer-events: none;
}

.stairs__track,
.stairs__done {
  fill: none;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.stairs__track { stroke: #3a3a3a; }
.stairs__done { stroke: #fff; }
.stairs__start { fill: #000; stroke: #8a8a8a; stroke-width: 1; }

.stairs__node {
  fill: #000;
  stroke: #6c6c6c;
  stroke-width: 1;
  transform-box: fill-box;
  transform-origin: center;
  transition: fill .3s, stroke .3s, scale .35s var(--ease-out);
}

.stairs__node.is-done { fill: #fff; stroke: #fff; }
.stairs__node.is-hot { stroke: #fff; scale: 1.5; }
.stairs__node.is-active { fill: #fff; stroke: #fff; scale: 1.4; }

.stairs__marker { fill: var(--c-accent); }
.stairs__marker rect { stroke: #fff; stroke-width: 1; }
.stairs__halo { fill: color-mix(in srgb, var(--c-accent) 32%, transparent); }

.stairs__step {
  position: absolute;
  display: block;
  padding: 0;
  border: 0;
  border-radius: 2px;
  background: none;
  cursor: pointer;
}

.stairs__step:focus-visible { outline: 1px solid #fff; outline-offset: 1px; }

.stairs__step::after {
  content: attr(data-label);
  position: absolute;
  left: 50%;
  bottom: calc(100% + 6px);
  padding: 5px 8px 4px;
  border-radius: 2px;
  background: #fff;
  font: 600 9px/1 var(--f-ui);
  letter-spacing: .14em;
  text-transform: uppercase;
  white-space: nowrap;
  color: #000;
  opacity: 0;
  translate: -50% 4px;
  pointer-events: none;
  transition: opacity .2s ease, translate .3s var(--ease-out);
}

.stairs__step:hover::after,
.stairs__step:focus-visible::after { opacity: 1; translate: -50% 0; }

/* the first label would stick out of the screen's left edge: anchor it left */
.stairs__step:first-of-type::after { left: 0; translate: 0 4px; }
.stairs__step:first-of-type:hover::after,
.stairs__step:first-of-type:focus-visible::after { translate: 0 0; }

/* Chapter label (src/core/chapter.js): "04 / 09 · (dial) 20:00" over the
   chapter title, set off from the staircase by a hairline (like the X on the
   right). Characters roll like an odometer on a change. */
.chapter {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 7px;
  min-width: 0;
  margin: 0;
  padding: 4px 0 4px 18px;
  border-left: 1px solid #262626;
  white-space: nowrap;
  color: #fff;
  pointer-events: none;
}

.chapter [hidden] { display: none; }

.chapter__sr {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
}

.chapter__meta {
  display: flex;
  align-items: center;
  gap: .6em;
  font: 500 9px/1 var(--f-mono);
  letter-spacing: .12em;
  color: #6f6f6a;
}

.chapter__num { color: #fff; }
.chapter__sep { color: #3a3a37; }

.chapter__clock {
  display: inline-flex;
  align-items: center;
  gap: .55em;
  color: var(--c-accent);
}

.chapter__dial { flex: none; overflow: visible; }
.chapter__face { fill: none; stroke: currentColor; stroke-width: 1; opacity: .5; }
.chapter__pin { fill: currentColor; }

/* the hands turn about their inner end (the dial's centre) */
.chapter__hand {
  stroke: currentColor;
  stroke-width: 1.15;
  stroke-linecap: round;
  transform-box: fill-box;
  transform-origin: 50% 100%;
  transition: transform 1.15s var(--ease-in-out), opacity .4s ease;
}

.chapter__hand--h { stroke-width: 1.45; }
.chapter__dial.is-instant .chapter__hand { transition: none; }
.chapter__dial.is-free .chapter__hand { opacity: 0; }

.chapter__title {
  font: 400 12px/1 var(--f-block);
  letter-spacing: .07em;
  text-transform: uppercase;
  color: #fff;
}

/* odometer: each slot clips its faces, which roll through it */
.roll { display: inline-flex; }

.roll__slot {
  display: inline-grid;
  overflow: hidden;
  padding: .14em 0;
  margin: -.14em 0;
}

.roll__face { grid-area: 1 / 1; white-space: pre; }

/* Right cluster: the box art's fine print, then X. */
.bar__end {
  display: flex;
  align-items: center;
  justify-self: end;
  gap: 16px;
  min-width: 0;
}

.bar__print {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 4px;
  min-width: 0;
  text-align: right;
}

.bar__fine,
.bar__copy {
  margin: 0;
  text-transform: uppercase;
  white-space: nowrap;
}

.bar__fine {
  font: 400 9px/1.2 var(--f-block);
  letter-spacing: .12em;
  color: #6f6f6a;
}

.bar__fine:empty { display: none; }

.bar__copy {
  font: 500 9px/1.3 var(--f-mono);
  letter-spacing: .1em;
  color: #8c8c86;
}

.bar__x {
  display: grid;
  flex: none;
  place-items: center;
  width: 38px;
  height: 38px;
  border-left: 1px solid #262626;
  padding-left: 14px;
  box-sizing: content-box;
  color: #fff;
  transition: opacity .2s ease;
}

.bar__x svg { width: 15px; height: 15px; transition: transform .3s var(--ease-out); }
.bar__x:hover svg { transform: scale(1.12); }
.bar__x[aria-disabled='true'] { cursor: default; }

/* ----------------------------------------------------------------- notices */

.notice {
  position: fixed;
  left: 50%;
  bottom: 12vh;
  z-index: 50;
  width: min(92vw, 440px);
  margin: 0;
  padding: 18px 22px;
  translate: -50% 0;
  border: 1px solid rgb(255 255 255 / .14);
  border-radius: 4px;
  background: rgb(10 10 10 / .92);
  text-align: center;
  font: 400 13px/1.5 var(--f-ui);
  color: #cfcfcf;
}

.notice[hidden] { display: none; }
.notice__title { margin: 0 0 4px; font: 400 18px/1.3 var(--f-block); letter-spacing: .02em; color: #fff; }
.notice__text { margin: 0; }

.notice__btn {
  margin-top: 12px;
  padding: 7px 16px;
  border: 1px solid #fff;
  border-radius: 999px;
  background: none;
  font: 600 10px/1 var(--f-ui);
  letter-spacing: .2em;
  text-transform: uppercase;
  cursor: pointer;
}

.notice--toast { bottom: calc(var(--bar-h) + 24px); }

.hud {
  position: fixed;
  top: 10px;
  left: 10px;
  z-index: 60;
  margin: 0;
  padding: 7px 9px;
  border-radius: 3px;
  background: rgb(0 0 0 / .66);
  font: 11px/1.45 var(--f-mono);
  color: var(--c-neon);
  white-space: pre;
  pointer-events: none;
}

/* ------------------------------------------------------------- responsive */

@media (max-width: 900px) {
  /* footer: the chapter label needs the room; the copyright wraps */
  .bar__fine { display: none; }
  .bar__copy { max-width: 17em; white-space: normal; text-wrap: balance; }

  .ov-manifesto { justify-content: center; align-items: flex-start; padding: 9vh var(--gutter) 0; }
  .ov-manifesto::before {
    content: '';
    position: absolute;
    inset: 0 0 35%;
    background: linear-gradient(rgb(0 0 0 / .72), rgb(0 0 0 / .45) 60%, transparent);
  }
  .ov-manifesto__box { width: min(100%, 34em); padding: 1.1em 0 1.1em 1.3em; font-size: 12.5px; }
}

@media (max-width: 640px) {
  :root { --bar-h: 64px; }

  .loader__brand { font-size: clamp(40px, 12vw, 56px); }

  /* staircase and chapter label on the left, X on the right; the fine print
     and copyright move to the end of the finale (.ov-cta__legal) */
  .bar { column-gap: 14px; }
  .bar__end { gap: 10px; }
  .bar__print { display: none; }
  .bar__x { width: 30px; height: 40px; padding-left: 8px; margin-right: -6px; }
  .chapter { gap: 6px; padding-left: 14px; }
  .chapter__meta { font-size: 8.5px; letter-spacing: .08em; }
  .chapter__title { font-size: 11px; letter-spacing: .05em; }
  .ov-cta__legal { display: flex; }

  .subs__item { font-size: 11.5px; }
  .scroll-btn { width: 64px; height: 64px; }
  .ov-lines__list { gap: .8em; }
  .ov-cta__title { font-size: clamp(30px, 10.4vw, 46px); line-height: 1.1; }
  .ov-cta__lead,
  .ov-cta__hl { display: block; }
  .ov-cta__hl { margin: .1em 0 0; }
  .ov-cta__actions { flex-direction: column; gap: 18px; }
}

@media (max-height: 520px) and (orientation: landscape) {
  .ov-lines__list { font-size: 14px; gap: .5em; }
  /* short screens: a smaller sticker, closer to the bar, keeps the lower part of
     the frame (the s4 lineup, the s5 summit, the manifesto's last line) clear */
  .subs { bottom: calc(var(--bar-h) + env(safe-area-inset-bottom) + 9px); }
  .subs__item { font-size: 10.5px; padding: .46em .8em .4em 1.95em; border-width: 1.5px; }
  .scroll-btn { width: 58px; height: 58px; }
  .has-bar .scroll-btn { bottom: calc(var(--bar-h) + env(safe-area-inset-bottom) + 12px); }
  /* a phone on its side (e.g. 640x360) keeps the wide finale — one-line
     headline, buttons side by side — instead of the stacked portrait one,
     which does not fit above the bar */
  .ov-cta__inner { transform: none; }
  .ov-cta__title { font-size: clamp(26px, 5.3vw, 92px); line-height: 1.04; }
  .ov-cta__lead,
  .ov-cta__hl { display: inline-block; }
  .ov-cta__hl { margin: 0 0 0 .06em; }
  .ov-cta__actions { flex-direction: row; gap: 12px 22px; margin-top: 16px; }
  .ov-cta__legal { margin-top: 14px; }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .2s !important;
    transition-delay: 0s !important;
  }
  .ov-lines__item { filter: none; transform: none; }
  .ov-cta__lead,
  .ov-cta__hl { transform: rotate(var(--r, 0deg)); }
  .ov-cta__foil { display: none; }
  .loader__brand { transform: none; }
}

/* The app could not start (src/core/boot.js): the static wordmark over the notice. */
.load-failed .seal,
.load-failed .edition { display: none; }

/* Headless check mode: DOM snaps to its final state for the screenshot. */
.is-check *,
.is-check *::before,
.is-check *::after { transition: none !important; }
