/* ============================================================
   TENET — to the brief
   Neutrals in restraint · a single controlled light (amber) as the
   only emotional colour · a monumental axis holds the sacred centre ·
   all type, nav and interaction retreat to the edges as annotation.
   Type: monumental grotesk mass vs. microscopic mono annotation.
   ============================================================ */

:root{
  /* NIGHT (default): warm near-black concrete world.
     the single controlled light reads as tungsten amber. */
  --bg:        #0b0a08;
  --surface:   #161310;
  --text:      #efe9dd;
  --concrete:  #b3a48d;
  --muted:     rgba(239,233,221,0.52);
  --faint:     rgba(239,233,221,0.30);
  --rule:      rgba(239,233,221,0.12);
  --tick:      rgba(239,233,221,0.5);
  --beam:      rgba(239,233,221,0.5);

  /* the single controlled light source — the ONLY emotional colour */
  --light:     #ea7b2c;
  --light-lit: #f8ab54;
  --light-soft:rgba(234,123,44,0.5);
  --axis-glow: 0.30;   /* the light's intensity — rises in the light-event section */

  --font-display:'Space Grotesk','Neue Montreal','Helvetica Neue',Arial,sans-serif;
  --font-mono:   'JetBrains Mono','SF Mono',ui-monospace,Menlo,Consolas,monospace;

  --fs-hero:   clamp(3rem, 12vw, 12rem);
  --fs-display:clamp(2.3rem, 6.5vw, 6rem);
  --fs-mid:    clamp(1.6rem, 3vw, 2.6rem);
  --fs-body:   11px;
  --fs-lede:   clamp(1rem, 1.2vw, 1.15rem);

  --track-tight:-0.02em;
  --track-anno: 0.24em;

  --layout-center:60vw;
  --layout-edge:  4vw;
  --maxw:1460px;

  --ease:cubic-bezier(0.16,1,0.3,1);
  --ease-inertia:cubic-bezier(0.22,0.61,0.36,1);
  --t-tone:900ms;
}

/* DAY: raking sun on pale concrete. same restraint, inverted ground;
   the single light becomes a deeper ochre so it still reads as the sun. */
:root[data-theme="light"]{
  --bg:        #e7e3da;
  --surface:   #f1ede5;
  --text:      #1b1a17;
  --concrete:  #8b877d;
  --muted:     rgba(27,26,23,0.56);
  --faint:     rgba(27,26,23,0.34);
  --rule:      rgba(27,26,23,0.13);
  --tick:      rgba(27,26,23,0.32);
  --beam:      rgba(27,26,23,0.42);
  --light:     #895210;
  --light-lit: #a86315;
  --light-soft:rgba(137,82,16,0.42);
  --axis-glow: 0.5;
}

/* smooth crossfade when the clock flips the theme */
body,.stage,.display,.eyebrow,.lede,.anno,.side,.nav__brand,.nav__brand span,.nav__links a,
.work__name,.work__meta i,.work__title,.studies__title,.manifesto__title,.contact__title,
.manifesto__line,.contact__mail,.foot{
  transition:background-color var(--t-tone) var(--ease-inertia),
             color var(--t-tone) var(--ease-inertia),
             border-color var(--t-tone) var(--ease-inertia);
}
.stage + .stage,.work__row,.work__list,.manifesto__row{transition:border-color var(--t-tone) var(--ease-inertia)}

*{margin:0;padding:0;box-sizing:border-box}
html{-webkit-text-size-adjust:100%;scroll-behavior:smooth}
.js.has-portal, .js.has-portal body{overflow:hidden;height:100%}
body{
  background:var(--bg); color:var(--text);
  font-family:var(--font-mono); font-size:var(--fs-body); letter-spacing:0.02em;
  line-height:1.7; min-height:100vh; overflow-x:hidden; -webkit-font-smoothing:antialiased;
  transition:background var(--t-tone) var(--ease-inertia);
}
img,video{max-width:100%;display:block}
a{color:inherit;text-decoration:none}
button{-webkit-appearance:none;appearance:none;background:none;border:0;color:inherit;font:inherit;cursor:pointer}
::selection{background:var(--light);color:var(--bg)}
:focus-visible{outline:1px solid var(--light);outline-offset:4px}
.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}
.skip{position:absolute;left:-9999px;top:0;background:var(--text);color:var(--bg);padding:8px 12px;z-index:300}
.skip:focus{left:12px;top:12px}

/* reveal + word masks (JS-gated) */
.word{display:inline-block;overflow:hidden;vertical-align:top}
.word__i{display:inline-block;will-change:transform}
.js .word__i{transform:translateY(104%)}
.js [data-reveal]{opacity:0;transform:translateY(24px);will-change:transform,opacity}
.js [data-reveal].is-in{opacity:1;transform:none;transition:opacity .8s var(--ease),transform .8s var(--ease)}

/* ============================================================
   PORTAL — the threshold you open yourself.
   A drawn elevation, split on the axis into two leaves. Press and
   hold: it strains, then yields, and the site is revealed THROUGH
   the widening aperture rather than cut to after it.
   All dynamic values (part / scale / glow) are written from app.js.
   ============================================================ */
.portal{position:relative}
.js .portal{position:fixed;inset:0;z-index:100;overflow:hidden;background:var(--bg);
  cursor:pointer;touch-action:none;-webkit-user-select:none;user-select:none;
  transition:opacity .8s var(--ease),visibility 0s linear .8s}
.is-entered .portal{opacity:0;pointer-events:none;visibility:hidden}

.portal__defs{position:absolute;width:0;height:0;overflow:hidden}

/* the light behind the leaves — revealed as they part. JS writes width/opacity */
.portal__spill{position:absolute;left:50%;top:0;bottom:0;width:0;transform:translateX(-50%);
  z-index:2;pointer-events:none;opacity:0;
  background:radial-gradient(closest-side at 50% 50%,var(--light-lit),var(--light-soft) 45%,transparent 78%)}

/* the field carries the dolly-through scale and the strain tremor */
.portal__field{position:absolute;inset:0;z-index:1;will-change:transform}

/* two leaves, each clipping one half of a single full-width plate — the
   linework AND the wordmark are one object torn down the centre */
.portal__leaf{position:absolute;top:0;bottom:0;width:50vw;overflow:hidden;
  background:var(--bg);will-change:transform}
.portal__leaf--l{left:0;width:calc(50vw + 1px)}   /* 1px overlap kills the subpixel seam */
.portal__leaf--r{right:0}
.portal__plate{position:absolute;top:0;height:100%;width:100vw}
.portal__leaf--l .portal__plate{left:0}
.portal__leaf--r .portal__plate{left:-50vw}
.portal__draw{position:absolute;inset:0;width:100%;height:100%;fill:none;
  stroke-linecap:round;stroke-linejoin:round}

/* the elevation's line hierarchy — same grammar as the Vitruvian figure */
.pe{stroke-dasharray:2400;stroke-dashoffset:2400;animation:pe-draw 1.9s var(--ease) forwards;animation-delay:var(--d,0s)}
.pe--faint{stroke:var(--rule);stroke-width:1}
.pe--line{stroke:var(--concrete);stroke-width:1.4;opacity:.62}
.pe--soft{stroke:var(--beam);stroke-width:1;opacity:.22}
.pe--axis{stroke:var(--light);stroke-width:1.2;opacity:.55}
.pe--aperture{stroke:var(--light-lit);stroke-width:1.6;opacity:.8;
  filter:drop-shadow(0 0 6px var(--light-soft))}
.pe__node{fill:var(--light-lit);opacity:0;animation:pe-node 1s var(--ease) 1.5s forwards}
.pe__node--sm{fill:var(--concrete)}
@keyframes pe-draw{to{stroke-dashoffset:0}}
@keyframes pe-node{to{opacity:.9}}

/* the lockup rides on the plate, centred on the VIEWPORT, so the leaf
   clip splits TENET exactly on its own axis */
.portal__lockup{position:absolute;left:0;top:50%;width:100vw;transform:translateY(-50%);
  display:grid;justify-items:center;gap:clamp(16px,3.6vh,36px);text-align:center;pointer-events:none}
.portal__kicker{font-family:var(--font-mono);font-weight:400;line-height:1;text-transform:uppercase;
  font-size:clamp(0.72rem,2vw,1.5rem);letter-spacing:.5em;text-indent:.5em;color:var(--muted)}
.portal__dot{color:var(--light-lit)}
.portal__title{font-family:var(--font-display);font-weight:600;font-size:var(--fs-hero);line-height:.9;
  letter-spacing:.34em;text-indent:.34em;text-transform:uppercase;color:var(--text);
  text-shadow:0 6px 70px rgba(234,123,44,.28)}

/* the axis itself, holding the seam */
.portal__seamline{position:absolute;left:50%;top:0;bottom:0;width:2px;transform:translateX(-50%);
  z-index:3;pointer-events:none;opacity:.2;
  background:linear-gradient(180deg,transparent,var(--light) 16%,var(--light-lit) 50%,var(--light) 84%,transparent)}

/* Sit the target ON the node where the axis meets the ground line.
   The elevation is drawn with preserveAspectRatio="xMidYMid slice" on a
   1000x700 viewBox, so scale = max(vw/1000, vh/700) and the node at (500,600)
   lands at 50% + 250*scale. min(...,88%) keeps it on screen when a very wide
   viewport slices the ground line out of frame entirely. */
.portal__inner{position:absolute;left:50%;
  top:min(calc(50% + max(25vw,35.7143vh)),88%);
  transform:translate(-50%,-50%);z-index:4;
  display:grid;justify-items:center;text-align:center;padding:var(--layout-edge);
  transition:opacity .5s var(--ease)}
.portal.is-opening .portal__inner,.portal.is-opening .portal__hint{opacity:0;pointer-events:none}

/* the hold target — a ring that fills under sustained pressure.
   These colours WERE hardcoded light-on-dark, on the assumption that this UI
   only ever sits on the black frame. It does not: .portal takes var(--bg), so
   in day theme the ring and its hint were #efe9dd on #e7e3da — about 1.05:1,
   i.e. invisible, and the site had no visible way in between 07:00 and 19:00.
   Everything here is a token now, so it follows whichever theme is showing. */
.hold{display:inline-flex;align-items:center;gap:16px;background:none;border:0;cursor:pointer;
  font-family:var(--font-mono);font-size:var(--fs-body);text-transform:uppercase;
  letter-spacing:var(--track-anno);color:var(--text);padding:8px}
.hold__ring{position:relative;display:grid;place-items:center;width:52px;height:52px;flex:0 0 52px}
.hold__ring svg{position:absolute;inset:0;width:52px;height:52px;transform:rotate(-90deg);fill:none}
.hold__track{stroke:var(--rule);stroke-width:1.5}
.hold__prog{stroke:var(--light-lit);stroke-width:1.5;stroke-linecap:round;
  stroke-dasharray:138.23;stroke-dashoffset:138.23;filter:drop-shadow(0 0 5px var(--light-soft))}
.hold__dot{width:6px;height:6px;border-radius:50%;background:var(--light-lit);
  box-shadow:0 0 12px 2px var(--light-soft);transition:transform .4s var(--ease)}
.hold:hover .hold__dot,.hold:focus-visible .hold__dot{transform:scale(1.5)}
.hold:focus-visible{outline:1px solid var(--light);outline-offset:6px;border-radius:999px}
/* the label is gone — the hint below the node carries the instruction */

.portal__hint{position:absolute;bottom:clamp(24px,5vh,54px);left:50%;transform:translateX(-50%);z-index:4;
  width:max-content;max-width:min(88vw,34ch);text-align:center;text-wrap:balance;
  font-family:var(--font-mono);font-size:9px;letter-spacing:var(--track-anno);
  /* --muted, not a fixed .34 near-white: this is the only instruction telling
     a visitor how to get in, so it has to survive both themes. */
  color:var(--muted);text-transform:uppercase;transition:opacity .5s var(--ease)}

.flash{position:fixed;inset:0;z-index:110;pointer-events:none;opacity:0;
  background:radial-gradient(60% 120% at 50% 50%,#fff6e6,#f8ab54 40%,transparent 75%)}
.flash.is-burst{animation:burst 1.1s var(--ease) forwards}
@keyframes burst{0%{opacity:0}22%{opacity:.55}100%{opacity:0}}

@media (prefers-reduced-motion:reduce){
  .pe{stroke-dashoffset:0;animation:none}
  .pe__node{opacity:.9;animation:none}
}

/* the whole site waits under the portal */
.site{opacity:1;transition:opacity 1s var(--ease) .15s}
.js.has-portal .site{opacity:0}

/* ============================================================
   THE AXIS — sacred centre, continuous down the page
   ============================================================ */
.axis{position:fixed;top:0;bottom:0;left:50%;width:1px;transform:translateX(-50%);z-index:1;pointer-events:none;
  background:linear-gradient(180deg,transparent,var(--light-soft) 6%,var(--light) 50%,var(--light-soft) 94%,transparent);
  box-shadow:0 0 22px 1px rgba(234,123,44,var(--axis-glow));
  opacity:0;transition:box-shadow var(--t-tone) var(--ease-inertia),opacity 1.2s var(--ease)}
.is-entered .axis{opacity:1}
.axis::after{content:"";position:absolute;inset:0 -1px;
  background:repeating-linear-gradient(180deg,transparent 0 60px,var(--tick) 60px 61px);opacity:.5}

/* ============================================================
   NAV + EDGE ANNOTATION
   ============================================================ */
.nav{position:fixed;inset:0 0 auto 0;z-index:60;display:grid;grid-template-columns:1fr auto;align-items:start;
  padding:clamp(16px,2.2vw,28px) var(--layout-edge);mix-blend-mode:difference;color:#fff;
  transform:translateY(-120%);transition:transform .7s var(--ease)}
.is-entered .nav{transform:translateY(0)}
.nav__brand{font-family:var(--font-mono);font-size:var(--fs-body);text-transform:uppercase;letter-spacing:var(--track-anno);line-height:1.4}
.nav__brand b{display:block;font-weight:500}
.nav__brand span{display:block;color:rgba(255,255,255,.6)}
.nav__links{display:flex;gap:clamp(16px,2vw,34px)}
.nav__links a{font-family:var(--font-mono);font-size:var(--fs-body);text-transform:uppercase;letter-spacing:var(--track-anno);position:relative;padding-bottom:3px}
.nav__links a::after{content:"";position:absolute;left:0;right:0;bottom:0;height:1px;background:currentColor;transform:scaleX(0);transform-origin:right;transition:transform .45s var(--ease)}
.nav__links a:hover::after{transform:scaleX(1);transform-origin:left}
.nav__links{transition:opacity .45s var(--ease),transform .45s var(--ease)}
/* on the landing (hero in view) the vertical list already lists the sections,
   so the top links are hidden to avoid duplication; they return on scroll */
.at-hero .nav__links{opacity:0;transform:translateY(-6px);pointer-events:none}
.nav__right{display:flex;align-items:center;gap:clamp(16px,2.4vw,34px)}
.theme-tgl{display:inline-flex;align-items:center;gap:8px;font-family:var(--font-mono);font-size:var(--fs-body);
  text-transform:uppercase;letter-spacing:var(--track-anno);color:inherit;cursor:pointer}
.theme-tgl__ic{width:9px;height:9px;border-radius:50%;border:1px solid currentColor;transition:background .4s var(--ease)}
.theme-tgl[data-mode="dark"] .theme-tgl__ic{background:currentColor}
.theme-tgl[data-mode="light"] .theme-tgl__ic{background:transparent;box-shadow:0 0 0 2px transparent}
@media(max-width:720px){.nav__links{display:none}}

.anno{font-family:var(--font-mono);font-size:10px;text-transform:uppercase;letter-spacing:var(--track-anno);color:var(--muted)}
.anno--tl,.anno--tr,.anno--bl,.anno--br{position:absolute;z-index:4}
.anno--tl{top:var(--layout-edge);left:var(--layout-edge)}
.anno--tr{top:var(--layout-edge);right:var(--layout-edge);text-align:right}
.anno--bl{bottom:var(--layout-edge);left:var(--layout-edge)}
.anno--br{bottom:var(--layout-edge);right:var(--layout-edge);text-align:right}
.side{position:absolute;top:50%;writing-mode:vertical-rl;text-transform:uppercase;letter-spacing:.5em;font-size:10px;color:var(--faint);z-index:4}
.side--l{left:calc(var(--layout-edge) - 4px);transform:translateY(-50%) rotate(180deg)}
.side--r{right:calc(var(--layout-edge) - 4px);transform:translateY(-50%)}
/* MOBILE EDGES — the annotations are the studio's voice, so they stay. They are
   scaled and pulled in rather than switched off. Only the two top corners go:
   they sit under the brand lockup and the theme toggle and would collide. */
@media(max-width:640px){
  .anno--tl,.anno--tr{display:none}
  /* bottom:16px, not the desktop inset — at 8px the desktop value pushed their
     baseline to 821 on an 812 viewport and clipped them below the fold */
  /* `.hero .anno--bl` (0,2,0) not `.anno--bl` (0,1,0): the base rules are
     declared LATER in this file and a media query adds no specificity, so a
     matching-specificity override here is silently discarded. top:auto is also
     required — the base anchors these with `top`, making a bare `bottom` inert. */
  .hero .anno--bl,.hero .anno--br{font-size:8px;letter-spacing:.16em;max-width:42vw;
    top:auto;bottom:16px}
  .hero .anno--bl{left:var(--layout-edge)}
  .hero .anno--br{right:var(--layout-edge);text-align:right}
  .side{font-size:8px;letter-spacing:.34em}
  .side--l{left:2px}
  .side--r{right:2px}
  /* 16vh top AND bottom pushed the hero past 100svh, so its bottom corners fell
     below the fold. Trimmed so the section fits the first screen exactly. */
  .stage{padding-block:clamp(76px,10vh,120px)}

  /* Drop the lockup and the index down the screen on phones. Both base rules are
     declared LATER in this file, so these selectors carry extra specificity
     (0,2,0 and 0,3,0) to win outright instead of depending on source order. */
  .hero .hero__masthead{top:clamp(240px,33vh,340px)}
  .stage.hero .center{transform:translateY(clamp(96px,17vh,190px))}
}

/* ============================================================
   SECTION SHELL — centre sacred, edges annotate
   ============================================================ */
.stage{position:relative;z-index:2;min-height:100svh;display:grid;place-items:center;padding:clamp(110px,16vh,190px) var(--layout-edge)}
/* no rule between sections — every stage shares var(--bg), so with the hairline
   gone the joins are genuinely invisible rather than merely subtle */
.stage + .stage{border-top:0}
.center{width:min(var(--layout-center),var(--maxw));margin-inline:auto;display:grid;gap:clamp(20px,3vh,34px);justify-items:center;text-align:center}
@media(max-width:900px){:root{--layout-center:92vw}}
.eyebrow{font-family:var(--font-mono);font-size:var(--fs-body);text-transform:uppercase;letter-spacing:.3em;color:var(--muted)}
.eyebrow--light{color:var(--light)}
.lede{font-family:var(--font-mono);font-size:var(--fs-lede);line-height:1.6;color:var(--muted);max-width:44ch}
.display{font-family:var(--font-display);font-weight:500;font-size:var(--fs-display);line-height:.96;letter-spacing:var(--track-tight);text-wrap:balance}

/* ---------- HERO — the intersection, inside the dome ---------- */
.hero{overflow:hidden}
.hero__bg{position:absolute;inset:0;z-index:0;pointer-events:none}
.hero__bg img{width:100%;height:100%;object-fit:cover;object-position:center 42%}
.hero__bg::after{content:"";position:absolute;inset:0;
  background:
    /* TOPMOST — resolves the photograph into the next section's colour, whatever
       the theme. It has to sit above the scrims: previously the hardcoded dark
       radial painted over the layer carrying var(--bg), so the bottom never
       actually reached the section colour and left a hard seam — obvious in day,
       where grey haze met cream. */
    linear-gradient(180deg, transparent 56%, var(--bg) 100%),
    radial-gradient(125% 95% at 50% 32%, transparent 34%, rgba(11,10,8,.5) 84%),
    linear-gradient(180deg, rgba(11,10,8,.3) 0%, transparent 24%, rgba(11,10,8,.4) 72%, transparent 100%)}
/* the fade already carries the join; a rule on top of it reads as a hard line */
.hero + .stage{border-top:0}
.hero .center{position:relative;z-index:2;transform:translateY(clamp(70px,13vh,150px))}
.hero__object{z-index:1}
/* legible over the dark dome, whatever the theme */
.hero .eyebrow{color:rgba(239,233,221,.7)}
.hero__display{color:#f3ede1}
.hero__sub{color:rgba(239,233,221,.82)}
.hero .anno{color:rgba(239,233,221,.55)}
.hero .side{color:rgba(239,233,221,.34)}
.hero__beam{background:linear-gradient(90deg,transparent,rgba(239,233,221,.14) 16%,rgba(239,233,221,.5) 50%,rgba(239,233,221,.14) 84%,transparent)}
/* masthead — the video's opening title, at the top of the hero */
.hero__masthead{position:absolute;top:clamp(180px,27vh,300px);left:50%;transform:translateX(-50%);z-index:3;
  width:100%;padding-inline:var(--layout-edge);display:grid;justify-items:center;text-align:center;gap:clamp(10px,1.6vh,20px)}
.hero__kicker{font-family:var(--font-mono);text-transform:uppercase;line-height:1;
  font-size:clamp(.72rem,1.7vw,1.1rem);letter-spacing:.5em;text-indent:.5em;color:#efe9dd}
.hero__kicker .hero__dot{color:var(--light-lit)}
.hero__wm{position:relative;display:inline-block;line-height:0}
.hero__wordmark{margin:0;font-family:var(--font-display);font-weight:600;text-transform:uppercase;color:#fff;
  font-size:clamp(2.4rem,7vw,6rem);letter-spacing:.3em;text-indent:.3em;line-height:.9;
  /* dark only — the amber lives behind the wordmark in .hero__wm::before */
  text-shadow:0 6px 34px rgba(0,0,0,.6)}
.hero__seam{position:absolute;top:-16%;bottom:-16%;left:50%;width:2px;transform:translateX(-50%);
  background:linear-gradient(180deg,transparent,var(--light) 20%,var(--light-lit) 50%,var(--light) 80%,transparent);
  box-shadow:0 0 18px 2px rgba(248,171,84,.5);z-index:2}

/* the section tabs — a vertical list on the axis, centre of the page */
.hero__menu{display:grid;justify-items:center;gap:clamp(8px,1.7vh,20px);margin-top:clamp(14px,2.6vh,32px)}
.hero__menu a{display:inline-flex;align-items:baseline;gap:16px;color:#fff;text-decoration:none;
  opacity:0;transform:translateY(14px);animation:menuIn .7s var(--ease) forwards}
/* The index number hangs OUTSIDE the link's box. As a flex sibling it was part
   of the centred pair, so every word sat right of the axis by half the number's
   width plus the gap. Absolute keeps it visible but out of the centring maths,
   so the word itself lands on the axis. */
.hero__menu i{position:absolute;right:100%;margin-right:14px;top:50%;transform:translateY(-50%);
  font-family:var(--font-mono);font-style:normal;font-size:11px;letter-spacing:.24em;
  color:#f8ab54;width:2ch;text-align:right}
.hero__menu span{position:relative;font-family:var(--font-display);font-weight:500;
  font-size:clamp(1.5rem,3.4vw,2.7rem);letter-spacing:-.015em;line-height:1.04;
  transition:transform .4s var(--ease),color .4s var(--ease)}
.hero__menu span::after{content:"";position:absolute;left:0;right:100%;bottom:-3px;height:1px;background:#f8ab54;transition:right .45s var(--ease)}
.hero__menu a:hover span,.hero__menu a:focus-visible span{color:#f8ab54;transform:translateX(6px)}
.hero__menu a:hover span::after,.hero__menu a:focus-visible span::after{right:0}

/* ---- LEVITATION ----------------------------------------------------
   The index hangs in the air over the dome. Each row drifts on its own
   phase and amplitude so the list never moves as one block, and the
   light beneath it answers the drift — tight and bright when the row
   sits low, wide and faint as it rises. That inverse relationship is
   what reads as "floating" rather than "sliding". */
/* TWO animations, not one. The row starts at opacity:0 and depends on menuIn
   to fade in — replacing animation-name would leave the whole index invisible.
   Longhand lists keep the entrance and the float on separate tracks; levitate
   is second so its transform wins once menuIn has done its fade. */
.hero__menu a{position:relative;--in:.3s;--lev:0s;--amp:7px;--dur:7.4s;
  animation-name:menuIn,levitate;
  animation-duration:.7s,var(--dur);
  animation-timing-function:var(--ease),var(--ease-inertia);
  animation-iteration-count:1,infinite;
  animation-direction:normal,alternate;
  animation-fill-mode:forwards,none;
  animation-delay:var(--in),var(--lev);
  will-change:transform}
.hero__menu a:nth-child(1){--in:.30s;--lev:-0.0s;--amp:6px;  --dur:7.2s}
.hero__menu a:nth-child(2){--in:.42s;--lev:-1.1s;--amp:8px;  --dur:8.1s}
.hero__menu a:nth-child(3){--in:.54s;--lev:-2.3s;--amp:5px;  --dur:6.6s}
.hero__menu a:nth-child(4){--in:.66s;--lev:-3.4s;--amp:9px;  --dur:8.6s}
.hero__menu a:nth-child(5){--in:.78s;--lev:-4.6s;--amp:6.5px;--dur:7.7s}

/* the cast light — it is the shadow's opposite, so it lives under the row */
.hero__menu a::before{content:"";position:absolute;left:50%;bottom:-15px;
  width:72%;height:16px;transform:translateX(-50%);pointer-events:none;
  background:radial-gradient(50% 50% at 50% 50%,rgba(248,171,84,.40),rgba(248,171,84,.10) 48%,transparent 74%);
  filter:blur(7px);
  animation-name:levitate-cast;animation-duration:var(--dur);
  animation-timing-function:var(--ease-inertia);animation-iteration-count:infinite;
  animation-direction:alternate;animation-delay:var(--lev)}

/* a held breath of amber around the letterforms, so they read as lit
   objects suspended in front of the dome rather than type printed on it */

/* ==== THE LIGHT BEHIND EVERYTHING ===================================
   Sits between the dome photograph (z 0) and the content (z 2/3), so the
   wordmark and the index are lit BY it rather than each carrying its own
   unrelated glow. Three parts: the source, the shafts it throws down the
   axis, and the bounce it leaves on the floor. */
/* The scrim resolved the PHOTO into var(--bg), but this layer sits above it and
   kept adding light (the bounce screens amber right at the hero's foot). So the
   hero's last rows were brighter than the next section's flat --bg and the step
   was still visible. Masking the whole light layer out before the boundary is
   what actually removes the seam, in both themes. */
.hero__light{position:absolute;inset:0;z-index:1;pointer-events:none;overflow:hidden;
  -webkit-mask-image:linear-gradient(180deg,#000 58%,rgba(0,0,0,.35) 84%,transparent 97%);
          mask-image:linear-gradient(180deg,#000 58%,rgba(0,0,0,.35) 84%,transparent 97%)}
/* the helix rides in the light layer: above the dome, below the type */
.hero__helix{position:absolute;inset:0;width:100%;height:100%;pointer-events:none}

/* the cursor's warm pool, in the light layer so the dome cannot hide it.
   JS writes the transform (including its own -50% centring), so nothing here
   sets transform — it would be overwritten on the first pointer move. */
.hero__cursor{position:absolute;left:0;top:0;
  width:clamp(300px,40vw,580px);aspect-ratio:1;border-radius:50%;
  pointer-events:none;opacity:0;transition:opacity .55s var(--ease);
  background:radial-gradient(closest-side,rgba(248,171,84,.17),rgba(234,123,44,.06) 46%,transparent 72%);
  mix-blend-mode:screen}
@media (prefers-reduced-motion:reduce){.hero__cursor{display:none}}

/* the source — a broad warm bloom on the sacred centre */
.hero__light::before{content:"";position:absolute;left:50%;top:44%;
  width:min(155vw,1600px);height:min(125vh,1150px);transform:translate(-50%,-50%);
  background:
    radial-gradient(34% 38% at 50% 50%,rgba(255,238,210,.15),rgba(248,171,84,.07) 46%,transparent 72%),
    radial-gradient(64% 54% at 50% 42%,rgba(234,123,44,.06),transparent 76%);
  filter:blur(30px);
  animation:hero-bloom 11s var(--ease-inertia) infinite alternate}

/* volumetric shafts fanning out of the dome, masked so they fade as they fall */
.hero__shafts{position:absolute;left:50%;top:-30%;width:min(200vw,2000px);height:155%;
  transform:translateX(-50%);mix-blend-mode:screen;filter:blur(13px);opacity:.5;
  background:repeating-conic-gradient(from 152deg at 50% 4%,
    transparent 0deg 2.6deg,
    rgba(255,233,201,.13) 2.6deg 4.1deg,
    transparent 4.1deg 7.4deg);
  -webkit-mask-image:radial-gradient(58% 66% at 50% 6%,#000 12%,rgba(0,0,0,.55) 46%,transparent 80%);
          mask-image:radial-gradient(58% 66% at 50% 6%,#000 12%,rgba(0,0,0,.55) 46%,transparent 80%);
  animation:hero-shafts 24s var(--ease-inertia) infinite alternate}
/* keep the rays as rays — they should streak across the dome, not fog it */
.hero__shafts{filter:blur(7px)}

/* the floor takes the light back — a low, wide bounce under the index */
.hero__bounce{position:absolute;left:50%;bottom:-8%;width:min(120vw,1400px);height:38%;
  transform:translateX(-50%);filter:blur(26px);mix-blend-mode:screen;
  background:radial-gradient(50% 50% at 50% 100%,rgba(248,171,84,.20),rgba(234,123,44,.07) 48%,transparent 76%);
  animation:hero-bounce 13s var(--ease-inertia) infinite alternate}

/* the wordmark gets the same source behind it, not a glow of its own */
.hero__wm{isolation:isolate}
.hero__wm::before{content:"";position:absolute;left:50%;top:50%;z-index:-1;pointer-events:none;
  width:132%;height:250%;transform:translate(-50%,-50%);
  background:radial-gradient(46% 42% at 50% 50%,rgba(255,241,220,.34),rgba(248,171,84,.15) 48%,transparent 76%);
  filter:blur(18px);
  animation:hero-bloom 9s var(--ease-inertia) infinite alternate}

@keyframes hero-bloom{
  from{opacity:.70;transform:translate(-50%,-50%) scale(.96)}
  to  {opacity:1;  transform:translate(-50%,-50%) scale(1.06)}
}
@keyframes hero-shafts{
  from{opacity:.16;transform:translateX(-50%) rotate(-1.1deg)}
  to  {opacity:.34;transform:translateX(-50%) rotate(1.1deg)}
}
@keyframes hero-bounce{
  from{opacity:.30;transform:translateX(-50%) scaleX(.94)}
  to  {opacity:.58;transform:translateX(-50%) scaleX(1.06)}
}

/* ---- BACKLIGHT -----------------------------------------------------
   A source sitting behind each row, hidden by the words themselves.
   isolation:isolate keeps the z-index:-1 layer inside the row, so it
   falls behind the letterforms but stays in front of the dome. It
   swells as the row rises — the inverse of the pool beneath it, so the
   two together read as one object moving through a light. */
.hero__menu a{isolation:isolate}
.hero__menu a::after{content:"";position:absolute;z-index:-1;pointer-events:none;
  left:50%;top:48%;width:116%;height:150%;
  transform:translate(-50%,-50%);   /* base centring, so it survives reduced-motion */
  background:
    radial-gradient(44% 50% at 50% 50%,rgba(255,241,222,.34),rgba(248,171,84,.17) 44%,transparent 72%),
    radial-gradient(76% 88% at 50% 50%,rgba(234,123,44,.12),transparent 76%);
  filter:blur(11px);
  animation-name:backlight;animation-duration:var(--dur);
  animation-timing-function:var(--ease-inertia);animation-iteration-count:infinite;
  animation-direction:alternate;animation-delay:var(--lev)}

/* The glyphs stay clean. All the warmth lives BEHIND them in ::after — an
   amber text-shadow reads as haze sitting on the letters and dulls them, so
   the only shadow here is a dark one for separation. */
.hero__menu span{
  color:#fff;
  text-shadow:0 6px 26px rgba(0,0,0,.7),0 1px 2px rgba(0,0,0,.5)}

@keyframes backlight{
  from{opacity:.68;transform:translate(-50%,-50%) scale(.94)}
  to  {opacity:1;  transform:translate(-50%,-50%) scale(1.07)}
}

@keyframes levitate{
  from{transform:translateY(calc(var(--amp) * 0.55))}
  to  {transform:translateY(calc(var(--amp) * -1))}
}
@keyframes levitate-cast{
  from{opacity:.62;transform:translateX(-50%) scale(1)}
  to  {opacity:.24;transform:translateX(-50%) scale(1.22)}
}

@media (prefers-reduced-motion:reduce){
  .hero__light::before,.hero__shafts,.hero__bounce,.hero__wm::before{animation:none}
  .hero__menu a,.hero__menu a::before,.hero__menu a::after{animation:none}
  .hero__menu a::before{opacity:.4}
  .hero__menu a::after{opacity:.85}   /* the depth stays; only the drift stops */
}
.hero__menu a:focus-visible{outline:none}
/* the old single-value entrance stagger lived here; it now comes from --in
   above, because a single delay would apply to BOTH animations and clobber
   the float's negative offset */
@keyframes menuIn{to{opacity:1;transform:none}}
.hero__object{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);z-index:0;width:min(96vw,1200px);height:1px;pointer-events:none;will-change:transform}
.hero__beam{position:absolute;top:50%;left:0;right:0;height:1px;transform:translateY(-50%);
  background:linear-gradient(90deg,transparent,var(--rule) 16%,var(--beam) 50%,var(--rule) 84%,transparent)}
.hero__node{position:absolute;top:50%;left:50%;width:9px;height:9px;border-radius:50%;transform:translate(-50%,-50%);
  background:var(--light);box-shadow:0 0 20px 3px var(--light-soft);z-index:2}
.js .hero__object{opacity:0}
.hero__display{font-family:var(--font-display);font-weight:500;font-size:var(--fs-hero);line-height:.9;letter-spacing:var(--track-tight);position:relative;z-index:2}
.hero__sub{position:relative;z-index:2;margin-inline:auto}
.cta{position:relative;z-index:2;display:inline-flex;align-items:center;gap:12px;margin-top:6px;
  font-family:var(--font-mono);font-size:var(--fs-body);text-transform:uppercase;letter-spacing:var(--track-anno);
  color:var(--bg);background:var(--light);padding:16px 30px;border-radius:999px;will-change:transform;transition:background .4s var(--ease)}
.cta--ghost{background:transparent;color:var(--text);border:1px solid var(--light-soft)}
.cta--ghost:hover{background:var(--light);color:var(--bg)}
.cta__x{transition:transform .5s var(--ease)}
.cta:hover .cta__x{transform:translateX(5px)}
/* bottom:0 so the cue's line reaches the hero's edge and reads as one stroke
   with the axis continuing in the section below, instead of two broken pieces */
.hero__scroll{position:absolute;left:50%;bottom:0;transform:translateX(-50%);z-index:4;display:grid;gap:8px;justify-items:center}
/* --muted, not --faint: at .34 on the pale day background DESCEND all but
   vanished. The shadow is theme-neutral and keeps it legible over the photo. */
.hero__scroll span{font-size:9px;letter-spacing:var(--track-anno);color:var(--muted);text-transform:uppercase;
  text-shadow:0 1px 10px rgba(0,0,0,.35)}
/* Matched to .axis, which is what it meets: same 1px width, same --light (not
   the paler --light-lit it used to use) and the same glow. Without the glow and
   at the lighter tint it read as a thinner, fainter line than the one below it. */
.hero__scroll i{width:1px;height:clamp(52px,8vh,84px);
  background:linear-gradient(180deg,transparent,var(--light) 24%,var(--light));
  box-shadow:0 0 22px 1px rgba(234,123,44,var(--axis-glow))}

/* ---------- SELECTED WORK — entries straddle the axis ---------- */
.work__title,.studies__title,.manifesto__title,.contact__title{font-family:var(--font-display);font-weight:500;font-size:var(--fs-display);line-height:.96;letter-spacing:var(--track-tight)}
.work__list{width:100%;list-style:none;margin-top:clamp(28px,5vh,56px);border-top:1px solid var(--rule)}
.work__row{display:grid;grid-template-columns:1fr auto 1fr;align-items:center;gap:clamp(14px,4vw,64px);
  padding-block:clamp(16px,2.4vh,30px);border-bottom:1px solid var(--rule)}
.work__loc{text-align:right}
.work__name{font-family:var(--font-display);font-weight:500;font-size:var(--fs-mid);letter-spacing:var(--track-tight);white-space:nowrap;text-align:center}
.work__meta i{font-style:normal;color:var(--light)}
@media(max-width:640px){.work__row{grid-template-columns:1fr;gap:5px;text-align:center}.work__loc{text-align:center}.work__name{white-space:normal}}

/* ---------- SPATIAL STUDIES — the light event ---------- */
.studies .center{gap:28px}
.studies__scale{position:absolute;bottom:calc(var(--layout-edge) + 18px);left:var(--layout-edge);right:var(--layout-edge);
  display:flex;justify-content:space-between;color:var(--faint);font-size:9px;letter-spacing:.24em;text-transform:uppercase}
.studies__scale span{position:relative;padding-top:12px}
.studies__scale span::before{content:"";position:absolute;top:0;left:50%;width:1px;height:7px;background:var(--rule)}
@media(max-width:640px){.studies__scale{display:none}}

/* ---------- MANIFESTO — monumental lines, edge indices ---------- */
.manifesto__list{width:100%;list-style:none;margin-top:clamp(24px,4vh,48px)}
.manifesto__row{display:grid;grid-template-columns:56px 1fr;gap:clamp(16px,4vw,60px);align-items:baseline;
  padding-block:clamp(18px,3vh,36px);border-top:1px solid var(--rule);text-align:left}
.manifesto__row:last-child{border-bottom:1px solid var(--rule)}
.manifesto__row .anno{padding-top:.6em}
.manifesto__line{font-family:var(--font-display);font-weight:400;font-size:var(--fs-mid);line-height:1.24;letter-spacing:var(--track-tight);max-width:26ch}
.manifesto__row--key .manifesto__line{font-weight:500}
.manifesto__row--light .manifesto__line{color:var(--light-lit)}
@media(max-width:560px){.manifesto__row{grid-template-columns:36px 1fr}.manifesto__line{max-width:none}}

/* ---------- VITRUVIAN — the canon, centred on the axis ---------- */
.vit .lede{margin-inline:auto}
.vit__title{font-family:var(--font-display);font-weight:500;font-size:var(--fs-display);line-height:.96;letter-spacing:var(--track-tight)}
.vit-fig{position:relative;width:min(78vw,500px);aspect-ratio:1;margin:clamp(12px,2vh,26px) auto}
.vit-fig svg{width:100%;height:100%;overflow:visible}
.vit-guide{stroke:var(--light-soft);stroke-width:1}
.vit-geo{stroke:var(--concrete);stroke-width:1.5}
.vit-line{stroke:var(--text);stroke-width:2}
.vit-line--soft{opacity:.6}
.vit-node{fill:var(--light)}
.vit-node--sm{fill:var(--light-lit)}
.vit-fig [data-draw]{stroke-dasharray:var(--len);stroke-dashoffset:var(--len)}
.js .vit-fig.is-drawn [data-draw]{stroke-dashoffset:0;transition:stroke-dashoffset 1.6s var(--ease-inertia)}
.vit-fig .vit-node{opacity:0}
.js .vit-fig.is-drawn .vit-node{opacity:1;transition:opacity 1s ease 1s}
.vit-stats{display:flex;gap:clamp(28px,5vw,56px);justify-content:center;flex-wrap:wrap;margin-top:8px}
.vit-stat b{display:block;font-family:var(--font-display);font-weight:500;font-size:clamp(1.4rem,2.4vw,1.9rem);color:var(--light-lit);letter-spacing:var(--track-tight)}
.vit-stat span{font-family:var(--font-mono);font-size:10px;letter-spacing:var(--track-anno);text-transform:uppercase;color:var(--muted)}

/* ---------- CONTACT — the axis terminates ---------- */
.contact .center{gap:26px}
.contact__term{width:13px;height:13px;border:1px solid var(--light);border-radius:50%;position:relative}
.contact__term::after{content:"";position:absolute;inset:3px;border-radius:50%;background:var(--light);box-shadow:0 0 16px 2px var(--light-soft)}
.contact__mail{margin-top:8px}

/* ---------- FOOTER ---------- */
.foot{position:relative;z-index:2;display:flex;justify-content:space-between;gap:16px;flex-wrap:wrap;
  padding:clamp(28px,4vh,44px) var(--layout-edge);border-top:1px solid var(--rule);
  font-family:var(--font-mono);font-size:10px;letter-spacing:var(--track-anno);text-transform:uppercase;color:var(--muted)}
.veil{position:fixed;inset:0;background:var(--bg);z-index:90;pointer-events:none;transform:translateY(100%);will-change:transform}

/* ============================================================
   MOTION GRAPHICS LAYER
   ============================================================ */
/* drifting ember / light-field (canvas) + ambient light sweep */
.fx{position:fixed;inset:0;z-index:0;pointer-events:none;opacity:0;transition:opacity 1.4s var(--ease)}
.is-entered .fx{opacity:1}
.sweep{position:fixed;inset:-30%;z-index:0;pointer-events:none;mix-blend-mode:screen;opacity:0;
  background:radial-gradient(38% 30% at 50% 32%, rgba(234,123,44,.12), transparent 70%);
  transition:opacity 1.6s var(--ease);animation:sweep 20s ease-in-out infinite alternate}
.is-entered .sweep{opacity:.7}
@keyframes sweep{from{transform:translate(-7%,-4%) scale(1)}to{transform:translate(7%,7%) scale(1.12)}}

/* soft light that follows the cursor (positioned by JS) */
.cursor-glow{position:fixed;top:0;left:0;width:460px;height:460px;margin:-230px 0 0 -230px;z-index:0;
  pointer-events:none;mix-blend-mode:screen;opacity:0;transition:opacity .6s var(--ease);
  background:radial-gradient(closest-side, rgba(248,171,84,.14), transparent 70%)}
.is-entered .cursor-glow{opacity:1}

/* a pulse of light travelling down the sacred axis */
.axis__pulse{position:absolute;left:50%;top:0;width:3px;height:16vh;
  transform:translate(-50%,-20vh);border-radius:2px;filter:blur(1px);
  background:linear-gradient(180deg,transparent,var(--light-lit),transparent);
  animation:axisRun 5s cubic-bezier(.5,0,.5,1) infinite}
@keyframes axisRun{0%{transform:translate(-50%,-20vh);opacity:0}
  8%{opacity:1}92%{opacity:1}100%{transform:translate(-50%,108vh);opacity:0}}

/* the hero node emits expanding rings */
@keyframes ripple{0%{transform:scale(1);opacity:.85}100%{transform:scale(8);opacity:0}}

/* THE ONE POINT — moved off the beam and onto the interpunct of
   STUDIO · DESIGN, so the mark the axes converge on is the mark already
   sitting in the lockup rather than a second dot lower down. */
.hero__node{display:none}
/* The bullet glyph only reserves space now — its ink is not centred in its box,
   and the kicker's .5em letter-spacing pushes that box right of the visual gap.
   Drawing the dot instead means the dot and its rings share one centre, so they
   can never drift apart.

   The -.25em is required, not cosmetic. The span's box includes the trailing
   letter-space that follows the bullet, so its box centre lands half a
   letter-space RIGHT of the true axis. Measured: seam, masthead, wordmark and
   kicker all centre on 665, while the raw box centre is 669.4 — a 4.4px error,
   exactly half the kicker's 8.8px letter-spacing. Shifting by -.25em puts the
   mark back on the axis that runs through the N of TENET, without altering the
   STUDIO / DESIGN spacing. */
/* Horizontal placement is NOT set here. Every attempt to predict it in CSS was
   defeated by inline metrics — trailing letter-space, then a second one added by
   the <i>, then a different box when the containing block changed. app.js now
   measures the seam and the mark and writes the residual to --dot-nudge, so the
   mark lands on the axis at any font size or viewport. See alignDot(). */
.hero__kicker{position:relative}
.hero__kicker .hero__dot{position:relative;display:inline-block;color:transparent;
  transform:translateX(var(--dot-nudge,0px))}
/* the mark itself — a border-radius circle, which the compositor antialiases.
   A radial-gradient with a 0.3px stop does not get that treatment and reads as
   a stair-stepped edge at this size. */
.hero__kicker .hero__dot > i{position:absolute;left:50%;top:50%;
  width:9px;height:9px;margin:-4.5px 0 0 -4.5px;border-radius:50%;
  background:var(--light-lit);pointer-events:none;
  box-shadow:0 0 9px 1px var(--light-soft),0 0 22px 3px rgba(234,123,44,.45)}
/* The rings belong to the MARK, not to the span, and use inset:0 — so they are
   the mark's own box, scaled. Concentricity is structural: there is no second
   copy of the centring arithmetic that could disagree with the first. They were
   previously siblings, each independently re-deriving left/top/margin, which is
   what let the ring drift off the dot.
   scale(1.8) start keeps the first ring clear of the mark — at scale(1) it sat
   flush against it and read as a second dot stacked on the first. */
.hero__kicker .hero__dot > i::before,.hero__kicker .hero__dot > i::after{content:"";
  position:absolute;inset:0;border-radius:50%;box-sizing:border-box;
  border:1px solid var(--light-soft);pointer-events:none;
  animation:dot-ripple 3.4s ease-out infinite}
.hero__kicker .hero__dot > i::after{animation-delay:1.7s}
@keyframes dot-ripple{
  0%{transform:scale(1.8);opacity:.7}
  100%{transform:scale(9);opacity:0}
}
@media (prefers-reduced-motion:reduce){
  .hero__kicker .hero__dot > i::before,.hero__kicker .hero__dot > i::after{
    animation:none;opacity:.3;transform:scale(1.8)}
}

/* the monumental headline breathes with the light */
.hero__display{animation:breathe 7s ease-in-out infinite}
@keyframes breathe{0%,100%{text-shadow:0 0 0 rgba(234,123,44,0)}50%{text-shadow:0 0 46px rgba(234,123,44,.14)}}

/* continuous microtype ticker */
.ticker{position:relative;z-index:2;overflow:hidden;border-top:1px solid var(--rule);
  padding-block:15px;white-space:nowrap;-webkit-mask-image:linear-gradient(90deg,transparent,#000 6%,#000 94%,transparent);
  mask-image:linear-gradient(90deg,transparent,#000 6%,#000 94%,transparent)}
.ticker__track{display:inline-flex;will-change:transform;animation:ticker 34s linear infinite}
.ticker__track span{font-family:var(--font-mono);font-size:11px;letter-spacing:.24em;text-transform:uppercase;
  color:var(--muted);padding-right:0}
.ticker__track em{font-style:normal;color:var(--light-lit);padding:0 22px}
.ticker:hover .ticker__track{animation-play-state:paused}
@keyframes ticker{from{transform:translateX(0)}to{transform:translateX(-50%)}}

/* Vitruvian: the square turns slowly inside the circle once drawn */
.js .vit-fig.is-drawn .vit-square{transform-box:fill-box;transform-origin:center;animation:vitSpin 72s linear infinite}
.js .vit-fig.is-drawn .vit-guide{transform-box:fill-box;transform-origin:center;animation:vitSpin 120s linear infinite reverse}
@keyframes vitSpin{to{transform:rotate(360deg)}}

/* ---------- reduced motion ---------- */
@media(prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.001ms!important;transition-duration:.001ms!important;scroll-behavior:auto!important}
  .js .word__i{transform:none}.js [data-reveal]{opacity:1;transform:none}.js .hero__object{opacity:1}
  .vit-fig [data-draw]{stroke-dashoffset:0}.vit-fig .vit-node{opacity:1}
}
