/* jorge antolín — portfolio
   palette: ink / cream / ultramarine sea / coral / acid yellow / leaf green

   type system (v4) — why these three:
   · anton (display): bricolage grotesque had to go — it's the liga u
     face and half the internet's. anton is a sports-poster condensed
     with a huge x-height that reads like a jersey number even in
     lowercase: loud, gamberro, unmistakably "league night". one weight,
     zero temptation to over-art-direct it.
   · dm sans (body): humanist, warm, effortless at small sizes — it lets
     anton shout by staying calm. variable, so weights are free.
   · space mono (labels/hud): kept on purpose. it is the captain's
     voice — nautical logbook energy — and it never touched liga u. */

:root {
  --ink: #14120f;
  --cream: #f6eedd;
  --sea: #2b47d9;
  --sea-deep: #101b52;
  --coral: #ff5a3c;
  /* v8.7: coral is a FILL colour (2.7:1 on cream). any coral TEXT on a light
     surface uses coral-ink (5.2:1 on cream, 5.15 on the card white). */
  --coral-ink: #c93514;
  --yellow: #ffd23f;
  --green: #3ec46d;
  --peach: #ffd9a6;
  --font-display: "Anton", "Arial Narrow", system-ui, sans-serif;
  --font-body: "DM Sans", system-ui, sans-serif;
  --font-mono: "Space Mono", monospace;
}

* { box-sizing: border-box; margin: 0; padding: 0; }

html {
  scroll-behavior: smooth;
  scroll-padding-top: 84px;       /* anchors land clear of the fixed topbar */
  background: #0a1230;            /* bottom overscroll = abyss */
  /* v8: clip (not hidden — no new scroll container, sticky keeps working).
     kills the layout-viewport expansion on phones: without it the tilted
     marquee + any decorative overhang made mobile browsers lay the page
     out wider than device-width and render it zoomed out + pannable. */
  overflow-x: clip;
  /* v8.1: ios safari text autosizing was inflating blocks of text (the
     captain's bubble most visibly) to ~2.5x. we size our own type. */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

body {
  font-family: var(--font-body);
  background: var(--cream);
  color: var(--ink);
  text-transform: lowercase;      /* everything lowercase, always */
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

a { color: inherit; }
button { font: inherit; }
/* v8.7: taps feel instant (no 300ms heuristics, no grey flash) */
a, button, .chip { touch-action: manipulation; -webkit-tap-highlight-color: transparent; }
h1, h2, h3 { text-wrap: balance; }
img { max-width: 100%; height: auto; display: block; }
.mono { font-family: var(--font-mono); font-size: .8em; letter-spacing: .02em; }

/* ---------- topbar ---------- */
#topbar {
  position: fixed;
  top: 0; left: 0; right: 0;
  display: flex;
  justify-content: space-between;
  align-items: center;
  /* v8: max() with safe-area so the notch never eats the brand or the
     toggle when a phone goes landscape (viewport-fit=cover is on) */
  padding: 14px max(clamp(16px, 4vw, 36px), env(safe-area-inset-right))
           14px max(clamp(16px, 4vw, 36px), env(safe-area-inset-left));
  z-index: 60;
  pointer-events: none;
}
#topbar > * { pointer-events: auto; }
body.mode-sea.case-open #topbar { display: none; }
.brand {
  font-weight: 700;
  font-size: 1.05rem;
  text-decoration: none;
  background: var(--ink);
  color: var(--cream);
  padding: 8px 14px;
  border-radius: 999px;
  transition: transform .2s;
}
@media (hover: hover) { .brand:hover { transform: rotate(-2deg) scale(1.03); } }
.brand .role { font-weight: 400; opacity: .75; }
@media (max-width: 640px) { .brand .role { display: none; } }

.chip {
  font-family: var(--font-mono);
  font-size: .72rem;
  letter-spacing: .07em;
  background: rgba(18, 16, 24, .82);
  color: var(--cream);
  padding: 8px 15px;
  border-radius: 999px;
}
@media (pointer: fine) {
  .chip { backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); }
}
.btn {
  border: 0;
  cursor: pointer;
  text-transform: lowercase;
  transition: transform .2s, background .2s;
}
@media (hover: hover) { .btn:hover { transform: scale(1.05); background: var(--coral-ink); } }
.btn:active { transform: scale(.96); }   /* v8.5: one press accent everywhere */
/* fingers need ≥44px targets */
@media (pointer: coarse) {
  .btn { padding: 13px 18px; }
}

/* ---------- 3d stage ---------- */
#stage {
  position: fixed;
  inset: 0;
  z-index: 10;
  display: none;
  touch-action: none;
}
body.mode-sea #stage { display: block; }
#stage canvas { display: block; width: 100%; height: 100%; }

/* cinematic finish: vignette + film grain over the 3d */
#stage::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 2;
  background: radial-gradient(ellipse at center, transparent 58%, rgba(16, 13, 40, .28) 100%);
}
#stage::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 3;
  opacity: .055;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/><feColorMatrix type='saturate' values='0'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
}

/* in-world title card, shown on first entry */
#title-card {
  position: fixed;
  inset: 0;
  z-index: 55;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  opacity: 0;
  /* v8.5: the name rises out of the water with the camera, it doesn't just
     fade in — same easing family as the surfacing menu (.22,.84,.28,1) */
  transform: translateY(18px) scale(.985);
  transition: opacity 1.1s ease, transform 1.6s cubic-bezier(.22, .84, .28, 1);
  text-align: center;
  padding: 0 20px;
}
#title-card.show { opacity: 1; transform: none; }
/* the exit keeps drifting up — never rewinds the entrance */
#title-card.bye { opacity: 0; transform: translateY(-14px); transition: opacity .9s ease, transform 1.3s ease; }
#title-card h2 {
  font-family: var(--font-display);
  font-size: clamp(2.6rem, 9vw, 6.8rem);
  font-weight: 400;
  letter-spacing: .01em;
  line-height: .95;
  color: var(--cream);
  text-shadow: 0 3px 26px rgba(16, 13, 40, .45);
}
#title-card p {
  font-family: var(--font-mono);
  font-size: clamp(.75rem, 2vw, .95rem);
  letter-spacing: .18em;
  color: var(--cream);
  margin-top: 1.1em;
  text-shadow: 0 2px 14px rgba(16, 13, 40, .5);
}

/* ---------- hud ---------- */
#hud { position: fixed; inset: 0; z-index: 50; pointer-events: none; }
#hud[hidden] { display: none; }
body.case-open #hud { display: none; }
#compass .chip { white-space: nowrap; }
.dock[hidden] { display: none; }
body:not(.mode-sea) #hud { display: none; }
#hint {
  position: absolute;
  bottom: max(22px, env(safe-area-inset-bottom));
  left: 50%;
  transform: translateX(-50%);
  opacity: .92;
  white-space: nowrap;
  animation: floaty 3s ease-in-out infinite;
}
@media (max-width: 640px) {
  #hint {
    white-space: normal;
    max-width: min(88vw, 340px);
    text-align: center;
    line-height: 1.35;
  }
}
@keyframes floaty {
  0%, 100% { transform: translateX(-50%) translateY(0); }
  50% { transform: translateX(-50%) translateY(-6px); }
}
#hint.fade { transition: opacity 1s; opacity: 0; }
/* v8: on narrow screens the 3-line hint chip and the beach button share the
   bottom lane — when a beach prompt (or the edge cta) is up, the hint yields.
   v8.1: class toggled from world.js — :has() invalidation is unreliable in
   ios safari (see the case-overlay bug), so no :has anywhere. */
@media (max-width: 640px) {
  #hud.prompt-up #hint {
    opacity: 0;
    transition: opacity .4s;
  }
}

.dock {
  position: absolute;
  bottom: 90px;
  left: 50%;
  transform: translateX(-50%);
  pointer-events: auto;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: 1.15rem;
  letter-spacing: .03em;
  /* tinted by the nearest island (world.js sets --hud-tint) */
  background: var(--hud-tint, var(--coral));
  color: var(--hud-tint-text, var(--ink));
  transition: background .6s ease, color .6s ease;
  border: 0;
  border-radius: 999px;
  padding: 14px 28px;
  cursor: pointer;
  text-transform: lowercase;
  box-shadow: 0 6px 0 rgba(0,0,0,.25);
  /* v8.5: was .9s/-8px — read as a toy. a slow, small swell sits in the
     same tempo family as the hint float and the golden-hour water; the
     one-shot entrance (dockin) restarts on every display flip, so the
     prompt rises into the lane instead of popping. if ios ever freezes
     the entrance animation the base state is fully visible — safe. */
  animation: dockin .35s cubic-bezier(.2, .9, .3, 1.2), bounce 1.8s .35s ease-in-out infinite;
  /* v8.2: long island names must wrap inside the pill, never overflow a
     narrow window ("beach at ministerie van justitie" @320px) */
  max-width: min(86vw, 520px);
  text-align: center;
  line-height: 1.2;
}
@keyframes bounce {
  0%, 100% { transform: translateX(-50%) translateY(0); }
  50% { transform: translateX(-50%) translateY(-6px); }
}
@keyframes dockin {
  from { opacity: 0; transform: translateX(-50%) translateY(16px); }
  to { opacity: 1; transform: translateX(-50%) translateY(0); }
}

/* v6 · edge cta: the "next island" mail chip — same dock-btn language,
   parked a notch higher so it never collides with a beach prompt */
.edge-cta {
  text-decoration: none;
  display: inline-block;
  bottom: 90px;
  background: var(--yellow);
  color: var(--ink);
}
.edge-cta[hidden] { display: none; }

/* the captain's speech bubble */
#island-toast {
  position: absolute;
  top: 78px;
  left: 50%;
  transform: translateX(-50%) translateY(-8px);
  display: flex;
  align-items: flex-end;
  gap: 10px;
  opacity: 0;
  transition: opacity .35s, transform .35s cubic-bezier(.2, .9, .3, 1.3);
  pointer-events: none;
  /* v8.1: width:max-content escapes abs-pos shrink-to-fit — with left:50%
     the available width is only HALF the viewport, which on phones crushed
     the bubble to ~140px and stacked the captain's lines 1-2 words tall.
     desktop never noticed (half of a laptop > the 460px cap). */
  width: max-content;
  max-width: min(88vw, 460px);
}
#island-toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
.cap-avatar {
  flex: 0 0 auto;
  filter: drop-shadow(0 3px 8px rgba(12, 11, 30, .35));
  animation: capbob 2.6s ease-in-out infinite;
}
@keyframes capbob {
  0%, 100% { transform: rotate(-3deg) translateY(0); }
  50% { transform: rotate(3deg) translateY(-3px); }
}
.cap-bubble {
  position: relative;
  background: var(--cream);
  color: var(--ink);
  border: 2px solid var(--ink);
  border-radius: 16px;
  border-bottom-left-radius: 3px;
  padding: 9px 16px 11px;
  box-shadow: 0 8px 22px rgba(12, 11, 30, .28);
}
.cap-bubble::before {
  content: "";
  position: absolute;
  left: -9px;
  bottom: 9px;
  width: 0; height: 0;
  border-top: 7px solid transparent;
  border-bottom: 7px solid transparent;
  border-right: 9px solid var(--ink);
}
.cap-bubble::after {
  content: "";
  position: absolute;
  left: -6px;
  bottom: 11px;
  width: 0; height: 0;
  border-top: 5px solid transparent;
  border-bottom: 5px solid transparent;
  border-right: 7px solid var(--cream);
}
.cap-label {
  display: block;
  font-family: var(--font-mono);
  font-size: .58rem;
  letter-spacing: .16em;
  color: var(--hud-tint-deep, var(--coral-ink));
  transition: color .6s ease;
  margin-bottom: 2px;
}
.cap-bubble p {
  font-weight: 700;
  font-size: clamp(.95rem, 2.6vw, 1.2rem);
  line-height: 1.25;
}
/* v8.1: on phones the bubble reads like a chat message, not a headline —
   wider lane (≈78vw incl. avatar) and a fixed, calm type size so lines
   carry 4+ words. same treatment as the hud chips: we own the sizing. */
@media (max-width: 640px) {
  #island-toast { max-width: 80vw; }
  .cap-bubble p { font-size: 1rem; }
  .cap-bubble { padding: 8px 14px 10px; }
}

/* ---------- controls card ---------- */
#controls-card {
  position: fixed;
  inset: 0;
  z-index: 56;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  opacity: 0;
  transition: opacity .6s ease;
}
#controls-card.show { opacity: 1; }
#controls-card .controls-inner {
  /* v8.5: same rise accent as the title card, scaled down for a card */
  transform: translateY(12px) scale(.985);
  transition: transform .9s cubic-bezier(.22, .84, .28, 1);
}
#controls-card.show .controls-inner { transform: none; }
.controls-inner {
  background: rgba(12, 11, 20, .82);
  border: 1.5px solid rgba(246, 238, 221, .35);
  border-radius: 20px;
  padding: 26px 34px 22px;
  text-align: center;
  color: var(--cream);
  box-shadow: 0 18px 50px rgba(12, 11, 30, .4);
}
@media (pointer: fine) {
  .controls-inner { backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); }
}
.controls-title {
  font-family: var(--font-mono);
  font-size: .62rem;
  letter-spacing: .22em;
  color: var(--coral);
  margin-bottom: 14px;
}
.controls-keys .krow {
  display: flex;
  justify-content: center;
  gap: 6px;
  margin-bottom: 6px;
}
.controls-keys kbd {
  font-family: var(--font-mono);
  font-size: .95rem;
  width: 42px;
  height: 42px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1.5px solid rgba(246, 238, 221, .7);
  border-bottom-width: 3px;
  border-radius: 9px;
  background: rgba(246, 238, 221, .07);
}
.controls-keys p {
  font-family: var(--font-mono);
  font-size: .72rem;
  letter-spacing: .08em;
  margin-top: 10px;
  opacity: .85;
}
.controls-alt {
  font-family: var(--font-mono);
  font-size: .62rem;
  letter-spacing: .06em;
  opacity: .55;
  margin-top: 12px;
  max-width: 30ch;
  margin-left: auto;
  margin-right: auto;
}
.touch-icon {
  width: 54px;
  height: 54px;
  margin: 0 auto;
  border: 1.5px solid rgba(246, 238, 221, .6);
  border-radius: 50%;
  position: relative;
}
.touch-icon span {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 16px;
  height: 16px;
  margin: -8px 0 0 -8px;
  border-radius: 50%;
  background: var(--cream);
  animation: touchdrag 1.6s ease-in-out infinite;
}
@keyframes touchdrag {
  0%, 100% { transform: translate(0, 6px); }
  50% { transform: translate(0, -8px); }
}

/* ---------- compass 2.0: you are the needle ---------- */
.compass {
  position: absolute;
  right: max(18px, env(safe-area-inset-right));
  bottom: max(18px, env(safe-area-inset-bottom));
  width: 118px;
  height: 118px;
  pointer-events: auto;
  display: flex;
  align-items: center;
  justify-content: center;
}
.compass::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: radial-gradient(circle at 50% 38%, rgba(34, 30, 48, .88), rgba(12, 11, 20, .92));
  border: 1.5px solid rgba(246, 238, 221, .85);
  box-shadow: 0 10px 28px rgba(12, 11, 30, .35), inset 0 0 22px rgba(0, 0, 0, .35);
}
@media (pointer: fine) {
  .compass::before { backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); }
}
.compass::after {
  /* inner ring */
  content: "";
  position: absolute;
  inset: 10px;
  border-radius: 50%;
  border: 1px solid rgba(246, 238, 221, .22);
  pointer-events: none;
}
.compass-rose {
  position: absolute;
  inset: 3px;
  border-radius: 50%;
  transition: transform .1s linear;
  pointer-events: none;
}
.compass-n {
  position: absolute;
  top: 3px;
  left: 50%;
  transform: translateX(-50%);
  font-family: var(--font-mono);
  font-size: .6rem;
  color: var(--hud-tint, var(--coral));
  transition: color .6s ease;
  font-weight: 700;
}
.compass-tick {
  position: absolute;
  width: 1.5px;
  height: 7px;
  background: rgba(246, 238, 221, .45);
  left: 50%;
  top: 50%;
}
.compass-tick.t-e { transform: translate(-50%, -50%) rotate(90deg) translateY(-51px); }
.compass-tick.t-s { transform: translate(-50%, -50%) rotate(180deg) translateY(-51px); }
.compass-tick.t-w { transform: translate(-50%, -50%) rotate(270deg) translateY(-51px); }
.compass-needle {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  pointer-events: none;
  filter: drop-shadow(0 2px 5px rgba(0, 0, 0, .45));
}
.compass-dot {
  position: absolute;
  left: 50%;
  top: 50%;
  /* v8.7: the BUTTON is 24px (wcag 2.2 target size) but only a 15px disc is
     painted — the transparent border keeps the crowded cluster readable */
  width: 24px;
  height: 24px;
  margin: -12px 0 0 -12px;
  border-radius: 50%;
  border: 4.5px solid transparent;
  background-clip: padding-box;
  z-index: 2;                 /* above the dial's ::after ring, which painted over the dots */
  cursor: pointer;
  padding: 0;
  transition: box-shadow .2s;
  box-shadow: 0 0 8px rgba(0, 0, 0, .4);
}
/* invisible halo so the touch target is ≥44px */
.compass-dot::before {
  content: "";
  position: absolute;
  inset: -1.5px;
  border-radius: 50%;
  border: 1.5px solid var(--cream);
  pointer-events: none;
}
.compass-dot:focus-visible {
  outline: 2px solid var(--cream);
  outline-offset: 3px;
}
.compass-dot:hover { box-shadow: 0 0 0 4px rgba(246,238,221,.3), 0 0 10px rgba(246,238,221,.4); }
.compass-dot.visited::after {
  content: "✓";
  position: absolute;
  inset: -4.5px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: .75rem;
  color: var(--ink);
  font-weight: 700;
  /* v8.5: the ✓ lands with the same spring accent as the rest of the hud */
  animation: dotcheck .4s cubic-bezier(.2, .9, .3, 1.3);
}
@keyframes dotcheck {
  from { transform: scale(0); opacity: 0; }
  to { transform: scale(1); opacity: 1; }
}
.compass-dot.targeted { animation: dotpulse 1s infinite; }
@keyframes dotpulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(246,238,221,.6); }
  50% { box-shadow: 0 0 0 7px rgba(246,238,221,0); }
}
.compass-counter {
  position: absolute;
  bottom: -6px;
  left: 50%;
  transform: translateX(-50%);
  font-size: .6rem;
  padding: 3px 9px;
  white-space: nowrap;
}
@media (max-width: 640px) {
  .compass { width: 96px; height: 96px; bottom: 74px; }
}
/* v8.2: under ~430px a centered pill can't share the bottom lane with the
   right-anchored compass — lift the prompts clear above it (compass top
   sits at bottom:170px on these widths) */
@media (max-width: 430px) {
  .dock { bottom: 182px; }
  .edge-cta { bottom: 182px; }
}

/* ---------- v8.4 chart zoom chips (non-touch pointers only) ---------- */
#zoom-ctl {
  position: absolute;
  left: max(18px, env(safe-area-inset-left));
  bottom: max(18px, env(safe-area-inset-bottom));
  display: flex;
  flex-direction: column;
  gap: 7px;
  pointer-events: auto;
}
.zoom-btn {
  width: 36px;
  height: 36px;
  padding: 0;
  border-radius: 50%;
  font-size: 1.05rem;
  line-height: 1;
  display: grid;
  place-items: center;
  transition: transform .2s, background .2s, opacity .3s;
}
/* v8.5: at the dolly's floor/ceiling the chip says so instead of playing
   dead — the "+" is disabled at spawn (default framing IS the close-up) */
.zoom-btn:disabled {
  opacity: .35;
  cursor: default;
}
.zoom-btn:disabled:hover { transform: none; background: rgba(18, 16, 24, .82); }

/* ---------- touch joystick ---------- */
.joy-base {
  position: absolute;
  width: 108px;
  height: 108px;
  margin: -54px 0 0 -54px;
  border-radius: 50%;
  border: 2px solid rgba(246,238,221,.5);
  background: rgba(20,18,15,.15);
  pointer-events: none;
}
.joy-base[hidden] { display: none; }
.joy-nub {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 42px;
  height: 42px;
  border-radius: 50%;
  background: var(--cream);
  border: 3px solid var(--ink);
  transform: translate(-50%, -50%);
  box-shadow: 0 3px 0 rgba(0,0,0,.3);
}

/* ---------- loader: dark room before the reveal ---------- */
#loader {
  position: fixed;
  inset: 0;
  z-index: 90;
  background: #0d0c16;
  color: var(--cream);
  display: flex;
  align-items: center;
  justify-content: center;
}
#loader[hidden] { display: none; }
/* v8.3: loader-first boot — it ships visible in the html and leaves with a
   fade (opacity transition on an already-visible element: safe on ios,
   unlike animations armed in the same frame as a display flip). */
#loader { transition: opacity .5s ease; }
#loader.out { opacity: 0; pointer-events: none; }
.loader-inner { text-align: center; }
.loader-inner .loader-mark {
  font-family: var(--font-display);
  font-size: clamp(2.2rem, 7vw, 4.6rem);
  font-weight: 400;
  letter-spacing: .01em;
  line-height: 1;
}
.loader-inner .loader-mark em {
  font-style: normal;
  color: var(--coral);
}
.loader-line {
  width: min(220px, 50vw);
  height: 1.5px;
  background: rgba(246, 238, 221, .18);
  margin: 26px auto 18px;
  position: relative;
  overflow: hidden;
}
.loader-line::after {
  content: "";
  position: absolute;
  left: -40%;
  top: 0;
  height: 100%;
  width: 40%;
  background: var(--coral);
  animation: loadsweep 1.1s ease-in-out infinite;
}
@keyframes loadsweep {
  to { left: 100%; }
}
.loader-inner p {
  font-family: var(--font-mono);
  font-size: .72rem;
  letter-spacing: .14em;
  opacity: .7;
}

/* ---------- v6 · the menu is the deep ----------
   you dove off a canoe to get here, so scrolling is descending.
   #depth-bg (js-injected, aria-hidden) is one water column baked into
   the document height: luminous surface → ultramarine → abyss. text
   over the deep zones flips to cream (AA), the cases stay light rafts. */
#depth-bg {
  position: absolute;
  top: 0; left: 0; right: 0;
  z-index: 1;
  pointer-events: none;
  overflow: hidden;
  background: linear-gradient(180deg,
    var(--cream) 0%,
    #e3ecda 9%,
    #b8dfd4 20%,
    #64b7b4 34%,
    #3c6fd1 50%,
    #2b47d9 62%,
    #1b2f8e 76%,
    #101b52 88%,
    #0a1230 100%);
}
body.mode-sea #depth-bg, body.case-open #depth-bg { display: none; }
/* promote the content column to its own layer over the water column
   (also dodges gpu-compositing quirks with the tall gradient layer).
   content mode only: in sea mode the open case is position:fixed and
   a transformed ancestor would re-anchor it. */
body.mode-content #content { transform: translateZ(0); }
/* god-rays at the surface, gone by the second screen */
.depth-rays {
  position: absolute;
  top: -4vh; left: 0; right: 0;
  height: 92vh;
  background:
    linear-gradient(102deg, transparent 30%, rgba(253, 246, 230, .38) 34%, transparent 40%,
      transparent 52%, rgba(253, 246, 230, .28) 57%, transparent 64%,
      transparent 72%, rgba(253, 246, 230, .2) 75%, transparent 80%);
  -webkit-mask-image: linear-gradient(180deg, rgba(0,0,0,.9), transparent 85%);
  mask-image: linear-gradient(180deg, rgba(0,0,0,.9), transparent 85%);
  filter: blur(2px);
}
/* sparse bubbles drifting up the deep half (not created under reduced motion) */
.depth-bubbles span {
  position: absolute;
  border-radius: 50%;
  border: 2px solid rgba(246, 238, 221, .34);
  background: rgba(246, 238, 221, .05);
  animation: depthbub ease-in-out infinite alternate;
}
@keyframes depthbub {
  from { transform: translateY(0); }
  to { transform: translateY(-46px); }
}
/* the abyssal local */
.depth-angler {
  position: absolute;
  left: 74%;                    /* the text column keeps the left lane */
  animation: anglersway 7s ease-in-out infinite;
}
.depth-angler .angler-lure { animation: anglerlure 2.6s ease-in-out infinite; }
@keyframes anglersway {
  0%, 100% { transform: translate(0, 0) rotate(-2deg); }
  50% { transform: translate(14px, -10px) rotate(2deg); }
}
@keyframes anglerlure {
  0%, 100% { opacity: .95; }
  50% { opacity: .35; }
}
@media (max-width: 760px) { .depth-angler { left: 62%; } }

/* deep-zone ink: about + footer live under the thermocline.
   scoped to body.js (the gradient only exists with js). */
body.js .about, body.js .about h2, body.js footer { color: var(--cream); }
body.js .about .social a { border-color: rgba(246, 238, 221, .75); color: var(--cream); }
body.js .about .social a:hover { background: var(--cream); color: var(--sea-deep); }
body.js .about .contact a, body.js .about .press a { color: var(--yellow); }
body.js .stamp circle { stroke: var(--cream); }
body.js .stamp text { fill: var(--cream); }
body.js .stamp path[fill="#2b47d9"] { fill: #7f96f2; }
body.js footer { opacity: .8; }
/* the route dashes fade out in deep water instead of fighting it */
body.js .case::before { border-left-color: rgba(246, 238, 221, .35); }

/* ---------- content layer ---------- */
#content {
  position: relative;
  z-index: 20;
  max-width: 1080px;
  margin: 0 auto;
  padding: 120px max(clamp(18px, 5vw, 48px), env(safe-area-inset-right))
           60px max(clamp(18px, 5vw, 48px), env(safe-area-inset-left));
}
body.mode-sea #content, body.mode-sea footer { display: none; }

.hero { min-height: 62vh; display: flex; flex-direction: column; justify-content: center; position: relative; }
/* generative type field behind the hero (js-injected, decorative) */
.hero-field {
  position: absolute;
  inset: -60px calc(-1 * clamp(18px, 5vw, 48px));
  width: calc(100% + 2 * clamp(18px, 5vw, 48px));
  height: calc(100% + 120px);
  pointer-events: none;
  z-index: -1;
}
.hero > *:not(.hero-field) { position: relative; }

/* flotsam: nautical margin dwellers with scroll physics (js-injected) */
#flotsam { position: absolute; inset: 0; pointer-events: none; z-index: 15; overflow: visible; }
body.mode-sea #flotsam, body.case-open #flotsam { display: none; }
.flot { position: absolute; filter: drop-shadow(2px 3px 0 rgba(20, 18, 15, .14)); will-change: transform; }
@media (max-width: 760px) { .flot { transform: scale(.75); } }
h1 {
  font-family: var(--font-display);
  font-size: clamp(2.6rem, 8.4vw, 5.8rem);
  font-weight: 400;              /* anton has one weight; it's plenty */
  line-height: 1.02;
  letter-spacing: .004em;
}
.h1-role {
  display: block;
  font-family: var(--font-body);
  font-size: clamp(1.05rem, 2.6vw, 1.6rem);
  font-weight: 700;
  letter-spacing: 0;
  color: var(--coral-ink);
  margin-top: .5em;
}
.lede { font-size: clamp(1.05rem, 2.4vw, 1.35rem); max-width: 46ch; margin-top: 1.4em; }
.lede-sub { font-size: clamp(1rem, 2vw, 1.15rem); margin-top: .8em; color: var(--sea); font-weight: 600; }
.scroll-hint { margin-top: 3em; font-family: var(--font-mono); font-size: .8rem; opacity: .74; }

/* ---------- marquee ---------- */
.marquee {
  overflow: hidden;
  border-top: 2px solid var(--ink);
  border-bottom: 2px solid var(--ink);
  padding: 12px 0;
  margin: 4vh calc(-1 * clamp(18px, 5vw, 48px)) 2vh;
  background: var(--ink);
  transform: rotate(-.6deg) scale(1.02);
}
.marquee-track {
  display: flex;
  width: max-content;
  animation: marquee 26s linear infinite;
}
.marquee-track span {
  font-family: var(--font-mono);
  font-size: .78rem;
  letter-spacing: .18em;
  color: var(--cream);
  white-space: nowrap;
}
@keyframes marquee {
  to { transform: translateX(-50%); }
}

/* ---------- cases: ports on the chart ---------- */
.case {
  position: relative;
  margin: 15vh 0;
  border: 3px solid var(--ink);
  border-radius: 22px;
  background: #fffdf6;
  box-shadow: 8px 8px 0 rgba(20,18,15,.12);
  transition: transform .35s ease, box-shadow .35s ease;
}
@media (hover: hover) {
  .case:hover {
    transform: translateY(-5px) rotate(-.25deg);
    box-shadow: 10px 14px 0 rgba(20,18,15,.14);
  }
}
/* dashed route up to each port, with a colored marker */
.case::before {
  content: "";
  position: absolute;
  left: 50%;
  top: -13vh;
  height: 13vh;
  border-left: 2.5px dashed rgba(20, 18, 15, .3);
}
.case::after {
  content: attr(data-num);
  position: absolute;
  top: 10px;
  right: 22px;
  font-family: var(--font-display);
  font-size: clamp(4rem, 12vw, 8rem);
  font-weight: 400;
  line-height: 1;
  color: transparent;
  -webkit-text-stroke: 2px rgba(20, 18, 15, .1);
  pointer-events: none;
}
.case .case-head::before {
  content: "";
  position: absolute;
  left: 50%;
  top: -9px;
  width: 18px;
  height: 18px;
  margin-left: -9px;
  border-radius: 50%;
  background: var(--dot, var(--coral));
  border: 3px solid var(--ink);
}
/* scroll reveal (js only) */
body.js .case, body.js .about {
  opacity: 0;
  transform: translateY(34px);
  transition: opacity .7s ease, transform .7s cubic-bezier(.2, .8, .3, 1), box-shadow .35s ease;
}
body.js .case.inview, body.js .about.inview {
  opacity: 1;
  transform: none;
  transition: opacity .6s ease, transform .45s ease, box-shadow .35s ease;
}
body.js.mode-sea .case, body.js.mode-sea .about { opacity: 1; transform: none; transition: none; }
.case-head { position: relative; padding: clamp(20px, 4vw, 40px) clamp(20px, 4vw, 40px) 0; }
.case-tag {
  font-family: var(--font-mono);
  font-size: .72rem;
  background: var(--tag, var(--ink));
  color: var(--ink);
  padding: 5px 12px;
  border-radius: 999px;
  border: 2px solid var(--ink);
  display: inline-block;
}
.case h2 {
  font-family: var(--font-display);
  font-size: clamp(2rem, 6vw, 3.8rem);
  font-weight: 400;
  letter-spacing: .004em;
  margin-top: .5em;
  line-height: 1.04;
}
.case-claim { font-size: clamp(1.05rem, 2.4vw, 1.3rem); color: var(--coral-ink); font-weight: 600; margin-top: .5em; max-width: 55ch; }
.case-body { padding: clamp(20px, 4vw, 40px); display: grid; gap: 28px; }

.case-text p { max-width: 62ch; margin-bottom: 1em; }
.case-meta {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 10px 26px;
  margin-top: 1.2em;
  font-family: var(--font-mono);
  font-size: .8rem;
}
.case-meta span { display: block; opacity: .72; font-size: .72rem; }

.poster-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr));
  gap: 14px;
}
.poster-grid img {
  border: 3px solid var(--ink);
  border-radius: 12px;
  transition: transform .25s;
}
@media (hover: hover) { .poster-grid img:hover { transform: rotate(-1.2deg) scale(1.02); } }

/* video */
.video-wrap { position: relative; }
.video-wrap iframe,
.video-placeholder {
  width: 100%;
  aspect-ratio: 16 / 9;
  border: 3px solid var(--ink);
  border-radius: 12px;
}
.video-placeholder {
  background: repeating-linear-gradient(45deg, #e9dfc8, #e9dfc8 14px, #f1e8d4 14px, #f1e8d4 28px);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font-weight: 600;
}

/* v8.6 · language chips under the capullos embed (english / español cuts).
   built by main.js only when config ships more than one video, so a single
   video keeps the bare iframe. same pill language as the social links; the
   selected one fills with the island color and takes the contrast-safe
   --dot-text main.js computes per case. */
.video-langs {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  margin-top: 12px;
}
.lang-chip,
.case-links a {
  font-family: var(--font-mono);
  font-size: .8rem;
  color: var(--ink);
  background: transparent;
  border: 2px solid var(--ink);
  border-radius: 999px;
  padding: 6px 15px;
  cursor: pointer;
  text-decoration: none;
  display: inline-block;
  transition: background .2s, color .2s, transform .2s;
}
.lang-chip:hover,
.case-links a:hover { transform: translateY(-1px); }
.lang-chip.on {
  background: var(--dot, var(--ink));
  color: var(--dot-text, var(--cream));
  font-weight: 700;
}
.case-links a:hover { background: var(--ink); color: var(--cream); }

/* v8.6 · the campaign's own sites, as chips instead of naked urls */
.case-links {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 1em;
  max-width: 62ch;
}

/* v8.6 · the two numbers that close the argument (capullos: €11 in,
   €32,800 of earned media out). anton for the figure, mono for the label,
   painted in the island color with the AA-safe --dot-text main.js picks. */
.case-stats {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 12px 34px;
  margin: 0 0 1.4em;
  padding: 16px 20px;
  max-width: 62ch;
  border: 3px solid var(--ink);
  border-radius: 12px;
  background: var(--dot, var(--coral));
  color: var(--dot-text, var(--ink));
}
.case-stats strong {
  display: block;
  font-variant-numeric: tabular-nums;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(1.7rem, 4.6vw, 2.4rem);
  line-height: 1;
}
.case-stats span {
  font-family: var(--font-mono);
  font-size: .72rem;
  opacity: .72;
}

@media (prefers-reduced-motion: reduce) {
  .lang-chip, .case-links a { transition: none; }
  .lang-chip:hover, .case-links a:hover, .ai-poster:hover { transform: none; }
}

/* v7: placeholder slots in a poster grid — same visual language as
   the capullos video placeholder (cream stripes, mono aside), poster
   proportions so the swap to real images is 1:1 */
.case-slot {
  aspect-ratio: 1000 / 1415;
  border: 3px solid var(--ink);
  border-radius: 12px;
  background: repeating-linear-gradient(45deg, #e9dfc8, #e9dfc8 14px, #f1e8d4 14px, #f1e8d4 28px);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 16px;
  text-align: center;
  font-weight: 600;
}
.case-slot .mono { font-size: .75rem; opacity: .75; }

/* ---------- about ---------- */
.about { position: relative; margin: 16vh 0 8vh; max-width: 68ch; }
.stamp {
  position: absolute;
  top: -30px;
  right: -10px;
  animation: stampspin 22s linear infinite;
  opacity: .85;
}
@keyframes stampspin {
  to { transform: rotate(360deg); }
}
@media (max-width: 760px) {
  .stamp { position: static; margin-bottom: 18px; width: 84px; }
}
.about h2 { font-family: var(--font-display); font-size: clamp(1.8rem, 5vw, 3rem); font-weight: 400; margin-bottom: .6em; letter-spacing: .004em; }
.about p { margin-bottom: 1em; }
.social { display: flex; gap: 12px; margin: 1.4em 0; flex-wrap: wrap; }
.social a {
  font-family: var(--font-mono);
  font-size: .85rem;
  border: 2px solid var(--ink);
  border-radius: 999px;
  padding: 7px 16px;
  text-decoration: none;
  transition: background .2s, color .2s;
}
.social a:hover { background: var(--ink); color: var(--cream); }
.contact a { color: var(--coral-ink); font-weight: 700; }
.press { font-size: .92rem; }
.press a { color: var(--coral-ink); font-weight: 700; }

footer {
  position: relative;
  z-index: 20;
  text-align: center;
  padding: 30px 20px 44px;
  font-family: var(--font-mono);
  font-size: .75rem;
  opacity: .65;
}

/* ---------- case overlay (3d mode) ----------
   v8.2.2 · ios paint hardening. jorge's iphone showed the open case IN
   LAYOUT (display:block, correct rect, no js errors) yet invisible. two
   ios-specific traps addressed here:
   1. visibility must NEVER depend on the entry animation. the old rule
      animated from opacity:0 in the same style update as display:none→
      block — if webkit doesn't start (or freezes) that animation, the
      element stays at its reveal base (.case is opacity:0 for the scroll
      reveal). now the open state IS the final state (opacity:1, no
      reveal transform), and the animation rides on a .surfacing class
      that main.js adds on a double requestAnimationFrame AFTER the
      display flip has been committed. if the animation never runs, the
      card is simply there.
   2. translateZ(0) promotes the card to its own compositing layer —
      ios's paint order over a webgl canvas layer is not chrome's. */
body.mode-sea .case.open {
  display: block;
  position: fixed;
  z-index: 70;
  inset: clamp(8px, 3vw, 40px);
  overflow-y: auto;
  margin: 0;
  overscroll-behavior: contain;
  opacity: 1;
  transform: translateZ(0);
}
body.mode-sea .case.open.surfacing {
  animation: surface .45s cubic-bezier(.2, .9, .3, 1.2);
}
/* v8.5 · the close mirrors the open: the card sinks back under. cosmetic
   only — main.js removes .open on a timeout, never on animationend (ios:
   visibility must never depend on an animation running). `forwards` holds
   the faded frame if the timeout lands a tick late. */
body.mode-sea .case.open.sinking {
  animation: sink .3s cubic-bezier(.55, 0, .8, .6) forwards;
  pointer-events: none;
}
@keyframes sink {
  from { transform: translate3d(0, 0, 0) scale(1); opacity: 1; }
  to { transform: translate3d(0, 54px, 0) scale(.965); opacity: 0; }
}
/* v8.2.3 · the endgame for the ios invisible-case bug: with every computed
   style verified correct on the device (op:1, vis:visible, z:70, rect in
   viewport), ios STILL refused to composite html above the webgl layer.
   so while a case is open we remove the webgl layer from the equation
   entirely — the world is paused anyway, and a hidden canvas also stops
   burning battery. scoped to ios/ipadOS via -webkit-touch-callout (the
   webkit-touch-only property), so desktop keeps the world peeking around
   the card edges. visibility (not display) keeps clientWidth alive for
   the resize self-heal. */
@supports (-webkit-touch-callout: none) {
  body.mode-sea.case-open #stage { visibility: hidden; }
}
@keyframes surface {
  from { transform: translate3d(0, 60px, 0) scale(.96); opacity: 0; }
  to { transform: translate3d(0, 0, 0) scale(1); opacity: 1; }
}
/* v8.1: was `#content:has(.case.open)` — ios safari failed to invalidate
   :has() for a class change inside this display:none subtree, so the case
   opened logically (hud hid, world paused) but never painted. openCase()
   already sets body.case-open: use that. no :has() in this stylesheet. */
body.mode-sea.case-open #content {
  display: block;
  /* v8.3 · belt-and-braces for the ios compositing bug: while a case is
     open, the content layer becomes its own fixed, isolated stacking
     context well above the (hidden) stage, so the card's paint order can
     never depend on the webgl layer. NO transform/filter here — either
     would become the containing block for the fixed .case.open inside. */
  position: fixed;
  inset: 0;
  z-index: 65;
  isolation: isolate;
}
body.mode-sea.case-open #content > *:not(.open) { display: none; }
body.mode-sea #content { padding: 0; max-width: none; }

.case .close-case {
  position: sticky;
  top: 14px;
  margin-left: calc(100% - 60px);
  width: 46px;
  height: 46px;
  border-radius: 50%;
  border: 3px solid var(--ink);
  background: var(--dot, var(--coral));
  color: var(--dot-text, var(--cream));
  font-size: 1.3rem;
  font-weight: 700;
  cursor: pointer;
  display: none;
  z-index: 5;
}
body.mode-sea .case.open .close-case { display: block; }

/* about overlay reuse */
body.mode-sea .about.open,
body.mode-sea .hero.open { display: block; }

/* ---------- v5 dive/board transitions: the sea is a real place ----------
   #sea-veil is the underwater beat between worlds: turquoise → deep
   ultramarine, rising bubbles, soft god-rays. it sits ABOVE the 3d stage
   (z 10) and BELOW the content column (z 20), so the menu literally
   surfaces over the water. */
#sea-veil {
  position: fixed;
  inset: 0;
  z-index: 15;
  pointer-events: none;
  opacity: 0;
  background: linear-gradient(180deg, #49c9bd -8%, #2b47d9 42%, #101b52 105%);
  transition: opacity .45s ease;
  overflow: hidden;
}
#sea-veil.show { opacity: 1; }
/* god-rays */
#sea-veil::before, #sea-veil::after {
  content: "";
  position: absolute;
  top: -20%;
  width: 24%;
  height: 90%;
  background: linear-gradient(180deg, rgba(253, 246, 230, .32), transparent 75%);
  transform: skewX(-14deg);
  filter: blur(6px);
}
#sea-veil::before { left: 26%; }
#sea-veil::after { left: 58%; width: 14%; transform: skewX(-20deg); }
/* bubbles: only animate while the veil is up */
#sea-veil .bub {
  position: absolute;
  bottom: -30px;
  border-radius: 50%;
  border: 2px solid rgba(253, 246, 230, .55);
  background: rgba(253, 246, 230, .08);
  animation: none;
  opacity: 0;
}
#sea-veil.show .bub { animation: bubrise linear infinite; }
@keyframes bubrise {
  0% { transform: translateY(0) translateX(0); opacity: 0; }
  12% { opacity: .9; }
  100% { transform: translateY(-110vh) translateX(14px); opacity: 0; }
}

/* the menu as a cream raft: parked a viewport down, surfacing over the
   veil. transform-only, so the no-js layout is byte-identical. */
body.menu-below #content, body.menu-below footer { transform: translateY(102vh); }
body.menu-anim #content, body.menu-anim footer {
  transition: transform .78s cubic-bezier(.22, .84, .28, 1);
}
body.menu-anim #content, body.menu-below #content {
  background: var(--cream);
  border-radius: 26px 26px 0 0;
  box-shadow: 0 -30px 90px rgba(16, 27, 82, .38);
}
/* HUD steps out of frame during both sequences */
body.diving #hud, body.boarding #hud { opacity: 0; transition: opacity .45s ease; pointer-events: none; }
body.diving #hud *, body.boarding #hud * { pointer-events: none; }
body.diving #topbar, body.boarding #topbar { opacity: 0; transition: opacity .45s ease; pointer-events: none; }

/* ---------- reduced motion ---------- */
@media (prefers-reduced-motion: reduce) {
  /* v8.7: a .01ms duration on an INFINITE animation restarts it ~100k times
     a second — the marquee, loader bar and stamp strobed instead of resting.
     finite animations still snap to their end frame; looping ones just stop. */
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
  .marquee-track, .stamp, .cap-avatar, #hint, .dock, .compass-dot.targeted,
  .depth-bubbles span, .depth-angler, .depth-angler .angler-lure, .touch-icon span { animation: none !important; }
  .loader-line::after { animation: none !important; left: 0; width: 40%; }
  html { scroll-behavior: auto; }
}

/* ============================================================
   v8.7 · red-team pass — a11y, facade, pillars, print
   ============================================================ */

/* visually hidden, still read aloud */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* skip link: first tab stop, only visible while focused */
.skip-link {
  position: fixed;
  top: 10px; left: 10px;
  z-index: 100;
  transform: translateY(-160%);
  background: var(--ink);
  color: var(--cream);
  font-family: var(--font-mono);
  font-size: .8rem;
  letter-spacing: .05em;
  padding: 10px 16px;
  border-radius: 999px;
  text-decoration: none;
  transition: transform .2s ease;
}
.skip-link:focus-visible { transform: none; }
body.mode-sea .skip-link { display: none; }
#content:focus { outline: none; }

/* one focus language everywhere: a cream/ink double ring that reads on the
   cream page, the ultramarine deep, the cards and the 3d sea alike */
:where(a, button, [tabindex]):focus-visible {
  outline: 3px solid var(--ink);
  outline-offset: 3px;
  box-shadow: 0 0 0 6px var(--cream);
  border-radius: 999px;
}
.case a:focus-visible, .case-text a:focus-visible { border-radius: 4px; }
.chip:focus-visible, .dock:focus-visible, .close-case:focus-visible, .compass-dot:focus-visible {
  outline-color: var(--cream);
  box-shadow: 0 0 0 6px var(--ink);
}
body.js .about a:focus-visible, body.js footer a:focus-visible {
  outline-color: var(--cream);
  box-shadow: 0 0 0 6px var(--sea-deep);
}
.dock:focus-visible { transform: translateX(-50%); }   /* never lose the pill's centring */

/* the loader's way out: the sea is a gift, not a gate */
.loader-skip {
  margin-top: 26px;
  background: transparent;
  color: var(--cream);
  border: 1.5px solid rgba(246, 238, 221, .55);
  border-radius: 999px;
  padding: 9px 18px;
  font-family: var(--font-mono);
  font-size: .72rem;
  letter-spacing: .08em;
  cursor: pointer;
  text-transform: lowercase;
  transition: background .2s, color .2s;
}
@media (hover: hover) { .loader-skip:hover { background: var(--cream); color: var(--ink); } }
.loader-skip:focus-visible { outline-color: var(--cream); box-shadow: none; }
@media (pointer: coarse) { .loader-skip { padding: 14px 20px; } }

/* youtube facade — no player, no trackers, no ~1MB of embed js until asked */
.yt-facade {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  padding: 0;
  border: 3px solid var(--ink);
  border-radius: 12px;
  overflow: hidden;
  cursor: pointer;
  background: #0d0c16;
  color: var(--cream);
}
.yt-facade img { width: 100%; height: 100%; object-fit: cover; }
.yt-facade::after {              /* soft vignette so the label always reads */
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(180deg, transparent 45%, rgba(13, 12, 22, .72));
  pointer-events: none;
}
.yt-play {
  position: absolute;
  left: 50%; top: 50%;
  z-index: 1;
  width: clamp(56px, 11vw, 84px);
  aspect-ratio: 1;
  margin: calc(clamp(56px, 11vw, 84px) / -2) 0 0 calc(clamp(56px, 11vw, 84px) / -2);
  border-radius: 50%;
  background: var(--yellow);
  border: 3px solid var(--ink);
  box-shadow: 0 5px 0 rgba(0, 0, 0, .3);
  transition: transform .2s ease;
}
.yt-play::before {
  content: "";
  position: absolute;
  left: 53%; top: 50%;
  transform: translate(-50%, -50%);
  border-left: clamp(16px, 3.2vw, 24px) solid var(--ink);
  border-top: clamp(10px, 2vw, 15px) solid transparent;
  border-bottom: clamp(10px, 2vw, 15px) solid transparent;
}
.yt-label {
  position: absolute;
  z-index: 1;
  left: 16px; right: 16px; bottom: 12px;
  font-family: var(--font-mono);
  font-size: .75rem;
  letter-spacing: .06em;
  text-align: left;
  text-transform: lowercase;
}
@media (hover: hover) { .yt-facade:hover .yt-play { transform: scale(1.08); } }
.yt-facade:focus-visible { outline-color: var(--ink); border-radius: 12px; }

/* island 02: the three pillars (replaces the striped placeholders) */
.pillars {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(230px, 100%), 1fr));
  gap: 14px;
}
.pillar {
  position: relative;
  border: 3px solid var(--ink);
  border-radius: 12px;
  background: repeating-linear-gradient(45deg, #fbf3dc, #fbf3dc 14px, #f7edcf 14px, #f7edcf 28px);
  padding: 22px 20px 20px;
  overflow: hidden;
}
.pillar::after {
  content: attr(data-n);
  position: absolute;
  right: 12px; top: 2px;
  font-family: var(--font-display);
  font-size: 4.2rem;
  line-height: 1;
  color: transparent;
  -webkit-text-stroke: 2px rgba(20, 18, 15, .14);
  pointer-events: none;
}
.pillar h3 {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(1.4rem, 3.4vw, 1.8rem);
  line-height: 1.05;
  margin-bottom: .45em;
}
.pillar p { max-width: 34ch; }
.case-text a { text-decoration-thickness: 2px; text-underline-offset: 3px; }

/* v8.8 · island 01, the main case: numbered chapters (problem → positioning →
   identity & voice → community engine; results + learnings join once jorge
   sends real material). same anton-outline numerals as the pillars. */
.case-chapters { list-style: none; display: grid; gap: 14px; }
.case-chapter {
  position: relative;
  border-top: 3px solid var(--ink);
  padding: 18px 64px 6px 0;
}
.case-chapter::after {
  content: attr(data-n);
  position: absolute;
  right: 0; top: 10px;
  font-family: var(--font-display);
  font-size: 3.2rem;
  line-height: 1;
  color: transparent;
  -webkit-text-stroke: 2px rgba(20, 18, 15, .16);
  pointer-events: none;
}
.case-chapter h3 {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(1.4rem, 3.4vw, 1.9rem);
  line-height: 1.05;
  margin-bottom: .4em;
}
.case-chapter p { max-width: 62ch; }
.case-chapter .poster-grid { margin-top: 12px; }
.case-chapter .case-slot { aspect-ratio: 4 / 3; }

/* the student concepts say so, plainly, in the same card language */
.case-honest {
  font-family: var(--font-mono);
  font-size: .82rem;
  border-left: 4px solid var(--dot, var(--ink));
  padding-left: 12px;
}

/* v8.9 · island 03, ai direction: a shelf of pieces, newest first. each
   piece = a 9:16 poster + its facts, side by side once the card is wide.
   the poster is drawn in css (night stadium, vertical beam — the reel's
   cover, translated) until jorge clears the real cover and master. */
.ai-pieces { display: grid; gap: 28px; }
.ai-piece {
  display: grid;
  gap: 22px;
  grid-template-columns: minmax(0, 1fr);
  border-top: 3px solid var(--ink);
  padding-top: 22px;
}
@media (min-width: 760px) {
  .ai-piece { grid-template-columns: minmax(190px, 250px) minmax(0, 1fr); align-items: start; }
}
.ai-poster {
  position: relative;
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 250px;
  aspect-ratio: 9 / 16;
  padding: 16px 16px 14px;
  border: 3px solid var(--ink);
  border-radius: 12px;
  overflow: hidden;
  background: linear-gradient(#0b0a14, #1b1631 62%, #2b1f2f);
  color: var(--cream);
  text-decoration: none;
  transition: transform .25s;
}
@media (hover: hover) { .ai-poster:hover { transform: rotate(-1.2deg) scale(1.02); } }
.ai-poster-kicker, .ai-poster-cta {
  position: relative;
  z-index: 1;
  font-family: var(--font-mono);
  font-size: .7rem;
}
.ai-poster-kicker { color: #ffc77a; }
.ai-poster-beam {
  position: absolute;
  left: 70%;
  top: 0;
  width: 6px;
  height: 77%;
  background: linear-gradient(rgba(255, 244, 220, 0), rgba(255, 244, 220, .95));
  box-shadow: 0 0 24px 8px rgba(255, 244, 220, .12);
}
.ai-poster-head {
  position: relative;
  z-index: 1;
  margin-top: 18%;
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 6.4vw, 1.9rem);
  line-height: 1.04;
}
.ai-poster-head .nowrap { white-space: nowrap; }
.ai-poster-stadium {
  position: absolute;
  left: 7%;
  right: 7%;
  bottom: 12%;
  height: 17%;
  border-radius: 22px;
  background:
    linear-gradient(90deg, transparent 58%, #ffcf8a 58%, #ffcf8a 61%, transparent 61%) center 30% / 100% 42% no-repeat,
    repeating-linear-gradient(transparent 0 9px, rgba(255, 166, 74, .85) 9px 11px) center 26% / 86% 62% no-repeat,
    #14121d;
}
.ai-poster-cta {
  margin-top: auto;
  align-self: flex-start;
  background: var(--cream);
  color: var(--ink);
  border: 2px solid var(--ink);
  border-radius: 999px;
  padding: 4px 10px;
}
.ai-piece-meta { font-family: var(--font-mono); font-size: .78rem; opacity: .8; margin-bottom: .4em; }
.ai-piece-meta span { color: var(--coral-ink); }
.ai-piece-text h3 {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(1.5rem, 3.6vw, 2rem);
  line-height: 1.05;
  margin-bottom: .45em;
}
.ai-piece-text p { max-width: 62ch; margin-bottom: 1em; }
.ai-caption {
  border-left: 4px solid var(--dot, var(--ink));
  padding-left: 14px;
  font-weight: 600;
}
.ai-caption p { margin-bottom: .3em; }
.ai-translation { font-family: var(--font-mono); font-size: .82rem; padding-left: 18px; opacity: .85; }
.ai-media { display: grid; gap: 14px; justify-items: start; }
.ai-pending-master { max-width: 250px; margin: 0; }

/* ?draft=1 only: the holes. loud on purpose — never meant for the public */
.case-pending {
  margin-top: 12px;
  padding: 14px 16px;
  border: 3px dashed var(--coral-ink);
  border-radius: 12px;
  background: repeating-linear-gradient(45deg, #fff3ea, #fff3ea 14px, #ffe9dc 14px, #ffe9dc 28px);
}
.case-pending .mono { font-family: var(--font-mono); font-size: .72rem; color: var(--coral-ink); margin-bottom: .3em; }
.case-pending-wide { aspect-ratio: 16 / 9; display: flex; flex-direction: column; justify-content: center; text-align: center; margin: 0; }
.case-pending-stats { border-style: dashed; }

/* long copy never drags the layout wider than the card */
.case-text, .case-head h2 { overflow-wrap: anywhere; }
.case-text p, .about p { hyphens: manual; }

/* forced-colors (windows high contrast): borders carry the design */
@media (forced-colors: active) {
  .chip, .dock, .lang-chip, .yt-facade, .pillar { border: 2px solid CanvasText; }
  .yt-play { forced-color-adjust: none; }
}

/* print / save-as-pdf: a recruiter can print the work, no sea required */
@media print {
  #stage, #hud, #loader, #title-card, #controls-card, #topbar, #flotsam, #depth-bg,
  #sea-veil, .marquee, .skip-link, .stamp, .hero-field, .close-case, .video-langs { display: none !important; }
  html, body { background: #fff !important; }
  body.js .case, body.js .about { opacity: 1 !important; transform: none !important; }
  body.js .about, body.js .about h2, body.js footer, body.js .about .contact a { color: #000 !important; }
  .case { break-inside: avoid; margin: 24px 0; box-shadow: none; }
  .case::before { display: none; }
  .yt-facade { display: none; }
  a[href^="http"]::after { content: " (" attr(href) ")"; font-size: .8em; }
}
