/* ============================================================
   LONGY — he is long
   Palette measured by k-means off the supplied artwork.
   The ocean is cyan→indigo→black. LONGY is the only violet.
   ============================================================ */

@font-face{
  font-family:'Fredoka';
  font-style:normal;
  font-weight:300 700;
  font-display:swap;
  src:url('../assets/font/fredoka-latin.woff2') format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,
                U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,
                U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}

:root{
  /* LONGY */
  --w-abyss:#0b0d25; --w-deep:#25215e; --w-shadow:#3840b0; --w-core:#564acc;
  --w-mid:#6b4dc1;   --w-light:#8764dd; --w-rim:#b789e8;   --w-spec:#edcdf5;
  /* water column */
  --s0:#7adef3; --s1:#4ac4ec; --s2:#29b3db; --s3:#1080b7;
  --s4:#055a99; --s5:#095278; --s6:#143946; --s7:#0c1919;

  --ease:cubic-bezier(.22,.61,.36,1);
  --font:'Fredoka',ui-rounded,'Segoe UI Rounded','Hiragino Maru Gothic ProN',
         system-ui,-apple-system,sans-serif;
  --mono:ui-monospace,'SF Mono',SFMono-Regular,'JetBrains Mono',Menlo,monospace;

  --safe-t:env(safe-area-inset-top,0px);
  --safe-b:env(safe-area-inset-bottom,0px);
  --safe-r:env(safe-area-inset-right,0px);
  --safe-l:env(safe-area-inset-left,0px);
}

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

html{
  background:var(--s4);
  overflow-x:clip;
  -webkit-text-size-adjust:100%;
  scrollbar-width:none;
}
html::-webkit-scrollbar{display:none}

body{
  margin:0;
  min-height:100svh;
  font-family:var(--font);
  color:#eaf6ff;
  background:
    linear-gradient(180deg,
      var(--s0) 0%, var(--s1) 8%, var(--s2) 18%, var(--s3) 33%,
      var(--s4) 50%, var(--s5) 68%, var(--s6) 85%, var(--s7) 100%);
  overflow-x:clip;
  overscroll-behavior-y:none;
}

/* the page is black until the first frame is composited — no flash of
   half-built world, no layout pop */
html[data-boot] #stage,
html[data-boot] #track,
html[data-boot] #gauge{opacity:0}
html:not([data-boot]) #stage,
html:not([data-boot]) #track,
html:not([data-boot]) #gauge{opacity:1;transition:opacity .7s var(--ease)}

.sr-only{
  position:absolute;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0;
}

/* ============================================================
   STAGE — everything visual is fixed; the document only scrolls
   ============================================================ */

#stage{
  position:fixed;
  inset:0;
  z-index:0;
  pointer-events:none;
}

#water,#spine,#fx{
  position:absolute;
  inset:0;
  width:100%;
  height:100%;
  display:block;
}
#water{z-index:0}
#spine{z-index:1;opacity:0}
#fx{z-index:4}

/* film grade over the whole world: vignette + a touch of lift in the blacks */
#grade{
  position:absolute;
  inset:0;
  z-index:5;
  pointer-events:none;
  /* NOT mix-blend-mode: a fullscreen blended layer forces the compositor to
     read back everything behind it every frame, which is one of the most
     expensive things you can do on a mobile GPU. A plain dark radial over
     normal blending is visually equivalent here and costs nothing. */
  background:
    radial-gradient(132% 96% at 50% 36%, transparent 40%, rgba(5,13,27,.26) 75%, rgba(3,7,16,.56) 100%);
}

/* ---- the camera -------------------------------------------------- */

#world{
  position:absolute;
  inset:0;
  z-index:2;
  perspective:clamp(620px,155vw,1250px);
  perspective-origin:50% 46%;
}

#cam{
  position:absolute;
  inset:0;
  transform-style:preserve-3d;
  will-change:transform;
}

/* ---- distant environment plates ---------------------------------- */

.plate{
  position:absolute;
  top:50%;
  left:50%;
  width:var(--w);
  aspect-ratio:1/1;
  margin:0;
  transform:translate3d(-50%,-50%,var(--z));
  background-size:cover;
  background-position:center;
  opacity:0;
  filter:saturate(.5);
  /* feather every edge so a plate never reads as a pasted rectangle */
  -webkit-mask-image:radial-gradient(closest-side,#000 42%,transparent 100%);
          mask-image:radial-gradient(closest-side,#000 42%,transparent 100%);
}
.plate.is-near{will-change:transform,opacity}
.plate[data-plate="temple"]{background-image:url('../assets/img/plate-temple.webp')}
.plate[data-plate="wreck"] {background-image:url('../assets/img/plate-wreck.webp')}
.plate[data-plate="kelp"]  {background-image:url('../assets/img/plate-kelp.webp')}
.plate[data-plate="hull"]  {background-image:url('../assets/img/plate-hull.webp')}

/* ---- scenes ------------------------------------------------------- */

.scene{
  position:absolute;
  inset:0;
  display:grid;
  place-items:center;
  opacity:0;
  visibility:hidden;
  will-change:auto;
}
.scene.is-near{visibility:visible;will-change:transform,opacity,filter}
/* every scene but the hero gives up the lower band to its caption */
.scene:not(#sc0){padding-bottom:22svh}

.art{
  position:relative;
  display:block;
  width:var(--w);
  --mh:56svh;                 /* the vertical band the artwork may occupy */
  transform-origin:50% 50%;
  /* the one shadow in the whole design: he is lit from above, in water */
  filter:drop-shadow(0 26px 46px rgba(5,14,34,.52));
}
.art img{
  display:block;
  width:auto;
  height:auto;
  max-width:100%;
  max-height:var(--mh);
  margin-inline:auto;
  user-select:none;
  -webkit-user-drag:none;
}

/* ---- caustic light ON him, not just behind him --------------------
   A seamless caustic tile, masked by the cutout's own alpha and screened
   over it, so the same water that lights the background plays across his
   body. Two layers at different scales and opposed drift; because the tile
   is seamless, translating by a whole number of tiles loops forever.
   Sized to the image box by JS (measure()) — the image is bounded by
   max-height, so it is often narrower than .art and `inset:0` would skew
   the mask. */
.caus{
  position:absolute;
  overflow:hidden;
  pointer-events:none;
  opacity:var(--co,0);
  -webkit-mask:var(--m) center / 100% 100% no-repeat;
          mask:var(--m) center / 100% 100% no-repeat;
  mix-blend-mode:screen;
}
.caus i{
  position:absolute;
  inset:-75%;
  display:block;
  background:url('../assets/img/caustic.webp') repeat;
  background-size:30% auto;
  will-change:transform;
  animation:causA 23s linear infinite;
}
.caus i + i{
  background-size:52% auto;
  opacity:.5;
  animation:causB 31s linear infinite;
}
@keyframes causA{ from{transform:translate3d(0,0,0)}       to{transform:translate3d(30%,-30%,0)} }
@keyframes causB{ from{transform:translate3d(0,0,0)}       to{transform:translate3d(-52%,26%,0)} }

/* each set-piece gets its own size envelope, tuned per viewport */
.art--wordmark{--w:min(92vw,1120px);--mh:44svh}
.art--portrait{--w:min(104vw,880px)}
.art--thinker {--w:min(50vw,330px)}
.art--titanic {--w:min(88vw,760px)}
.art--cash    {--w:min(80vw,620px)}
.art--chair   {--w:min(74vw,600px)}
/* the porthole is square, so one min() bounds both axes */
.art--loop    {--w:min(84vw,620px,56svh)}

/* the closing video sits in a soft-edged porthole so it dissolves into
   the water instead of ending at a rectangle */
.loop-frame{
  position:relative;
  border-radius:50%;
  overflow:hidden;
  aspect-ratio:1/1;
  -webkit-mask-image:radial-gradient(closest-side,#000 58%,rgba(0,0,0,.55) 82%,transparent 99%);
          mask-image:radial-gradient(closest-side,#000 58%,rgba(0,0,0,.55) 82%,transparent 99%);
}
.loop-frame video{
  width:100%;
  height:100%;
  object-fit:cover;
  transform:scale(1.06);
}
.loop-frame::after{
  content:'';
  position:absolute;
  inset:0;
  border-radius:50%;
  box-shadow:inset 0 0 90px 24px rgba(6,16,30,.72);
  pointer-events:none;
}

/* ---- the entrance -------------------------------------------------
   He rises into frame once, on first paint. Scroll is never blocked —
   if you scroll through it, the camera simply takes over mid-rise. */

@keyframes surface{
  0%  {transform:translate3d(0,13svh,-260px) scale(.86);opacity:0;
       filter:blur(13px) drop-shadow(0 26px 46px rgba(5,14,34,.52))}
  55% {opacity:1}
  100%{transform:translate3d(0,0,0) scale(1);opacity:1;
       filter:blur(0) drop-shadow(0 26px 46px rgba(5,14,34,.52))}
}
html:not([data-boot]) .art--wordmark{
  animation:surface 1.55s cubic-bezier(.19,.82,.3,1) both;
}
html:not([data-boot]) .cue{
  /* `backwards`, never `both`: a filled-forwards animation keeps winning the
     cascade after it ends and would pin opacity at 1, overriding the inline
     fade JS applies once you start scrolling. */
  animation:cuein .9s var(--ease) .95s backwards;
}
@keyframes cuein{from{opacity:0}to{opacity:1}}

/* the only hover state on the page: he is pokeable */
@media (hover:hover) and (pointer:fine){
  body.pokeable{cursor:grab}
  body.pokeable:active{cursor:grabbing}
}

/* ---- X link --------------------------------------------------------
   Top-right, fixed, above everything. Rendered as a bubble, because every
   other round thing on this page is one — the same rim + specular treatment
   the fx canvas draws, so it belongs to the world rather than sitting on
   top of it. Dark inner edge so the rim survives the bright surface water
   as well as the abyss.
   Clear of the depth gauge, which is vertically centred on the same edge. */

#xlink{
  position:fixed;
  z-index:9;
  top:calc(15px + var(--safe-t));
  right:calc(14px + var(--safe-r));
  width:46px;
  height:46px;
  display:grid;
  place-items:center;
  border-radius:50%;
  -webkit-tap-highlight-color:transparent;
  transition:transform .4s var(--ease), filter .4s var(--ease);
}
#xlink::after{content:'';position:absolute;inset:-5px}   /* 50px target */
#xlink:focus-visible{outline:1px solid rgba(214,240,255,.7);outline-offset:5px}

.xb{
  position:relative;
  width:100%;
  height:100%;
  display:grid;
  place-items:center;
  border-radius:50%;
  /* a bubble: bright rim, near-empty middle, a hint of body */
  background:
    radial-gradient(circle at 34% 30%, rgba(240,252,255,.34), rgba(205,237,255,.07) 48%, transparent 62%),
    radial-gradient(circle at 50% 50%, rgba(150,110,225,.10) 40%, rgba(10,38,62,.38) 74%, transparent 78%);
  box-shadow:
    /* dark inner edge first, so the bright rim survives the surface water too */
    inset 0 0 0 2.6px rgba(12,40,66,.30),
    inset 0 0 0 1.7px rgba(240,253,255,1),
    inset 0 0 12px 1px rgba(170,222,255,.34),
    0 2px 16px rgba(4,16,34,.5);
  animation:xfloat 6.5s ease-in-out infinite;
}

/* A bioluminescent halo. Violet is LONGY's colour and nothing else on the page
   is allowed it, so this reads as *his* link rather than as UI chrome — and it
   separates the bubble from both the cyan surface and the black abyss.
   Opacity + scale only: compositor-only, unlike animating box-shadow. */
.xb::before{
  content:'';
  position:absolute;
  inset:-8px;
  border-radius:50%;
  pointer-events:none;
  background:radial-gradient(circle,
    rgba(183,137,232,.42) 0%, rgba(150,110,225,.18) 48%, transparent 70%);
  animation:xglow 4.6s ease-in-out infinite;
}
@keyframes xglow{
  0%,100%{opacity:.40;transform:scale(.92)}
  50%    {opacity:1;   transform:scale(1.10)}
}
.xb svg{
  width:18px;
  height:18px;
  fill:rgba(240,252,255,.96);
  filter:drop-shadow(0 1px 3px rgba(3,14,30,.7));
}
/* the specular pop every bubble on this page has */
/* far enough out that it never lands on the glyph's upper-left arm */
.xb-spec{
  position:absolute;
  top:13%;
  left:15%;
  width:5px;
  height:5px;
  border-radius:50%;
  background:rgba(255,255,255,.92);
  filter:blur(.4px);
}

@keyframes xfloat{
  0%,100%{transform:translate3d(0,0,0)}
  50%    {transform:translate3d(-1.5px,-5px,0)}
}

@media (hover:hover) and (pointer:fine){
  #xlink:hover{transform:scale(1.11)}
  #xlink:hover .xb{box-shadow:
    inset 0 0 0 1.6px rgba(255,255,255,1),
    inset 0 0 14px 2px rgba(176,232,255,.45),
    0 4px 22px rgba(120,190,255,.3);}
}

@media (max-width:620px){
  #xlink{width:42px;height:42px;top:calc(12px + var(--safe-t));right:calc(11px + var(--safe-r))}
  #xlink::after{inset:-4px}                 /* keep a 50px target */
  .xb svg{width:16px;height:16px}
  .xb-spec{width:5px;height:5px}
}

/* ---- sound ---------------------------------------------------------
   The one control on the page. Mirrors the depth gauge on the other edge.
   Visually 22px, but the hit area is 44px per WCAG 2.5.5. */

#snd{
  position:fixed;
  z-index:9;
  left:calc(14px + var(--safe-l));
  bottom:calc(16px + var(--safe-b));
  width:26px;
  height:26px;
  padding:0;
  border:0;
  background:none;
  cursor:pointer;
  opacity:.42;
  transition:opacity .35s var(--ease);
  -webkit-tap-highlight-color:transparent;
}
#snd::after{content:'';position:absolute;inset:-9px}   /* 44px target */
/* it breathes twice, a few seconds in, so you notice there is sound at all —
   then never again. Removed the moment it is used. */
#snd.hint{animation:sndhint 2.1s var(--ease) 2 both}
@keyframes sndhint{
  0%,100%{opacity:.42;transform:scale(1)}
  48%    {opacity:.95;transform:scale(1.18)}
}
#snd:hover,#snd:focus-visible{opacity:.9}
#snd:focus-visible{outline:1px solid rgba(214,240,255,.6);outline-offset:6px;border-radius:3px}
#snd[aria-pressed="true"]{opacity:.92}

.snd-g{position:absolute;inset:0;display:block}
.snd-g i{
  position:absolute;
  left:3px;
  top:50%;
  width:5px;
  height:5px;
  margin-top:-2.5px;
  border-radius:50%;
  background:rgba(226,246,255,.95);
}
/* two arcs that only animate once sound is actually on */
.snd-g i:nth-child(2),
.snd-g i:nth-child(3){
  left:9px;
  top:50%;
  width:7px;
  height:14px;
  margin-top:-7px;
  border-radius:0 14px 14px 0;
  background:none;
  border:1.5px solid rgba(226,246,255,.95);
  border-left:0;
  opacity:.25;
}
.snd-g i:nth-child(3){left:15px;width:9px;height:20px;margin-top:-10px;border-radius:0 20px 20px 0}
#snd[aria-pressed="true"] .snd-g i:nth-child(2){animation:sndw 1.8s ease-in-out infinite}
#snd[aria-pressed="true"] .snd-g i:nth-child(3){animation:sndw 1.8s ease-in-out .3s infinite}
@keyframes sndw{0%,100%{opacity:.2}50%{opacity:1}}

/* ============================================================
   DEPTH GAUGE — a dive computer, not a navbar
   ============================================================ */

#gauge{
  position:fixed;
  z-index:8;
  top:0;
  bottom:0;
  right:calc(14px + var(--safe-r));
  width:34px;
  display:flex;
  flex-direction:column;
  align-items:flex-end;
  justify-content:center;
  pointer-events:none;
  font-family:var(--mono);
}
.gauge-rail{
  position:relative;
  width:1px;
  height:min(42svh,340px);
  background:linear-gradient(180deg,transparent,rgba(214,240,255,.42) 12%,rgba(214,240,255,.42) 88%,transparent);
  margin-right:7px;
}
.gauge-tick{
  position:absolute;
  right:-3px;
  top:0;
  width:8px;
  height:8px;
  border-radius:50%;
  background:var(--w-rim);
  box-shadow:0 0 12px 3px rgba(183,137,232,.75);
  transform:translate3d(0,-50%,0);
  will-change:transform;
}
.gauge-read{
  position:absolute;
  right:0;
  top:calc(50% + min(24svh,192px));
  font-size:11.5px;
  letter-spacing:.14em;
  color:rgba(222,243,255,.78);
  font-variant-numeric:tabular-nums;
  text-shadow:0 1px 10px rgba(0,20,40,.9);
  white-space:nowrap;
}
.gauge-read i{font-style:normal;opacity:.5;margin-left:.25em}

/* ============================================================
   SCROLL TRACK — the only real document flow
   ============================================================ */

#track{
  position:relative;
  z-index:6;
  margin:0;
  pointer-events:none;
}

.beat{
  height:calc(var(--h) * 1svh);
  display:grid;
  place-items:center;
  padding:0 22px;
  position:relative;
}
/* the beats are pure scroll spacers now — the words live in #stage's band */
.beat--say{pointer-events:none}

.say{
  position:fixed;
  z-index:7;
  left:0;
  right:0;
  bottom:clamp(40px,10svh,118px);
  margin:0 auto;
  padding:0 20px;
  max-width:15ch;
  text-align:center;
  font-weight:500;
  font-size:clamp(1.45rem,6.2vw,3.1rem);
  line-height:1.04;
  letter-spacing:-.018em;
  color:#f2fbff;
  text-shadow:0 2px 26px rgba(4,16,36,.75), 0 0 70px rgba(10,40,70,.5);
  opacity:0;
  text-wrap:balance;
}
.wd{
  display:inline-block;
  opacity:0;
  transform:translate3d(0,26px,0);
  will-change:opacity,transform;
}
.say--last{color:var(--w-rim);text-shadow:0 0 34px rgba(134,100,223,.6),0 2px 20px rgba(2,8,20,.9)}

/* scroll cue: three bubbles rising, on the hero only */
.cue{
  position:fixed;
  z-index:7;
  pointer-events:none;
  bottom:calc(5svh + var(--safe-b));
  left:50%;
  transform:translateX(-50%);
  display:flex;
  gap:9px;
  align-items:flex-end;
}
.cue-dot{
  width:7px;height:7px;border-radius:50%;
  background:rgba(236,250,255,.85);
  box-shadow:0 0 10px rgba(190,235,255,.8), inset -1px -1px 2px rgba(120,190,230,.9);
  animation:rise 2.4s var(--ease) infinite;
}
.cue-dot:nth-child(2){animation-delay:.3s;width:5px;height:5px}
.cue-dot:nth-child(3){animation-delay:.62s;width:9px;height:9px}
@keyframes rise{
  0%  {transform:translateY(12px) scale(.6);opacity:0}
  28% {opacity:.95}
  100%{transform:translateY(-30px) scale(1.06);opacity:0}
}

/* ---- the lore -----------------------------------------------------
   Everything above this point is wordless. This is the one place the page
   actually speaks, so it is treated as the payoff rather than as body copy:
   a specimen card at 4000 m, lit by the bioluminescence it is floating in. */

.beat--lore{
  pointer-events:auto;     /* the only selectable text on the page */
}

.lore{
  max-width:92vw;
  margin:0 auto;
  text-align:center;
  display:grid;
  gap:1.15em;
  justify-items:center;
}

/* a single bioluminescent point, the same language as the depth tick */
.lore-mark{
  width:7px;
  height:7px;
  border-radius:50%;
  background:var(--w-rim);
  box-shadow:0 0 16px 5px rgba(183,137,232,.55);
  opacity:0;
  margin-bottom:.6em;
}

.lore-p{
  margin:0 auto;
  max-width:48ch;      /* ch resolves against this element's own size */
  font-size:clamp(1.02rem,3.7vw,1.33rem);
  line-height:1.62;
  font-weight:300;
  letter-spacing:.004em;
  color:rgba(228,244,255,.9);
  text-shadow:0 2px 24px rgba(3,10,24,.85);
  text-wrap:pretty;
  opacity:0;
  transform:translate3d(0,20px,0);
  will-change:opacity,transform;
}

/* the binomial is a binomial */
.lore-p i{
  font-style:italic;
  color:var(--w-rim);
  letter-spacing:.012em;
}

/* his length belongs to the same numeric voice as the depth gauge */
.lore-p b{
  font-family:var(--mono);
  font-weight:400;
  font-size:.92em;
  color:#f0f8ff;
  letter-spacing:.02em;
}

/* ---- the end ------------------------------------------------------ */

.beat--end{
  grid-auto-flow:row;
  gap:clamp(16px,3.2svh,32px);
  align-content:center;
  justify-items:center;
  padding-bottom:calc(3svh + var(--safe-b));
}
/* the signature is a sign-off under the lore, not a second headline */
.beat--end .lore{margin-bottom:clamp(2px,1.2svh,14px)}
.sign{
  width:min(46vw,250px);
  height:auto;
  opacity:.5;
  filter:saturate(.75) brightness(.86) drop-shadow(0 0 40px rgba(110,80,200,.45));
}
.fine{
  margin:0;
  font-size:clamp(.66rem,2.7vw,.8rem);
  letter-spacing:.2em;
  text-transform:lowercase;
  color:rgba(200,228,248,.4);
  text-align:center;
}

/* ============================================================
   RESPONSIVE
   ============================================================ */

/* phones: he is closer to the camera and the gauge gets out of the way */
@media (max-width:620px){
  .art{--mh:50svh}
  .art--wordmark{--w:94vw;--mh:34svh}
  .art--portrait{--w:118vw}
  .art--thinker {--w:min(62vw,250px)}
  .art--titanic {--w:98vw}
  .art--cash    {--w:92vw}
  .art--chair   {--w:86vw}
  .art--loop    {--w:min(94vw,50svh)}
  .art{filter:drop-shadow(0 16px 30px rgba(5,14,34,.5))}
  .scene:not(#sc0){padding-bottom:26svh}
  #gauge{right:calc(8px + var(--safe-r));width:26px}
  .gauge-rail{height:min(34svh,250px)}
  .gauge-read{top:calc(50% + min(19svh,140px));font-size:9.5px;letter-spacing:.1em}
}

/* short landscape phones: nothing may be taller than the viewport */
@media (max-height:520px) and (orientation:landscape){
  .art{--mh:52svh}
  .art--wordmark{--w:min(64vw,600px);--mh:40svh}
  .art--portrait{--w:min(62vw,520px)}
  .art--thinker {--w:min(20vw,150px)}
  .art--titanic {--w:min(52vw,440px)}
  .art--cash    {--w:min(46vw,380px)}
  .art--chair   {--w:min(34vw,290px)}
  .art--loop    {--w:min(40vw,52svh)}
  .say{font-size:clamp(1.1rem,3.4vw,1.9rem)}
  .lore-p{font-size:clamp(.9rem,2.1vw,1.05rem);line-height:1.5}
  .lore{gap:.8em;max-width:52ch}
  .scene:not(#sc0){padding-bottom:15svh}
  .cue{bottom:3svh}
}

/* wide desktop: let the set-pieces breathe, push the camera back */
@media (min-width:1500px){
  #world{perspective:1500px}
  .art--wordmark{--w:min(62vw,1060px)}
  .art--portrait{--w:min(52vw,960px)}
}

/* ============================================================
   REDUCED MOTION — the whole choreography collapses to cross-fades.
   Nothing moves. The page still works and still looks composed.
   ============================================================ */

@media (prefers-reduced-motion:reduce){
  html:not([data-boot]) #stage,
  html:not([data-boot]) #track,
  html:not([data-boot]) #gauge{transition:none}
  .cue-dot{animation:none;opacity:.7}
  .snd-g i,
  .xb,
  .xb::before,
  #snd.hint{animation:none!important}
  .caus i{animation:none}
  html:not([data-boot]) .art--wordmark,
  html:not([data-boot]) .cue{animation:none}
  .scene{filter:none!important}
  .lore-p,.lore-mark{opacity:1!important;transform:none!important}
  .art{transform:none!important}
  .loop-frame video{transform:none}
  *{scroll-behavior:auto!important}
}
