/* ==========================================================================
   Novalis Prime — one-page site
   Concept: "walk through the door". The page is a journey through the arch
   of the brand mark: dark chamber -> light through the doorway -> arrival.

   Everything tunable lives in :root. Change it once here, it changes
   everywhere. Section styles below follow the order of the page.

   Two classes the engine adds carry the moving parts:
     .js-reveal on <html>   — [data-reveal] copy is hidden until it enters
     .is-live on .hero/.res — the pinned, scroll-driven scenes are armed
   Without them (no script, or reduced motion) every section lays out as
   plain, fully visible copy.
   ========================================================================== */

/* Brand typeface (headings + wordmark; also the contact form's free-text
   fields, so subset to Latin + Latin Extended-A/B, not just this file's own
   copy). True variable axis is 400-800; the range below extends down to 300
   so the font-weight:300 passages elsewhere in this file still match this
   face — the browser clamps to the nearest real instance (300 -> 400)
   instead of falling through to the next font in the stack. */
@font-face{
  font-family:'Sen';
  src:url('../assets/fonts/Sen-VariableFont_wght.woff2') format('woff2');
  font-weight:300 800;
  font-style:normal;
  font-display:swap;
}

:root{
  /* --- Brand colour. Prime Green sampled from the identity artwork. ------ */
  --green:        #B3B935;
  --green-deep:   #8A8F27;
  --green-glow:   #D6DC6A;

  /* --- Chamber (dark) --------------------------------------------------- */
  --ink:          #0A0B08;
  --on-ink:       #F4F4EE;
  --on-ink-soft:  #C2C3B7;
  --on-ink-dim:   #8B8D80;

  /* --- Paper (light) ---------------------------------------------------- */
  --paper:        #FAFAF6;
  --paper-2:      #FFFFFF;
  /* Sage green, sampled from the doorway light (site/js/hero3d.js lightMap).
     Used only where a section must pick up straight from the doorway with
     no seam — see .hero__beyond and .section--who. */
  --paper-green:  #CDDDBA;
  --slate:        #53544C;
  --slate-light:  #83847A;
  --line:         #DEDDD2;
  --line-dark:    #2A2C22;

  /* --- Type ------------------------------------------------------------- */
  /* Sen, loaded locally above (see @font-face). */
  --font-display: 'Sen', 'Archivo', system-ui, sans-serif;
  --font-body:    'DM Sans', system-ui, sans-serif;
  --font-mono:    'IBM Plex Mono', ui-monospace, monospace;

  /* --- Rhythm ----------------------------------------------------------- */
  --wrap:         1180px;
  --gutter:       clamp(24px, 5vw, 48px);
  --section-y:    clamp(96px, 13vh, 160px);
  --nav-h:        82px;
  /* Read by the engine, so the breakpoint is written once, here. */
  --layout:       wide;

  /* --- Motion ----------------------------------------------------------- */
  --ease-out:     cubic-bezier(.22,.61,.36,1);
  --ease-soft:    cubic-bezier(.4,0,.2,1);
}

/* ==========================================================================
   Base
   ========================================================================== */

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

html{
  /* No CSS smooth scrolling: Lenis carries it, and the JS fallback uses
     scrollIntoView. The two together made programmatic scrolls unreliable. */
  scroll-behavior:auto;
  -webkit-text-size-adjust:100%;
}

body{
  margin:0;
  background:var(--ink);
  color:var(--slate);
  font-family:var(--font-body);
  font-size:16px;
  line-height:1.65;
  -webkit-font-smoothing:antialiased;
  -moz-osx-font-smoothing:grayscale;
  overflow-x:hidden;
}

img{ max-width:100%; display:block; }
a{ color:inherit; text-decoration:none; }

/* Every interactive thing shows the hand. */
a, button, label{ cursor:pointer; }
input, textarea{ cursor:text; }
button:disabled{ cursor:default; }

h1,h2,h3{
  font-family:var(--font-display);
  font-weight:500;
  color:var(--ink);
  margin:0;
  letter-spacing:-0.02em;
  line-height:1.12;
}

.wrap{
  width:100%;
  max-width:var(--wrap);
  margin:0 auto;
  padding:0 var(--gutter);
}

/* Small tracked mono label that opens most sections. */
.eyebrow{
  font-family:var(--font-mono);
  font-size:11.5px;
  font-weight:500;
  letter-spacing:.22em;
  text-transform:uppercase;
  color:var(--green-deep);
  margin:0 0 22px;
}
/* On dark ground the eyebrow lifts to the brighter green. */
.section--ink .eyebrow,
.hero .eyebrow{ color:var(--green); }

/* Small mono numeral that labels an item. */
.num{
  display:block;
  font-family:var(--font-mono);
  font-size:11px;
  letter-spacing:.2em;
  color:var(--slate-light);
}

.lede{
  font-family:var(--font-body);
  font-size:clamp(16px, 1.15vw, 18px);
  line-height:1.7;
  color:var(--slate);
}

/* Centred display-face line that closes a section. */
.section__close{
  margin:48px auto 0;
  max-width:60ch;
  text-align:center;
  font-family:var(--font-display);
  font-size:clamp(17px, 1.4vw, 20px);
  line-height:1.5;
  color:var(--ink);
}

/* Two columns, the left one pinned while the right one scrolls. */
.split{
  display:grid;
  grid-template-columns:var(--split, .9fr 1.1fr);
  gap:clamp(40px, 6vw, 96px);
  align-items:start;
}
.split__side{ position:sticky; top:calc(var(--nav-h) + 30px); }

/* A pinned stage: one screen tall, clipped. Sticks only when its scene is
   live; otherwise it is a plain screen-tall block. */
.stage{
  position:relative;
  height:100vh;
  height:100svh;
  overflow:hidden;
}
.is-live > .stage{ position:sticky; top:0; }

/* ==========================================================================
   Nav — floats over the dark hero, then picks up a paper background
   ========================================================================== */

.nav{
  position:fixed;
  inset:0 0 auto 0;
  z-index:80;
  height:var(--nav-h);
  display:flex;
  align-items:center;
  /* The engine writes --nav-out: the bar steps aside while the camera passes
     through the doorway — white nav over white light is unreadable. */
  --nav-out:0;
  opacity:calc(1 - var(--nav-out));
  transition:background .4s var(--ease-soft);
}
.nav__inner{
  display:flex;
  align-items:center;
  justify-content:space-between;
}

/* Two logos stacked in one slot. We cross-fade rather than swap the src,
   so there is no flash while the second file decodes. */
.nav__logo{ position:relative; display:block; height:42px; width:185px; }
.nav__logo img{
  position:absolute;
  inset:0;
  height:100%;
  width:auto;
  transition:opacity .4s var(--ease-soft);
}
.nav__logo img.is-light{ opacity:1; }
.nav__logo img.is-dark{ opacity:0; }
/* On hover the mark catches the light: a soft Prime Green glow. */
.nav__logo img{ transition:opacity .4s var(--ease-soft), filter .5s var(--ease-soft); }
.nav__logo:hover img{ filter:drop-shadow(0 0 7px rgba(214,220,106,.55)); }
.nav.is-paper .nav__logo:hover img{ filter:drop-shadow(0 0 6px rgba(179,185,53,.45)); }

.nav__cta{
  font-family:var(--font-mono);
  font-size:11.5px;
  letter-spacing:.14em;
  text-transform:uppercase;
  color:var(--on-ink);
  border:1px solid rgba(244,244,238,.32);
  padding:11px 22px;
  border-radius:2px;
  transition:background .3s var(--ease-soft), color .3s var(--ease-soft),
             border-color .3s var(--ease-soft);
}
.nav__cta:hover{ background:var(--green); border-color:var(--green); color:var(--ink); }

/* The bar follows the theme of the section under it (the engine sets one
   of these from each section's data-theme). Over the hero: a light scrim,
   so the bar reads but the scene shows. Over dark sections: a solid bar,
   so copy scrolls behind it, not through it. */
.nav.is-hero{
  background:linear-gradient(to bottom, rgba(10,11,8,.62), rgba(10,11,8,0));
}
.nav.is-ink{   background:rgba(10,11,8,.94); }
.nav.is-paper{ background:rgba(250,250,246,.94); }
/* The hand-off zone: the hero's tail end and the whole "who we are"
   section (matches --paper-green). The bar picks up the doorway green
   here instead of jumping straight to paper. */
.nav.is-paper.is-doorzone{ background:rgba(205,221,186,.94); }
.nav.is-paper .nav__logo img.is-light{ opacity:0; }
.nav.is-paper .nav__logo img.is-dark{  opacity:1; }
.nav.is-paper .nav__cta{ color:var(--ink); border-color:rgba(14,15,12,.28); }
.nav.is-paper .nav__cta:hover{ background:var(--ink); border-color:var(--ink); color:var(--paper); }

/* Section links. Pushed to the right, beside the call to action. */
.nav__links{
  display:flex;
  gap:30px;
  margin-left:auto;
  margin-right:36px;
}
.nav__links a{
  font-family:var(--font-mono);
  font-size:11.5px;
  font-weight:500;
  letter-spacing:.14em;
  text-transform:uppercase;
  color:var(--on-ink-soft);
  transition:color .3s var(--ease-soft);
}
.nav__links a:hover{ color:var(--green); }
.nav.is-paper .nav__links a{ color:var(--slate); }
.nav.is-paper .nav__links a:hover{ color:var(--ink); }

/* The menu button (phones and tablets): two bars that become a cross. */
.nav__menu{
  display:none;
  appearance:none;
  background:none;
  border:1px solid rgba(244,244,238,.32);
  border-radius:2px;
  width:44px;
  height:40px;
  margin-left:12px;
  padding:0;
  position:relative;
  transition:border-color .3s var(--ease-soft);
}
.nav__menu span{
  position:absolute;
  left:12px; right:12px;
  height:1.5px;
  background:var(--on-ink);
  top:50%;
  transition:transform .35s var(--ease-soft), background .3s var(--ease-soft);
}
.nav__menu span:first-child{ transform:translateY(-4px); }
.nav__menu span:last-child{  transform:translateY(3px); }
.nav.is-open .nav__menu span:first-child{ transform:rotate(45deg); }
.nav.is-open .nav__menu span:last-child{  transform:rotate(-45deg); }
.nav.is-paper .nav__menu{ border-color:rgba(14,15,12,.28); }
.nav.is-paper .nav__menu span{ background:var(--ink); }
.nav__links-cta{ display:none; }

/* Smooth scrolling is handed to Lenis when it loads. These two rules are
   the ones its documentation asks for. */
html.lenis, html.lenis body{ height:auto; }
html.menu-open, html.menu-open body,
html.contact-open, html.contact-open body{ overflow:hidden; }
.lenis.lenis-smooth{ scroll-behavior:auto !important; }

/* ==========================================================================
   HERO — the pinned camera push through the doorway
   --------------------------------------------------------------------------
   .hero is a tall scroll track. .hero__stage sticks to the viewport while
   the track passes, which gives us the scroll distance to move the camera.
   ========================================================================== */

.hero{
  position:relative;
  z-index:10;             /* above the finale, like every section */
  background:var(--ink);
}
.hero.is-live{ height:340vh; }   /* the scroll distance the push gets to use */

.hero__stage{
  /* Until the scene renders (or without WebGL): a dark room in plain CSS. */
  background:radial-gradient(140% 110% at 62% 70%, #1B2410 0%, #0F1409 40%, #080A05 72%, #040503 100%);
  isolation:isolate;

  /* Where the doorway stands. The light and the threshold hang off these
     two values, so the scene stays assembled if the doorway moves. Right of
     centre, with the copy to its left — the same composition as the
     identity artwork. */
  --door-x:71%;
  --door-y:70%;
  /* The doorway of the identity render: a wide arch, a semicircular head on
     short legs, planted on the floor. Height follows from width (the
     render's door is 226 wide by 289 tall). Its top lines up with the
     headline and its foot with the end of the copy. */
  --door-w:clamp(200px, 21vw, 310px);
  --door-ratio:1.279;

  /* Scroll-driven values the engine writes here. */
  --copy-out:0;      /* 0..1, the copy clearing out as the camera moves */
  --inside:0;        /* 0..1, how far inside the light we are */
  --room-out:0;      /* 0..1, the room letting go once the light fills the screen */
}

/* --- Layer 1: what lies beyond the doorway ------------------------------ */
/* Sits under everything. Once the camera has passed through this is all
   that is left, and it matches the background colour of the next section.
   Only once the scene renders — until then (or without WebGL) the dark
   room of the stage's own background shows. */
.hero__beyond{
  position:absolute;
  inset:0;
  background:var(--paper-green);   /* exactly section--who's background */
  z-index:0;
  display:none;
}
.hero.is-rendered .hero__beyond{ display:block; }

/* --- Layer 2: the room, rendered ----------------------------------------- */
/* hero3d.js draws the wall, the arch, the floor and the light into this
   canvas; the camera is framed to .hero__anchor. It lets go once the light
   fills the screen, revealing the paper beneath. Until the scene is ready
   (or without WebGL) the dark stage shows through. */
.hero__canvas{
  position:absolute;
  inset:0;
  z-index:2;
  width:100%;
  height:100%;
  display:block;
  opacity:0;
  transition:opacity .6s var(--ease-soft);
}
.hero.is-rendered .hero__canvas{ opacity:calc(1 - var(--room-out)); transition:none; }

/* Where the doorway stands and how wide it is. Invisible; the camera is
   framed so the door fills exactly this box, on every layout. */
.hero__anchor{
  position:absolute;
  left:var(--door-x);
  top:var(--door-y);
  width:var(--door-w);
  aspect-ratio:1 / var(--door-ratio);
  translate:-50% -100%;     /* centred on --door-x, its foot on --door-y */
  visibility:hidden;
  pointer-events:none;
}

/* Grain and vignette: still layers, painted once. */
.hero__grain{
  position:absolute;
  inset:0;
  z-index:5;
  pointer-events:none;
  opacity:calc(.07 * (1 - var(--inside)));
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='3'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='220' height='220' filter='url(%23n)'/%3E%3C/svg%3E");
}
.hero__vignette{
  position:absolute;
  inset:0;
  z-index:4;
  pointer-events:none;
  background:radial-gradient(120% 90% at 50% 52%, transparent 44%, rgba(0,0,0,.4) 100%);
  opacity:calc(1 - var(--inside));
}

/* --- Hero copy ----------------------------------------------------------- */
.hero__copy{
  position:absolute;
  inset:0;
  z-index:7;
  display:flex;
  align-items:center;
  pointer-events:none;
  /* Drifts up and clears out as the camera starts to move. */
  transform:translate3d(0, calc(var(--copy-out) * -70px), 0);
  opacity:calc(1 - var(--copy-out));
  will-change:transform, opacity;
}
.hero__copy .wrap{ pointer-events:auto; }

/* The copy holds the left of the frame and the doorway holds the right.
   These widths are what keep the two from meeting. */
.hero h1{
  color:var(--on-ink);
  font-size:clamp(31px, 3.9vw, 54px);
  font-weight:400;
  letter-spacing:-0.03em;
  line-height:1.1;
  max-width:min(19ch, 48vw);
  margin-bottom:28px;
  text-wrap:balance;
}
/* Each sentence is its own block so the browser balances the two lines of
   each one on its own, instead of leaving the last word stranded. */
.hero h1 span,
.hero h1 em{
  display:block;
  text-wrap:balance;
}
.hero h1 em{
  font-style:normal;
  color:var(--green);
}

.hero__lede{
  color:var(--on-ink-soft);
  font-size:clamp(15px, 1.15vw, 17px);
  line-height:1.7;
  max-width:min(44ch, 45vw);
  margin:0;
}

/* The hero copy uses the page's reveal, only slower and later. */
.js-reveal .hero__copy [data-reveal]{
  transition-duration:.9s;
  transition-delay:calc(var(--i, 0) * 110ms + 200ms);
}

/* --- Scroll cue ---------------------------------------------------------- */
.hero__cue{
  position:absolute;
  left:50%;
  bottom:34px;
  z-index:8;
  transform:translateX(-50%);
  display:flex;
  flex-direction:column;
  align-items:center;
  gap:12px;
  font-family:var(--font-mono);
  font-size:10px;
  letter-spacing:.24em;
  text-transform:uppercase;
  color:var(--on-ink-dim);
  opacity:calc(1 - var(--copy-out));
  pointer-events:none;
}
.hero__cue span{ display:block; }
.hero__cue i{
  display:block;
  width:1px;
  height:46px;
  background:linear-gradient(to bottom, transparent, var(--green));
  animation:cue 2.4s var(--ease-soft) infinite;
  transform-origin:50% 0;
}
@keyframes cue{
  0%       { transform:scaleY(0);   opacity:0; }
  35%      { transform:scaleY(1);   opacity:1; }
  70%,100% { transform:scaleY(1);   opacity:0; }
}

/* ==========================================================================
   Sections
   ========================================================================== */

.section{
  position:relative;
  z-index:10;
  padding:var(--section-y) 0;
  background:var(--paper);
}

/* Dark rooms between the paper ones. */
.section--ink{ background:var(--ink); color:var(--on-ink-soft); }
.section--ink h2,
.section--ink h3{ color:var(--on-ink); }
.section--ink .lede{ color:var(--on-ink-soft); }

/* Every section heading after the hero shares one voice. Written with
   :where() so a plain class can override it without a specificity race. */
:where(.section, .res) h2{
  font-size:clamp(26px, 3vw, 40px);
  font-weight:400;
  text-wrap:balance;
}

/* ==========================================================================
   Reveal on enter — the engine adds .is-in
   ========================================================================== */

/* The hidden state is scoped to .js-reveal, which the engine adds as soon
   as it starts. If the script never runs, no copy is hidden. */
.js-reveal [data-reveal]{
  opacity:0;
  transform:translate3d(0, 26px, 0);
  transition:opacity .85s var(--ease-out), transform .85s var(--ease-out);
  transition-delay:calc(var(--i, 0) * 90ms);
}
.js-reveal [data-reveal].is-in{ opacity:1; transform:none; }

/* ==========================================================================
   2. WHO WE ARE — the first room on the other side of the doorway
   ========================================================================== */

/* Sits directly under the sticky hero. Starts on the same green the
   doorway resolves to, so the handoff has no seam, then eases back to the
   site's normal paper so the rest of the section — and everything after
   it — reads as before. 480px clears the eyebrow and the three-line
   heading on every layout before the fade finishes. */
.section--who{
  padding-top:clamp(80px, 11vh, 130px);
  background:linear-gradient(to bottom, var(--paper-green), var(--paper) 480px);
}

/* Three sentences, one per line, set large. The arrival on the far side. */
.who__title{
  font-size:clamp(34px, 4.6vw, 64px);
  font-weight:300;
  line-height:1.06;
  letter-spacing:-.03em;
  max-width:12.5em;
  margin-bottom:clamp(48px, 6vw, 88px);
}
.who__title span{ display:block; }
.who__title span:last-child{ color:var(--green-deep); }

.who__cols{
  display:grid;
  grid-template-columns:repeat(3, 1fr);
  gap:clamp(24px, 3vw, 48px);
}
.who__col{ border-top:1px solid var(--line); padding-top:22px; }
.who__n{ color:var(--green-deep); margin-bottom:16px; }
.who__col p{ margin:0; font-size:clamp(14.5px, 1vw, 16px); line-height:1.7; }

.who__quote{
  margin:clamp(64px, 8vw, 110px) 0 0;
  text-align:center;
}
.who__quote::before{
  content:"";
  display:block;
  width:36px;
  height:2px;
  background:var(--green);
  margin:0 auto 28px;
}
.who__quote p{
  margin:0 auto;
  max-width:24ch;
  font-family:var(--font-display);
  font-weight:300;
  font-size:clamp(24px, 2.8vw, 40px);
  line-height:1.3;
  letter-spacing:-.02em;
  color:var(--ink);
  text-wrap:balance;
}
.who__closing{
  margin:clamp(40px, 5vw, 64px) auto 0;
  max-width:62ch;
  text-align:center;
  font-size:14.5px;
  line-height:1.7;
  color:var(--slate-light);
}

/* ==========================================================================
   3. APPROACH — the pinned scene: the line that holds
   --------------------------------------------------------------------------
   .res is the scroll track. .res__stage pins. The copy has four layers
   that cross-fade (thesis / risk / principles / closing line); inside the
   principles layer, the three stack up one beat at a time. Each layer says
   which beats it owns in data-beats. The engine writes --p (progress
   0..1), --tx on each drifting word, and the cumulative classes b1..b5 on
   the section (bK means "beat K has been reached").

   Everything below is written twice over: the finished, static look is the
   default; the hidden start states are scoped to .res.is-live inside
   :where(), so they lose to the bK reveals and never apply without the
   engine.
   ========================================================================== */

.res{
  position:relative;
  z-index:10;
}
.res.is-live{ height:520vh; }

.res__stage{
  background:
    radial-gradient(120% 80% at 68% 58%, #15170F 0%, #0C0D09 55%, #070805 100%);
}
/* Not live: a plain section, beats stacked, no clipping. */
.res:not(.is-live) .res__stage{ height:auto; overflow:visible; padding:var(--section-y) 0; }

.res__grid{
  height:100%;
  display:grid;
  grid-template-columns:minmax(0, 1fr) minmax(0, 1fr);
  gap:clamp(32px, 5vw, 80px);
  align-items:center;
  padding-top:calc(var(--nav-h) + 8px);   /* stays clear of the nav */
}
.res:not(.is-live) .res__grid{ height:auto; align-items:start; padding-top:0; }

/* --- Copy: the layers ---------------------------------------------------- */
.res__copy{ position:relative; }
.res.is-live .res__copy{ display:grid; align-items:center; }
.res__beat{ max-width:34rem; }
.res:not(.is-live) .res__beat{ margin-bottom:44px; }
.res:not(.is-live) .res__beat:last-child{ margin-bottom:0; }
/* Live: all layers share one cell and cross-fade. */
.res.is-live :where(.res__beat){
  grid-area:1 / 1;
  opacity:0;
  transform:translate3d(0, 28px, 0);
  transition:opacity .6s var(--ease-out), transform .6s var(--ease-out);
  pointer-events:none;
}
.res__beat.is-active{ opacity:1; transform:none; pointer-events:auto; }
.res__beat.is-past{ transform:translate3d(0, -28px, 0); }

.res__beat h2{
  font-size:clamp(28px, 3.2vw, 44px);
  margin-bottom:24px;
}
.res__p{
  margin:0;
  font-size:clamp(15px, 1.05vw, 16.5px);
  line-height:1.7;
  max-width:46ch;
}
.res__big{
  font-family:var(--font-display);
  font-weight:400;
  font-size:clamp(24px, 2.6vw, 36px);
  line-height:1.25;
  letter-spacing:-.02em;
  color:var(--on-ink);
  margin:0 0 22px;
  text-wrap:balance;
}
.res__num{
  font-family:var(--font-mono);
  font-size:12px;
  letter-spacing:.22em;
  color:var(--green);
  margin:0;
}
.res__num::after{ content:" / 03"; color:var(--on-ink-dim); }

/* The three principles stack up; each arrives on its own beat. */
.res__principles{ list-style:none; margin:0; padding:0; }
.pr{
  display:grid;
  grid-template-columns:auto 1fr;
  column-gap:30px;
  row-gap:12px;
  align-items:baseline;
  padding:26px 0;
  border-top:1px solid var(--line-dark);
  transition:opacity .6s var(--ease-out), transform .6s var(--ease-out);
}
.res.is-live :where(.pr){ opacity:0; transform:translate3d(0, 14px, 0); }
.pr .res__num{ grid-row:1; }
.pr h3{
  grid-row:1;
  margin:0;
  font-size:clamp(24px, 2.4vw, 32px);
  font-weight:400;
  letter-spacing:-.02em;
}
.pr .res__p{ grid-column:2; font-size:15px; max-width:40ch; }
.res.b2 .pr-1,
.res.b3 .pr-2,
.res.b4 .pr-3{ opacity:1; transform:none; }

/* The thesis: its own beat, alone, large. */
.res__beat--thesis{ max-width:38rem; }
.res__quote{
  margin:0;
  padding:0;
  font-family:var(--font-display);
  font-weight:300;
  font-size:clamp(26px, 2.9vw, 42px);
  line-height:1.28;
  letter-spacing:-.025em;
  color:var(--on-ink);
  text-wrap:balance;
}
.res__beat--thesis.is-active .res__quote{ animation:thesis-in 1s var(--ease-out) both; }
@keyframes thesis-in{
  from{ opacity:0; transform:translate3d(0, 22px, 0) scale(.97); }
  to  { opacity:1; transform:none; }
}

/* --- Scene: what changes drifts above; the line runs below ---------------- */
.res__scene{
  position:relative;
  height:min(66vh, 600px);
  overflow:hidden;
}
.res:not(.is-live) .res__scene{ height:min(50vh, 460px); }

/* The things that change. Scroll-driven, so it is calm when the visitor
   is, and reversible. Kept to the upper half so nothing crosses the line.
   Only live: still words over a still line would read as a mistake. */
.res__drift{
  display:none;
  position:absolute;
  left:0; right:0; top:0;
  height:50%;
  z-index:1;
  overflow:hidden;
  transition:opacity 1s var(--ease-soft);
  -webkit-mask-image:linear-gradient(to right, transparent, #000 12%, #000 88%, transparent);
          mask-image:linear-gradient(to right, transparent, #000 12%, #000 88%, transparent);
}
.res.is-live .res__drift{ display:block; }
.res__drift span{
  position:absolute;
  left:0;
  top:calc(var(--y, 0) * 100%);   /* each word owns a row; see the markup */
  white-space:nowrap;
  font-family:var(--font-display);
  font-weight:300;
  letter-spacing:-.01em;
  color:var(--on-ink);
  transform:translate3d(var(--tx, 0px), 0, 0);   /* the engine writes --tx */
  will-change:transform;
}
.res__drift .d1{ font-size:clamp(12px, .95vw, 13.5px); opacity:.28; }
.res__drift .d2{ font-size:clamp(14px, 1.2vw, 17px);   opacity:.42; }
.res__drift .d3{ font-size:clamp(17px, 1.5vw, 21px);   opacity:.6; }
.res.b5 .res__drift{ opacity:.22; }

.res__line{
  position:absolute;
  left:0; right:0; bottom:2%;
  width:100%;
  height:auto;
  z-index:2;
  overflow:visible;
  /* --line runs 0..1 with the scroll (the engine writes it while live).
     Each part owns a stretch of it: today's line 0–.28, the disruption
     .28–.58, then the three trajectories one after another, .58–.72,
     .72–.86, .86–1. Not live: held at 0 so nothing pokes through the
     clipped, shorter-than-full-height scene before the section engages. */
  --t1:clamp(0, var(--line, 0) / .28, 1);
  --t2:clamp(0, (var(--line, 0) - .28) / .30, 1);
  --b1:clamp(0, (var(--line, 0) - .58) / .14, 1);
  --b2:clamp(0, (var(--line, 0) - .72) / .14, 1);
  --b3:clamp(0, (var(--line, 0) - .86) / .14, 1);
}
.res.is-live{ --line:0; }
.res__line path{ fill:none; }
.res__line text{
  font-family:var(--font-mono);
  letter-spacing:.16em;
  text-transform:uppercase;
  fill:var(--on-ink-dim);
  transition:fill .6s var(--ease-soft);
}
.res__line .lbl{ font-size:13px; }
.res__line .blbl{ font-size:12px; letter-spacing:.1em; }
/* Labels and dots appear as the line reaches them. */
.res__line .lbl-1{  opacity:clamp(0, var(--line, 0) * 30, 1); }
.res__line .lbl-2{  opacity:clamp(0, (var(--line, 0) - .30) * 15, 1); }
.res__line .lbl-3{  opacity:clamp(0, (var(--line, 0) - .60) * 15, 1); }
.res__line .blbl-1{ opacity:clamp(0, (var(--line, 0) - .71) * 30, 1); }
.res__line .blbl-2{ opacity:clamp(0, (var(--line, 0) - .85) * 30, 1); }
.res__line .blbl-3{ opacity:clamp(0, (var(--line, 0) - .985) * 60, 1); }
.res.b5 .blbl-1{ fill:var(--green-glow); }

.res__line .dot{ fill:var(--green); transition:fill .6s var(--ease-soft); transform-box:fill-box; transform-origin:center; }
/* Each dot grows in as the line reaches it — scaled as well as faded, so
   nothing of it can show before then. */
.res__line .dot-0{ --k:clamp(0, var(--line, 0) * 30, 1); }
.res__line .dot-1{ --k:clamp(0, (var(--line, 0) - .71) * 30, 1); }
.res__line .dot-2{ --k:clamp(0, (var(--line, 0) - .85) * 30, 1); }
.res__line .dot-3{ --k:clamp(0, (var(--line, 0) - .985) * 60, 1); }
.res__line .dot{ opacity:var(--k, 0); transform:scale(var(--k, 0)); }
.res.b5 .dot-1{ fill:var(--green-glow); }

/* Perform, then Endure: the line draws itself forward with the scroll.
   Through the disruption it is disturbed — a signal under stress — and holds. */
.res__line .seg{
  stroke:var(--green);
  stroke-width:2;
  stroke-linecap:round;
  stroke-linejoin:round;
  stroke-dasharray:1;
}
/* Chrome leaves a stray fleck at the tip of a curved dashed path even at
   full dashoffset (pathLength rounding on the curve) — Safari doesn't.
   seg-1 is a straight line (H, no curve to round), so it isn't affected;
   seg-2 (the wavy disruption path) is, and gets an opacity gate on the
   same progress variable — it snaps to 1 well before the dash itself
   becomes visibly drawn, so the reveal still reads as governed by the
   dash, not the opacity. */
.res__line .seg-1{ stroke-dashoffset:calc(1 - var(--t1)); }
.res__line .seg-2{ stroke-dashoffset:calc(1 - var(--t2)); opacity:clamp(0, var(--t2, 0) * 200, 1); }

/* Endure: it holds, and brightens where it is hit — a wide, faint stroke
   under the line, no filter. */
.res__line .seg-glow{
  stroke:var(--green-glow);
  stroke-width:10;
  stroke-linecap:round;
  stroke-linejoin:round;
  stroke-dasharray:1;
  stroke-dashoffset:calc(1 - var(--t2));
  opacity:calc(var(--t2) * .3);
}
/* The field of stress around the disruption: a soft glow that rises with
   the wave. */
.res__line .field{ opacity:calc(var(--t2) * .22); }

/* Adapt: beyond the disruption, three trajectories, one after another. */
.res__line .br{
  stroke:var(--green);
  stroke-width:1.6;
  stroke-linecap:round;
  stroke-dasharray:1;
  transition:stroke .8s var(--ease-soft), stroke-width .8s var(--ease-soft);
}
/* Same Chrome curved-dash fleck as the .seg paths above; same fix. */
.res__line .br-1{ stroke-dashoffset:calc(1 - var(--b1)); opacity:clamp(0, var(--b1, 0) * 200, 1); }
.res__line .br-2{ stroke-dashoffset:calc(1 - var(--b2)); opacity:clamp(0, var(--b2, 0) * 200, 1); }
.res__line .br-3{ stroke-dashoffset:calc(1 - var(--b3)); opacity:clamp(0, var(--b3, 0) * 200, 1); stroke:var(--on-ink-dim); }
/* The closing line lights the adaptive path — the one we help build —
   and everything else on the diagram steps back. */
.res__line .br-glow{
  stroke:var(--green-glow);
  stroke-width:9;
  stroke-linecap:round;
  opacity:0;
  transition:opacity 1s var(--ease-soft) .2s;
}
.res.b5 .br-1{ stroke:var(--green-glow); stroke-width:2.4; }
.res.b5 .br-glow{ opacity:.28; }
.res.b5 .br-2,
.res.b5 .br-3{ stroke:var(--on-ink-dim); }
.res.b5 .res__line .seg,
.res.b5 .res__line .br-2,
.res.b5 .res__line .br-3,
.res.b5 .res__line .dot-0,
.res.b5 .res__line .dot-2,
.res.b5 .res__line .dot-3,
.res.b5 .res__line .lbl,
.res.b5 .res__line .blbl-2,
.res.b5 .res__line .blbl-3{ opacity:.35; transition:opacity .8s var(--ease-soft); }
.res.b5 .res__line .seg-glow,
.res.b5 .res__line .field{ opacity:.06; transition:opacity .8s var(--ease-soft); }

/* Progress through the pinned scene, so it never feels stuck. */
.res__progress{
  display:none;
  position:absolute;
  right:clamp(18px, 2.4vw, 34px);
  top:50%;
  width:1px;
  height:120px;
  transform:translateY(-50%);
  background:rgba(244,244,238,.14);
}
.res.is-live .res__progress{ display:block; }
.res__progress i{
  display:block;
  width:100%;
  height:100%;
  background:var(--green);
  transform-origin:50% 0;
  transform:scaleY(var(--p, 0));
}

/* ==========================================================================
   4. EXPERTISE — six doors in a row. Open one.
   ========================================================================== */

.exp__head{
  display:grid;
  grid-template-columns:1.1fr .9fr;
  gap:clamp(32px, 5vw, 80px);
  align-items:end;
  margin-bottom:44px;
}
.exp__head h2{ max-width:18ch; }
.exp__head .lede{ margin:0; max-width:46ch; }

/* An eyebrow with a trailing rule. */
.exp__hint{
  color:var(--slate-light);
  margin-bottom:16px;
  display:flex;
  align-items:center;
  gap:12px;
}
.exp__hint::after{ content:""; width:28px; height:1px; background:currentColor; opacity:.5; }

.doors{
  list-style:none;
  margin:0;
  padding:0;
  display:flex;
  gap:10px;
  height:clamp(620px, 80vh, 760px);
  --door-open-share:3.8;   /* the open door's flex share; the engine reads it */
}

.door{
  position:relative;
  flex:1 1 0;
  min-width:0;
  display:flex;
  flex-direction:column;
  cursor:pointer;
  border:1px solid var(--line);
  /* Radii larger than the box: the browser scales them down to a true
     semicircle at any width, so the head stays an arch as the door opens. */
  border-radius:999px 999px 0 0;
  background:var(--paper-2);
  overflow:hidden;
  isolation:isolate;
  transition:flex-grow .75s var(--ease-soft),
             border-color .5s var(--ease-soft),
             box-shadow .5s var(--ease-soft);
}
.door.is-open{
  flex-grow:var(--door-open-share);
  /* Content sits low in the door; the crown of the arch stays open. */
  justify-content:flex-end;
  border-color:rgba(179,185,53,.6);
  box-shadow:0 20px 60px -30px rgba(14,15,12,.18);
}
.door:not(.is-open):hover{ border-color:rgba(179,185,53,.6); }

/* Light behind the door: a hint on hover, full when open. */
.door::before{
  content:"";
  position:absolute;
  inset:0;
  z-index:0;
  background:radial-gradient(90% 60% at 50% 100%, #FFF6DF 0%, rgba(255,246,223,0) 72%);
  opacity:0;
  transition:opacity .8s var(--ease-soft);
  pointer-events:none;
}
.door.is-open::before{ opacity:1; }
.door:not(.is-open):hover::before{ opacity:.85; transition:opacity .3s var(--ease-soft); }

.door__head{
  appearance:none;
  background:none;
  border:0;
  margin:0;
  color:inherit;
  font:inherit;
  text-align:left;
  position:relative;
  z-index:2;
  display:flex;
  flex-direction:column;
  align-items:center;
  width:100%;
  flex:1 1 auto;                    /* fills the closed door: any click opens it */
  padding:26px 14px 22px;
}
.door__head:focus-visible{ outline:2px solid var(--green); outline-offset:-6px; border-radius:inherit; }
.door.is-open .door__head{
  flex:0 0 auto;
  align-items:flex-start;
  /* Clears the curve of the arch head, which is half the door's width. */
  padding:26% 40px 0;
  /* Laid out at the door's final width from the first frame, so the text
     never re-wraps while the door widens. The engine sets --door-open-w. */
  width:calc(var(--door-open-w, 100%) - 2px);
}

/* Number sits in the crown of the arch. */
.door__num{
  font-family:var(--font-mono);
  font-size:12px;
  letter-spacing:.2em;
  color:var(--slate-light);
  transition:color .4s var(--ease-soft);
}
.door.is-open .door__num,
.door:not(.is-open):hover .door__num{ color:var(--green-deep); }

/* Closed: the title runs down the door. Open: it sits under the crown. */
.door__title{
  writing-mode:vertical-rl;
  font-family:var(--font-display);
  font-weight:400;
  font-size:clamp(17px, 1.3vw, 20px);
  letter-spacing:-.01em;
  line-height:1.15;
  color:var(--ink);
  margin:0;
  padding-top:44px;
  animation:door-title-v .8s var(--ease-soft) both;
}
.door.is-open .door__title{
  writing-mode:horizontal-tb;
  font-size:clamp(24px, 2.2vw, 32px);
  letter-spacing:-.02em;
  line-height:1.15;
  margin:14px 0 0;
  padding:0;
  text-wrap:balance;
  animation:door-title-h .95s var(--ease-soft) both;
}
/* The title changes direction the moment the door is clicked. Holding it
   invisible while the door widens hides the swap. Two names for the same
   keyframes, because an animation only restarts when its name changes. */
@keyframes door-title-v{
  0%, 55% { opacity:0; }
  100%    { opacity:1; }
}
@keyframes door-title-h{
  0%, 55% { opacity:0; }
  100%    { opacity:1; }
}

/* Open/close mark: a drawn plus at the foot of a closed door, a cross on
   an open one (phones only — on desktop the open door hides it). */
.door__mark{
  position:relative;
  flex:none;
  width:11px;
  height:11px;
  margin-top:auto;
  transition:opacity .3s var(--ease-soft), transform .4s var(--ease-soft);
}
.door__mark::before,
.door__mark::after{
  content:"";
  position:absolute;
  left:0; top:50%;
  width:100%; height:1.25px;
  background:var(--slate-light);
  transition:transform .4s var(--ease-soft), background .3s var(--ease-soft);
}
.door__mark::after{ transform:rotate(90deg); }
.door.is-open .door__mark{ opacity:0; position:absolute; }

.door__panel{
  position:relative;
  z-index:2;
  flex:0 0 0px;
  min-height:0;
  display:flex;
  flex-direction:column;
  padding:0 40px;
  overflow:hidden;
  opacity:0;
  visibility:hidden;
  transform:translate3d(0, 12px, 0);
  transition:opacity .5s var(--ease-soft), transform .5s var(--ease-soft), visibility 0s .5s;
}
.door.is-open .door__panel{
  flex:0 1 auto;
  width:calc(var(--door-open-w, 100%) - 2px);
  padding-top:18px;
  padding-bottom:12px;
  opacity:1;
  visibility:visible;
  transform:none;
  transition:opacity .55s var(--ease-soft) .5s, transform .55s var(--ease-soft) .5s, visibility 0s;
}
.door__panel-in{
  min-height:0;
  overflow:auto;
  scrollbar-width:thin;
  padding-right:6px;
}
.door__caption{
  font-family:var(--font-display);
  font-size:clamp(16px, 1.2vw, 18px);
  line-height:1.4;
  color:var(--green-deep);
  margin:0 0 16px;
}
.door__panel p{
  font-size:14px;
  line-height:1.62;
  color:var(--slate);
  margin:0 0 12px;
}
.door__panel p:last-child{ margin-bottom:0; }

/* ==========================================================================
   5. POINT OF VIEW — the adaptive enterprise
   ========================================================================== */

.pov__lead h2{
  font-size:clamp(28px, 3.4vw, 46px);
  max-width:14ch;
}
.pov__lead h2 em{ font-style:normal; color:var(--green); display:block; }

/* Body copy first (low specificity), then the statement on top of it. */
.pov__body > :where(p){
  margin:0 0 30px;
  font-size:clamp(15px, 1.05vw, 16.5px);
  line-height:1.75;
  max-width:58ch;
}
.pov__statement{
  font-family:var(--font-display);
  font-weight:300;
  font-size:clamp(24px, 2.5vw, 36px);
  line-height:1.3;
  letter-spacing:-.02em;
  color:var(--on-ink);
  margin:0 0 34px;
  max-width:none;
  text-wrap:balance;
}

/* "Read the full point of view" — a quiet text button, no rules. */
.pov__more{ margin:0 0 30px; }
.pov__more-btn{
  appearance:none;
  background:none;
  border:0;
  margin:0;
  padding:4px 0;
  display:inline-flex;
  align-items:center;
  gap:14px;
  text-align:left;
  font-family:var(--font-mono);
  font-size:11.5px;
  letter-spacing:.2em;
  text-transform:uppercase;
  color:var(--green);
  transition:color .3s var(--ease-soft);
}
.pov__more-btn span{
  background-image:linear-gradient(currentColor, currentColor);
  background-repeat:no-repeat;
  background-size:0 1px;
  background-position:0 100%;
  padding-bottom:3px;
  transition:background-size .4s var(--ease-soft);
}
.pov__more-btn:hover{ color:var(--green-glow); }
.pov__more-btn:hover span{ background-size:100% 1px; }
.pov__more-btn:focus-visible{ outline:2px solid var(--green); outline-offset:4px; }
.pov__more-btn i{
  position:relative;
  width:12px;
  height:12px;
  flex:none;
  transition:transform .45s var(--ease-soft);
}
.pov__more-btn i::before,
.pov__more-btn i::after{
  content:"";
  position:absolute;
  left:0; top:50%;
  width:100%; height:1px;
  background:currentColor;
  transition:transform .45s var(--ease-soft);
}
.pov__more-btn i::after{ transform:rotate(90deg); }
.pov__more.is-open .pov__more-btn i::after{ transform:rotate(0deg); }

.pov__more-panel{
  display:grid;
  grid-template-rows:0fr;
  transition:grid-template-rows .65s var(--ease-soft);
}
.pov__more.is-open .pov__more-panel{ grid-template-rows:1fr; }
.pov__more-in{
  min-height:0;
  overflow:hidden;
  opacity:0;
  transform:translate3d(0, 8px, 0);
  visibility:hidden;
  transition:opacity .4s var(--ease-soft), transform .4s var(--ease-soft), visibility 0s .65s;
}
.pov__more.is-open .pov__more-in{
  opacity:1;
  transform:none;
  visibility:visible;
  transition:opacity .5s var(--ease-soft) .2s, transform .5s var(--ease-soft) .2s, visibility 0s;
}
.pov__more-in p{
  font-size:15px;
  line-height:1.75;
  margin:0 0 16px;
  max-width:58ch;
}
.pov__more-in p:first-child{ margin-top:22px; }
.pov__more-in p:last-child{ margin-bottom:6px; }

.pov__note{
  margin:0;
  font-family:var(--font-mono);
  font-size:11.5px;
  letter-spacing:.06em;
  color:var(--on-ink-dim);
}

/* ==========================================================================
   6. HOW WE ENGAGE — six ways in. The one in front of you lights up.
   ========================================================================== */

.engage__grid{ --split:.85fr 1.15fr; }
.engage__side h2{ margin-bottom:24px; max-width:14ch; }
.engage__role{ font-size:15.5px; margin:0 0 20px; color:var(--slate); }

.tags{
  list-style:none;
  margin:0;
  padding:0;
  display:flex;
  flex-wrap:wrap;
  gap:8px;
}
.tags li{
  font-family:var(--font-mono);
  font-size:11.5px;
  letter-spacing:.04em;
  padding:8px 14px;
  border:1px solid var(--line);
  border-radius:999px;
  color:var(--ink);
}

.starts{ list-style:none; margin:0; padding:0; }
.starts li{
  position:relative;
  padding:26px 0 26px 44px;
  border-top:1px solid var(--line);
  font-family:var(--font-display);
  font-weight:400;
  font-size:clamp(19px, 1.9vw, 26px);
  line-height:1.35;
  letter-spacing:-.015em;
  color:rgba(83,84,76,.38);
  transition:color .5s var(--ease-soft);
}
.starts li:last-child{ border-bottom:1px solid var(--line); }
/* The reveal rule sets its own transition list; this keeps colour in it. */
.js-reveal .starts li{
  transition:opacity .85s var(--ease-out), transform .85s var(--ease-out), color .5s var(--ease-soft);
  transition-delay:calc(var(--i, 0) * 90ms), calc(var(--i, 0) * 90ms), 0s;
}
/* A small arch beside each line; the current one is lit. */
.starts li i{
  position:absolute;
  left:0;
  top:34px;
  width:14px;
  height:18px;
  border:1px solid var(--line);
  border-bottom:0;
  border-radius:7px 7px 0 0;
  transition:border-color .5s var(--ease-soft), background .5s var(--ease-soft);
}
.starts li.is-current{ color:var(--ink); }
.starts li.is-current i{
  border-color:var(--green);
  background:linear-gradient(to top, rgba(179,185,53,.4), rgba(179,185,53,0));
}

/* ==========================================================================
   7. ABOUT — the firm. Pinned title, narrative that lights as it is read,
   three pillars.
   ========================================================================== */

.about{ border-top:1px solid var(--line); }
.about__grid{ --split:.8fr 1.2fr; }
.about__side h2{ margin-bottom:26px; max-width:12ch; }
.about__where{
  margin:0;
  font-family:var(--font-mono);
  font-size:11.5px;
  letter-spacing:.16em;
  text-transform:uppercase;
  color:var(--ink);
}
.about__where span{ color:var(--slate-light); margin-right:10px; }

/* Kinetic reading: the engine wraps each word in a span and lights them
   in order as the paragraph passes the middle of the screen. */
.about__read p{
  margin:0 0 1.1em;
  font-family:var(--font-display);
  font-weight:300;
  font-size:clamp(22px, 2.3vw, 32px);
  line-height:1.4;
  letter-spacing:-.02em;
  color:var(--ink);
}
.about__read p:last-child{ margin-bottom:0; }
.about__read .w{
  /* .28 alpha read 1.5:1 against paper — too faint for low-vision readers,
     not just "dim". .85 keeps the dim/lit contrast the effect needs while
     clearing WCAG AA's 4.5:1 minimum (comes out ~5:1). */
  color:rgba(83,84,76,.85);
  transition:color .5s var(--ease-soft);
}
.about__read .w.is-lit{ color:var(--ink); }

/* Three pillars: quiet cards that light on hover, and, on a phone, when
   they pass the middle of the screen (.is-current, styled in the narrow
   block below). */
.pillars{
  list-style:none;
  margin:clamp(56px, 7vw, 96px) 0 0;
  padding:0;
  display:grid;
  grid-template-columns:repeat(3, 1fr);
  gap:14px;
}
.pillar{
  position:relative;
  padding:30px 28px 34px;
  border:1px solid var(--line);
  border-radius:3px;
  background:var(--paper-2);
  overflow:hidden;
  isolation:isolate;
  cursor:default;
  transition:border-color .5s var(--ease-soft), transform .5s var(--ease-soft), box-shadow .5s var(--ease-soft);
}
.pillar::before{
  content:"";
  position:absolute;
  inset:0;
  z-index:0;
  background:radial-gradient(90% 70% at 50% 110%, #FFF6DF 0%, rgba(255,246,223,0) 70%);
  opacity:0;
  transition:opacity .6s var(--ease-soft);
  pointer-events:none;
}
.pillar:hover{
  border-color:rgba(179,185,53,.7);
  transform:translate3d(0, -3px, 0);
  box-shadow:0 24px 50px -30px rgba(14,15,12,.28);
}
.pillar:hover::before{ opacity:1; }
.pillar__n{
  position:relative;
  z-index:1;
  margin-bottom:22px;
  transition:color .4s var(--ease-soft);
}
.pillar:hover .pillar__n{ color:var(--green-deep); }
.pillar p{
  position:relative;
  z-index:1;
  margin:0;
  font-family:var(--font-display);
  font-weight:400;
  font-size:clamp(19px, 1.7vw, 24px);
  line-height:1.3;
  letter-spacing:-.015em;
  color:var(--ink);
  text-wrap:balance;
}

/* ==========================================================================
   8. CONTACT
   ========================================================================== */

.contact__head{
  max-width:62ch;
  margin-bottom:clamp(40px, 5vw, 64px);
}
.contact h2{
  font-size:clamp(28px, 3.4vw, 46px);
  max-width:20ch;
  margin-bottom:22px;
}
.contact .lede{ margin:0; max-width:56ch; }

.cform__grid{
  display:grid;
  grid-template-columns:1fr 1fr;
  gap:0 clamp(32px, 5vw, 72px);
}
.cform__grid > div{ display:flex; flex-direction:column; }
.field{ display:block; margin-bottom:22px; }
.field--grow{ flex:1; display:flex; flex-direction:column; }
.field--grow textarea{ flex:1; }
.field span{
  display:block;
  font-family:var(--font-mono);
  font-size:10.5px;
  letter-spacing:.2em;
  text-transform:uppercase;
  color:var(--on-ink-dim);
  margin-bottom:8px;
}
.field input,
.field textarea{
  width:100%;
  background:transparent;
  border:0;
  border-bottom:1px solid rgba(244,244,238,.16);
  border-radius:0;
  padding:8px 0 14px;
  color:var(--on-ink);
  font-family:var(--font-display);
  font-weight:300;
  font-size:19px;
  line-height:1.45;
  letter-spacing:-.01em;
  outline:none;
  -webkit-appearance:none;
  transition:border-color .3s var(--ease-soft);
}
/* The message box is tall, so it carries a ghost frame — barely there —
   with the placeholder anchoring the top-left. */
.field textarea{
  border:1px solid rgba(244,244,238,.1);
  border-radius:2px;
  padding:14px 16px;
  resize:none;
  min-height:172px;
}
.field input::placeholder,
.field textarea::placeholder{ color:rgba(244,244,238,.28); font-weight:300; }
.field input:focus{ border-bottom-color:var(--green); }
.field textarea:focus{ border-color:rgba(179,185,53,.5); }
.field input:user-invalid{ border-bottom-color:#B9553F; }

.cform__foot{
  display:flex;
  align-items:center;
  gap:24px;
  flex-wrap:wrap;
  margin-top:26px;
}
.btn{
  appearance:none;
  border:1px solid var(--green);
  background:var(--green);
  color:var(--ink);
  font-family:var(--font-mono);
  font-size:11.5px;
  letter-spacing:.14em;
  text-transform:uppercase;
  padding:14px 22px;
  border-radius:2px;
  display:inline-flex;
  align-items:center;
  gap:12px;
  transition:background .3s var(--ease-soft), border-color .3s var(--ease-soft);
}
.btn svg{ width:16px; height:16px; transition:transform .3s var(--ease-soft); }
.btn:hover{ background:var(--green-glow); border-color:var(--green-glow); }
.btn:hover svg{ transform:translateX(4px); }
.btn:focus-visible{ outline:2px solid var(--on-ink); outline-offset:3px; }

/* Where the firm is, and what time it is there. */
.cform__where{
  margin:0;
  display:flex;
  gap:14px;
  align-items:baseline;
  font-family:var(--font-mono);
  font-size:11.5px;
  letter-spacing:.14em;
  text-transform:uppercase;
  color:var(--on-ink-dim);
}
.cform__time{ color:var(--green); }
.cform__time:empty{ display:none; }

/* ==========================================================================
   Finale — contact + footer revealed from beneath the page
   --------------------------------------------------------------------------
   The wrapper is sticky to the bottom of the viewport and sits under the
   sections above (which are opaque and z-index 10). As the last section
   scrolls away, the finale is uncovered rather than scrolled into view.
   Only on screens tall enough to hold it in one view.
   ========================================================================== */

.finale{ position:relative; z-index:1; }
/* The engine adds .has-reveal only when the finale fits under the nav in
   one screen; otherwise it scrolls in like any other block. */
.has-reveal .finale{ position:sticky; bottom:0; }
.has-reveal .finale .contact{ padding-top:clamp(56px, 7vh, 88px); padding-bottom:clamp(36px, 5vh, 64px); }
.has-reveal .contact__head{ margin-bottom:clamp(28px, 4vh, 44px); }
.has-reveal .contact h2{ font-size:clamp(26px, 2.8vw, 38px); margin-bottom:16px; }
.has-reveal .field{ margin-bottom:16px; }
.has-reveal .field textarea{ min-height:150px; }
.has-reveal .cform__foot{ margin-top:18px; }
.has-reveal .foot{ padding:24px 0; }
/* A soft edge on the page as it lifts off the finale. */
.has-reveal .about::after{
  content:"";
  position:absolute;
  left:0; right:0; bottom:-1px;
  height:1px;
  box-shadow:0 18px 60px 20px rgba(0,0,0,.35);
  pointer-events:none;
}

/* ==========================================================================
   Footer
   ========================================================================== */

.foot{
  position:relative;
  z-index:10;
  overflow:hidden;
  background:var(--ink);
  border-top:2px solid var(--green);   /* the green edge from the identity */
  padding:32px 0;
  color:var(--on-ink-dim);
  font-family:var(--font-mono);
  font-size:11.5px;
  letter-spacing:.06em;
}
.foot .wrap{
  position:relative;
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:16px;
  flex-wrap:wrap;
}
/* The mark, large and quiet, standing in the corner. */
.foot__mark{
  position:absolute;
  right:0;
  bottom:-38%;
  width:clamp(150px, 15vw, 220px);
  height:auto;
  opacity:.11;
  pointer-events:none;
}
/* The wordmark sits low in its own image (the mark glyph rides above it),
   so the flex box's geometric center isn't the text's optical center —
   nudge up to line up with the tagline/copyright baseline beside it. */
.foot__logo{ display:block; transform:translateY(-6.1px); }
.foot img{ height:42px; width:auto; opacity:.85; transition:opacity .3s var(--ease-soft); }
.foot__logo:hover img{ opacity:1; }
.foot p{ margin:0; }

/* ==========================================================================
   Contact modal — the CTA opens the real form in place instead of scrolling
   the page to it (that scroll used to fast-forward the hero and the
   approach graph through their whole scroll-driven motion). The form node
   itself is moved here and back by novalis.js; nothing here is a copy.
   ========================================================================== */

.modal{
  position:fixed;
  inset:0;
  z-index:90;
  display:flex;
  justify-content:flex-end;
  align-items:stretch;
  visibility:hidden;
  pointer-events:none;
}
.modal.is-open{ visibility:visible; pointer-events:auto; }

.modal__backdrop{
  position:absolute;
  inset:0;
  cursor:pointer;
  background:rgba(7,8,5,.72);
  /* A flat dark wash barely reads against the site's own near-black
     sections; a blur separates the modal from the page regardless of
     which section (dark or paper) sits behind it. */
  backdrop-filter:blur(6px);
  -webkit-backdrop-filter:blur(6px);
  opacity:0;
  transition:opacity .35s var(--ease-soft);
}
.modal.is-open .modal__backdrop{ opacity:1; }

/* A right-docked drawer, not a centered dialog — full viewport height, so
   the message box (below) always has real room to write in, on any
   screen. It slides in from off-canvas rather than fading up in place. */
.modal__panel{
  position:relative;
  z-index:1;
  display:flex;
  flex-direction:column;
  width:min(480px, 100%);
  height:100%;
  overflow-y:auto;
  background:var(--ink);
  color:var(--on-ink);
  border:0;
  border-left:1px solid var(--line-dark);
  padding:clamp(28px, 4vw, 44px);
  opacity:0;
  transform:translate3d(100%, 0, 0);
  transition:opacity .35s var(--ease-soft), transform .4s var(--ease-soft);
}
.modal.is-open .modal__panel{ opacity:1; transform:none; }
.modal__panel .eyebrow{ margin-bottom:18px; }

/* A plain, static "X" — unlike the hamburger it never needs a second
   state, so the two bars just sit crossed rather than morphing. */
.modal__close{
  position:absolute;
  top:18px; right:18px;
  width:32px; height:32px;
  display:grid;
  place-items:center;
  background:none;
  border:0;
  cursor:pointer;
}
.modal__close span{
  position:absolute;
  width:16px;
  height:1px;
  background:var(--on-ink-dim);
  transition:background .3s var(--ease-soft);
}
.modal__close span:first-child{ transform:rotate(45deg); }
.modal__close span:last-child{  transform:rotate(-45deg); }
.modal__close:hover span{ background:var(--on-ink); }
.modal__close:focus-visible{ outline:2px solid var(--on-ink); outline-offset:4px; }

/* The form is only ever revealed on scroll (data-reveal) when it sits in
   its normal place at the bottom of the page. Inside the modal it should
   just be there, fully visible, with none of that competing motion — the
   panel's own open animation is the only motion the user should see. */
.modal__body [data-reveal]{ opacity:1 !important; transform:none !important; transition:none !important; }
/* The form's native two-column grid needs the section's full width to
   breathe; a 480px drawer reads as one column instead. The drawer's
   full viewport height means there is real room to spend, so rather
   than just stacking the fields, the message box is left to grow and
   fill whatever space remains below name/organization/email — it
   stays a proper size to write in, on any screen. */
.modal__body{ flex:1; min-height:0; display:flex; flex-direction:column; }
.modal__body .cform{ flex:1; min-height:0; display:flex; flex-direction:column; }
.modal__body .cform__grid{ flex:1; min-height:0; display:flex; flex-direction:column; }
.modal__body .cform__grid > div:last-child{ flex:1; min-height:0; }

/* ==========================================================================
   Narrow screens
   ========================================================================== */

@media (max-width:880px){
  :root{ --nav-h:70px; --layout:narrow; }

  .hero.is-live{ height:300vh; }

  /* Stacked instead of side by side: the doorway stands in the upper half
     and the copy sits underneath it, so the two never overlap. */
  .hero__stage{
    --door-x:50%;
    --door-y:40%;
    --door-w:clamp(128px, 40vw, 170px);
  }
  /* The copy starts a clear step below the door's foot (40% of the screen
     plus a margin — in viewport units, since % padding follows width), on
     a dark floor so the light never sits behind it. */
  .hero__copy{
    align-items:flex-start;
    padding-top:calc(40svh + 8vh);
    padding-bottom:9vh;
    background:linear-gradient(to bottom, rgba(10,11,8,0) 40%, rgba(10,11,8,.86) 56%, rgba(10,11,8,.94) 100%);
  }
  .hero h1{ font-size:clamp(26px, 7vw, 36px); max-width:none; margin-bottom:18px; }
  .hero__lede{ font-size:14px; max-width:none; }
  .hero .eyebrow{ margin-bottom:16px; }
  .hero__cue{ bottom:18px; }

  .who__cols{ grid-template-columns:1fr; gap:22px; }

  .nav__logo{ height:34px; width:150px; }
  /* Button and menu: the same height, square menu, one clear gap. */
  .nav__cta{ height:40px; display:inline-flex; align-items:center; font-size:10.5px; padding:0 16px; margin-left:auto; }
  .nav__menu{ display:block; width:40px; height:40px; margin-left:10px; }
  /* The section links become a panel under the bar. */
  .nav__links{
    position:fixed;
    left:0; right:0;
    top:var(--nav-h);
    bottom:0;
    margin:0;
    padding:28px var(--gutter) 40px;
    flex-direction:column;
    gap:0;
    /* Solid, not translucent: a semi-transparent fixed panel over the
       hero's WebGL canvas lets the canvas's own compositor layer bleed
       through in Chrome, no matter how high the alpha — the menu reads
       as see-through with the hero still faintly visible behind it. */
    background:var(--ink);
    opacity:0;
    visibility:hidden;
    transform:translate3d(0, -8px, 0);
    transition:opacity .35s var(--ease-soft), transform .35s var(--ease-soft), visibility 0s .35s;
  }
  .nav.is-open .nav__links{
    opacity:1;
    visibility:visible;
    transform:none;
    transition:opacity .35s var(--ease-soft), transform .35s var(--ease-soft), visibility 0s;
  }
  .nav__links a{
    font-family:var(--font-display);
    font-size:28px;
    font-weight:300;
    letter-spacing:-.02em;
    color:var(--on-ink);
    padding:16px 0;
    border-bottom:1px solid var(--line-dark);
  }
  .nav.is-paper .nav__links{ background:var(--paper); }
  .nav.is-paper .nav__links a{ color:var(--ink); border-bottom-color:var(--line); }
  .nav__links-cta{
    display:block;
    margin-top:28px;
    border:0 !important;
    font-family:var(--font-mono) !important;
    font-size:12px !important;
    letter-spacing:.16em;
    text-transform:uppercase;
    color:var(--green) !important;
  }
  .nav.is-open{ opacity:1 !important; }

  /* --- Contact modal: already a full-width sheet below 480px (see the
       drawer rules above); just leave room under the nav bar. --------- */
  .modal__panel{
    border:0;
    padding:calc(var(--nav-h) + 24px) var(--gutter) 32px;
  }

  /* --- Approach: line above, copy below ---------------------------------- */
  .res.is-live{ height:560vh; }
  .res__grid{
    grid-template-columns:1fr;
    grid-template-rows:auto 1fr;
    gap:0;
    align-items:start;
  }
  /* The scene sizes to its content: a band of words, a breath, the graph.
     The extra top clearance also settles the graph lower on the screen —
     the copy below stays centered in what's left, so pushing the graph
     down eats into that gap instead of just adding empty space above it. */
  .res__scene{ grid-row:1; height:auto; padding-top:72px; }
  /* Only the words with a phone row (.m) show, each on its --ym row. The
     band runs to the screen edges so words fade out at the sides rather
     than being cut at the column. */
  .res__drift{
    position:relative;
    height:118px;
    left:calc(var(--gutter) * -1);
    right:auto;
    width:calc(100% + var(--gutter) * 2);
    -webkit-mask-image:linear-gradient(to right, transparent, #000 18%, #000 82%, transparent);
            mask-image:linear-gradient(to right, transparent, #000 18%, #000 82%, transparent);
  }
  .res__drift span{ top:calc(var(--ym, var(--y, 0)) * 100%); }
  .res__drift span:not(.m){ display:none; }
  .res__drift .d2{ font-size:13px; }
  .res__drift .d3{ font-size:15px; }
  /* The graph follows in flow, full width, with labels and strokes scaled
     up to stay readable at this size. */
  .res__line{ position:relative; bottom:auto; max-height:none; margin-top:14px; }
  .res__line .lbl{ font-size:20px; }
  .res__line .blbl{ font-size:18px; }
  .res__line .seg,
  .res__line .br{ stroke-width:3.2; }
  .res__line .dot{ r:5; }
  .res__line .seg-glow{ stroke-width:16; }
  .res__line .br-glow{ stroke-width:14; }
  .res__copy{ grid-row:2; align-self:center; padding:3vh 0; }
  .res__beat h2{ font-size:clamp(24px, 6.5vw, 32px); margin-bottom:16px; }
  .res__big{ font-size:clamp(21px, 5.6vw, 27px); }
  .res__p{ font-size:14px; }
  .pr{ padding:12px 0; column-gap:16px; row-gap:4px; }
  .pr h3{ font-size:20px; }
  .pr .res__p{ font-size:13.5px; line-height:1.55; }
  .res__quote{ font-size:clamp(21px, 5.6vw, 26px); }
  .res__progress{ right:14px; height:90px; }

  /* --- Expertise: a vertical list of doors ------------------------------- */
  .exp__head{ grid-template-columns:1fr; gap:18px; align-items:start; margin-bottom:28px; }
  .doors{ flex-direction:column; height:auto; gap:10px; }
  .door{ border-radius:20px 20px 0 0; flex:none; }
  .door.is-open{ flex-grow:0; justify-content:flex-start; }
  .door__head,
  .door.is-open .door__head{
    flex:none;
    flex-direction:row;
    align-items:center;
    gap:16px;
    padding:18px 20px;
    width:auto;
  }
  .door.is-open .door__head{ padding-bottom:6px; }
  .door__title,
  .door.is-open .door__title{
    writing-mode:horizontal-tb;
    font-size:17px;
    margin:0;
    padding:0;
    animation:none;
    text-wrap:balance;
  }
  .door.is-open .door__title{ font-size:19px; }
  .door__mark{ margin:0 0 0 auto; align-self:center; }
  .door.is-open .door__mark{ opacity:1; position:static; transform:rotate(45deg); }
  .door.is-open .door__mark::before,
  .door.is-open .door__mark::after{ background:var(--green-deep); }
  .door__panel{
    display:grid;
    grid-template-rows:0fr;
    flex:none;
    padding:0 20px;
    opacity:1;
    visibility:visible;
    transform:none;
    transition:grid-template-rows .5s var(--ease-soft), padding .5s var(--ease-soft);
  }
  .door.is-open .door__panel{
    grid-template-rows:1fr;
    width:auto;
    padding-bottom:22px;
    transition:grid-template-rows .5s var(--ease-soft), padding .5s var(--ease-soft);
  }
  .door__panel-in{ min-height:0; height:auto; overflow:hidden; padding-right:0; }

  /* --- Split sections: single column ------------------------------------- */
  .split{ grid-template-columns:1fr; gap:36px; }
  .split__side{ position:static; }
  .about__read p{ font-size:clamp(19px, 5.4vw, 24px); }
  .pillars{ grid-template-columns:1fr; gap:10px; }
  .pillar{ padding:22px 20px 24px; }
  /* Stacked, the pillar in the middle of the screen takes the hover state. */
  .pillar.is-current{
    border-color:rgba(179,185,53,.7);
    transform:translate3d(0, -3px, 0);
    box-shadow:0 24px 50px -30px rgba(14,15,12,.28);
  }
  .pillar.is-current::before{ opacity:1; }
  .pillar.is-current .pillar__n{ color:var(--green-deep); }
  .starts li{ padding:20px 0 20px 34px; }
  .starts li i{ top:26px; }
  .cform__grid{ grid-template-columns:1fr; }
  .cform__where{ margin-left:0; width:100%; }
}

/* Tablets (single-column layout, but wide): the diagram must not grow to
   the full width, headings may run wider than on a phone, the door a
   little larger. */
@media (min-width:560px) and (max-width:880px){
  .hero__stage{ --door-w:clamp(160px, 30vw, 230px); }
  .res__beat{ max-width:40rem; }
  .res__line{ width:min(100%, 620px); margin-left:auto; margin-right:auto; display:block; }
  .res__drift{ height:150px; }
  .res__line .lbl{ font-size:16px; }
  .res__line .blbl{ font-size:14.5px; }
  .res__line .seg,
  .res__line .br{ stroke-width:2.4; }
  .res__line .dot{ r:4; }
  .res__line .seg-glow{ stroke-width:12; }
  .res__line .br-glow{ stroke-width:10; }
  .res__drift .d2{ font-size:15px; }
  .res__drift .d3{ font-size:17px; }
  .who__title,
  .pov__lead h2,
  .engage__side h2,
  .about__side h2,
  .contact h2{ max-width:22ch; }
  .exp__head h2{ max-width:24ch; }
}

/* Portrait tablets on the desktop layout (iPad Pro): the door grows and
   sits level with the copy, so the two read as one composition. */
@media (min-width:881px) and (max-width:1200px) and (orientation:portrait){
  .hero__stage{ --door-x:69%; --door-y:64%; --door-w:clamp(230px, 28vw, 330px); }
  .hero h1{ max-width:min(19ch, 50vw); }
}

/* The full call to action does not fit beside the logo on a phone. */
.nav__cta .is-narrow{ display:none; }
@media (max-width:560px){
  .nav__cta .is-wide{ display:none; }
  .nav__cta .is-narrow{ display:inline; }
}

/* ==========================================================================
   Reduced motion — the engine leaves the scenes un-armed (no .is-live) and
   marks all copy revealed, so only the remaining transitions need turning off.
   ========================================================================== */

@media (prefers-reduced-motion:reduce){
  .hero__cue i{ animation:none; opacity:1; }
  .js-reveal [data-reveal]{ transition:none; }
  .about__read .w,
  .pillar,
  .door,
  .door__panel,
  .door__title,
  .door::before,
  .pov__more-panel,
  .pov__more-in,
  .pov__more-btn i,
  .pov__more-btn i::before,
  .pov__more-btn i::after,
  .starts li,
  .starts li i,
  .modal__backdrop,
  .modal__panel{ transition:none; animation:none; }
}
