/* S.D.O.O. — v2 image-crossfade portal. New build (v1 = index.html/app.js, untouched). */

/* ---- AutreDisplay ---- */
@font-face{
  font-family:'AutreDisplay';
  src:url('fonts/AutreDisplay-Regular.woff2') format('woff2');
  font-weight:400; font-style:normal; font-display:swap;
}
@font-face{
  font-family:'AutreDisplay';
  src:url('fonts/AutreDisplay-Italic.woff2') format('woff2');
  font-weight:400; font-style:italic; font-display:swap;
}

/* ---- Neue Haas Grotesk Display (Act 2 body) ---- */
@font-face{
  font-family:'NeueHaasGroteskDisplay';
  src:url('fonts/NeueHaasGroteskDisplay-55Roman-Trial.otf') format('opentype');
  font-weight:400; font-style:normal; font-display:swap;
}
@font-face{
  font-family:'NeueHaasGroteskDisplay';
  src:url('fonts/NeueHaasGroteskDisplay-56Italic-Trial.otf') format('opentype');
  font-weight:400; font-style:italic; font-display:swap;
}

*{ margin:0; padding:0; box-sizing:border-box; }
html,body{ background:#040406; }
body{ color:#eef3ec; font-family:'AutreDisplay', Georgia, serif; }

/* scroll-lock (sigil gate + once Act 1 ends) */
html.locked, html.locked body{ overflow:hidden; height:100%; touch-action:none; overscroll-behavior:none; }

/* fixed cinematic canvas behind everything */
#gl{
  position:fixed; top:0; left:0; width:100vw;
  height:100vh; height:100svh;   /* svh = the bar-SHOWN visible area. Act 1 is scroll-locked so the iOS bar never hides; lvh (large/bar-hidden viewport) was taller than what's visible, framing the sigil below centre */
  display:block; z-index:0;
}

/* ---- portal text overlay (DOM; every block stays INSIDE the centred black disc) ----
   CRITICAL (mobile): this layer is an EXPLICIT box that matches #gl EXACTLY (fixed, top:0,
   100svh) and the text blocks are ABSOLUTE within it, so their `top:50%` resolves against
   the SAME box the WebGL disc is centred in. Previously the blocks were `position:fixed;
   top:50%`, which mobile browsers resolve against the VISIBLE (visual) viewport, while the
   canvas/disc is centred in the LARGE viewport — so when the address bar showed, the text
   drifted off the disc centre, and in opposite directions in Chrome (top bar) vs Safari
   (bottom bar). Keep #ptext's box identical to #gl or this breaks again. */
#ptext{
  position:fixed; top:0; left:0; width:100vw;
  height:100vh; height:100svh;               /* MUST stay identical to #gl */
  z-index:2; pointer-events:none;
}
.pt{
  position:absolute; top:50%; left:50%;      /* centred in #ptext == the canvas box == the disc */
  transform:translate(-50%,-50%);
  width:max-content; max-width:94vmin;       /* shrink to content so left/right align gives the ragged look, block stays centred */
  opacity:0; will-change:opacity, transform, filter;
  line-height:0.95; letter-spacing:-0.01em;  /* tight, matching the references */
}
.pt .inner{ display:block; }
.pt i{ font-style:italic; }

/* internal alignment only — the block itself stays centred; sizes matched to the references */
#pt1 .inner{ text-align:center; font-size:2.95vmin; }

#pt2 .inner{ text-align:left; font-size:3.15vmin; }
#pt2 .inner span{ display:block; }
#pt2 .t2b{ padding-left:2.5em; }   /* IRRATIONAL — indented ~2 tabs */

#pt3 .inner{ text-align:right; font-size:2.6vmin; }
#pt3 .inner span{ display:block; }
/* desktop: nudge text 3 down a touch — LIFE's line-box leaves empty descender space below
   the glyphs, so the visible ink centres high; this re-centres the INK in the disc. Mobile
   (which is sized differently) keeps the plain top:50% from .pt. */
@media (min-width:768px){ #pt3{ top:52.5%; } }
#pt3 .t3c{ text-align:center; }    /* "contain the most" is centred within the right-aligned block */
/* shrink-wrap to the word so the glitch clip-blocks map onto LIFE itself (not the full-width
   block); margin-left:auto keeps it right-aligned on its own line */
#pt3 .big{ display:block; width:max-content; margin-left:auto; font-size:5.4vmin; line-height:1.0; margin-top:.06em; position:relative; }
/* LIFE: datamosh-style glitch ported from the sigil's intermittent digital burst — RANDOM,
   BRIEF, BLOCKY, NON-UNIFORM. lifeGlitch() in portal.js overlays a pool of shard copies of the
   word; during a brief burst each active shard is clipped to a randomly placed rectangle of
   random size (mix of wide thin row-tears + compact blocks), shifted sideways with an RGB
   split, re-rolled in chunky steps. At rest all shards are hidden and the base word reads clean. */
#pt3 .big .lifeShard{
  position:absolute; left:0; top:0; width:100%;
  text-align:inherit; white-space:nowrap; pointer-events:none;
  color:#eef3ec; opacity:0; will-change:transform, opacity, clip-path;
}
#pt4 .inner{ text-align:left; font-size:8.6vmin; line-height:0.86; }
#pt4 .inner span{ display:block; }
/* per-line indents mirror the reference: FORBIDDEN hangs leftmost, TERRITORIES/SOUL right */
#pt4 .i-a{ padding-left:1.2em; }   /* (Welcome) TO THE */
#pt4 .i-b{ padding-left:0; }       /* FORBIDDEN — leftmost */
#pt4 .i-c{ padding-left:3.5em; }   /* TERRITORIES — rightmost */
#pt4 .i-d{ padding-left:3.4em; }   /* OF THE SOUL */

/* mobile: nudge text events 1-3 a touch bigger (disc unchanged; keep padding to the ring) */
@media (max-width:767px){
  #pt1 .inner{ font-size:3.55vmin; }
  #pt2 .inner{ font-size:3.75vmin; }
  /* text 3 grows ~1.53x on arrival (TXT_S3 in portal.js); on narrow portrait that
     compounding read too large, so the base is held smaller here. */
  #pt3 .inner{ font-size:2.95vmin; }
  #pt3 .big{ font-size:5.5vmin; }
  #pt3{ top:51.5%; }   /* small downward nudge to centre the ink (see desktop note above) */
}

/* scroll cue: gently bouncing down-arrow, arrives last, fades once the gate triggers.
   absolute within #ptext (the canvas box) so it sits a fixed distance below the sigil on
   mobile too — same large-viewport reference as the disc/text (see #ptext note). */
#hint{
  position:absolute; left:50%; top:64%;
  transform:translate(-50%,-50%);
  z-index:3; color:#eef3ec;
  opacity:0; transition:opacity .8s ease; pointer-events:none;
}
#hint.show{ opacity:.75; }
#hint svg{ display:block; width:13px; height:auto; animation:bob 1.6s ease-in-out infinite; }
@media (min-width:768px){ #hint{ top:84%; } #hint svg{ width:12px; } }   /* desktop: sit the arrow below the sigil (was overlapping it), a touch larger */
@media (max-width:767px){ #hint{ top:69%; } }   /* mobile: sit the arrow a touch lower, clear of the (larger) sigil */
@keyframes bob{ 0%,100%{ transform:translateY(0); } 50%{ transform:translateY(7px); } }

/* tall spacer that gives Act 1 its scrub length (1:1 with scroll) */
#portal{ position:relative; z-index:1; height:600vh; pointer-events:none; }

/* ---- Acts 2–4: in-place crossfading overlays (advance on submit, NOT by page scroll) ---- */
.act{
  position:fixed; inset:0; z-index:4;
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  gap:1.4rem; padding:8vh 8vw; text-align:center; background:#040406;
  opacity:0; pointer-events:none; transition:opacity .9s ease; overflow-y:auto;
  /* per-line reveal timing (Acts 2–4) — tune these four */
  --rise-dur:1s; --rise-stag:.18s; --rise-dist:32px; --rise-ease:cubic-bezier(.16,1,.3,1);
}
.act.active{ opacity:1; pointer-events:auto; }

/* ---- Acts 2–4: each line slides up + fades in, staggered. Re-runs whenever an act
   gains .active (showAct). Lines cascade within each content column. ---- */
@keyframes riseIn{
  from{ opacity:0; transform:translateY(var(--rise-dist)); }
  to  { opacity:var(--rise-o,1); transform:translateY(0); }
}
/* --rise-delay0 = when this column STARTS revealing (0 = as soon as the act opens).
   Set per-column to sequence columns; lines then cascade by --rise-stag from there. */
.act.active :is(.a2copy,.a2form,.act-body) > *:not(.scrollcue){
  animation:riseIn var(--rise-dur) var(--rise-ease) both; will-change:transform, opacity;
  animation-delay:var(--rise-delay0, 0s);
}
.act.active :is(.a2copy,.a2form,.act-body) > *:nth-child(2){ animation-delay:calc(var(--rise-delay0, 0s) + 1 * var(--rise-stag)); }
.act.active :is(.a2copy,.a2form,.act-body) > *:nth-child(3){ animation-delay:calc(var(--rise-delay0, 0s) + 2 * var(--rise-stag)); }
.act.active :is(.a2copy,.a2form,.act-body) > *:nth-child(4){ animation-delay:calc(var(--rise-delay0, 0s) + 3 * var(--rise-stag)); }
.act.active :is(.a2copy,.a2form,.act-body) > *:nth-child(5){ animation-delay:calc(var(--rise-delay0, 0s) + 4 * var(--rise-stag)); }
.act.active :is(.a2copy,.a2form,.act-body) > *:nth-child(6){ animation-delay:calc(var(--rise-delay0, 0s) + 5 * var(--rise-stag)); }
.act h2{ font-size:clamp(1.5rem,3.6vmin,2.4rem); font-weight:400; letter-spacing:.01em; line-height:1.2; max-width:100%; }
.act .sub{ opacity:.85; --rise-o:.85; font-style:italic; font-size:clamp(1.05rem,2.4vmin,1.4rem); margin-top:.2rem; max-width:100%; }
.act p{ max-width:min(34rem,100%); opacity:.82; --rise-o:.82; font-size:clamp(.95rem,2.1vmin,1.15rem); line-height:1.6; }
.act .prompt{ opacity:.95; --rise-o:.95; font-size:clamp(1.05rem,2.4vmin,1.35rem); max-width:min(30rem,100%); }
.act form{ display:flex; flex-direction:column; gap:1.1rem; width:min(34rem,90vw); }
.act textarea, .act input[type=email]{
  background:#0b0b0f; border:1px solid #2a2a33; color:#eef3ec;
  font:inherit; padding:.9rem 1rem; border-radius:6px; width:100%;
}
.act textarea{ min-height:8rem; resize:vertical; }
.act ::placeholder{ color:#8a8a93; opacity:1; }
.act button{
  background:#eef3ec; color:#0b0b0f; border:0; font:inherit;
  padding:.8rem 1.8rem; border-radius:6px; cursor:pointer; align-self:center;
}
/* honeypot — hidden from humans + AT, present for bots */
.hp{ position:absolute; left:-9999px; width:1px; height:1px; overflow:hidden; }

/* sigil mark above Acts 3 & 4 — screen blend drops the JPEG's black so it sits
   seamlessly on the near-black page. */
.act-sigil{ display:block; width:min(30rem,86vw); height:auto; mix-blend-mode:screen; pointer-events:none; }
/* Acts 3 & 4: the text/form sits in a body block of fixed min-height, so the
   whole composition stays vertically centred (not top-heavy) AND the sigil keeps
   the same position through the Act 3 -> Act 4 crossfade (identical group height). */
#act3, #act4{ justify-content:center; }
.act-body{ display:flex; flex-direction:column; align-items:center; justify-content:center; gap:1.4rem; min-height:17rem; max-width:100%; }
/* Act 4 has no form, so nudge its text a touch up toward the sigil. Transform is
   visual-only — body layout height is unchanged, so the sigil stays locked. */
#act4 .act-body{ transform:translateY(-1rem); }

/* Act 2 body copy — Neue Haas Grotesk Display (heading/sub stay AutreDisplay) */
#act2 .a2copy p:not(.sub){ font-family:'NeueHaasGroteskDisplay', system-ui, sans-serif; }

/* anonymity note beneath the Act 2 submit button — small italic disclaimer */
.act .note{
  font-family:'NeueHaasGroteskDisplay', system-ui, sans-serif;
  font-style:italic; opacity:.5; font-size:clamp(.62rem,1.25vmin,.74rem);
  line-height:1.45; max-width:min(26rem,90vw); margin-top:-.2rem;
  text-wrap:balance; align-self:center; text-align:center;
}
/* inline submit error (set by JS on a rejected/failed POST) */
.act .formstatus:empty{ display:none; }
.act .formstatus{ margin-top:.4rem; font-size:clamp(.8rem,1.8vmin,.95rem); color:#d98a72; text-align:center; }

/* Act 2 — DESKTOP: copy left, form right (side by side) */
@media (min-width:768px){
  #act2{ flex-direction:row; align-items:center; justify-content:center; gap:6vw; text-align:center; }
  #act2 .a2copy{ max-width:32rem; display:flex; flex-direction:column; gap:1rem; }
  #act2 .a2form{ width:min(26rem,42vw); display:flex; flex-direction:column; gap:1.1rem;
                 --rise-delay0:3s; }   /* desktop: form/right column reveals ~3s after the left copy */
  #act2 .a2form form{ width:100%; }   /* fill the (narrower) column instead of the wider .act-form default */
  #act2 .a2copy p{ margin:0; }
}
/* mobile-only scroll cue at the bottom of the Act 2 copy page (tap = smooth glide to the form) */
.scrollcue{ display:none; }
@keyframes cueIn{ from{ opacity:0; } to{ opacity:.6; } }
/* Act 2 — MOBILE: copy first, then scroll down to the form (two snap pages) */
@media (max-width:767px){
  #act2{ display:block; padding:0; overflow:hidden auto; -webkit-overflow-scrolling:touch; }  /* JS drives the copy<->form glide; native scroll stays as the keyboard safety valve (see a2hijack) */
  #act2 .a2copy, #act2 .a2form{
    width:84vw; margin:0 auto;                 /* explicit width so text wraps to the viewport */
    min-height:100svh; display:flex; flex-direction:column;
    align-items:stretch; justify-content:center; gap:1.1rem; text-align:center;
    padding:9vh 0;
  }
  #act2 .a2form form{ width:100%; }
  #act2 .a2copy{ position:relative; }
  #act2 .scrollcue{   /* #act2 scope beats the .act button (filled submit) rule */
    display:block; position:absolute; left:50%; bottom:calc(3vh + env(safe-area-inset-bottom, 0px));
    transform:translateX(-50%); appearance:none; -webkit-appearance:none; background:none; border:0; padding:.6rem;
    color:#eef3ec; opacity:.6; cursor:pointer; -webkit-tap-highlight-color:transparent;
  }
  #act2 .scrollcue svg{ display:block; width:15px; height:auto; animation:bob 1.6s ease-in-out infinite; }
  #act2.active .scrollcue{ animation:cueIn .6s ease 1.9s both; }   /* fade in only AFTER the copy reveal has finished (~1.7s) */
}

@media (prefers-reduced-motion: reduce){
  #hint svg{ animation:none; } #hint{ transition:none; } .act{ transition:none; }
  #act2.active .scrollcue, #act2 .scrollcue svg{ animation:none; }
  .act.active :is(.a2copy,.a2form,.act-body) > *{ animation:none; }
  #pt3 .big .lifeShard{ display:none; }   /* lifeGlitch() also bails on reduce; this is belt-and-braces */
}
