/* ============================================================
   VIREVO — Cinematic Landing Experience
   Palette : bg #f6f2e9 · grid #c7a45d · ink #1e3727
   Type    : Poppins (display, body, labels) · Bebas Neue (page titles)
   ============================================================ */
:root{
  --bg:      #f6f2e9;
  --grid:    #c7a45d;
  --ink:     #1e3727;
  --ink-soft:rgba(30,55,39,.62);
  --cream:   #f6f2e9;
  --gold-dim:rgba(199,164,93,.35);
  --gold-faint:rgba(199,164,93,.16);
  --ink-mute:#e4dfd3;          /* pale bone — giant vision type */
  --stone:#c1b9ae;             /* warm stone — showcase CTA button */
  /* Full fallback stacks. These mattered the moment the webfonts failed to
     arrive: 'Calibri' and 'Segoe UI' are Windows faces and 'Poppins' comes
     from Google, so on a Mac with the CDN unreachable every one of these
     chains ran out and the browser fell back to its default — Times. A
     system sans at the end keeps the page looking like itself even when
     nothing external loads. */
  --font-display:'Poppins',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,
                 'Helvetica Neue',Arial,sans-serif;
  /* Poppins, not Carlito. virevo-type-scale.md establishes that the system
     has two typefaces and only two — Bebas Neue for the page title, Poppins
     for everything else. The note at .hk-copy below already recorded that
     Carlito was effectively vestigial: every real paragraph on the site
     (#intro .wd-body, .cw-bio, .ft-col p) overrode it to Poppins, so it only
     ever surfaced through the bare <body> inheritance. */
  --font-body:'Poppins',-apple-system,BlinkMacSystemFont,'Segoe UI',
              Roboto,'Helvetica Neue',Arial,sans-serif;
  /* The label face. Poppins, not a monospace — virevo-type-scale.md is
     explicit that the system has two typefaces and only two: Bebas Neue for
     the page title and Poppins for everything else. The small uppercase
     label it calls the "Tile Sub-heading" is Poppins 600 at +0.09em, which
     is the role this token fills. The variable keeps its old name so the
     ~80 rules that reference it did not all have to be rewritten; it is a
     label/eyebrow token now, not a monospace one. */
  --font-mono:'Poppins',-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif;
  --nav-h:72px;
  /* the intro scene's floor — the corners of its radial wash */
  --ink-deep:#0c1710;
  /* one notch darker than --ink, not all the way to --ink-deep/black — the
     wordmark's own mid-gradient stop, promoted here for #bii so the two
     stay the same value on purpose */
  --ink-mid:#16291c;
  /* TWO GREENS, BECAUSE ONE CANNOT SERVE BOTH GROUNDS.
     --mint is the accent for type sitting ON the dark surfaces; it was
     already in the build as a raw hex on the dictionary numerals and is
     promoted here so there is one place to change it. It is deliberately
     NOT reused on cream: #86d9a1 against --bg measures about 1.7:1, far
     under the 4.5:1 body text needs, so light grounds get --mint-deep
     instead, which clears 7:1. Same accent, two grounds, two values. */
  --mint:#86d9a1;
  --mint-deep:#1a5c3a;
}
/* One viewport height for the whole build.
   It has to be the LARGE viewport — the height with the address bar retracted.
   dvh was wrong because it tracks the bar and moves mid-scroll; freezing
   innerHeight at load was wrong the other way, because at load the bar is
   still showing, so every pinned layer was sized to the short viewport and
   came up an address-bar short once the bar slid away. lvh is that larger
   height and, crucially, is constant — it does not change as the bar moves.
   With the bar visible the page simply extends a little under it, which is
   the normal behaviour and invisible in practice.
   The 100vh fallback is deliberate, not lazy: on mobile, vh has always
   resolved against the large viewport too. JS reads this value rather than
   setting it, so both sides measure from exactly the same number. */
:root{--vh:100vh;}
@supports (height:100lvh){ :root{--vh:100lvh;} }

*{margin:0;padding:0;box-sizing:border-box;}
html{scroll-behavior:auto;}
/* NO SIDEWAYS SCROLL, AND NOT AT THE COST OF STICKY.
   Several things here are 100vw wide or hang off a negative margin, and
   100vw counts the scrollbar — so on any page with a vertical bar they
   overrun by its width and the browser offers a horizontal one. Scroll it
   and every section appears cut off down its left edge, which is what was
   happening in the footer.

   clip, not hidden. overflow:hidden turns the element into a scroll
   container, and a scroll container between a sticky element and its
   ancestor breaks the stickiness — this whole page is built on sticky
   stages, so hidden would have quietly disabled most of it. clip refuses
   the overflow without creating that container. */
html,body{overflow-x:clip;}
html,body{background:var(--bg);color:var(--ink);}
body{
  font-family:var(--font-body);
  font-size:17px;line-height:1.6;
  /* clip, not hidden — overflow-x:hidden makes the body a scroll container,
     which stops position:sticky from pinning against the viewport and breaks
     the EDO card section. clip suppresses sideways overflow without that. */
  overflow-x:clip;
  -webkit-font-smoothing:antialiased;
}
::selection{background:var(--grid);color:var(--bg);}
img,video{display:block;max-width:100%;}
a{color:inherit;text-decoration:none;}
h1,h2,h3,h4,.display{font-family:var(--font-display);}
.mono{font-family:var(--font-mono);text-transform:uppercase;letter-spacing:.14em;font-weight:600;}

/* ---------- custom scrollbar ---------- */
::-webkit-scrollbar{width:10px;}
::-webkit-scrollbar-track{background:var(--bg);}
::-webkit-scrollbar-thumb{background:var(--gold-dim);border-radius:6px;}
::-webkit-scrollbar-thumb:hover{background:var(--grid);}

/* ============ LOADER ============ */
#loader{
  position:fixed;inset:0;z-index:1000;background:var(--ink);
  display:flex;align-items:center;justify-content:center;flex-direction:column;gap:22px;
  transition:opacity .9s cubic-bezier(.77,0,.18,1),visibility .9s;
}
#loader.done{opacity:0;visibility:hidden;pointer-events:none;}
/* THE CLIP FILLS THE SCREEN, AND THE LOADER SITS ON IT.
   It comes out of the flex column and becomes the loader's ground: fixed
   to the four edges, behind the wordmark, the bar and the tagline. Those
   are still laid out by the column exactly as before — they simply have
   the film behind them now instead of above them.

   object-fit:cover rather than contain, because "full screen" means no
   letterboxing: the frame is filled and whichever dimension is long gets
   trimmed. Each cut is already framed for its own shape, so the trim is
   small either way.

   THE WASH IS GONE. There used to be a dark gradient across the whole film
   here — 42% at the top deepening to 62% at the foot — so the clip was
   playing at a little over half brightness and reading as dimmed. The film
   now shows at full strength.

   It was doing a real job, though: the wordmark and the 11px tagline are
   light type sitting straight on the picture, and on a bright frame they
   would wash out. That job moves to the type itself, as a shadow carried by
   the letters rather than a veil over the whole image — invisible on the
   dark frames this clip mostly holds, and there to catch the bright ones. */
#loader .ld-film{
  position:absolute;inset:0;z-index:0;
  overflow:hidden;line-height:0;pointer-events:none;
}
#loader .ld-film video{width:100%;height:100%;object-fit:cover;display:block;}
/* everything else in the loader rides above the film */
#loader .word,#loader .bar,#loader .tag{position:relative;z-index:1;}
/* what the removed wash used to do, carried by the type instead */
#loader .word{text-shadow:0 2px 18px rgba(8,12,9,.55),0 0 3px rgba(8,12,9,.35);}
#loader .tag{text-shadow:0 1px 10px rgba(8,12,9,.7);}

/* THE THREE PIECES SIT LOWER ON THE FRAME.
   The loader centres its column, so the group was on the midline. Rather
   than nudging each of the three, the COLUMN is pushed: a top padding of
   30% of the viewport moves the whole group down by that much and keeps
   the spacing between them exactly as it was. Padding rather than a
   translate so the group can still never overflow the bottom — with
   justify-content:center it re-centres in what is left, so on a short
   window it settles rather than being pushed off. */
#loader{padding-top:30vh;padding-top:calc(var(--vh) * 0.30);}

#loader .word{
  font-family:var(--font-display);font-weight:300;font-size:clamp(2rem,6vw,4rem);
  letter-spacing:.42em;text-indent:.42em;color:var(--cream);
  overflow:hidden;display:flex;
}
#loader .word span{display:inline-block;transform:translateY(110%);animation:riseUp .9s cubic-bezier(.22,1,.36,1) forwards;}
#loader .word span:nth-child(1){animation-delay:.05s}#loader .word span:nth-child(2){animation-delay:.1s}
#loader .word span:nth-child(3){animation-delay:.15s}#loader .word span:nth-child(4){animation-delay:.2s}
#loader .word span:nth-child(5){animation-delay:.25s}#loader .word span:nth-child(6){animation-delay:.3s}
@keyframes riseUp{to{transform:translateY(0);}}
/* THE LINE IS CARRIED OUT BY TWO NODES, FROM ONE.
   It used to be a fill growing left to right inside a clipped track. Now a
   single plus sits on the centre, splits into two, and the rule is drawn
   between them as they travel to the ends — so the line never exists
   without something at its head.

   The two nodes start on exactly the same pixel and hold there for the
   first 14% of the run, which is what reads as ONE plus before it becomes
   two. Nothing switches or is swapped in: they are simply coincident, and
   then they are not.

   overflow is visible now, not hidden: the old track clipped a growing
   width, but a scaled rule stays inside its own box and the nodes have to
   be able to sit ON the ends rather than be cut off at them. */
#loader .bar{
  --bw:min(280px,60vw);
  position:relative;width:var(--bw);height:1px;
  background:rgba(246,242,233,.2);overflow:visible;
}
#loader .bar i{
  display:block;height:100%;width:100%;background:var(--grid);
  transform:scaleX(0);transform-origin:50% 50%;
  animation:ldLine 1.6s cubic-bezier(.77,0,.18,1) forwards;
}
@keyframes ldLine{0%,14%{transform:scaleX(0);} 100%{transform:scaleX(1);}}

#loader .ld-p{
  position:absolute;top:50%;left:50%;
  width:11px;height:11px;margin:-5.5px 0 0 -5.5px;
}
#loader .ld-p::before,#loader .ld-p::after{content:"";position:absolute;background:var(--grid);}
#loader .ld-p::before{left:0;right:0;top:50%;height:1px;margin-top:-.5px;}
#loader .ld-p::after{top:0;bottom:0;left:50%;width:1px;margin-left:-.5px;}
/* translateX off the track's own width rather than animating left, so the
   pair rides the compositor and stays exactly on the rule's ends */
#loader .ld-p.l{animation:ldNodeL 1.6s cubic-bezier(.77,0,.18,1) forwards;}
#loader .ld-p.r{animation:ldNodeR 1.6s cubic-bezier(.77,0,.18,1) forwards;}
@keyframes ldNodeL{0%,14%{transform:translateX(0);} 100%{transform:translateX(calc(var(--bw) / -2));}}
@keyframes ldNodeR{0%,14%{transform:translateX(0);} 100%{transform:translateX(calc(var(--bw) /  2));}}
/* Same size, same tracking, same caps — only the face changes. It was on
   the mono stack from the .mono class in the markup; this puts it on the
   display face the wordmark above it uses, so the two read as one lockup
   rather than two typefaces stacked. */
#loader .tag{
  color:rgba(246,242,233,.5);font-size:11px;
  font-family:'Poppins',var(--font-display);font-weight:300;
}

/* ============ WORDMARK REVEAL ============
   Driven entirely by GSAP — see playIntroReveal() in main.js. The letters
   sit above the loader (z-index 1000) and start off-stage via transform,
   so the loader can be cut on the same tick without the swap being seen.

   THE FIELD THE ACT HAPPENS ON.
   The same green at the centre that the loader was sitting on, falling to
   a much deeper green at the corners — which is what lets the loader be
   cut rather than faded: both sides of the hand-off match. The wash also
   gives the empty frame a middle to sit the wordmark in; a flat fill would
   leave the letters floating on a field with no depth to it.

   Sized 120% and anchored at the centre so the darkest stop lands outside
   the viewport's corners rather than exactly on them: a gradient that
   finishes ON the corner puts its steepest falloff in frame and the corner
   reads as a vignette ring instead of shadow. */
#introBg{
  position:fixed;inset:0;z-index:999;
  pointer-events:none;
  background:radial-gradient(120% 120% at 50% 50%,
    var(--ink) 0%, var(--ink) 30%, #16291c 62%, var(--ink-deep) 100%);
}
#introLetters{
  position:fixed;inset:0;z-index:1002;
  display:flex;align-items:center;justify-content:center;
  overflow:hidden;pointer-events:none;opacity:0;
}
/* the wordmark and the pronunciation move as one — see the markup note */
.il-lockup{position:relative;}
/* TRACKING IS MARGIN, NOT letter-spacing.
   letter-spacing adds its space after EVERY character, the last one
   included, and browsers disagree about whether that final space counts
   toward the line box. Where it does, the wordmark's box is .42em wider
   than the glyphs that draw it — so the glyphs sit half that (about 17px
   at this size) left of centre, while the pronunciation underneath
   centres on the box and appears shoved right. Nothing is misaligned in
   the markup; the two are simply centred on different things.

   Carrying the tracking as a right margin on each letter, and taking it
   off the last one, makes the box exactly equal to the glyphs everywhere
   — so centring the phrase on the box centres it on the word. */
.il-stage{
  font-family:var(--font-display);font-weight:300;
  font-size:clamp(2.4rem,8vw,5rem);
  letter-spacing:0;
  color:var(--cream);
  text-shadow:0 2px 18px rgba(8,12,9,.55),0 0 3px rgba(8,12,9,.35);
}
.il-v,.il-rest{display:inline-block;position:relative;}
/* the individual letters of IREVO — inline-block so each can be given its
   own transform in the right-align beat without disturbing the others */
.il-l{display:inline-block;}
.il-v,.il-l{margin-right:.42em;}
.il-rest .il-l:last-child{margin-right:0;}
/* OUT OF FLOW, DELIBERATELY.
   position:absolute keeps the phrase from counting toward the lockup's
   height, which is what lets VIREVO sit on the true centre of the screen
   before the phrase exists. In flow, the lockup would be centred as
   wordmark-plus-phrase from the very first frame and the V would land
   noticeably high with nothing underneath it to explain why. */
.il-phon{
  position:absolute;top:100%;left:50%;transform:translateX(-50%);
  margin:0;white-space:nowrap;
  font-family:var(--font-mono);font-size:clamp(10px,1.5vw,14px);
  letter-spacing:.16em;color:rgba(246,242,233,.62);
  /* the clip the characters rise out of. padding rather than a taller line
     box because overflow clips at the PADDING edge — so the descenders in
     ʊ and ɪ have somewhere to be once they have arrived. */
  overflow:hidden;padding:.35em 0 .5em;font-weight:600;}
.il-phon .ph-c{display:inline-block;will-change:transform;}
/* the part of speech, picked out in the house gold */
.il-phon .ph-gold{color:var(--grid);}

/* WORD WRAPPERS EXIST TO PROTECT WRAPPING.
   Splitting a heading into individually-transformed characters gives the
   line breaker permission to break between any two of them, since bare
   inline-block characters carry none of the word boundaries plain text
   does — the line would come back broken mid-word. Each word is
   therefore a nowrap box, and only the spaces between words stay
   breakable. Shared by .hk-h and .su-h via splitWords() in main.js. */
.df-w{display:inline-block;white-space:nowrap;}
.df-c{display:inline-block;will-change:transform;}

/* soft ellipse under V, scaled/faded by JS in step with its fall so the
   drop reads as landing on something rather than just stopping */
.il-shadow{
  position:absolute;left:50%;bottom:-.14em;width:70%;height:.22em;
  transform:translateX(-50%) scale(.15);border-radius:50%;
  background:rgba(0,0,0,.45);filter:blur(4px);opacity:0;
  pointer-events:none;
}

/* ============ HOOKS — auto carousel ============
   An ACCORDION, not a slider. All three panels stay on screen and the
   active one opens out while the others stay collapsed beside it — so the
   change of state is a widening rather than a translation, and nothing
   ever leaves the frame. That is the whole reason the flex-basis is what
   animates here rather than a track offset.

   Held off-screen until the intro has finished unwinding; JS adds .is-up. */
/* Above the intro's own layers (wash 999, letters 1002), not below them:
   it has to fade in ON TOP of the wash the intro leaves behind. Dropping
   the wash first instead would flash the page underneath for the length
   of the crossfade. The spent layers are taken down afterwards, by JS,
   once this one is opaque. */
/* THE GREEN IS THE STARTING COLOUR, NOT A THING TO FADE OVER.
   Crossfading a cream panel in on top of the intro's green wash is what
   produced the jerk: two layers at partial opacity read as a wash-out
   rather than a change of ground. So this panel opens ALREADY green —
   the same --ink the intro ends on, which makes the hand-off invisible —
   and its own background-color is then tweened to cream. One surface
   changing colour, no dissolve between two.

   Content is faded in separately, late in that tween, because the type
   here is coloured for cream and would be unreadable over the green.
   The tween itself is in startHooks(). */
#hooks{
  position:fixed;inset:0;z-index:1004;
  display:flex;flex-direction:column;justify-content:center;
  padding:clamp(20px,3.4vh,38px) clamp(20px,3.2vw,54px);
  gap:clamp(14px,2.2vh,26px);
  background:var(--ink);
  visibility:hidden;pointer-events:none;
}
#hooks.is-up{visibility:visible;pointer-events:auto;}

/* ---------- header ---------- */
.hk-top{
  display:flex;align-items:center;justify-content:space-between;
  gap:18px;flex:0 0 auto;
}
.hk-ix{
  margin:0;display:flex;align-items:center;gap:14px;
  font-family:var(--font-display);font-weight:600;font-size:11.5px;
  letter-spacing:.09em;text-transform:uppercase;
  color:var(--mint-deep);
}
.hk-ctrl{display:flex;align-items:center;gap:12px;}
/* Sized and tracked to match .sec-index (11.5px / .2em) — the real "01 /
   05" counter used elsewhere on the page. This is the same element doing
   the same job, "01 / 03" against a live carousel instead of a static
   section list, and had drifted to its own 12px / .18em. */
.hk-count{
  margin:0 6px 0 0;font-family:var(--font-mono);font-size:11.5px;
  letter-spacing:.2em;color:var(--ink-soft);
  /* the counter changes under an animation; tabular figures stop the
     row from twitching as 01 becomes 02 */
  font-variant-numeric:tabular-nums;font-weight:600;}
/* 44px, which is the floor for a touch target — the visible circle is
   the whole control, so there is no need to extend the hit area */
.hk-btn{
  width:44px;height:44px;border-radius:50%;
  display:grid;place-items:center;padding:0;cursor:pointer;
  background:transparent;border:1px solid var(--gold-dim);
  color:var(--ink);
  transition:background .35s cubic-bezier(.22,1,.36,1),
             border-color .35s,color .35s,transform .35s;
}
.hk-btn svg{width:20px;height:20px;fill:none;stroke:currentColor;
  stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round;}
.hk-btn.hk-play svg{fill:currentColor;stroke:none;}
.hk-solid{background:var(--ink-deep);border-color:var(--ink-deep);color:var(--cream);}
@media (hover:hover){
  .hk-btn:hover{border-color:var(--ink);transform:translateY(-1px);}
  .hk-solid:hover{background:var(--ink);}
}
.hk-btn:focus-visible{outline:2px solid var(--mint-deep);outline-offset:3px;}
/* the pause glyph is shown while running, the play glyph while stopped */
.hk-play .hk-i-play{display:none;}
.hk-play[aria-pressed="true"] .hk-i-pause{display:none;}
.hk-play[aria-pressed="true"] .hk-i-play{display:block;}

/* ---------- the rail ----------
   A stated height rather than "whatever is left". The panels used to
   stretch to the full column and left the screen feeling like one tall
   slab; capped, they read as cards and there is room under them for the
   way out. clamp keeps it sane between a laptop and a tall display. */
.hk-track{
  flex:0 0 auto;min-height:0;
  height:clamp(340px,50vh,520px);
  display:flex;gap:clamp(10px,1.1vw,18px);
  touch-action:pan-y;   /* leaves vertical scrolling to the page */
}
/* FLEX-GROW IS DRIVEN BY GSAP, NOT BY A CSS TRANSITION.
   `transition:flex-grow` was the obvious way to write this and it does
   not work: the declared value never takes at all. Computed flex-grow
   stays pinned at the base 1 — even when set inline — while
   getAnimations() reports nothing running, so there is no transition to
   wait out and no value to arrive at. Dropping the transition makes the
   very same declaration apply instantly.

   It should not have been a CSS transition regardless. Transitioning a
   layout property is against the motion rules this build already follows
   — transform and opacity only — and an accordion is the one shape where
   that rule cannot be honoured outright, because the panels genuinely
   resize. The compromise is to keep the resize but drive it from the
   ticker that runs everything else, over three elements, once every six
   seconds. background stays on a CSS transition: that one is paint, not
   layout, and behaves. */
/* A GRID, SO ONE IMAGE CAN LIVE IN TWO PLACES.
   The render is a single element per card and the grid decides where it
   sits: stacked between the kicker and the heading while the panel is
   collapsed, and a full-height column down the right once it opens. Two
   <img> tags swapped by display would have been simpler to write and
   would have downloaded and decoded the same picture twice.

   Padding is on the kicker and the text, never on the card, so the plate
   can run to the card's own edges the way the reference has it. */
/* THE PLATE IS ABSOLUTE AND ITS GEOMETRY IS TWEENED, NOT RE-HOMED.
   This was a grid whose template changed between the two states — a band
   under the kicker when collapsed, a right-hand column when open. Grid
   templates cannot interpolate, so the render was cut from one slot and
   pasted into the other on a single frame while the card around it went
   on resizing for another 900ms. That one-frame jump is the jerk.

   Taking the plate out of the grid and giving it percentage geometry
   makes both states the same four numbers, so GSAP can move between them
   — and because they are percentages of a card that is itself resizing
   every frame, the render tracks the box instead of fighting it. See the
   MEDIA_BAND / MEDIA_COL pair in main.js. */
.hk-card{
  position:relative;overflow:hidden;
  flex:1 1 0;min-width:0;
  background:var(--ink-mute);
  display:flex;flex-direction:column;
  transition:background .9s cubic-bezier(.77,0,.18,1);
}
.hk-text{
  flex:0 0 auto;margin-top:auto;min-width:0;
  display:flex;flex-direction:column;
  padding:0 clamp(18px,2.2vw,34px) clamp(18px,2.2vw,34px);
}
/* 2.2 against the other two 1s puts the open panel on ~52% of the rail
   and leaves the collapsed pair ~24% each — enough for their kicker to
   stay on one line, which is where it was wrapping at a heavier ratio.
   This is also the no-JS resting state.

   --ink, the same deep green every other dark surface on the site uses,
   rather than the near-black --ink-deep it started on. --ink-deep stays
   where it belongs: the corners of the intro's radial wash, the solid
   next button and the active progress bar, all of which read as black in
   the reference and should. */
.hk-card.is-on{flex-grow:2.2;background:var(--ink);}
/* The stripes stay: they are the plate the render stands on, and on the
   collapsed panels they are what keeps an empty-ish band from reading as
   a gap. Faint on the light ground, slightly stronger on the dark one. */
.hk-media{
  position:absolute;overflow:hidden;pointer-events:none;
  /* the collapsed band — the resting values, and what a no-JS visitor
     gets; JS moves these four to the column values when a card opens */
  left:0;top:18%;width:100%;height:42%;
  background-image:repeating-linear-gradient(135deg,
    rgba(30,55,39,.05) 0 9px, transparent 9px 19px);
}
.hk-card.is-on .hk-media{
  background-image:repeating-linear-gradient(135deg,
    rgba(246,242,233,.05) 0 9px, transparent 9px 19px);
}
/* contain, not cover: these are cut-out characters and cropping one at
   the knees is worse than leaving air around it. Anchored to the foot of
   its box so he stands on the bottom edge rather than floating. */
.hk-img{
  display:block;width:100%;height:100%;
  object-fit:contain;object-position:50% 100%;
  padding:clamp(8px,1vw,16px);
  will-change:transform,opacity;
}

/* ---------- card type ---------- */
/* carries its own padding now that it is a grid child rather than a
   child of a padded body */
.hk-kick{
  margin:0;padding:clamp(18px,2.2vw,34px) clamp(18px,2.2vw,34px) 0;
  display:flex;align-items:center;gap:12px;
  font-family:var(--font-mono);font-size:11.5px;
  letter-spacing:.22em;text-transform:uppercase;font-weight:600;}
.hk-kick span{color:var(--ink-soft);}
.hk-card.is-on .hk-kick span{color:rgba(246,242,233,.55);}

.hk-h{
  margin:0;font-family:var(--font-display);font-weight:400;
  font-size:clamp(1.15rem,1.9vw,2.35rem);
  line-height:1.24;letter-spacing:-.01em;
  color:var(--ink-soft);max-width:18ch;
  transition:color .9s cubic-bezier(.77,0,.18,1);
}
.hk-card.is-on .hk-h{color:var(--cream);max-width:15ch;}
.hk-rule{
  display:block;width:clamp(60px,7vw,120px);height:1px;
  margin:clamp(16px,2.4vh,30px) 0;
  background:var(--gold-dim);transform-origin:left center;
}
.hk-card.is-on .hk-rule{background:rgba(246,242,233,.35);}
/* HISTORICAL NOTE. --font-body used to be Carlito, and it was effectively
   the unused token: the base <body> reset plus this one rule, while every
   actual paragraph on the site (#intro .wd-body, .cw-bio, .ft-col p) already
   overrode to Poppins. Carlito here made this card's copy the one paragraph
   on the page in a different typeface from every other paragraph, which is
   why it was overridden. The token is Poppins now, so the two agree; the
   explicit family below is kept because the weight still differs. */
.hk-copy{
  margin:0;font-family:var(--font-display);font-weight:300;
  font-size:clamp(14px,1.05vw,17px);line-height:1.6;
  color:var(--ink-soft);max-width:34ch;
}
.hk-card.is-on .hk-copy{color:rgba(246,242,233,.72);}
.hk-copy em{font-style:normal;color:var(--mint-deep);}
.hk-card.is-on .hk-copy em{color:var(--mint);}
/* The collapsed cards are too narrow to carry a rule and a paragraph as
   well as a heading — the heading is the part that has to survive, so the
   other two are dropped rather than squeezed. */
.hk-card:not(.is-on) .hk-rule,
.hk-card:not(.is-on) .hk-copy{display:none;}

/* ---------- foot ---------- */
.hk-foot{
  flex:0 0 auto;display:flex;align-items:center;justify-content:space-between;
  gap:18px;padding-top:clamp(6px,1vh,14px);
}
.hk-bars{display:flex;gap:8px;}
/* 24×24 is the web pointer-target minimum; the visible bar is 3px, so the
   button carries transparent padding to reach it without looking heavy */
.hk-bar{
  width:58px;height:24px;padding:0;border:0;background:none;cursor:pointer;
  display:grid;align-items:center;
}
.hk-bar::after{
  content:"";display:block;height:3px;width:100%;
  background:var(--gold-dim);
  transition:background .5s cubic-bezier(.22,1,.36,1);
}
.hk-bar.is-on::after{background:var(--ink-deep);}
.hk-bar:focus-visible{outline:2px solid var(--mint-deep);outline-offset:2px;}
.hk-hint{
  margin:0;font-family:var(--font-mono);font-size:10.5px;
  letter-spacing:.26em;text-transform:uppercase;color:var(--ink-soft);font-weight:600;}

/* the per-character pieces the heading is revealed with */
.hk-h .df-c{display:inline-block;will-change:transform;}

/* ---------- narrow ----------
   Three columns stop being columns well before the phone build: the
   collapsed cards cannot hold a heading at all. The rail becomes a stack
   and only the active panel is kept, which is what the controls were
   already driving. */
@media (max-width:860px){
  .hk-track{flex-direction:column;height:clamp(320px,46vh,460px);}
  .hk-card{flex:0 0 auto;}
  .hk-card:not(.is-on){display:none;}
  .hk-card.is-on{flex:1 1 auto;}
  .hk-h{max-width:none;}
  .hk-count{display:none;}
}
@media (max-width:520px){
  /* THE RENDER STAYS ON THE PHONE, AND IT STANDS IN THE FLOW.
     It used to be display:none here. Bringing it back as the absolute
     band it uses on a wide card does not work at this width: the panel
     holds a kicker, a four-line heading, a rule and two lines of copy in
     about 390px, so an overlaid band lands straight on top of the
     heading — the boots sat in the first line.

     In flow it cannot overlap anything. The card is already a column, so
     the plate only needs an order between the kicker and the text and a
     height of its own, and the three stack. The rail is given more room
     to make that fit. main.js leaves the geometry alone below this
     width for the same reason — inline left/top/width/height from the
     tween would take the plate straight back out of the flow. */
  /* sized to what the panel actually holds — taller and the auto top
     margin on the text opens a dead gap under the render */
  .hk-track{height:clamp(450px,57vh,580px);}
  .hk-kick{order:0;}
  .hk-media{
    order:1;flex:0 0 auto;
    position:relative;left:auto;top:auto;width:auto;
    height:clamp(150px,21vh,215px);
    margin-top:clamp(6px,1.2vh,14px);
  }
  .hk-text{order:2;margin-top:auto;}
  .hk-hint{display:none;}
}

/* ============ SKIP ============
   Above every stage this intro has (#introLetters 1002, #bii 1003,
   #hooks 1004, #suite 1005), so one element does the whole job instead of
   one per
   stage. Dark glass over both a green field and a beige one — tested
   deliberately, since this is the one control on screen for the entire
   intro and has to read on whichever ground happens to be under it when
   it appears. */
.intro-skip{
  position:fixed;top:20px;right:20px;z-index:1010;
  display:inline-flex;align-items:center;gap:8px;
  height:40px;padding:0 16px;cursor:pointer;
  border:1px solid rgba(246,242,233,.4);
  background:rgba(8,16,11,.35);
  backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);
  color:var(--cream);
  font-family:var(--font-mono);font-size:11px;
  letter-spacing:.22em;text-transform:uppercase;
  opacity:0;visibility:hidden;pointer-events:none;
  transition:opacity .4s cubic-bezier(.22,1,.36,1),visibility .4s,
             background .3s,border-color .3s;font-weight:600;}
.intro-skip.is-up{opacity:1;visibility:visible;pointer-events:auto;}
.intro-skip svg{
  width:14px;height:14px;fill:none;stroke:currentColor;
  stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round;
}
@media (hover:hover){
  .intro-skip:hover{background:rgba(8,16,11,.55);border-color:var(--cream);}
}
.intro-skip:focus-visible{outline:2px solid var(--mint);outline-offset:3px;}
@media (max-width:520px){
  /* icon only — the word plus its padding is a disproportionate claim on
     a 375px-wide corner next to the wordmark reveal or the carousel's own
     counter */
  .intro-skip{top:14px;right:14px;height:36px;width:36px;padding:0;justify-content:center;}
  .intro-skip span{display:none;}
}

/* ============ SUITE — three names ============
   Opens on the beige the carousel ends on and is tweened to deep green
   in startSuite(), the mirror of the move that brought the carousel in.
   Same reasoning as #hooks: one surface changing colour, never two
   layers crossfading, because that is what reads as a jerk. */
/* SCROLLABLE ON EVERY WIDTH, NOT JUST THE PHONE BUILD.
   This used to centre its content and only grow a scroll container below
   760px, on the assumption that three cards plus a heading always fit a
   desktop viewport. They do not always — .su-h alone can push the total
   past the fold — and centred content with no overflow route made
   whatever fell below the bottom edge simply unreachable. Scrolling is
   therefore always on, same as #bii's own free-scroll pattern, including
   the Lenis exclusion and the manual wheel/touch drive in startSuite() —
   see the long comment on #bii/#biiScroll in this file for why the
   native browser chaining cannot be trusted to pick this container on
   its own. */
#suite{
  --su-line:rgba(246,242,233,.30);
  position:fixed;inset:0;z-index:1005;
  display:flex;flex-direction:column;justify-content:flex-start;
  gap:clamp(18px,3vh,40px);
  padding:clamp(20px,3.4vh,40px) clamp(20px,3.2vw,54px);
  background:var(--bg);
  visibility:hidden;pointer-events:none;
  overflow-y:auto;overscroll-behavior:contain;
  scrollbar-width:none;-webkit-overflow-scrolling:touch;
}
#suite::-webkit-scrollbar{display:none;}
#suite.is-up{visibility:visible;pointer-events:auto;}

/* ---------- header ---------- */
/* Two columns, two rows: the kicker spans the top and the heading and the
   note share the row beneath it, both starting on the same line. */
.su-top{
  flex:0 0 auto;display:grid;
  gap:clamp(10px,1.6vh,20px) clamp(16px,2.4vw,48px);
  grid-template-columns:minmax(0,1.35fr) minmax(0,1fr);
  align-items:start;
}
.su-ix{
  grid-column:1 / -1;margin:0;
  display:flex;align-items:center;gap:14px;
  font-family:var(--font-mono);font-size:11.5px;
  letter-spacing:.22em;text-transform:uppercase;color:var(--mint);font-weight:600;}
/* PAGE TITLE ROLE, PER virevo-type-scale.md — same font, same clamp() and
   same letter-spacing/line-height pair as .bii-title uses for "BUILT IN
   INDIA"; this is the second and only other place on the site asking for
   that exact role. */
/* Same size as .bii-sec-h's "SEVEN REASONS"/"ONE TURN — OR SIX" — the
   full Page Title clamp (89–215px) read as too heavy here once it was
   actually in place, so this now matches the other Bebas Neue heading
   role on the site rather than the giant wordmark-scale one. */
.su-h{
  margin:0;font-family:'Bebas Neue',var(--font-display);font-weight:400;
  font-size:clamp(1.9rem,4vw,3.3rem);line-height:1.35;
  letter-spacing:0;text-transform:uppercase;color:var(--cream);
}
@media (min-width:1024px){ .su-h{line-height:1.20;} }
/* Poppins light — see the note on .hk-copy for the history: every
   real paragraph on the site is set in the display face, and this is one
   of only two places left that still asked for the body-reset face
   instead. */
.su-note{
  margin:0;font-family:var(--font-display);font-weight:300;
  font-size:clamp(14px,1.1vw,17px);line-height:1.62;
  color:rgba(246,242,233,.66);max-width:44ch;
  justify-self:end;
}

/* ---------- the three ---------- */
.su-grid{
  flex:0 0 auto;display:grid;gap:clamp(12px,1.6vw,26px);
  grid-template-columns:repeat(3,minmax(0,1fr));
  height:clamp(320px,46vh,520px);
}
/* overflow stays VISIBLE so the corner pluses can sit centred on the
   corners rather than losing their outer half to a clip. Nothing else
   here overflows: the render is object-fit:contain at 100% of the card
   and the veil is inset:0. */
.su-card{
  position:relative;min-width:0;overflow:visible;
  background:rgba(0,0,0,.22);
  background-image:repeating-linear-gradient(135deg,
    rgba(246,242,233,.035) 0 9px, transparent 9px 19px);
  cursor:pointer;
}
.su-card:focus-visible{outline:2px solid var(--mint);outline-offset:6px;}

/* ---------- the drawn frame ----------
   Four edges that run out from a corner and four pluses that land as a
   line reaches them. Every piece starts collapsed or invisible and is
   sequenced by drawBox(); the transform-origins below are what decide
   which way each edge grows. */
.su-fx{position:absolute;inset:0;z-index:4;pointer-events:none;display:block;}
.su-e{position:absolute;background:var(--su-line);}
.su-e-t{top:0;left:0;width:100%;height:1px;transform-origin:left center;}
.su-e-b{bottom:0;left:0;width:100%;height:1px;transform-origin:left center;}
.su-e-l{top:0;left:0;width:1px;height:100%;transform-origin:center top;}
.su-e-r{top:0;right:0;width:1px;height:100%;transform-origin:center top;}
.su-p{position:absolute;width:11px;height:11px;}
.su-p::before,.su-p::after{content:"";position:absolute;background:var(--su-line);}
.su-p::before{left:0;right:0;top:50%;height:1px;margin-top:-.5px;}
.su-p::after{top:0;bottom:0;left:50%;width:1px;margin-left:-.5px;}
.su-p-tl{top:0;left:0;margin:-5.5px 0 0 -5.5px;}
.su-p-tr{top:0;right:0;margin:-5.5px -5.5px 0 0;}
.su-p-br{bottom:0;right:0;margin:0 -5.5px -5.5px 0;}
.su-p-bl{bottom:0;left:0;margin:0 0 -5.5px -5.5px;}

/* ---------- render ----------
   The figure takes the TOP of the card, not all of it. Filling the card
   put the names and the roles straight across the character's legs and
   the small letterspaced role line became unreadable — a scrim alone
   could not rescue type sitting on that much detail. Sixty-six percent
   leaves the lower third as the entry's own ground.

   The scrim still earns its place: these are cut-outs on a striped
   plate, and a hem or a sandal reaching into the text needs something to
   sink into. */
.su-shot{
  position:absolute;left:0;right:0;top:0;z-index:0;
  width:100%;height:66%;
  object-fit:contain;object-position:50% 100%;
  padding:clamp(14px,1.8vw,26px) clamp(10px,1.2vw,18px) 0;
}
.su-card::after{
  content:"";position:absolute;left:0;right:0;bottom:0;z-index:1;
  height:56%;pointer-events:none;
  background:linear-gradient(to top,
    rgba(8,16,11,.94) 0%, rgba(8,16,11,.72) 38%, rgba(8,16,11,0) 100%);
}

/* ---------- resting summary vs. the full entry ----------
   .su-face is what the card says at rest; .su-veil is the same card with
   its gloss, laid over the render behind a translucent plate. They share
   their padding and bottom alignment, so the crossfade between them
   reads as the entry gaining detail rather than as two blocks swapping. */
/* stacking, bottom to top: render, scrim, resting summary, veil, frame */
.su-face,.su-veil{
  position:absolute;left:0;right:0;bottom:0;
  padding:clamp(16px,1.9vw,30px);
}
.su-face{z-index:2;}
/* BOTTOM-ALIGNED BY margin-top:auto, NOT BY justify-content.
   The two look identical while the gloss fits and behave completely
   differently when it does not. justify-content:flex-end overflows
   UPWARD, past the top edge, where there is nothing to scroll — the
   longest of these entries came within 3px of having its name and its
   Devanagari silently cut off the top on a short window. An auto top
   margin holds the block to the foot when there is room and lets it
   start at the top and scroll when there is not, so nothing is ever
   lost. overscroll-behavior keeps that scroll from chaining to the
   page underneath. */
/* SCROLLABLE, BUT NOT VISIBLY SO.
   overflow-y:auto is what stopped the longer glosses clipping their own
   name off the top — it has to stay. But it also means macOS mouse users
   get an overlay scrollbar drawn on hover even on cards that are nowhere
   near needing one, and a scrollbar down the middle of a translucent
   plate over a render is not a look anyone asked for. Hiding the bar
   while leaving overflow:auto keeps the safety net without the tell. */
.su-veil{
  top:0;z-index:3;display:flex;flex-direction:column;
  overflow-y:auto;overscroll-behavior:contain;
  scrollbar-width:none;
  background:rgba(8,16,11,.78);
  backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);
  opacity:0;transition:opacity .45s cubic-bezier(.22,1,.36,1);
}
.su-veil::-webkit-scrollbar{display:none;}
.su-veil>:first-child{margin-top:auto;}
.su-face{transition:opacity .35s cubic-bezier(.22,1,.36,1);}
@media (hover:hover){
  .su-card:hover .su-veil{opacity:1;}
  .su-card:hover .su-face{opacity:0;}
}
/* keyboard and touch get the same thing — hover is not a route everyone
   has, and this content is the point of the card */
.su-card:focus-visible .su-veil,.su-card.is-open .su-veil{opacity:1;}
.su-card:focus-visible .su-face,.su-card.is-open .su-face{opacity:0;}

.su-dev{
  margin:0 0 .35em;font-family:var(--font-display);font-weight:500;
  font-size:clamp(1.1rem,1.5vw,1.6rem);line-height:1;
  color:rgba(246,242,233,.5);
}
.su-veil .su-dev{color:var(--mint);}
.su-name{
  margin:0;font-family:var(--font-display);font-weight:500;
  font-size:clamp(1.5rem,2.5vw,2.6rem);line-height:1;
  letter-spacing:.02em;text-transform:uppercase;color:var(--cream);
  display:flex;align-items:baseline;gap:.5em;flex-wrap:wrap;
}
.su-prn{
  font-family:var(--font-mono);font-size:13px;font-style:normal;
  letter-spacing:.04em;text-transform:none;
  color:rgba(246,242,233,.52);font-weight:600;}
.su-rule{
  display:block;width:100%;height:1px;
  margin:clamp(12px,1.8vh,22px) 0;
  background:rgba(246,242,233,.22);transform-origin:left center;
}
.su-role{
  margin:clamp(8px,1.2vh,14px) 0 0;
  font-family:var(--font-mono);font-size:11px;
  letter-spacing:.2em;text-transform:uppercase;color:var(--mint);font-weight:600;}
.su-veil .su-role{margin-top:0;}
/* Poppins light — see the note on .hk-copy. */
.su-body{
  margin:clamp(10px,1.4vh,18px) 0 0;font-family:var(--font-display);font-weight:300;
  font-size:clamp(13px,1vw,16px);line-height:1.62;
  color:rgba(246,242,233,.78);
}
.su-body em{font-style:italic;color:var(--cream);}
.su-quote{
  margin:clamp(12px,1.8vh,22px) 0 0;font-family:var(--font-display);
  font-weight:400;font-style:italic;
  font-size:clamp(14px,1.1vw,17px);line-height:1.45;color:var(--cream);
}

/* ---------- the way in ---------- */
.su-enter-wrap{flex:0 0 auto;display:flex;justify-content:center;}
/* cream on green, the inverse of the carousel's button — the one filled
   control on a dark screen */
.su-enter{
  display:inline-flex;align-items:center;gap:14px;
  min-height:52px;padding:0 clamp(22px,2.4vw,34px);
  cursor:pointer;border:1px solid var(--cream);
  background:var(--cream);color:var(--ink);
  font-family:var(--font-mono);font-size:12px;
  letter-spacing:.22em;text-transform:uppercase;
  transition:background .45s cubic-bezier(.22,1,.36,1),color .45s;font-weight:600;}
.su-enter svg{
  width:20px;height:20px;flex:0 0 auto;
  fill:none;stroke:currentColor;stroke-width:1.5;
  stroke-linecap:round;stroke-linejoin:round;
  transition:transform .45s cubic-bezier(.22,1,.36,1);
}
@media (hover:hover){
  .su-enter:hover{background:transparent;color:var(--cream);}
  .su-enter:hover svg{transform:translateX(5px);}
}
.su-enter:focus-visible{outline:2px solid var(--mint);outline-offset:3px;}

/* the per-character pieces the heading is revealed with */
.su-h .df-c,.su-ix .df-c{display:inline-block;will-change:transform;}

@media (max-width:980px){
  .su-top{grid-template-columns:1fr;}
  .su-note{justify-self:start;}
}
/* ---------- phone ----------
   ONE SCROLL CONTAINER, AND THE GLOSS IS NOT A HOVER.
   This had a scroll container inside a scroll container — .su-grid
   scrolled, and each .su-veil scrolled inside it — which is where the
   two scrollbars and the stuck feeling came from: a drag would grab
   whichever of the two it landed on. #suite is now the only thing that
   scrolls on a phone and its bar is hidden, since the section is
   auto-scrolled anyway.

   There is also no hover on a phone, so the veil cannot be a hover
   state. It comes out of its overlay, drops its plate and its blur, and
   is laid out IN FLOW under the render — the render, then the whole
   entry, one name per screenful. */
@media (max-width:760px){
  .su-grid{
    grid-template-columns:1fr;height:auto;
    gap:clamp(20px,3.4vh,34px);
  }
  .su-card{display:flex;flex-direction:column;}
  .su-shot{
    position:relative;inset:auto;
    height:clamp(190px,32vh,300px);flex:0 0 auto;
  }
  /* the scrim exists to lift type off the render; stacked, nothing
     overlaps and it only muddies the plate */
  .su-card::after{display:none;}
  .su-face{display:none;}
  .su-veil{
    position:relative;top:auto;left:auto;right:auto;bottom:auto;
    opacity:1;overflow:visible;
    background:none;backdrop-filter:none;-webkit-backdrop-filter:none;
    padding-top:0;
  }
  .su-veil>:first-child{margin-top:0;}
  .su-enter{font-size:11px;letter-spacing:.16em;}
}

/* ============ BUILT IN INDIA ============
   Sits between the wordmark and the carousel — see the markup comment in
   index.html and startBii() in main.js. A shade darker than every other
   dark surface on the site (--ink-deep rather than --ink) and, unlike
   #hooks and #suite, never tweened away from it: this stage opens on the
   darker green and holds it the whole way down.

   #bii itself is the same fixed full-viewport layer every other intro
   stage is; #biiScroll is what actually scrolls, natively, so a real
   wheel/trackpad/touch gesture moves through this content with no JS
   standing in the way of it — the same device #suite already falls back
   to on a phone (see its mobile block below). */
#bii{
  position:fixed;inset:0;z-index:1003;
  background:var(--ink-mid);
  visibility:hidden;pointer-events:none;
}
#bii.is-up{visibility:visible;pointer-events:auto;}
.bii-scroll{
  position:absolute;inset:0;overflow-y:auto;overscroll-behavior:contain;
  scrollbar-width:none;-webkit-overflow-scrolling:touch;
}
.bii-scroll::-webkit-scrollbar{display:none;}
.bii-inner{
  position:relative;z-index:1;
  max-width:1140px;margin:0 auto;
  padding:clamp(70px,11vh,120px) clamp(20px,4.4vw,54px) clamp(90px,12vh,130px);
}

/* ---------- ambient cursor glow ----------
   A soft gold wash that follows the pointer around the whole stage,
   ported from the reference's .spot. Sits behind .bii-inner (z-index 1)
   at z-index 0, fixed to the section rather than scrolling with it, and
   only turns on for hover-capable pointers — see the pointermove wiring
   in startBii(). */
.bii-amb{position:absolute;inset:0;z-index:0;overflow:hidden;pointer-events:none;}
.bii-spot{
  position:absolute;inset:0;opacity:0;transition:opacity .5s;
  background:radial-gradient(480px circle at var(--mx,50%) var(--my,30%),
    rgba(199,164,93,.10),transparent 62%);
}
#bii.bii-pointer .bii-spot{opacity:1;}

/* ---------- head ---------- */
.bii-head{max-width:920px;}
.bii-eyebrow{
  display:inline-flex;align-items:center;gap:10px;
  padding:7px 16px 7px 12px;border:1px solid rgba(246,242,233,.16);
  border-radius:999px;background:rgba(246,242,233,.04);
  font-family:var(--font-display);font-size:11px;font-weight:600;
  letter-spacing:.09em;text-transform:uppercase;color:rgba(246,242,233,.7);
}
.bii-dot{width:6px;height:6px;border-radius:50%;background:var(--mint);animation:biiPing 2.6s infinite;}
@keyframes biiPing{
  0%{box-shadow:0 0 0 0 rgba(134,217,161,.5);}
  70%{box-shadow:0 0 0 8px rgba(134,217,161,0);}
  100%{box-shadow:0 0 0 0 rgba(134,217,161,0);}
}
.bii-cycle{display:inline-block;height:1.3em;overflow:hidden;vertical-align:bottom;color:var(--grid);}
.bii-cycle-in{transition:transform .55s cubic-bezier(.22,1,.36,1);}
.bii-cycle-in span{display:block;height:1.3em;line-height:1.3em;white-space:nowrap;}

/* PAGE TITLE ROLE, PER virevo-type-scale.md.
   Bebas Neue is the one place on the real site's own type system that
   isn't Poppins — everything else in this section is. Sized off the
   spec's Option B clamp() (fitted between the 619px and 2304px captures),
   not the section's own inner max-width, because that clamp is what the
   title looks like everywhere else it appears on the site. */
.bii-title{
  margin:20px 0 0;font-family:'Bebas Neue',var(--font-display);font-weight:400;
  font-size:clamp(89px,calc(42.71px + 7.478vw),215px);
  line-height:.90;letter-spacing:.015em;text-transform:uppercase;
}
@media (min-width:1024px){ .bii-title{line-height:.95;} }
.bii-t-a{color:var(--grid);}
/* A SOLID COLOUR, NOT A GRADIENT.
   This was background-clip:text over a gradient, and it broke the moment
   splitWords() ran on the title: the clip needs the element's own text
   glyphs, and splitting wraps every character into its own inline-block
   span, so the "text" the parent could clip to was gone and the word
   painted invisible against the dark ground. --ink-mute is the token the
   colour table already names for exactly this job ("pale bone — giant
   vision type"), and at how close it sits to --cream the gradient would
   barely have read as one anyway. */
.bii-t-b{color:var(--ink-mute);}
/* GENERAL COPY ROLE, PER virevo-type-scale.md: Poppins 400, lead word 700,
   -0.015em tracking, 1.55 line-height. */
.bii-copy{
  margin-top:clamp(22px,3vw,34px);font-family:var(--font-display);font-weight:400;
  font-size:clamp(16px,calc(13.98px + 0.326vw),21.5px);line-height:1.55;
  letter-spacing:-.015em;color:rgba(246,242,233,.72);max-width:62ch;
}
.bii-copy strong{font-weight:700;color:var(--cream);}

.bii-btns{display:flex;gap:12px;flex-wrap:wrap;margin-top:clamp(26px,3vw,38px);}
.bii-btn{
  font-family:var(--font-display);font-size:11.5px;font-weight:600;
  letter-spacing:.18em;text-transform:uppercase;
  padding:14px clamp(18px,2vw,26px);border-radius:999px;cursor:pointer;
  transition:background .4s cubic-bezier(.22,1,.36,1),color .4s,border-color .4s;
}
.bii-btn-fill{border:1px solid var(--cream);background:var(--cream);color:var(--ink);}
.bii-btn-ghost{border:1px solid rgba(246,242,233,.28);background:transparent;color:rgba(246,242,233,.82);}
@media (hover:hover){
  .bii-btn-fill:hover{background:transparent;color:var(--cream);}
  .bii-btn-ghost:hover{border-color:var(--cream);color:var(--cream);}
}
.bii-btn:focus-visible{outline:2px solid var(--mint);outline-offset:3px;}

/* ---------- marquee ---------- */
.bii-marq{
  margin-top:clamp(48px,7vh,80px);overflow:hidden;
  border-top:1px solid rgba(246,242,233,.12);
  border-bottom:1px solid rgba(246,242,233,.12);
  padding:clamp(12px,1.6vh,18px) 0;
}
.bii-track{display:flex;width:max-content;animation:biiTick 34s linear infinite;}
.bii-marq:hover .bii-track{animation-play-state:paused;}
.bii-tick{display:flex;flex:0 0 auto;}
.bii-tick b{
  font-family:var(--font-display);font-size:11px;font-weight:600;
  letter-spacing:.09em;text-transform:uppercase;
  color:rgba(246,242,233,.4);white-space:nowrap;padding-right:26px;
}
.bii-tick b.bii-hi{color:var(--grid);}
.bii-tick i{font-style:normal;color:var(--mint);padding-right:26px;font-size:10px;}
@keyframes biiTick{to{transform:translateX(-50%);}}

/* ---------- section chrome (shared by the reasons and the comparison) ----------
   TILE SUB-HEADING role for .bii-kicker (Poppins 600, +0.09em, uppercase)
   and TILE HEADING role for .bii-sec-h — per virevo-type-scale.md's own
   "deliberate inversion": the heading between them reads lighter/greyer
   than the small label above it, uppercase, regular weight, not bold. */
.bii-sec{margin-top:clamp(64px,9vh,110px);}
.bii-kicker{
  display:flex;align-items:center;gap:14px;margin:0;
  font-family:var(--font-display);font-weight:600;font-size:clamp(15px,calc(14.30px + 0.113vw),17px);
  letter-spacing:.09em;text-transform:uppercase;color:var(--grid);
}
.bii-sec-h{
  margin:12px 0 0;font-family:'Bebas Neue',var(--font-display);font-weight:400;
  font-size:clamp(1.9rem,4vw,3.3rem);line-height:1.35;
  letter-spacing:0;text-transform:uppercase;color:rgba(246,242,233,.56);
}
@media (min-width:1024px){ .bii-sec-h{line-height:1.20;} }
.bii-sec-h em{font-style:normal;color:rgba(246,242,233,.32);}
/* letter-by-letter fade-up, run once from an IntersectionObserver in
   startBii() — hidden only once JS is confirmed live (see the .js gate
   note on splitWords()'s other callers), so a no-JS visitor still gets
   the heading rather than nothing. */
html.js .bii-sec-h .df-c{opacity:0;transform:translateY(16px);}

/* ---------- seven reasons — the tab strip, cloned from the reference ----------
   .bii-strip holds seven pill buttons (built by startBii()), one always
   aria-selected; .bii-panel is the one reusable detail card, repainted on
   each switch — see show()/runTab() in main.js. Ported class-for-class
   from the source's .strip/.tab/.panel, re-skinned onto this section's
   dark ground and Poppins-only type system. */
.bii-strip{
  display:flex;gap:8px;overflow-x:auto;scroll-snap-type:x mandatory;
  padding:4px 0 12px;margin-top:clamp(28px,3.6vw,44px);
  scrollbar-width:none;-webkit-overflow-scrolling:touch;
}
.bii-strip::-webkit-scrollbar{display:none;}
.bii-tab{
  scroll-snap-align:center;flex:0 0 auto;position:relative;overflow:hidden;
  cursor:pointer;display:flex;align-items:center;gap:9px;
  padding:10px 16px;border-radius:999px;border:1px solid rgba(246,242,233,.14);
  background:rgba(246,242,233,.03);color:rgba(246,242,233,.6);
  font-family:var(--font-display);font-weight:500;font-size:calc(clamp(15px,calc(14.30px + 0.113vw),17px)*.82);
  white-space:nowrap;transition:color .25s,border-color .25s,background .25s;
}
.bii-tab i{
  font-style:normal;font-family:'Bebas Neue',var(--font-display);font-weight:400;
  font-size:calc(clamp(15px,calc(14.30px + 0.113vw),17px)*.98);letter-spacing:.06em;
  color:rgba(246,242,233,.35);transition:color .25s;
}
@media (hover:hover){ .bii-tab:hover{color:var(--cream);border-color:rgba(246,242,233,.3);} }
.bii-tab[aria-selected="true"]{color:var(--ink-mid);background:var(--grid);border-color:var(--grid);}
.bii-tab[aria-selected="true"] i{color:rgba(12,23,16,.55);}
.bii-panel{
  position:relative;margin-top:clamp(18px,2.2vw,28px);
  border:1px solid rgba(246,242,233,.14);border-radius:clamp(14px,1.6vw,22px);
  background:linear-gradient(180deg,rgba(246,242,233,.045),rgba(246,242,233,.012));
  padding:clamp(22px,3.2vw,52px);overflow:hidden;
}
/* the cursor-tracking gold glow — see the pointermove wiring in startBii() */
.bii-panel::after{
  content:"";position:absolute;inset:0;border-radius:inherit;pointer-events:none;
  opacity:0;transition:opacity .4s;
  background:radial-gradient(340px circle at var(--px,50%) var(--py,50%),
    rgba(199,164,93,.13),transparent 62%);
}
@media (hover:hover){ .bii-panel:hover::after{opacity:1;} }
.bii-ghost-n{
  position:absolute;right:clamp(12px,2vw,32px);top:clamp(2px,.8vw,12px);
  font-family:'Bebas Neue',var(--font-display);font-weight:400;
  font-size:clamp(74px,10vw,164px);line-height:1;color:rgba(246,242,233,.045);
  pointer-events:none;
}
/* TILE SUB-HEADING role */
.bii-p-sub{
  margin:0;display:flex;align-items:center;gap:11px;
  font-family:var(--font-display);font-weight:600;font-size:clamp(15px,calc(14.30px + 0.113vw),17px);
  letter-spacing:.09em;text-transform:uppercase;color:var(--grid);
}
.bii-p-sub::before{content:"";width:0;height:1px;background:var(--grid);transition:width .4s cubic-bezier(.22,1,.36,1);}
.bii-panel.on .bii-p-sub::before{width:clamp(16px,2vw,30px);}
/* TILE HEADING role — lighter/greyer than the label above and the phrase below */
.bii-p-head{
  font-weight:400;font-size:clamp(20.8px,calc(18.71px + 0.338vw),26.5px);
  line-height:1.35;letter-spacing:0;text-transform:uppercase;
  color:rgba(246,242,233,.56);margin-top:clamp(20px,2.4vw,32px);max-width:22ch;
}
@media (min-width:1024px){ .bii-p-head{line-height:1.20;} }
/* letters, not words — the reveal is now a GSAP character stagger run
   from show() in main.js each time the panel repaints, using the same
   .df-w/.df-c split every other header on the site uses */
/* TILE COPY role */
.bii-p-copy{
  font-family:var(--font-display);font-weight:400;
  font-size:clamp(14.8px,calc(13.00px + 0.291vw),19.7px);line-height:1.50;
  letter-spacing:-.01em;color:rgba(246,242,233,.74);max-width:min(100%,620px);
  margin-top:clamp(14px,1.8vw,26px);opacity:0;transform:translateY(12px);
  transition:opacity .5s ease .2s,transform .5s cubic-bezier(.22,1,.36,1) .2s;
}
@media (min-width:1024px){ .bii-p-copy{line-height:1.55;} }
.bii-panel.on .bii-p-copy{opacity:1;transform:none;}
/* TILE KEY PHRASE role */
.bii-kp{
  font-family:var(--font-display);font-weight:600;
  font-size:clamp(26.6px,calc(24.03px + 0.415vw),33.6px);line-height:1.2;
  letter-spacing:0;text-transform:uppercase;color:var(--cream);
  margin-top:clamp(22px,3.6vw,56px);display:inline-block;position:relative;padding-bottom:11px;
}
.bii-kp::after{
  content:"";position:absolute;left:0;bottom:0;height:2px;width:100%;
  background:linear-gradient(90deg,var(--grid),var(--mint));
  transform:scaleX(0);transform-origin:left;transition:transform .55s cubic-bezier(.22,1,.36,1) .3s;
}
.bii-panel.on .bii-kp::after{transform:scaleX(1);}
.bii-chip{
  display:flex;align-items:flex-start;gap:10px;margin-top:clamp(18px,2.2vw,30px);
  padding:11px 16px;border:1px solid rgba(134,217,161,.16);border-radius:12px;
  background:rgba(134,217,161,.06);
  font-family:var(--font-display);font-weight:400;font-size:calc(clamp(14.8px,calc(13.00px + 0.291vw),19.7px)*.82);
  line-height:1.5;color:rgba(246,242,233,.62);max-width:min(100%,620px);
  opacity:0;transform:translateY(10px);
  transition:opacity .5s ease .34s,transform .5s cubic-bezier(.22,1,.36,1) .34s;
}
.bii-panel.on .bii-chip{opacity:1;transform:none;}
.bii-chip em{font-style:normal;color:var(--mint);font-weight:600;flex:0 0 auto;}

/* ---------- the comparison card ---------- */
.bii-card{
  position:relative;margin-top:clamp(28px,3.6vw,44px);
  border:1px solid rgba(246,242,233,.14);border-radius:18px;
  background:linear-gradient(180deg,rgba(246,242,233,.045),rgba(246,242,233,.012));
  padding:clamp(20px,2.8vw,44px);overflow:hidden;
}
.bii-asked{display:flex;gap:12px;align-items:flex-start;}
.bii-asked b{
  flex:0 0 auto;margin-top:3px;font-family:var(--font-display);font-size:10px;
  font-weight:600;letter-spacing:.09em;text-transform:uppercase;
  color:rgba(246,242,233,.42);border:1px solid rgba(246,242,233,.2);
  border-radius:6px;padding:5px 8px;
}
.bii-asked p{
  margin:0;font-family:var(--font-display);font-weight:400;letter-spacing:-.01em;
  font-size:clamp(14.8px,calc(13.00px + 0.291vw),19.7px);line-height:1.45;color:var(--cream);max-width:38ch;
}

.bii-seg{
  display:flex;margin-top:clamp(18px,2.2vw,28px);
  border:1px solid rgba(246,242,233,.16);border-radius:999px;
  padding:4px;position:relative;width:fit-content;max-width:100%;overflow:hidden;
}
.bii-slide{
  position:absolute;top:4px;bottom:4px;left:4px;border-radius:999px;
  background:var(--grid);
  transition:transform .45s cubic-bezier(.22,1,.36,1),width .45s cubic-bezier(.22,1,.36,1),background .3s;
}
.bii-seg.bii-t .bii-slide{background:var(--mint);}
.bii-seg button{
  position:relative;z-index:1;font-family:var(--font-display);font-size:11px;
  font-weight:600;letter-spacing:.09em;text-transform:uppercase;
  padding:9px 16px;border:0;background:none;color:rgba(246,242,233,.55);
  cursor:pointer;white-space:nowrap;transition:color .3s;
}
.bii-seg button[aria-pressed="true"]{color:var(--ink-deep);}

.bii-score{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(128px,1fr));
  gap:8px;margin-top:clamp(16px,2vw,24px);
}
.bii-sc{
  position:relative;overflow:hidden;border-radius:12px;padding:12px 14px;
  border:1px solid rgba(246,242,233,.12);background:rgba(246,242,233,.02);
}
.bii-sc small{
  display:block;font-family:var(--font-display);font-size:9.5px;font-weight:600;
  letter-spacing:.09em;text-transform:uppercase;color:rgba(246,242,233,.4);
}
.bii-sc b{
  display:block;margin-top:5px;font-family:var(--font-display);font-weight:600;
  font-size:clamp(22px,2.6vw,32px);line-height:1.05;color:rgba(246,242,233,.55);
}
.bii-sc.bii-good b{color:var(--mint);}
.bii-sc.bii-bad b{color:#e07a6a;}
.bii-sc::after{content:"";position:absolute;inset:0;background:var(--mint);opacity:0;pointer-events:none;}
.bii-sc.bii-flash::after{animation:biiFlash .6s ease;}
@keyframes biiFlash{0%{opacity:.16;}100%{opacity:0;}}

.bii-ans{position:relative;margin-top:clamp(16px,2vw,24px);height:340px;transition:height .6s cubic-bezier(.22,1,.36,1);}
.bii-ans>div{position:absolute;inset:0;transition:opacity .45s ease,filter .45s ease,transform .45s cubic-bezier(.22,1,.36,1);}

.bii-gen{
  overflow:hidden;
  -webkit-mask-image:linear-gradient(#000 60%,transparent 96%);
  mask-image:linear-gradient(#000 60%,transparent 96%);
}
.bii-scr{animation:biiGenScroll 38s linear infinite;}
.bii-gen p{
  margin:0 0 11px;font-family:var(--font-display);font-weight:400;letter-spacing:-.01em;
  font-size:calc(clamp(14.8px,calc(13.00px + 0.291vw),19.7px)*.86);line-height:1.7;color:rgba(246,242,233,.4);
}
.bii-gen p.bii-q{color:rgba(246,242,233,.6);}
.bii-gen p.bii-q::before{content:"?";display:inline-block;width:16px;color:#e07a6a;font-weight:700;}
@keyframes biiGenScroll{to{transform:translateY(-50%);}}

.bii-tojo{opacity:0;pointer-events:none;transform:translateY(10px);}
.bii-card.bii-done .bii-gen{opacity:0;filter:blur(8px);transform:scale(.985);}
.bii-card.bii-done .bii-tojo{opacity:1;pointer-events:auto;transform:none;}
.bii-tojo p{
  margin:0 0 13px;font-family:var(--font-display);font-weight:400;letter-spacing:-.01em;
  font-size:calc(clamp(14.8px,calc(13.00px + 0.291vw),19.7px)*.94);line-height:1.55;
  color:rgba(246,242,233,.82);max-width:62ch;opacity:0;transform:translateY(9px);
}
.bii-card.bii-done .bii-tojo p{
  opacity:1;transform:none;
  transition:opacity .4s ease var(--d),transform .4s cubic-bezier(.22,1,.36,1) var(--d);
}
.bii-tojo.bii-focus p{opacity:.24;transition:opacity .4s;}

/* THE HIGHLIGHT IS A BACKGROUND ON THE MARK ITSELF, NOT A CHILD BOX.
   It used to be a separate absolutely-positioned span (.bii-hl) scaled in
   behind the text. That only ever covered the mark's FIRST line fragment
   — position:absolute inside a wrapped inline element sizes and places
   itself against that one fragment, not the union of every line the text
   wrapped onto, so a two-line phrase lit up on line one and went bare on
   line two. box-decoration-break:clone is what a background (or a
   box-shadow) actually needs to repeat itself correctly across each
   wrapped line of an inline element; padding/border-radius clone with it
   too, which is why the resting underline below reads as one shape per
   line rather than one shape for the whole run. */
.bii-tojo mark{
  background:none;color:inherit;position:relative;display:inline;
  border-radius:4px;padding:1px 2px;
  box-decoration-break:clone;-webkit-box-decoration-break:clone;
  box-shadow:inset 0 -.08em 0 rgba(199,164,93,.35);
  transition:box-shadow .35s,color .35s,background-color .5s cubic-bezier(.22,1,.36,1);
}
.bii-tojo.bii-focus mark.bii-act{
  color:var(--ink-deep);box-shadow:none;background-color:var(--grid);
}
.bii-tojo.bii-focus mark.bii-g.bii-act{background-color:var(--mint);}
.bii-tojo.bii-focus p:has(mark.bii-act),.bii-tojo.bii-focus p.bii-actp{opacity:1;}
mark.bii-act{animation:biiPop .5s cubic-bezier(.22,1,.36,1);}
@keyframes biiPop{0%{letter-spacing:0;}55%{letter-spacing:.012em;}100%{letter-spacing:0;}}
.bii-callout{
  position:absolute;left:0;top:-2.35em;font-family:var(--font-display);
  font-size:9.5px;font-weight:700;letter-spacing:.09em;text-transform:uppercase;
  color:var(--grid);white-space:nowrap;opacity:0;transform:translateY(6px);
  transition:opacity .35s,transform .35s cubic-bezier(.22,1,.36,1);
  display:flex;align-items:center;gap:6px;
}
.bii-callout::after{content:"";width:22px;height:1px;background:currentColor;}
mark.bii-g .bii-callout{color:var(--mint);}
mark.bii-act .bii-callout{opacity:1;transform:none;}

.bii-gfx{
  margin-top:clamp(14px,1.8vw,22px);border:1px solid rgba(246,242,233,.1);
  border-radius:12px;overflow:hidden;background:var(--ink-mid);
  position:relative;opacity:0;transform:translateY(14px);
  transition:opacity .6s ease,transform .6s cubic-bezier(.22,1,.36,1);
}
.bii-card.bii-done .bii-gfx{opacity:1;transform:none;transition-delay:1.5s;}
.bii-gfx svg{display:block;width:100%;height:auto;}
.bii-gfx-lbl{
  position:absolute;right:10px;top:10px;font-family:var(--font-display);
  font-size:8.5px;font-weight:700;letter-spacing:.09em;text-transform:uppercase;
  color:var(--mint);background:rgba(8,16,11,.72);border:1px solid rgba(134,217,161,.3);
  border-radius:5px;padding:4px 7px;opacity:0;transform:translateY(-5px);
  transition:opacity .4s,transform .4s cubic-bezier(.22,1,.36,1);
}
.bii-gfx.bii-spot .bii-gfx-lbl{opacity:1;transform:none;}
.bii-gfx.bii-spot{box-shadow:0 0 0 2px var(--mint),0 0 44px rgba(134,217,161,.3);}
.bii-gfx .bii-el{opacity:0;transform:translateY(10px);transform-box:fill-box;transform-origin:center;}
.bii-gfx.bii-draw .bii-el{
  opacity:1;transform:none;
  transition:opacity .5s ease var(--d),transform .5s cubic-bezier(.22,1,.36,1) var(--d);
}
.bii-gfx.bii-draw .bii-pulse{animation:biiPulse 2.4s ease-in-out 2.2s infinite;}
@keyframes biiPulse{0%,100%{opacity:1;}50%{opacity:.55;}}

.bii-holdbar{
  display:flex;align-items:center;gap:14px;flex-wrap:wrap;
  margin-top:clamp(16px,2vw,26px);padding-top:clamp(14px,1.8vw,22px);
  border-top:1px solid rgba(246,242,233,.12);
}
.bii-hold{
  position:relative;width:clamp(50px,5vw,60px);height:clamp(50px,5vw,60px);
  border-radius:50%;border:0;cursor:pointer;
  background:conic-gradient(var(--grid) calc(var(--p,0)*1%),rgba(246,242,233,.11) 0);
  display:grid;place-items:center;flex:0 0 auto;touch-action:none;transition:transform .2s;
}
.bii-hold:active{transform:scale(.95);}
.bii-hold::before{content:"";position:absolute;inset:4px;border-radius:50%;background:var(--ink-mid);}
.bii-hold em{position:relative;font-style:normal;font-size:clamp(15px,1.5vw,19px);color:var(--grid);line-height:1;}
.bii-holdtxt{font-family:var(--font-display);font-weight:400;letter-spacing:-.01em;font-size:calc(clamp(14.8px,calc(13.00px + 0.291vw),19.7px)*.8);color:rgba(246,242,233,.6);line-height:1.45;}
.bii-holdtxt b{display:block;color:var(--cream);font-weight:600;font-size:calc(clamp(14.8px,calc(13.00px + 0.291vw),19.7px)*.9);}
.bii-again{
  margin-left:auto;font-family:var(--font-display);font-size:9.5px;font-weight:600;
  letter-spacing:.09em;text-transform:uppercase;background:none;
  border:1px solid rgba(246,242,233,.2);border-radius:999px;padding:9px 15px;
  color:rgba(246,242,233,.55);cursor:pointer;opacity:0;pointer-events:none;transition:.25s;
}
.bii-card.bii-done .bii-again{opacity:1;pointer-events:auto;}
@media (hover:hover){.bii-again:hover{border-color:var(--mint);color:var(--mint);}}

.bii-close{
  margin-top:clamp(24px,3vw,40px);font-family:var(--font-display);font-weight:400;
  letter-spacing:-.015em;
  font-size:clamp(16px,calc(13.98px + 0.326vw),21.5px);line-height:1.55;color:rgba(246,242,233,.6);max-width:52ch;
}
.bii-close b{color:var(--cream);font-weight:600;}

.bii-continue{
  display:flex;align-items:center;gap:8px;justify-content:center;
  margin:clamp(56px,7vh,80px) 0 0;font-family:var(--font-display);font-weight:600;
  font-size:10.5px;letter-spacing:.09em;text-transform:uppercase;color:rgba(246,242,233,.4);
}
.bii-continue svg{
  width:14px;height:14px;fill:none;stroke:currentColor;stroke-width:1.6;
  stroke-linecap:round;stroke-linejoin:round;animation:biiNudge 1.8s ease-in-out infinite;
}
@keyframes biiNudge{0%,100%{transform:translateY(0);}50%{transform:translateY(4px);}}

@media (max-width:640px){
  .bii-btns{flex-direction:column;align-items:stretch;}
  .bii-btn{text-align:center;}
  .bii-score{grid-template-columns:repeat(2,1fr);}
  .bii-ans{height:auto;min-height:300px;}
}

/* ============ NAV ============ */
#nav{
  position:fixed;top:18px;left:50%;transform:translateX(-50%);
  z-index:900;width:min(680px,calc(100% - 32px));height:var(--nav-h);
  display:grid;grid-template-columns:1fr auto 1fr;align-items:center;
  padding:0 28px;border-radius:6px;
  background:rgba(246,242,233,.22);
  backdrop-filter:blur(18px);-webkit-backdrop-filter:blur(18px);
  border:1px solid rgba(199,164,93,.22);
  transition:background .5s,border-color .5s,box-shadow .5s,
             transform .45s cubic-bezier(.22,1,.36,1),opacity .35s;
}
/* The bar belongs to the hero. Once the hero has gone it slides up out of
   frame for good and the pull-tab below takes over. The 40px past its own
   height clears the 18px top offset too. */
#nav.hidden{
  transform:translateX(-50%) translateY(calc(-100% - 40px));
  opacity:0;pointer-events:none;
}

/* ---------- pull-tab : the nav's stand-in past the hero ---------- */
.side-tab{
  position:fixed;right:0;top:15px;z-index:860;
  width:34px;height:56px;padding:0;cursor:pointer;
  border:1px solid rgba(199,164,93,.30);border-right:0;
  border-radius:8px 0 0 8px;
  background:rgba(17,28,21,.55);
  backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);
  display:flex;align-items:center;justify-content:center;
  /* parked fully off-stage until it is wanted */
  transform:translateX(110%);opacity:0;pointer-events:none;
  transition:transform .55s cubic-bezier(.16,1,.3,1),opacity .4s;
}
.side-tab.show{transform:translateX(0);opacity:1;pointer-events:auto;}
/* chevron drawn from two borders, pointing back into the page */
.st-arrow{
  display:block;width:8px;height:8px;margin-left:3px;
  border-left:1.5px solid rgba(246,242,233,.85);
  border-bottom:1.5px solid rgba(246,242,233,.85);
  transform:rotate(45deg);
  transition:transform .4s cubic-bezier(.22,1,.36,1);
}
.side-tab:hover .st-arrow{transform:rotate(45deg) translate(-2px,2px);}

/* ---------- right-hand drawer ---------- */
.side-pane{
  position:fixed;inset:0;z-index:870;
  background:rgba(17,28,21,.6);
  backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);
  opacity:0;visibility:hidden;
  transition:opacity .5s cubic-bezier(.77,0,.18,1),visibility 0s linear .5s;
}
.side-pane.open{opacity:1;visibility:visible;transition:opacity .5s cubic-bezier(.77,0,.18,1);}
.sd-inner{
  position:absolute;right:0;top:0;bottom:0;
  width:min(320px,82vw);
  background:linear-gradient(180deg,rgba(30,55,39,.98),rgba(18,32,24,.99));
  border-left:1px solid rgba(199,164,93,.28);
  box-shadow:-30px 0 80px rgba(0,0,0,.4);
  padding:24px 26px 22px;
  display:flex;flex-direction:column;
  transform:translateX(100%);
  transition:transform .6s cubic-bezier(.16,1,.3,1);
}
.side-pane.open .sd-inner{transform:translateX(0);}
.sd-top{display:flex;align-items:center;justify-content:space-between;}
.sd-logo{
  font-family:var(--font-display);font-weight:600;font-size:17px;
  letter-spacing:.3em;text-indent:.3em;color:#f6f2e9;
}
.sd-close{
  background:none;border:0;cursor:pointer;width:22px;height:22px;
  position:relative;padding:0;
}
.sd-close span{
  position:absolute;left:0;top:50%;width:100%;height:1.5px;background:#f6f2e9;
}
.sd-close span:nth-child(1){transform:rotate(45deg);}
.sd-close span:nth-child(2){transform:rotate(-45deg);}
.side-pane ul{list-style:none;margin:34px 0 0;padding:0;flex:1 1 auto;}
.side-pane li{
  border-bottom:1px solid rgba(246,242,233,.12);
  /* the resting state IS the arrived state, so a drawer that opens with
     transitions disabled still reads correctly */
  opacity:1;transform:none;
}
/* ---- ARRIVAL, RETIMED TO THE RECORDING ----
   This drawer already staggered, but on its own numbers: 0.06s apart and
   sliding in from the right on translateX. The reference does neither.

   Measured off the recording at 60fps: each label reaches full width in a
   single frame while its ink keeps darkening for ~0.45s — a whole word
   fading up, not a wipe and not a per-letter run — and the labels start
   exactly 6 frames apart, three gaps running, which is 0.100s. The first
   begins 0.08s after the panel itself has landed.

   The panel slides for .6s, so the first label starting at .08s overlaps it
   deliberately: the list is arriving as the drawer settles rather than
   queueing behind it, which is what the recording shows. */
.side-pane.open .sd-nav li{
  animation:mnRise .45s cubic-bezier(.17,.87,.28,1) both;
  animation-delay:calc(.08s + var(--n) * .10s);
}
.side-pane a{
  display:block;padding:15px 0;
  font-family:var(--font-display);font-weight:400;font-size:19px;
  letter-spacing:.02em;color:#f6f2e9;transition:color .35s,padding-left .35s;
}
.side-pane a:hover{color:var(--gold, #c7a45d);padding-left:6px;}
.sd-foot{
  padding-top:18px;font-size:9.5px;letter-spacing:.12em;
  color:rgba(246,242,233,.45);
}
#nav.solid{
  background:rgba(246,242,233,.22);
  border-color:var(--gold-dim);
  box-shadow:0 12px 40px rgba(30,55,39,.08);
}
#nav.solid .logo,#nav.solid .nav-link,#nav.solid .burger span{color:var(--ink);}
#nav.solid .burger span{background:var(--ink);}
.burger{background:none;border:0;cursor:pointer;width:34px;display:flex;flex-direction:column;gap:7px;justify-self:start;padding:6px 0;}
.burger span{display:block;height:2px;width:100%;background:var(--ink);transition:transform .4s cubic-bezier(.77,0,.18,1),opacity .3s,background .5s;}
.burger.open span:nth-child(1){transform:translateY(9px) rotate(45deg);}
.burger.open span:nth-child(2){transform:translateY(-0px) rotate(-45deg);}
.logo{
  font-family:var(--font-display);font-weight:600;font-size:22px;
  letter-spacing:.34em;text-indent:.34em;color:var(--ink);transition:color .5s;
}
.nav-link{
  justify-self:end;font-family:var(--font-mono);font-size:12px;letter-spacing:.18em;
  color:var(--ink);display:flex;align-items:center;gap:8px;transition:color .5s;font-weight:600;}
.nav-link em{font-style:normal;transition:transform .4s;}
.nav-link:hover em{transform:rotate(90deg);}

/* centered card menu — floats over a dimmed backdrop instead of covering
   the whole screen (matches https://db-longbow.webflow.io/'s nav overlay:
   a bordered, rounded card centred in the viewport with page content
   still visible, darkened, behind it). Applies at every viewport width —
   the burger/#menu pair has no separate desktop nav, so this is the one
   menu experience site-wide. */
#menu{
  position:fixed;inset:0;z-index:850;
  display:flex;align-items:center;justify-content:center;padding:24px;
  background:rgba(17,28,21,.6);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);
  opacity:0;visibility:hidden;
  transition:opacity .5s cubic-bezier(.77,0,.18,1),visibility 0s linear .5s;
}
#menu.open{opacity:1;visibility:visible;transition:opacity .5s cubic-bezier(.77,0,.18,1);}

.menu-card{
  width:min(420px,100%);max-height:calc(100vh - 48px);overflow-y:auto;
  /* THE TRACK IS THE PANEL, NOT THE PAGE.
     ::-webkit-scrollbar-track up at the top of this file paints every track
     in --bg — correct for the document, wrong inside a dark green card,
     where it drew a cream stripe down the right-hand edge. The card states
     its own, and the rules below do the same for WebKit. Only ever seen on
     a short screen now that the list itself is tighter. */
  scrollbar-width:thin;
  scrollbar-color:rgba(199,164,93,.45) transparent;
  background:linear-gradient(180deg,rgba(30,55,39,.98),rgba(18,32,24,.99));
  border:1px solid rgba(199,164,93,.28);border-radius:18px;
  box-shadow:0 30px 80px rgba(0,0,0,.4);
  padding:24px 26px 22px;
  transform:translateY(18px) scale(.97);opacity:0;
  transition:transform .5s cubic-bezier(.22,1,.36,1),opacity .4s;
}
#menu.open .menu-card{transform:translateY(0) scale(1);opacity:1;transition-delay:.08s;}

/* ---------- GENIE: standing the slide down ----------
   genie.js pours the panel out of the button it was opened from, and it
   owns the transform and the clip for the whole run. These transitions
   are the fallback underneath it — correct on their own, and a competing
   animation of the same property once the script is there. Cleared only
   when the script has announced itself on <html>, so a page that does not
   load it, or a reader on reduced motion, keeps the slide. */
html[data-genie] .menu-card,
html[data-genie] .sd-inner{transition:none;}

/* THE BACKDROP'S BLUR, WHILE THE PANEL IS MOVING.
   A backdrop-filter re-runs whenever anything painted above it repaints,
   and a clip-path change is a repaint — so the full-viewport blur was
   being recomputed on every frame of the pour to soften a page nobody is
   looking at. Throttled to a quarter speed, that was 31 dropped frames in
   64; without it, none. What is actually read through these backdrops is
   the 60%-opaque dark wash, which stays; the blur is a refinement on an
   already obscured page and is not missed for a third of a second.

   Toggled as an attribute on the root and matched here, NOT written to
   element.style from the script: both end at the same computed value, but
   measured over three runs each, the inline write still cost 16 dropped
   frames and this costs none. */
html[data-genie-moving] #menu,
html[data-genie-moving] .side-pane{
  backdrop-filter:none !important;
  -webkit-backdrop-filter:none !important;
}
.menu-card::-webkit-scrollbar{width:6px;}
.menu-card::-webkit-scrollbar-track{background:transparent;}
.menu-card::-webkit-scrollbar-thumb{background:rgba(199,164,93,.45);border-radius:6px;}
.menu-card::-webkit-scrollbar-thumb:hover{background:rgba(199,164,93,.7);}

.menu-top{
  display:flex;align-items:center;justify-content:space-between;gap:14px;
  padding-bottom:14px;border-bottom:1px solid rgba(199,164,93,.18);
}
.menu-close{
  background:none;border:0;cursor:pointer;width:20px;height:20px;
  position:relative;flex:none;
}
.menu-close span{
  position:absolute;left:0;top:50%;width:100%;height:1.5px;background:var(--cream);
  transform-origin:center;
}
.menu-close span:nth-child(1){transform:translateY(-50%) rotate(45deg);}
.menu-close span:nth-child(2){transform:translateY(-50%) rotate(-45deg);}
.menu-logo{
  font-family:var(--font-display);font-weight:600;font-size:15px;
  letter-spacing:.3em;text-indent:.3em;color:var(--cream);
}
.menu-tag{
  font-family:var(--font-mono);font-size:10px;letter-spacing:.16em;text-transform:uppercase;
  color:var(--grid);display:flex;align-items:center;gap:6px;flex:none;font-weight:600;}
.menu-tag em{font-style:normal;}

#menu ul{list-style:none;padding:3px 0 2px;}
#menu li{border-bottom:1px solid rgba(199,164,93,.12);}
#menu li:last-child{border-bottom:0;}
#menu a{
  display:block;padding:8px 2px;text-align:center;
  font-family:var(--font-display);font-weight:300;font-style:italic;
  font-size:1.5rem;color:var(--cream);transition:color .3s;
}
#menu a:hover{color:var(--grid);}
#menu .menu-foot{text-align:center;padding-top:14px;color:rgba(246,242,233,.45);font-size:10.5px;}

/* ============ HERO GRID FRAME (selection-canvas style) ============ */
.hero-frame{
  /* bottom inset is 0 — the bottom border sits flush with the section end */
  position:absolute;inset:16px 16px 0;z-index:5;pointer-events:none;
  border:1px solid rgba(199,164,93,.75);
}
.hero-frame .gl{position:absolute;background:rgba(199,164,93,.55);}
/* the frame is 16px shorter than the section (bottom inset 0), so the
   section's true midline sits at calc(50% - 8px) of the frame */
.gl.vc{left:50%;top:0;bottom:0;width:1px;}                    /* vertical centre, full height   */
.gl.vr{left:75%;top:calc(50% - 8px);bottom:0;width:1px;}      /* vertical 75%, bottom half only */
.gl.vl{left:25%;top:0;height:calc(50% - 8px);width:1px;}      /* vertical 25%, top half only    */
.gl.hm{top:calc(50% - 8px);left:0;right:0;height:1px;}        /* horizontal centre              */
/* every .gl line is a 1px-wide/tall box positioned by its left/top edge,
   so its visual stroke sits half a pixel past the intended percentage
   mark — while .handle nodes are truly centred on that mark via
   translate(-50%,-50%). Nudge each line back by half its own thickness
   so the stroke's centre lands exactly where the nodes land, everywhere
   this grid overlay is used. */
.gl.vc,.gl.vl,.gl.vr{transform:translateX(-50%);}
.gl.hm,.gl.hm1,.gl.hm2,.gl.edh,.gl.tmh{transform:translateY(-50%);}
/* mid-height handles (inline style top:50%) get the same -8px rebase */
.hero-frame .handle[style*="top:50%"]{top:calc(50% - 8px)!important;}
/* .morning-frame variants (#morning, #morning4, #execdec) have no bottom-
   inset seam, so a handle placed at a literal top:50% means exactly 50% —
   the -8px seam rebase above only applies to the default frame and must
   not shift these nodes off their true grid-line crossing */
.morning-frame .handle[style*="top:50%"]{top:50%!important;}
.hero-frame .handle{
  position:absolute;width:10px;height:10px;
  background:var(--cream);border:1px solid rgba(30,55,39,.3);
  transform:translate(-50%,-50%);box-shadow:0 1px 4px rgba(0,0,0,.25);
}

/* ============ HERO ============ */
#hero{height:420vh;position:relative;}
.hero-sticky{
  position:sticky;top:0;height:100vh;overflow:hidden;
  background:var(--bg) url("../../Images/bg.jpg") center/cover no-repeat;
}

/* ---------- hero media : centered character video over the studio backdrop ---------- */
.hero-scene{position:absolute;inset:0;z-index:1;overflow:hidden;}
.hero-video{
  position:absolute;left:50%;top:50%;
  height:min(74vh,54vw);aspect-ratio:16/9;width:auto;
  transform:translate(-50%,-53%);
  object-fit:cover;will-change:transform;
  /* feathered edges so the clip melts into bg.jpg */
  -webkit-mask-image:radial-gradient(ellipse 92% 90% at 50% 50%,#000 55%,transparent 97%);
  mask-image:radial-gradient(ellipse 92% 90% at 50% 50%,#000 55%,transparent 97%);
}
/* soft edge vignette to seat the scene */
.hero-shade{position:absolute;inset:0;z-index:2;pointer-events:none;background:radial-gradient(ellipse 130% 95% at 50% 35%,transparent 55%,rgba(199,164,93,.10) 100%);}

/* giant kinetic wordmark — clipped to the bottom-left two blocks,
   starting at the left gold grid line, auto-scrolling in a slow seamless loop */
.hero-wordclip{
  position:absolute;left:16px;bottom:0;z-index:4; /* under .hero-frame (z5) so grid lines + nodes stay on top */
  width:calc(50% - 16px);height:44vh;overflow:hidden;pointer-events:none;
}
.hero-wordmark{
  /* clip now sits on the bottom gold grid line (16px), so only a 1px gap is
     added here; .12em compensates Bebas Neue's descender space */
  position:absolute;bottom:calc(1px - .12em);left:0;display:flex;white-space:nowrap;
  font-family:'Bebas Neue',var(--font-display);font-weight:700;
  font-size:clamp(9rem,22vw,22rem);line-height:.85;letter-spacing:.02em;
  color:var(--ink);will-change:transform;user-select:none;
  animation:wmScroll 65s linear infinite;
}
@keyframes wmScroll{to{transform:translateX(-50%);}}
@keyframes cwScroll{to{transform:translate3d(calc(var(--cw-item) * -5),0,0);}}

/* frosted blur panel filling the bottom-right grid box —
   z-index below .hero-frame (5) so grid lines & nodes render on top;
   left edge matches the frame's 75% line: 16px inset + 75% of (100% - 32px) */
.hero-copybg{
  position:absolute;z-index:4;pointer-events:none;
  left:calc(75% - 7px);right:16px;top:calc(50% + 1px);bottom:0;
  background:rgba(246,242,233,.30);
  backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);
}
/* hero copy block — fitted inside the bottom-right grid box
   (between the 75% line, centre line, right & bottom frame borders) */
.hero-copy{
  position:absolute;z-index:7;
  left:calc(75% + 30px);right:46px;
  top:calc(50% + 44px);bottom:18px;
  will-change:transform,opacity;
}
.hero-chip{
  display:inline-flex;margin-bottom:26px;
  background:var(--ink);border-radius:8px;padding:7px 14px;
  max-width:170px;overflow:hidden;
  font-family:var(--font-mono);font-size:11px;letter-spacing:.22em;color:var(--cream);font-weight:600;}
.hero-chip .tickwin{
  display:inline-block;overflow:hidden;max-width:100%;
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 12%,#000 88%,transparent);
  mask-image:linear-gradient(90deg,transparent,#000 12%,#000 88%,transparent);
}
.hero-chip .tick{
  /* same seamless-loop structure as .hero-wordmark: flex track,
     two identical spans, parent slides -50% */
  display:flex;white-space:nowrap;width:max-content;
  animation:chipTick 7s linear infinite;
}
.hero-chip .tick > span{display:block;white-space:nowrap;}
@keyframes chipTick{to{transform:translateX(-50%);}}
.hero-title{
  font-family:'Poppins',serif;font-weight:300;color:var(--ink);
  font-size:clamp(1.4rem,2.1vw,2.1rem);line-height:1.3;
}
.hero-title .it{font-style:italic;font-weight:300;}
.hero-title .rule{display:inline-block;width:1.2em;height:1px;background:var(--ink);vertical-align:middle;margin-left:.25em;}
.hero-body{
  margin-top:30px;font-family:var(--font-mono);font-size:12px;line-height:1.95;
  letter-spacing:.1em;text-transform:uppercase;color:rgba(30,55,39,.72);font-weight:600;}
/* phase copy blocks that swap while scrubbing —
   chip + headline at the top, paragraph anchored to the box bottom */
.hero-phase{
  position:absolute;inset:0;opacity:0;transition:opacity .5s;pointer-events:none;
  display:flex;flex-direction:column;
}
.hero-phase.active{opacity:1;}
/* mobile-only hero copy + closing line — see the ≤980px block */
.hero-mcopy,.hero-foot{display:none;}
/* the EDO section, its strip, the speed and industries sections are
   mobile-only — see the ≤980px block */
#edo,.edo-strip,#speed,#industries{display:none;}
.hero-phases{position:relative;height:100%;}
.hero-body{margin-top:auto;}

.scroll-cue{
  position:absolute;z-index:7;left:44px;bottom:calc(50% + 26px);
  font-family:var(--font-mono);font-size:10.5px;letter-spacing:.28em;color:rgba(30,55,39,.6);
  display:flex;align-items:center;gap:14px;text-transform:uppercase;font-weight:600;}
.scroll-cue .line{width:56px;height:1px;background:rgba(30,55,39,.25);position:relative;overflow:hidden;}
.scroll-cue .line i{position:absolute;inset:0;background:var(--grid);animation:cue 2.2s cubic-bezier(.77,0,.18,1) infinite;}
@keyframes cue{0%{transform:translateX(-100%)}55%{transform:translateX(0)}100%{transform:translateX(100%)}}

/* ============ SECTION SHELL ============ */
section{position:relative;}
.shell{width:min(1280px,calc(100% - clamp(32px,6vw,96px)));margin:0 auto;}
.sec-head{display:flex;align-items:baseline;justify-content:space-between;gap:24px;margin-bottom:clamp(40px,6vw,84px);flex-wrap:wrap;}
.kicker{font-family:var(--font-mono);font-size:11.5px;letter-spacing:.3em;text-transform:uppercase;color:var(--grid);display:flex;align-items:center;gap:12px;font-weight:600;}
.kicker::before{content:"";width:34px;height:1px;background:var(--grid);}
.sec-index{font-family:var(--font-mono);font-size:11.5px;color:var(--ink-soft);letter-spacing:.2em;font-weight:600;}

/* split text reveal */
.reveal-line{overflow:hidden;display:block;}
.reveal-line>span{display:inline-block;transform:translateY(115%);transition:transform 1s cubic-bezier(.22,1,.36,1);will-change:transform;}
.inview .reveal-line>span{transform:translateY(0);}
.inview .reveal-line:nth-child(2)>span{transition-delay:.08s;}
.inview .reveal-line:nth-child(3)>span{transition-delay:.16s;}
.inview .reveal-line:nth-child(4)>span{transition-delay:.24s;}
.fade-up{opacity:0;transform:translateY(36px);transition:opacity 1s cubic-bezier(.22,1,.36,1),transform 1s cubic-bezier(.22,1,.36,1);}
.inview .fade-up,.fade-up.inview{opacity:1;transform:none;}
.d1{transition-delay:.1s}.d2{transition-delay:.2s}.d3{transition-delay:.3s}.d4{transition-delay:.4s}

/* ============ MANIFESTO ============ */
#manifesto{padding:clamp(110px,16vw,220px) 0 clamp(90px,12vw,180px);border-bottom:1px solid var(--gold-faint);}
.manifesto-text{
  font-family:var(--font-display);font-weight:300;
  font-size:clamp(1.6rem,4.2vw,3.4rem);line-height:1.35;max-width:22ch;
}
.manifesto-text .w{display:inline-block;opacity:.14;transition:opacity .5s;}
.manifesto-text .w.lit{opacity:1;}
.manifesto-text .accent{color:var(--grid);font-style:italic;}
.manifesto-side{
  margin-top:56px;display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:40px;max-width:860px;
}
.manifesto-side p{font-size:16px;color:var(--ink-soft);margin-top:14px;}
.manifesto-side p:first-of-type{margin-top:0;}
.manifesto-side .lbl{font-family:var(--font-mono);font-size:11px;letter-spacing:.25em;color:var(--ink);display:block;margin-bottom:12px;text-transform:uppercase;font-weight:600;}
.manifesto-side .mf-lead{
  display:inline-block;
  position:relative;
  font-family:var(--font-display);font-weight:700;font-style:italic;
  font-size:clamp(1.25rem,2.15vw,1.75rem);line-height:1.3;color:var(--ink);
  background:linear-gradient(100deg,var(--gold-faint) 0%,var(--gold-dim) 160%);
  padding:11px 22px 11px 24px;border-radius:3px;
  border-left:3px solid var(--grid);
  box-shadow:0 10px 24px -12px rgba(199,164,93,.55);
  margin-bottom:6px;
  letter-spacing:-.01em;
}

/* ============ MARQUEE ============ */
.marquee{overflow:hidden;padding:26px 0;border-top:1px solid var(--gold-faint);border-bottom:1px solid var(--gold-faint);background:var(--bg);}
.marquee-track{display:flex;gap:64px;width:max-content;will-change:transform;animation:marq 40s linear infinite;}
.marquee-track span{
  font-family:var(--font-mono);font-size:12px;letter-spacing:.3em;text-transform:uppercase;
  color:var(--ink-soft);display:flex;align-items:center;gap:64px;white-space:nowrap;font-weight:600;}
.marquee-track b{color:var(--grid);font-weight:400;}
@keyframes marq{to{transform:translateX(-50%);}}

/* ============ SOLUTIONS (models) ============ */
#solutions{padding:clamp(100px,14vw,200px) 0;}
.sol-grid{display:grid;grid-template-columns:1fr 1fr;gap:clamp(20px,3vw,44px);}
.sol-card{
  position:relative;border:1px solid var(--gold-dim);border-radius:8px;overflow:hidden;
  background:#141a16;min-height:640px;display:flex;flex-direction:column;justify-content:flex-end;
  transform-style:preserve-3d;transition:transform .6s cubic-bezier(.22,1,.36,1),box-shadow .6s;
  cursor:pointer;
}
.sol-card:hover{box-shadow:0 30px 80px rgba(30,55,39,.25);}
.sol-visual{position:absolute;inset:0;transition:transform 1.2s cubic-bezier(.22,1,.36,1);}
.sol-card:hover .sol-visual{transform:scale(1.06);}
.sol-visual.v1{background:
  radial-gradient(ellipse 80% 55% at 35% 30%,rgba(199,164,93,.35) 0%,transparent 60%),
  radial-gradient(ellipse 60% 60% at 80% 80%,rgba(246,242,233,.12) 0%,transparent 60%),
  linear-gradient(155deg,#1c241f 0%,#2c3a30 55%,#131814 100%);}
.sol-visual.v2{background:
  radial-gradient(ellipse 75% 50% at 70% 25%,rgba(199,164,93,.3) 0%,transparent 55%),
  radial-gradient(ellipse 70% 60% at 20% 85%,rgba(246,242,233,.1) 0%,transparent 60%),
  linear-gradient(205deg,#1a201c 0%,#33402f 50%,#10140f 100%);}
.sol-visual::after{
  content:"";position:absolute;inset:0;opacity:.5;
  background-image:linear-gradient(var(--gold-faint) 1px,transparent 1px),linear-gradient(90deg,var(--gold-faint) 1px,transparent 1px);
  background-size:56px 56px;
  mask-image:radial-gradient(ellipse 90% 80% at 50% 50%,#000 30%,transparent 75%);
  -webkit-mask-image:radial-gradient(ellipse 90% 80% at 50% 50%,#000 30%,transparent 75%);
}
.sol-glyph{
  position:absolute;top:34px;right:34px;font-family:var(--font-display);font-weight:300;
  font-size:clamp(4rem,7vw,6.5rem);line-height:1;color:rgba(246,242,233,.1);
  transition:color .6s;
}
.sol-card:hover .sol-glyph{color:rgba(199,164,93,.45);}
.sol-meta{position:relative;z-index:2;padding:clamp(22px,2.6vw,38px);}
.sol-meta .tag{font-family:var(--font-mono);font-size:10.5px;letter-spacing:.28em;color:var(--grid);text-transform:uppercase;font-weight:600;}
.sol-persona{
  font-family:var(--font-display);font-style:italic;font-weight:600;
  font-size:clamp(1.05rem,1.4vw,1.3rem);color:var(--cream);
  margin:8px 0 0;
}
.hook-line{
  display:block;margin-top:22px;
  font-family:var(--font-mono);font-weight:600;font-size:10.5px;letter-spacing:.22em;
  color:rgba(246,242,233,.45);text-transform:uppercase;
}
.hook-line i{
  font-style:italic;font-family:var(--font-display);font-weight:700;
  color:var(--grid);font-size:1.5em;margin-right:8px;line-height:0;vertical-align:-.1em;
}
.sol-meta h3{font-weight:400;font-size:clamp(1.6rem,2.4vw,2.2rem);color:var(--cream);margin:10px 0 8px;}
.sol-sub{font-family:var(--font-display);font-weight:700;font-size:clamp(1rem,1.2vw,1.2rem);color:var(--cream);margin-bottom:10px;}
.sol-meta p{font-size:14.5px;line-height:1.55;color:rgba(246,242,233,.65);max-width:38ch;}
.sol-meta p strong{color:var(--cream);font-weight:700;}
.sol-cta{
  margin-top:26px;display:inline-flex;align-items:center;gap:12px;
  font-family:var(--font-mono);font-size:11px;letter-spacing:.24em;color:var(--cream);text-transform:uppercase;font-weight:600;}
.sol-cta i{font-style:normal;display:inline-block;transition:transform .4s;}
.sol-card:hover .sol-cta i{transform:translateX(8px);}
.sol-cta::after{content:"";height:1px;flex:0 0 40px;background:var(--gold-dim);transition:flex-basis .5s;}
.sol-card:hover .sol-cta::after{flex-basis:72px;background:var(--grid);}

/* card 2 : Tojo cutout leans out of the card, flush with its right + bottom
   edges (no gap), overflowing past the top — wrapper carries no clip so the
   figure can bleed outside the card's own overflow:hidden boundary */
.sol-card-wrap{position:relative;}
.sol-card-wrap .sol-card{height:100%;}
.sol-lean{
  position:absolute;right:-30px;bottom:-5px;z-index:6;
  height:112%;max-width:72%;width:auto;
  object-fit:contain;object-position:right bottom;
  pointer-events:none;
}
/* wipe-in from the right : collapses to a vertical sliver on the right edge,
   then expands leftward to full width once the section is reached (shares
   the .reveal/.inview end-state + late-trigger JS with the other portraits) */
html.js .reveal.reveal-right:not(.inview){clip-path:polygon(100% 0%,100% 0%,100% 100%,100% 100%);}
/* wipe-up direction modifier : collapses to a flat sliver along the BOTTOM
   edge, then grows upward into the full rectangle (used for the Craft
   section portrait, .reveal already defines the shared full-rect .inview
   end state, so only the start state changes) */
html.js .reveal.reveal-up:not(.inview){clip-path:polygon(0% 100%,100% 100%,100% 100%,0% 100%);}
@media (max-width:980px){
  .sol-lean{height:90%;max-width:60%;right:-18px;}
}

/* ============ SPECS / METRICS ============ */
#metrics{border-top:1px solid var(--gold-faint);border-bottom:1px solid var(--gold-faint);}
.metric-grid{display:grid;grid-template-columns:repeat(4,1fr);}
.metric{
  padding:clamp(44px,5vw,80px) clamp(20px,3vw,48px);
  border-left:1px solid var(--gold-faint);position:relative;
}
.metric:first-child{border-left:0;}
.metric .num{
  font-family:var(--font-display);font-weight:300;
  font-size:clamp(2.6rem,5vw,4.4rem);line-height:1;color:var(--ink);
  display:flex;align-items:baseline;gap:4px;
}
.metric .num sub{font-size:.35em;color:var(--grid);font-family:var(--font-mono);letter-spacing:.1em;font-weight:600;}
.metric .lbl{margin-top:16px;font-family:var(--font-mono);font-size:11px;letter-spacing:.22em;text-transform:uppercase;color:var(--ink-soft);font-weight:600;}

/* ============ STORY (sticky editorial) ============ */
#story{padding:clamp(100px,14vw,200px) 0;}
.story-wrap{display:grid;grid-template-columns:1fr 1fr;gap:clamp(40px,6vw,110px);align-items:start;}
.story-visual{
  position:sticky;top:calc(var(--nav-h) + 48px);border-radius:8px;overflow:hidden;
  border:1px solid var(--gold-dim);aspect-ratio:4/5;
  background:
    radial-gradient(ellipse 80% 55% at 50% 78%,rgba(199,164,93,.22) 0%,transparent 65%),
    var(--bg);
  display:flex;align-items:flex-end;justify-content:center;
}
.story-img{
  max-width:72%;max-height:80%;width:auto;height:auto;margin-bottom:6%;
  object-fit:contain;
  filter:drop-shadow(0 22px 30px rgba(30,55,39,.16));
  animation:floaty 5.5s ease-in-out infinite;
}
@keyframes floaty{0%,100%{transform:translateY(0)}50%{transform:translateY(-14px)}}
.story-visual .corner{position:absolute;width:18px;height:18px;border:1px solid var(--grid);z-index:2;}
.story-visual .corner.tl{top:14px;left:14px;border-right:0;border-bottom:0;}
.story-visual .corner.tr{top:14px;right:14px;border-left:0;border-bottom:0;}
.story-visual .corner.bl{bottom:14px;left:14px;border-right:0;border-top:0;}
.story-visual .corner.br{bottom:14px;right:14px;border-left:0;border-top:0;}
.story-caption{position:absolute;bottom:18px;left:22px;z-index:2;font-family:var(--font-mono);font-size:10px;letter-spacing:.26em;color:rgba(30,55,39,.65);text-transform:uppercase;font-weight:600;}
.story-step{padding:clamp(48px,7vw,110px) 0;border-bottom:1px solid var(--gold-faint);}
.story-step:last-child{border-bottom:0;}
.story-step .idx{font-family:var(--font-mono);font-size:11px;color:var(--grid);letter-spacing:.3em;font-weight:600;}
.story-step h3{font-weight:400;font-size:clamp(1.5rem,2.4vw,2.2rem);margin:18px 0 16px;max-width:18ch;}
.story-step p{color:var(--ink-soft);max-width:46ch;}
.story-step p strong{color:var(--ink);font-weight:700;}

/* Hook-card classes (.tag/.sol-persona/.hook-line/.sol-sub) originate on the
   dark Solutions cards — reused here on the light Story background, so their
   cream-tinted colors need light-bg equivalents */
.story-step .tag{display:block;margin-top:14px;}
.story-step .sol-persona{color:var(--ink);margin:8px 0 0;max-width:46ch;}
.story-step .hook-line{color:var(--ink-soft);}
.story-step .sol-sub{color:var(--ink);max-width:46ch;}
.story-kicker{
  display:block;margin-top:14px;
  font-family:var(--font-mono);font-weight:600;font-size:10.5px;letter-spacing:.22em;
  color:var(--ink-soft);text-transform:uppercase;
}
.story-tagline{
  font-family:var(--font-display);font-style:italic;font-weight:600;
  font-size:clamp(1.05rem,1.4vw,1.3rem);color:var(--ink);margin:8px 0 0;max-width:46ch;
}
.story-rule{display:block;width:100%;max-width:340px;height:2px;background:var(--ink);margin:18px 0;opacity:.85;}
.story-cta{
  display:inline-flex;align-items:center;gap:10px;
  margin-top:22px;padding:13px 26px;
  font-family:var(--font-mono);font-size:12px;font-weight:600;letter-spacing:.14em;text-transform:uppercase;
  color:var(--bg);background:var(--ink);border-radius:2px;
  text-decoration:none;
  transition:background .35s ease,transform .35s ease,color .35s ease;
}
.story-cta i{font-style:normal;transition:transform .35s ease;}
.story-cta:hover{background:var(--grid);color:var(--ink);transform:translateY(-2px);}
.story-cta:hover i{transform:translateX(4px);}

/* ============ QUOTE ============ */
#quote{
  padding:clamp(120px,16vw,220px) 0;background:var(--ink);color:var(--cream);
  position:relative;overflow:hidden;
}
#quote .shell{position:relative;z-index:2;}
#quote .big{
  font-family:var(--font-display);font-weight:300;font-style:italic;
  font-size:clamp(1.8rem,4.4vw,3.6rem);line-height:1.35;max-width:26ch;
}
#quote .big .gold{color:var(--grid);}
#quote .attrib{margin-top:44px;font-family:var(--font-mono);font-size:11.5px;letter-spacing:.26em;text-transform:uppercase;color:rgba(246,242,233,.55);font-weight:600;}
#quote .ghost{
  position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);
  font-family:var(--font-display);font-weight:600;font-size:34vw;line-height:1;
  color:rgba(246,242,233,.028);white-space:nowrap;user-select:none;z-index:1;
  will-change:transform;
}

/* ============ CTA ============ */
#cta{padding:clamp(110px,15vw,210px) 0;text-align:center;overflow:hidden;}
#cta h2{font-weight:300;font-size:clamp(2.2rem,5.6vw,4.6rem);line-height:1.2;}
#cta h2 .it{font-style:italic;color:var(--grid);}
#cta p{margin:26px auto 0;max-width:52ch;color:var(--ink-soft);}
.btn-row{margin-top:52px;display:flex;gap:18px;justify-content:center;flex-wrap:wrap;}
.btn{
  position:relative;display:inline-flex;align-items:center;gap:14px;overflow:hidden;
  padding:18px 36px;border-radius:4px;border:1px solid var(--ink);
  font-family:var(--font-mono);font-size:12px;letter-spacing:.22em;text-transform:uppercase;
  transition:color .45s cubic-bezier(.77,0,.18,1);font-weight:600;}
.btn::before{
  content:"";position:absolute;inset:0;background:var(--ink);
  transform:translateY(101%);transition:transform .45s cubic-bezier(.77,0,.18,1);z-index:0;
}
.btn:hover::before{transform:translateY(0);}
.btn:hover{color:var(--cream);}
.btn span{position:relative;z-index:1;}
.btn.gold{border-color:var(--grid);color:var(--ink);}
.btn.gold::before{background:var(--grid);}
.btn.gold:hover{color:var(--ink);}
/* stone variant (showcase CTA) — stone bg / ink text, inverted on hover */
.btn.stone{background:var(--stone);border-color:var(--stone);color:var(--ink);}
.btn.stone::before{background:var(--ink);}
.btn.stone:hover{color:var(--stone);}

/* cream variant — for buttons sitting ON a dark panel rather than on the
   page. The base .btn draws itself in --ink, which against the hero card's
   glass is a border you cannot see and a label you cannot read. Same wipe,
   inverted: cream outline that floods cream and hands the label back to ink. */
.btn.cream{border-color:rgba(246,242,233,.46);color:#f6f2e9;}
.btn.cream::before{background:#f6f2e9;}
.btn.cream:hover{color:var(--ink);border-color:#f6f2e9;}

/* ============ FOOTER ============ */
footer{position:relative;border-top:1px solid var(--gold-dim);background:var(--bg);overflow:hidden;}
/* decorative grid/node overlay — mobile-only (see @media 980px below),
   matches the same design-canvas device used on every other section for
   visual consistency; hidden on desktop where the footer keeps its plain
   4-column layout untouched */
.foot-frame{display:none;}
.foot-grid{
  display:grid;grid-template-columns:2fr 1fr 1fr 1fr;gap:40px;
  padding:clamp(60px,8vw,100px) 0 60px;
}
.foot-grid .col h4{font-family:var(--font-mono);font-weight:600;font-size:11px;letter-spacing:.28em;text-transform:uppercase;color:var(--grid);margin-bottom:22px;}
.foot-grid .col a{display:block;padding:6px 0;color:var(--ink-soft);font-size:15px;transition:color .3s,transform .3s;}
.foot-grid .col a:hover{color:var(--ink);transform:translateX(6px);}
.foot-brand p{color:var(--ink-soft);max-width:34ch;font-size:15px;margin-top:18px;}
.foot-logo{font-family:var(--font-display);font-weight:600;letter-spacing:.3em;font-size:20px;}
.foot-word{
  font-family:var(--font-display);font-weight:600;text-align:center;
  font-size:clamp(4.5rem,17vw,17rem);line-height:.8;letter-spacing:-.01em;
  color:transparent;-webkit-text-stroke:1px var(--gold-dim);
  transform:translateY(22%);user-select:none;transition:color 1s;
}
.foot-word:hover{color:var(--gold-faint);}
.foot-bar{
  display:flex;justify-content:space-between;gap:20px;flex-wrap:wrap;
  padding:26px 0;border-top:1px solid var(--gold-faint);
  font-family:var(--font-mono);font-size:10.5px;letter-spacing:.2em;text-transform:uppercase;color:var(--ink-soft);font-weight:600;}

/* ============ RESPONSIVE ============ */
@media (max-width:980px){
  .sol-grid{grid-template-columns:1fr;}
  .metric-grid{grid-template-columns:1fr 1fr;}
  .metric:nth-child(3){border-left:0;}
  .metric{border-top:1px solid var(--gold-faint);text-align:center;}
  .metric:nth-child(-n+2){border-top:0;}
  .metric .num{justify-content:center;}
  .story-wrap{grid-template-columns:1fr;}
  .story-visual{position:relative;top:0;aspect-ratio:16/10;}
  .foot-grid{grid-template-columns:1fr 1fr;}
}
@media (max-width:980px){
  /* collapse the desktop scroll-scrub/sticky pin into plain stacked flow:
     video first, then the crossfading chip+title+body block right below
     it — nothing overlaps, and the body copy only comes into view once
     you scroll a little further past the video screen */
  #hero{height:auto;}
  #hero .hero-sticky{
    position:relative;height:auto;min-height:100vh;overflow:visible;
    padding:calc(var(--nav-h) + 30px) 20px 50px;display:block;
  }
  #hero .hero-frame,#hero .hero-wordclip,#hero .hero-copybg,#hero .hero-shade{display:none;}
  #hero .hero-scene{
    position:relative;inset:auto;height:44vh;min-height:260px;
    margin-top:30px;border-radius:10px;overflow:hidden;
  }
  #hero .hero-video{
    position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
    height:100%;width:auto;max-width:100%;
  }
  .hero-copy{
    position:relative;inset:auto;left:auto;right:auto;top:auto;bottom:auto;
    margin-top:28px;z-index:auto;
  }
  .hero-phases{height:auto;min-height:200px;}
  .scroll-cue{position:absolute;top:calc(var(--nav-h) + 8px);left:20px;bottom:auto;}
}
@media (max-width:640px){
  :root{--nav-h:60px;}
  body{font-size:16px;}
  .hero-phases{min-height:230px;}
  #hero .hero-scene{height:38vh;min-height:220px;}
  .scroll-cue{left:5vw;}
  .hero-frame{inset:10px 10px 0;}
  .gl.vl,.gl.vr{display:none;}
  .gl.hm{top:calc(50% - 5px);}
  .hero-frame .handle[style*="top:50%"]{top:calc(50% - 5px)!important;}
  .metric-grid{grid-template-columns:1fr;}
  .metric{border-left:0;}
  .foot-grid{grid-template-columns:1fr;text-align:center;}
  .foot-grid .col{display:flex;flex-direction:column;align-items:center;}
  .foot-brand p{margin-left:auto;margin-right:auto;}
  .foot-bar{justify-content:center;text-align:center;}
  .sol-card{min-height:520px;}
}
/* ============ SHOWCASE — section 2 : model card (Longbow clone) ============ */
#showcase{
  position:relative;height:100vh;
  background:var(--bg) url("../../Images/bg2.webp") center/cover no-repeat;
}
/* frame variant : the card cell (25% → 50%, bottom half) is outlined,
   so vl runs the BOTTOM half here and vr mirrors it on the top half */
.show-frame .gl.vl{top:calc(50% - 8px);height:auto;bottom:0;}
.show-frame .gl.vr{top:0;bottom:auto;height:calc(50% - 8px);}

/* continuous canvas seam : the hero's bottom line + handles ARE the seam,
   so this frame has no top border; instead its verticals bridge the 16px
   gap (hero's border is flush at the boundary + 16px showcase inset) */
.show-frame{border-top:none;}
.show-frame .bridge{
  position:absolute;top:-16px;height:16px;width:1px;
  background:rgba(199,164,93,.55);
}
.show-frame .bridge.b0{left:-1px;}              /* continues left border  */
.show-frame .bridge.b50{left:50%;}              /* continues centre line  */
.show-frame .bridge.b75{left:75%;}              /* continues 75% line     */
.show-frame .bridge.b100{left:100%;}            /* continues right border */

/* frosted model card — fills the 1st bottom-left grid box, 1px inside the
   frame border (16px) and the 25% line (which sits at calc(25% + 8px)) */
.show-card{
  position:absolute;z-index:4;
  left:17px;right:calc(75% - 7px);
  top:calc(50% + 1px);bottom:1px;
  background:rgba(246,242,233,.30);
  backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:12px;text-align:center;padding:20px 16px;overflow:hidden;
}
.show-name{
  font-family:var(--font-display);font-weight:300;color:var(--ink);
  font-size:clamp(1.05rem,1.5vw,1.5rem);line-height:1.22;
  max-width:100%;
}
.show-name .it{display:block;font-style:italic;}
/* thumb flexes to absorb the leftover height so nothing bleeds out of the cell */
.show-thumb{
  flex:1 1 auto;min-height:0;max-width:72%;
  display:flex;align-items:center;justify-content:center;
}
.show-thumb img{display:block;max-width:100%;max-height:100%;width:auto;height:auto;object-fit:contain;}
.show-caption{
  font-family:var(--font-mono);font-size:10.5px;letter-spacing:.14em;line-height:1.8;
  text-transform:uppercase;color:rgba(30,55,39,.72);max-width:34ch;font-weight:600;}

@media (max-width:980px){
  /* same fix as the hero: stop the video and the frosted text card from
     sitting on top of one another — video first as a boxed block, card
     flows below it in normal document order, revealed on further scroll */
  /* padding (not margin) on the section itself — a margin on the first
     child collapses through and shows the body's background as a gap
     between this section and the one before it */
  #showcase{height:auto;padding:40px 0 50px;}
  #showcase .hero-scene{
    position:relative;inset:auto;height:44vh;min-height:260px;
    border-radius:10px;overflow:hidden;
  }
  #showcase .hero-video{
    position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
    height:100%;width:auto;max-width:100%;
  }
  #showcase .hero-shade,#showcase .hero-frame{display:none;}
  .show-card{
    position:relative;inset:auto;left:auto;right:auto;top:auto;bottom:auto;
    margin:26px 20px 0;padding:24px 20px;
  }
}
@media (max-width:640px){
  #showcase .hero-scene{height:38vh;min-height:220px;}
}

/* ============ VISION — section 3 : giant marquee + question cards ============ */
#vision{
  position:relative;height:100vh;
  background:var(--bg) url("../../Images/bg.jpg") center/cover no-repeat;
}
/* frame variant : all three verticals run full height; no top border —
   the showcase's bottom line is the shared seam (same bridge pattern) */
.vision-frame{border-top:none;}
.vision-frame .gl.vl{top:0;height:auto;bottom:0;}
.vision-frame .gl.vr{top:0;height:auto;bottom:0;}
.vision-frame .bridge{
  position:absolute;top:-16px;height:16px;width:1px;
  background:rgba(199,164,93,.55);
}
.vision-frame .bridge.b0{left:-1px;}
.vision-frame .bridge.b25{left:25%;}
.vision-frame .bridge.b50{left:50%;}
.vision-frame .bridge.b75{left:75%;}
.vision-frame .bridge.b100{left:100%;}

/* top half : giant words distributed across the grid cells —
   A (cell 1) · New (cell 2) · Vision (cells 3+4, right) — all flush on the
   top seam line · Evolves. marquee (cell 3, bottom-aligned on the centre
   line). .vision-top starts at the seam (top:0 — the previous section's
   bottom border IS this section's top border) and side insets match the
   frame, so N% inside it == the frame's N% lines */
.vision-top{
  position:absolute;z-index:4;left:16px;right:16px;top:0;
  height:50%;overflow:hidden;pointer-events:none;user-select:none;
}
.vision-word{
  /* at line-height .82 Bebas caps already touch the em-box top,
     so top:0 puts the glyphs directly on the seam line */
  position:absolute;top:0;
  font-family:'Bebas Neue',var(--font-display);font-weight:700;
  font-size:clamp(5.5rem,16vw,17rem);line-height:.82;letter-spacing:.02em;
  color:var(--ink-mute);
}
.vw-a{left:0;}                                     /* flush on the left border  */
.vw-new{left:25%;width:25%;text-align:center;}
/* -.05em cancels the trailing letter-space (.02em) plus the final glyph's
   right side bearing so "N" sits flush on the right border
   (.vision-top clips the empty excess) */
.vw-vision{left:50%;right:-.05em;text-align:right;}
.vision-marquee{
  /* window spans cell 3 (50% → 75%); Vision is top-aligned so the loop
     can use the cell's full width */
  position:absolute;left:50%;right:25%;bottom:0;overflow:hidden;
}
.vm-track{
  display:flex;white-space:nowrap;width:max-content;
  font-family:'Bebas Neue',var(--font-display);font-weight:700;
  font-size:clamp(5.5rem,16vw,17rem);line-height:.82;letter-spacing:.02em;
  color:var(--ink);will-change:transform;
  animation:wmScroll 40s linear infinite;
  margin-bottom:-.12em;
}

/* question cards — frosted panels 1px inside their grid cells */
.q-card{
  position:absolute;z-index:4;top:calc(50% + 1px);bottom:1px;
  background:rgba(246,242,233,.45);
  backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);
  padding:clamp(16px,2vw,32px);overflow:hidden;
  display:grid;grid-template-columns:auto 1fr;gap:8px 16px;align-content:start;
}
.q-card.q1{left:17px;right:calc(75% - 7px);}
.q-card.q2{left:calc(50% + 1px);right:calc(25% + 9px);}
.q-card.q3{left:calc(75% - 7px);right:17px;}
.q-num{
  grid-row:1 / span 2;
  font-family:var(--font-display);font-style:italic;font-weight:600;
  font-size:clamp(2.2rem,3.8vw,3.6rem);line-height:1;color:var(--ink);
}
.q-title{
  font-family:var(--font-mono);font-weight:600;text-transform:uppercase;
  font-size:clamp(10px,.85vw,12.5px);letter-spacing:.18em;color:var(--ink);
  padding-top:.55em;
}
.q-title::after{content:"";display:block;width:26px;height:2px;background:var(--grid);margin-top:8px;}
.q-body{
  font-family:var(--font-display);font-style:italic;font-weight:300;
  font-size:clamp(.9rem,1.1vw,1.2rem);line-height:1.5;color:var(--ink-soft);
}
.q-body strong{color:var(--ink);font-weight:600;}

@media (max-width:980px){
  /* grid cells are too narrow for three cards — stack the section */
  #vision{height:auto;padding-bottom:70px;}
  .vision-frame{display:none;}
  .vision-top{position:static;height:auto;padding:100px 20px 0;}
  .vision-word{position:static;display:inline-block;margin-right:.25em;
    width:auto;text-align:left;}
  .vision-marquee{position:static;width:100%;margin-top:12px;}
  .q-card{position:static;margin:14px 20px;}
  .q-card.q1{margin-top:26px;}
}

/* ============ CROSSROAD — section 4 : portrait + question cells ============ */
/* Frame markup reuses .vision-frame (full-height verticals, no top border,
   bridges to the vision's bottom seam) + .cross-frame for future overrides */
#crossroad{position:relative;height:100vh;background:#8c7f75;}

/* Tojo portrait fills the left half (cells 1+2, both rows) under the
   frame lines (z1 < frame z5), edge-to-edge with the borders */
.cross-img{position:absolute;z-index:1;left:16px;right:50%;top:0;bottom:0;background:#8c7f75;}
.cross-img img{width:100%;height:100%;object-fit:cover;}
/* diagnostic: if the image genuinely fails to load (404/blocked), show a
   visible flag instead of a silent blank box so the failure is obvious */
.cross-img.img-error{background:repeating-linear-gradient(45deg,var(--gold-faint),var(--gold-faint) 10px,transparent 10px,transparent 20px);}
.cross-img.img-error::after{
  content:"tojo_head.png failed to load";position:absolute;inset:0;
  display:flex;align-items:center;justify-content:center;text-align:center;padding:20px;
  font-family:var(--font-mono);font-size:12px;letter-spacing:.1em;color:var(--ink);font-weight:600;}

/* Longbow-style load reveal — bottom-left corner wipe + slow scale settle,
   triggered by the shared IntersectionObserver adding .inview.
   The hidden state is gated behind html.js (added by main.js) so the
   image can never be stuck invisible if JS is stale, blocked or fails */
.reveal{overflow:hidden;}
/* corner wipe : collapsed to a point at the bottom-left corner, then the
   three moving points sweep out to the full rectangle so the image grows
   in from that corner and settles into place (Longbow-style reveal) */
html.js .reveal{clip-path:polygon(0% 100%,0% 100%,0% 100%,0% 100%);transition:clip-path 1.15s cubic-bezier(.77,0,.18,1);}
html.js .reveal img{transform:scale(1.18);transition:transform 1.7s cubic-bezier(.22,1,.36,1) .1s;}
html.js .reveal.inview{clip-path:polygon(0% 0%,100% 0%,100% 100%,0% 100%);}
html.js .reveal.inview img{transform:scale(1);}

/* content cells — no panel bg (flat, like the Longbow reference);
   rule + italic number + mono label at top, quote pinned to the bottom */
.cross-card{
  position:absolute;z-index:4;
  padding:clamp(18px,2.2vw,34px);
  display:flex;flex-direction:column;overflow:hidden;
}
.cross-card.c1{left:calc(75% - 7px);right:17px;top:1px;bottom:calc(50% + 1px);}
.cross-card.c2{left:calc(50% + 1px);right:calc(25% + 9px);top:calc(50% + 1px);bottom:1px;}
.cross-card.c3{left:calc(75% - 7px);right:17px;top:calc(50% + 1px);bottom:1px;}
.cx-rule{display:block;width:38px;height:2px;background:var(--grid);margin-bottom:16px;}
.cx-num{
  font-family:var(--font-display);font-style:italic;font-weight:600;
  font-size:clamp(2.6rem,4.5vw,4.2rem);line-height:1;color:var(--grid);
}
.cx-label{
  margin-top:12px;
  font-family:var(--font-mono);font-weight:600;text-transform:uppercase;
  font-size:clamp(10px,.85vw,12.5px);letter-spacing:.3em;color:var(--ink-soft);
}
.cx-quote{
  margin-top:auto;
  font-family:var(--font-display);font-style:italic;font-weight:300;
  font-size:clamp(1.15rem,1.8vw,1.8rem);line-height:1.45;color:var(--ink-soft);
}
.cx-quote strong{color:var(--ink);font-weight:600;}
/* right-aligned variant (cells 02 + verdict) */
.cx-right{text-align:right;}
.cx-right .cx-rule{margin-left:auto;}
/* verdict cell — light frosted panel behind the headline + closing line */
.cross-card.cx-final{
  background:rgba(255,255,255,.14);
  backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);
}
.cx-head{
  font-family:var(--font-display);font-weight:300;
  font-size:clamp(1.5rem,2.4vw,2.4rem);line-height:1.22;color:var(--ink);
}
.cx-head .it{font-style:italic;color:var(--grid);}
.cx-final .cx-rule{margin:18px 0 0 auto;}
.cx-final-body{
  margin-top:auto;
  font-family:var(--font-display);font-style:italic;font-weight:300;
  font-size:clamp(1.05rem,1.5vw,1.5rem);line-height:1.5;color:var(--ink-soft);
}
.cx-final-body strong{color:var(--grid);font-weight:600;}

@media (max-width:980px){
  /* stack : portrait, then the three cells (frame hidden via .vision-frame) */
  #crossroad{height:auto;padding-bottom:70px;}
  .cross-img{position:static;height:64vw;display:block;}
  .cross-card{position:static;margin:26px 20px 0;}
  /* 01 and 02 become full-bleed tinted bands — margin:0 so 01 butts
     straight up against the portrait above it with no gap */
  .cross-card.c1{margin:0 20px;padding:26px 20px;background:#e4dfd3;}
  .cross-card.c2{margin:0 20px;padding:26px 20px;background:#c5bfb1;}
  .cx-quote,.cx-final-body{margin-top:18px;}
}

/* ============ DELIVERY — section 5 : mirrored crossroad, portrait on the
   right, wipes in from the bottom-right corner, then breathes (Ken Burns) ============ */
#delivery{position:relative;height:100vh;background:#8c7f75;}

/* Tojo (point) portrait fills the right half (cells 3+4, both rows), under
   the frame lines — mirror of .cross-img */
.point-img{position:absolute;z-index:1;left:50%;right:16px;top:0;bottom:0;background:#8c7f75;}
.point-img img{width:100%;height:100%;object-fit:contain;object-position:center bottom;}
.point-img.img-error{background:repeating-linear-gradient(45deg,var(--gold-faint),var(--gold-faint) 10px,transparent 10px,transparent 20px);}
.point-img.img-error::after{
  content:"tojo_point.png failed to load";position:absolute;inset:0;
  display:flex;align-items:center;justify-content:center;text-align:center;padding:20px;
  font-family:var(--font-mono);font-size:12px;letter-spacing:.1em;color:var(--ink);font-weight:600;}

/* corner-wipe direction modifier : collapses to a point at the BOTTOM-RIGHT
   corner instead of the default bottom-left (.reveal already defines the
   shared full-rect .inview end state, so only the start state changes) */
html.js .reveal.reveal-br:not(.inview){clip-path:polygon(100% 100%,100% 100%,100% 100%,100% 100%);}

/* Ken Burns breathe : once the wipe settles into place, a slow continuous
   zoom in/out loop — scoped to .point-img only (crossroad's portrait keeps
   its one-shot settle, unaffected) */
@keyframes kenburns{0%,100%{transform:scale(1);}50%{transform:scale(1.07);}}
html.js .point-img.reveal.inview img.kenburns{animation:kenburns 9s ease-in-out 1.9s infinite;}

/* bottom-left cell : the pitch-vs-delivery statement, spans the full left
   half's bottom row (wider than crossroad's quarter-width cards).
   Frosted panel (unlike crossroad's flat cards) sits behind the copy, and
   everything is sized with vh in the mix (not just vw) + vertically
   centred so it always fits the cell regardless of window height. */
.cross-card.point-card{
  left:17px;right:calc(50% + 1px);top:calc(50% + 1px);bottom:1px;
  justify-content:center;gap:clamp(6px,1.1vh,13px);
  padding:clamp(8px,1.1vh,16px) clamp(14px,2vw,26px);
  background:rgba(255,255,255,.12);
  backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);
}
.point-card .cx-rule{margin-bottom:0;flex-shrink:0;}
.pt-head{
  font-family:var(--font-display);font-weight:700;
  font-size:clamp(1rem,3.1vh,1.7rem);line-height:1.12;color:var(--ink);
}
.pt-head .it{display:block;font-style:italic;font-weight:300;color:var(--grid);margin-top:5px;}
.pt-body{
  font-family:var(--font-display);font-style:italic;font-weight:300;
  font-size:clamp(.84rem,1.75vh,1.12rem);line-height:1.35;color:var(--ink-soft);
}
.pt-body strong{color:var(--ink);font-weight:600;}
.pt-note{
  font-family:var(--font-display);font-style:italic;font-weight:300;
  font-size:clamp(.78rem,1.5vh,1.02rem);line-height:1.35;color:rgba(30,55,39,.45);
}

@media (max-width:980px){
  /* stack : portrait, then the statement cell (frame hidden via .vision-frame) */
  #delivery{height:auto;padding-bottom:70px;}
  .point-img{position:static;height:64vw;}
  .cross-card.point-card{position:static;margin:26px 20px 0;justify-content:flex-start;}
}

/* ============ MORNING — new section : giant marquee + question cards,
   structural clone of #vision (section 3), dark-bg variant. Content,
   font-size formula, weight, tracking and color below are pulled
   directly from the live reference (Longbow, section 7 — .heading-marquee:
   font-size:clamp(10.625rem, 1.339rem + 14.51vw, 18.75rem); weight 400;
   line-height .8em; letter-spacing -.06em; color #fff — applied to all
   four words: The / Speed / Of / Lightness). The gold grid-line + handle
   overlay itself is a Virevo-only device, not part of the reference. ============ */
#morning{position:relative;height:140vh;background:#243728;}

/* verticals run full height (uniform 4-column grid, no seam to bridge —
   this section isn't preceded by another canvas-frame section, so the
   default .hero-frame border (all 4 sides) is kept as-is). Top/bottom
   inset is 0/0 (flush top border, no dead strip above the grid), so
   frame height == section height and all row math below is exact. */
.morning-frame{inset:0 16px 0;}
.morning-frame .gl.vl{top:0;height:auto;bottom:0;}
.morning-frame .gl.vr{top:0;height:auto;bottom:0;}

.gl.hm1{top:33.34%;left:0;right:0;height:1px;}                /* header / row-1 boundary — 3 equal rows */
.gl.hm2{top:66.67%;left:0;right:0;height:1px;}                /* row-1 / row-2 boundary — 3 equal rows */

.morning-top{
  position:absolute;z-index:4;left:16px;right:16px;top:0;
  height:33.34%;overflow:visible;pointer-events:none;user-select:none;
}
/* all four header words share one scale — the reference applies the
   exact same clamp to "The" / "Speed" / "Of" / "Lightness" alike */
.mo-word,.mo-track,.mo-giant{
  font-family:'Bebas Neue',var(--font-display);font-weight:400;
  font-size:clamp(10.625rem,1.339rem + 14.51vw,18.75rem);
  line-height:.8em;letter-spacing:-.06em;color:#fff;
}
.mo-word{position:absolute;top:0;white-space:nowrap;}
.mo-one{left:0;}                                   /* cell 1, flush left */
.mo-of{left:75%;}                                  /* cell 4, last grid box */
.mo-marquee{
  /* cell 2 (25% → 50%) — this cell has no static word, the loop IS its content */
  position:absolute;left:25%;right:50%;top:0;overflow:hidden;
}
.mo-track{
  display:flex;white-space:nowrap;width:max-content;
  will-change:transform;animation:wmScroll 40s linear infinite;
}
/* giant word — centered across the full row width, bottom-anchored so
   its baseline sits flush on the header row's bottom grid line */
.mo-giant{
  position:absolute;left:0;right:0;bottom:0;z-index:3;
  margin-bottom:-0.1em;
  text-align:center;white-space:nowrap;pointer-events:none;user-select:none;
}

/* row 1 (33.34%–66.67%) : cell 1 + cell 4 — row 2 (66.67%–100%) : cell 3 — 3 equal rows */
.mo-card{
  position:absolute;z-index:4;
  background:rgba(255,255,255,.035);
  padding:clamp(20px,2.4vw,36px);overflow:hidden;
  display:flex;flex-direction:column;
}
.mo-card.mc1{left:17px;right:calc(75% - 7px);top:calc(33.34% + 1px);bottom:calc(33.33% + 1px);}
/* mc2 "Precision & Weight Elimination" — row 3, column 2 */
.mo-card.mc2{left:calc(25% + 9px);right:calc(50% - 1px);top:calc(66.67% + 1px);bottom:1px;}
/* mc3 "The Daily Obligation" — row 2, column 3 (beside the image) */
.mo-card.mc3{left:calc(50% + 1px);right:calc(25% + 9px);top:calc(33.34% + 1px);bottom:calc(33.33% + 1px);}
/* mc4 "The Horizon" — row 3, column 4 (right-bottom) */
.mo-card.mc4{left:calc(75% - 7px);right:17px;top:calc(66.67% + 1px);bottom:1px;}

/* cell 2, row 1 — image tile next to "Adding Lightness" (cell 1) */
.mo-img{
  position:absolute;z-index:4;overflow:hidden;background:#1b2a1e;
}
.mo-img img{width:100%;height:100%;object-fit:cover;display:block;animation:moImgPan 16s ease-in-out infinite;}
/* image is taller than the grid box (object-fit:cover crops it) — pan the
   visible crop smoothly from the top of the image down to the bottom and
   back, looping continuously */
@keyframes moImgPan{
  0%,100%{object-position:center top;}
  50%{object-position:center bottom;}
}
.mo-img.mc-img1{left:calc(25% + 9px);right:calc(50% - 1px);top:calc(33.34% + 1px);bottom:calc(33.33% + 1px);}
/* row 3, column 3 — between "The Process, Not the Number" (mc2) and "The Horizon" (mc4) */
.mo-img.mc-img2{left:calc(50% + 1px);right:calc(25% + 9px);top:calc(66.67% + 1px);bottom:1px;}
.mo-num{
  display:inline-block;width:fit-content;
  font-family:var(--font-mono);font-weight:600;
  font-size:11px;letter-spacing:.1em;color:var(--grid);
  border:1px solid rgba(199,164,93,.5);border-radius:999px;
  padding:4px 12px;line-height:1;
}
.mo-title{
  font-family:var(--font-display);font-weight:400;
  font-size:clamp(1.4rem,2vw,2rem);line-height:1.25;color:var(--cream);
  margin-top:clamp(16px,2.4vh,28px);
}
.mo-body{
  font-family:var(--font-mono);text-transform:uppercase;letter-spacing:.03em;
  font-size:11px;line-height:1.7;color:rgba(246,242,233,.68);
  max-width:34ch;margin-top:clamp(40px,10vh,140px);font-weight:600;}
.mo-body strong{color:var(--grid);font-weight:600;}
/* mc1 (Functional Lens) and mc3 (Daily Obligation) run longer copy than the
   default card design was tuned for — pull the body up closer to the title
   so the paragraph doesn't overflow the grid box */
.mo-card.mc1 .mo-body,
.mo-card.mc2 .mo-body,
.mo-card.mc3 .mo-body,
.mo-card.mc4 .mo-body,
.mo-card.mc6 .mo-body{margin-top:clamp(14px,1.8vh,26px);}
/* mc6 has two stacked paragraphs — the first uses the title-gap above,
   the second only needs a small paragraph-to-paragraph gap */
.mo-card.mc6 .mo-body + .mo-body{margin-top:12px;}

/* ============ MORNING ROW 4 — closing tile row, single-row 4-column
   grid (own hero-frame instance, same column geometry as the row above) ============ */
#morning4{position:relative;height:46.67vh;background:#243728;}

/* column 1 — bold two-line closing statement, no card body */
.mo-card.mc5{
  left:17px;right:calc(75% - 7px);top:1px;bottom:1px;
  display:flex;flex-direction:column;justify-content:center;
}
/* no .mo-num badge above this title, so the default title margin-top
   (tuned to sit below a badge) is just dead space here — drop it so the
   bigger type stays vertically centered and clear of the box edges */
.mo-card.mc5 .mo-title{margin-top:0;}
.mo-title-bold{
  font-weight:700;
  font-size:clamp(2.2rem,3.4vw,3.6rem);
  line-height:1.18;letter-spacing:-.01em;
}

/* column 3 — "The One Hospital." card, same template as the row-3 cards */
.mo-card.mc6{left:calc(50% + 1px);right:calc(25% + 9px);top:1px;bottom:1px;}

/* column 4 — image, same pan animation as st11/st22 (shared .mo-img img rule) */
.mo-img.mc-img3{left:calc(75% - 7px);right:17px;top:1px;bottom:1px;}

@media (max-width:980px){
  /* padding (not margin) on the section itself — a margin on the first
     card collapses through and shows the body's background as a gap
     between this section and #morning above it */
  #morning4{height:auto;padding:34px 0 70px;}
  #morning4 .hero-frame{display:none;}
  #morning4 .mo-card{position:static;margin:14px 20px;}
  .mo-card.mc5,.mo-card.mc6{left:auto;right:auto;top:auto;bottom:auto;}
  .mo-card.mc5{display:block;margin-top:0;}
  #morning4 .mo-img{position:static;left:auto;right:auto;top:auto;bottom:auto;height:56vw;margin:14px 20px;}
}

@media (max-width:980px){
  #morning{height:auto;padding-bottom:70px;}
  .morning-frame{display:none;}
  .morning-top{position:static;height:auto;padding:100px 20px 0;}
  .mo-word{position:static;display:inline-block;margin-right:.25em;width:auto;text-align:left;}
  .mo-marquee{position:static;width:100%;margin-top:12px;}
  .mo-giant{position:static;display:block;top:auto;left:auto;right:auto;bottom:auto;text-align:left;margin-top:8px;font-size:clamp(3.5rem,16vw,8rem);}
  .mo-card{position:static;margin:14px 20px;}
  .mo-card.mc1,.mo-card.mc2,.mo-card.mc3,.mo-card.mc4{left:auto;right:auto;top:auto;bottom:auto;}
  .mo-card.mc1{margin-top:26px;}
  .mo-body{margin-top:20px;}
  .mo-img{position:static;left:auto;right:auto;top:auto;bottom:auto;height:56vw;margin:14px 20px;}
  .mo-img.mc-img1{left:auto;right:auto;top:auto;bottom:auto;}
}

/* ============ A VISION BORN OF KNOWLEDGE — marquee + giant word share
   the same column-2+3 window (25% -> 75%, centred on the 50% line),
   split top/bottom by the grid's centre line — exact clone of the
   reference's "FORGED / BY REASON" composition ============ */
#execdec{position:relative;height:100vh;background:#243728;}
.gl.edh{top:50%;left:0;right:0;height:1px;}

.ed-top{
  position:absolute;z-index:4;left:16px;right:16px;top:0;
  height:50%;overflow:hidden;pointer-events:none;user-select:none;
}
/* marquee window : columns 2+3 (25% -> 75%) */
.ed-marquee{position:absolute;left:25%;right:25%;top:0;overflow:hidden;}
.ed-marquee .mo-track{animation-duration:32s;}

/* giant word : same columns 2+3 window as the marquee — flush against
   the window's left edge (col 2's left border), no centring offset */
.ed-giant{
  left:25%;right:25%;
  text-align:left;
  font-size:calc(clamp(10.625rem,1.339rem + 14.51vw,18.75rem) - 10px);
}

/* placeholder strap-line : same columns 2+3 window, centred on 50%,
   pushed well clear of the giant word below the centre line */
.ed-copy{
  position:absolute;left:25%;right:25%;top:calc(50% + 100px);
  max-width:40ch;margin:0 auto;text-align:center;z-index:4;
}

@media (max-width:980px){
  #execdec{height:auto;padding-bottom:80px;}
  #execdec .hero-frame{display:none;}
  .ed-top{position:static;height:auto;padding:100px 20px 0;}
  .ed-marquee{position:static;left:auto;right:auto;width:100%;}
  .ed-giant{font-size:calc(clamp(10.625rem,1.339rem + 14.51vw,18.75rem) - 11px);margin-left:-5px;}
  .ed-copy{position:static;left:auto;right:auto;top:auto;transform:none;max-width:none;text-align:left;margin:24px 20px 0;}
}

/* ============ THE TEAM — 4x2 portrait/signature grid, clone of the
   reference's alternating headshot + signature composition. Placeholder
   character art (site's own Tojo mascot) stands in for real headshots. ============ */
#team{position:relative;height:100vh;background:#243728;}
.gl.tmh{top:50%;left:0;right:0;height:1px;}

.tm-cell{position:absolute;overflow:hidden;}
/* column formula, shared with other 4-col sections */
.tm-cell.t1,.tm-cell.t5{left:17px;right:calc(75% - 7px);}
.tm-cell.t2,.tm-cell.t6{left:calc(25% + 9px);right:calc(50% - 1px);}
.tm-cell.t3,.tm-cell.t7{left:calc(50% + 1px);right:calc(25% + 9px);}
.tm-cell.t4,.tm-cell.t8{left:calc(75% - 7px);right:17px;}
/* row formula */
.tm-cell.t1,.tm-cell.t2,.tm-cell.t3,.tm-cell.t4{top:1px;bottom:calc(50% + 1px);}
.tm-cell.t5,.tm-cell.t6,.tm-cell.t7,.tm-cell.t8{top:calc(50% + 1px);bottom:1px;}

.tm-photo{background:#1b2a1e;}
.tm-photo img{
  position:absolute;left:0;right:0;bottom:0;width:100%;height:100%;
  object-fit:contain;object-position:center bottom;
  filter:saturate(.75) brightness(.92);
}
.tm-photo::after{
  content:"";position:absolute;left:0;right:0;bottom:0;height:55%;z-index:2;
  background:linear-gradient(to top,rgba(0,0,0,.72),rgba(0,0,0,0));
  pointer-events:none;
}
.tm-name{
  position:absolute;left:14px;bottom:12px;z-index:3;
  font-family:var(--font-display);font-size:1.1rem;letter-spacing:.02em;
  color:var(--cream);
}

.tm-sig{
  background:rgba(255,255,255,.035);
  display:flex;align-items:center;justify-content:center;
}
.tm-sig span{
  font-family:var(--font-body,'Poppins',sans-serif);
  font-style:italic;font-weight:600;
  font-size:clamp(2.6rem,4.2vw,4.6rem);
  color:var(--grid);
}

@media (max-width:980px){
  #team{height:auto;padding:40px 20px;display:grid;grid-template-columns:1fr 1fr;gap:12px;}
  #team .hero-frame{display:none;}
  /* .tm-cell was position:static here, which strips its role as containing
     block for the absolutely-positioned children (.tm-photo img, ::after,
     .tm-name) — they then position against the nearest ancestor that IS
     positioned (#team itself), so every photo escaped its own box and
     overlapped across the whole section. Re-establish position:relative
     per-cell (matching the desktop t1-t8 selector specificity so it wins
     here) and neutralise the desktop calc() offsets so each cell is a
     clean, self-contained box sized to fit 2-per-row. */
  .tm-cell,
  .tm-cell.t1,.tm-cell.t2,.tm-cell.t3,.tm-cell.t4,
  .tm-cell.t5,.tm-cell.t6,.tm-cell.t7,.tm-cell.t8{
    position:relative;left:auto;right:auto;top:auto;bottom:auto;height:170px;
  }
  .tm-photo img{object-fit:cover;}
  .tm-sig span{font-size:2.1rem;}
  .tm-name{font-size:.9rem;}
}

/* ============================================================
   MOBILE 3-COLUMN CANVAS  (≤980px)
   The desktop canvas is a 4-column grid; on mobile those cells are far
   too narrow to hold anything, so the whole device drops to 3 equal
   columns with square rows (the reference layout) and every item is
   sized/placed against that lattice. Video blocks are the one thing
   allowed to take the full 3-column span.

   The lattice is painted as backgrounds on .hero-frame rather than with
   the desktop .gl / .handle child elements, because the row count has to
   adapt to however tall each section's content ends up — a fixed set of
   line divs can't do that. The desktop children are hidden here.
   Placed last in the file so it wins source-order ties against the
   earlier per-section mobile blocks.
   ============================================================ */
@media (max-width:980px){
  :root{
    --m-pad:2px;                                   /* canvas side inset  */
    --m-div:3;                                     /* columns per section */
    --m-cw:calc((100vw - var(--m-pad)*2)/var(--m-div));  /* one column   */
    /* rows are taller than they are wide — a square row can't hold a
       card's worth of copy once each card is only one column wide */
    --m-rh:calc(var(--m-cw)*1.7);
  }

  /* floating bar over the grid — darker glass, light type */
  #nav,#nav.solid{
    width:min(653px,calc((100% - 32px)*.96));
    /* auto/auto/1fr instead of 1fr/auto/1fr — the logo packs left against the
       burger and the tagline gets all the remaining width, so it sets on one
       line rather than wrapping */
    grid-template-columns:auto auto 1fr;
    /* matched to the copy panel in row 5 / column 4 — the two are meant to
       read as the same pane of glass, so this value tracks .hero-copy::before */
    background:rgba(42,56,47,.36);
    border-color:rgba(239,234,228,.16);
    backdrop-filter:blur(18px) saturate(120%);
    -webkit-backdrop-filter:blur(18px) saturate(120%);
    padding:0 18px;column-gap:12px;
  }
  .burger{width:25.5px;}
  .burger span,#nav.solid .burger span{background:#efeae4;}
  .logo,#nav.solid .logo{
    color:#efeae4;letter-spacing:.25em;text-indent:.25em;
    font-size:18px;justify-self:start;white-space:nowrap;
  }
  .nav-link,#nav.solid .nav-link{
    color:#efeae4;font-size:10px;letter-spacing:.06em;text-transform:none;
    white-space:nowrap;
  }

  #hero .hero-frame,#showcase .hero-frame,#vision .hero-frame{
    display:block;position:absolute;inset:0 var(--m-pad);
    z-index:5;pointer-events:none;
    border:1px solid rgba(199,164,93,.26);
    /* border-box origin — with the default padding-box the lattice started
       1px inside the border, so every painted row line sat a couple of px
       below the grid row boundary and content looked like it overflowed */
    background-origin:border-box;
    /* column lines every --m-cw · row lines every --m-rh */
    background-image:
      linear-gradient(to right,rgba(199,164,93,.19) 1px,transparent 1px),
      linear-gradient(to bottom,rgba(199,164,93,.19) 1px,transparent 1px);
    /* the column tile is a third of the FRAME, not calc()'d from 100vw —
       100vw includes the scrollbar, so a vw-derived column was a few px
       too wide, the lines drifted right across the section and the fourth
       node fell past the right edge and was clipped away entirely */
    background-size:calc(100%/var(--m-div)) 100%,100% var(--m-rh);
  }
  /* nodes — 9px squares centred on every column×row crossing: a tiled
     vertical-bar background intersected with a horizontal-band mask */
  #hero .hero-frame::after,#showcase .hero-frame::after,#vision .hero-frame::after,
  #crossroad .hero-frame::after,#delivery .hero-frame::after,
  #manifesto .hero-frame::after,#edo .hero-frame::after{
    content:"";position:absolute;
    /* nodes are 4.5px squares; the box bleeds half a node each side plus the
       1px border offset, so -3.25px / +6.5px */
    left:-3.25px;top:-3.25px;width:calc(100% + 6.5px);height:calc(100% + 6.5px);
    background-image:linear-gradient(to right,rgba(246,242,233,.35) 4.5px,transparent 4.5px);
    /* subtracting the bleed leaves exactly one frame column per tile, which
       keeps the nodes sitting on the painted lines */
    background-size:calc((100% - 4.5px)/var(--m-div)) var(--m-rh);
    -webkit-mask-image:linear-gradient(to bottom,#000 4.5px,transparent 4.5px);
            mask-image:linear-gradient(to bottom,#000 4.5px,transparent 4.5px);
    -webkit-mask-size:100% var(--m-rh);
            mask-size:100% var(--m-rh);
  }
  #hero .hero-frame > *,#showcase .hero-frame > *,#vision .hero-frame > *{display:none;}

  /* ---------- HERO : the desktop 2×4 canvas, doubled vertically ----------
     Desktop is 2 rows of 4 columns; mobile keeps the same 4 columns and the
     same block positions, but each desktop row becomes two so the boxes end
     up portrait rather than wide — 4 rows × 4 columns, 16 boxes.
       rows 1-7 : video, filling the whole canvas (nav floats over it)
       row 6    : VIREVO wordmark, columns 1-2, riding over the video
       row 3    : chip marquee + headline, column 4
       rows 4-5 : body copy, column 4 */
  /* rows are a seventh of the viewport rather than derived from the column
     width, so the seven of them fill exactly one screen. dvh is the second
     declaration so it wins where supported — plain vh overshoots on mobile
     browsers because it ignores the collapsing address bar. */
  #hero{--m-div:4;--m-rh:calc(100vh/7);--m-rh:calc(var(--vh)/7);}
  #hero .hero-sticky{
    position:relative;height:auto;min-height:0;overflow:visible;
    display:grid;grid-template-columns:repeat(4,minmax(0,1fr));grid-auto-rows:var(--m-rh);
    padding:0 var(--m-pad);
  }
  /* height:auto is load-bearing — the earlier ≤640px block sets height:38vh
     on .hero-scene, which would otherwise beat the grid row-span sizing and
     leave the video short of the row line below it */
  #hero .hero-scene{
    position:relative;inset:auto;margin:0;border-radius:0;height:auto;min-height:0;
    grid-column:1/-1;grid-row:1/8;overflow:hidden;z-index:1;
  }
  #hero .hero-video{
    position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
    width:100%;height:100%;max-width:none;object-fit:cover;
    -webkit-mask-image:none;mask-image:none;
  }
  /* the giant scrolling wordmark — hidden on mobile until now */
  /* row 2, columns 1-2 — it shares those cells with the video and rides
     over it, the way the wordmark overlaps the scene on desktop */
  #hero .hero-wordclip{
    display:block;position:relative;left:auto;bottom:auto;
    grid-column:1/3;grid-row:6/7;width:auto;height:auto;overflow:hidden;z-index:4;
  }
  #hero .hero-wordmark{
    /* Bebas caps are ~.72em, so .83 of the row height puts the glyphs at
       roughly 60% of the box */
    bottom:-.12em;font-size:calc(var(--m-rh)*.83);opacity:1;color:#efeae4;
  }
  /* column 4 only, spanning canvas rows 2-3 — the phase's own two rows map
     onto those: chip + headline in the upper, body copy in the lower */
  .hero-copy{
    position:relative;inset:auto;left:auto;right:auto;top:auto;bottom:auto;
    /* z-index 4 keeps the panel under .hero-frame (5) so the row line and
       its nodes draw across it — at 6 the panel covered them */
    grid-column:4;grid-row:5/7;margin:0;padding:0;z-index:4;
    /* the panel is a pinned pseudo-element and the box clips its own
       overflow, so it lands exactly on the two-row cell — as a plain
       background it tracked the copy's height instead, which ran past the
       top line and stopped short of the bottom one */
    align-self:stretch;justify-self:stretch;overflow:hidden;min-height:0;
  }
  .hero-copy::before{
    content:"";position:absolute;inset:0;z-index:0;pointer-events:none;
    /* same glass as the nav bar */
    background:rgba(42,56,47,.36);
    backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);
  }
  /* the cycling phases are desktop-only; mobile shows one static block */
  .hero-phases{display:none;}
  /* chip, then the headline 15px under it, then the body filling the rest
     and sitting on the bottom edge */
  .hero-mcopy{
    position:relative;z-index:1;
    display:grid;height:100%;min-width:0;min-height:0;
    grid-template-columns:minmax(0,1fr);
    /* fourth row is the CTA: auto, so it takes only what it needs and the
       body copy above it keeps the 1fr and stays pinned to its foot */
    grid-template-rows:auto auto minmax(0,1fr) auto;
    padding:10px 8px 10px 10px;row-gap:0;
  }
  .hm-chip{
    grid-column:1;grid-row:1;align-self:start;justify-self:start;margin:0;
    font-size:7.2px;letter-spacing:.1em;padding:3px 8px;max-width:100%;
    text-transform:uppercase;
  }
  .hm-chip b{font-weight:600;letter-spacing:.16em;}
  .hm-title{
    grid-column:1;grid-row:2;align-self:start;margin:15px 0 0;
    font-family:'Poppins',var(--font-display);font-weight:700;
    font-size:.65rem;line-height:1.16;text-transform:uppercase;
    color:var(--cream);
  }
  .hm-title .it{display:inline;font-style:normal;font-weight:700;color:#f7dbac;}
  .hm-body{
    grid-column:1;grid-row:3;align-self:end;margin:0;padding-top:8px;
    font-family:'Poppins',var(--font-display);
    font-size:8.1px;line-height:1.24;letter-spacing:.06em;
    text-transform:uppercase;color:var(--cream);
  }
  .hm-body strong{color:#f7dbac;font-weight:700;}
  /* THE CTA, SCALED TO THE CARD RATHER THAN HIDDEN.
     It used to be display:none here on the grounds that the phone card is
     only ~93px wide and the page's 18px/36px button would have come out at
     about 93x18 — unreadable and under any usable tap target. That reasoning
     was about the button's SIZE, not its presence, and it also took the
     button off tablets, where the same card is 150-245px and had room for it
     all along. So it is measured for the card instead:
       - it stretches to the card's full inner width, which makes the target
         as wide as the cell allows rather than shrink-wrapping to the label;
       - the label is allowed to wrap onto two lines and is centred, so a
         320px phone (card inner ~61px) sets it as two lines rather than
         spilling past the glass;
       - min-height puts a floor under the target independently of the type,
         so it stays tappable at the smallest step of the clamp;
       - every value is clamped against vw, so it grows with the card and by
         980px is a full-size control again. */
  .hm-cta{
    display:flex;grid-column:1;grid-row:4;
    justify-self:stretch;justify-content:center;text-align:center;
    gap:0;margin:clamp(9px,1.2vw,18px) 0 0;
    min-height:clamp(34px,4.4vw,46px);
    padding:clamp(8px,1vw,14px) clamp(5px,1.4vw,22px);
    font-size:clamp(6.9px,1.1vw,11px);letter-spacing:.06em;line-height:1.3;
  }
  /* closing line across the last row */
  .hero-foot{
    display:block;grid-column:1/-1;grid-row:7/8;
    align-self:center;text-align:center;z-index:4;padding:0 10px;
  }
  .hf-lead{
    font-family:var(--font-display);font-weight:300;
    font-size:.76rem;line-height:1.2;color:#ffffff;
    padding-bottom:9.5px;margin-bottom:8.55px;
    border-bottom:1px solid rgba(246,242,233,.5);
    /* tighter and lighter: the wide 10px pass was reading as a smudge around
       the letters rather than lifting them off the footage */
    text-shadow:0 1px 5px rgba(18,30,22,.5),0 1px 2px rgba(18,30,22,.38);
  }
  .hf-sub{
    font-family:var(--font-display);font-style:italic;font-weight:300;
    font-size:.62rem;letter-spacing:.06em;color:#ffffff;
    /* tighter and lighter: the wide 10px pass was reading as a smudge around
       the letters rather than lifting them off the footage */
    text-shadow:0 1px 5px rgba(18,30,22,.5),0 1px 2px rgba(18,30,22,.38);
  }
  .scroll-cue{display:none;}

  /* ---------- EDO : 7 rows × 4 columns, dark canvas ----------
     row 1   : "Virevo / Introduces" (col 3) · giant DO fragment (col 4)
     row 2   : E + Executive (col 1) · D + Decision (col 2)
     row 3   : O + Optimisation (col 4)
     rows 2-5: the character, columns 2-3
     rows 6-7: two cards — chip + title on row 6, body on row 7 */
  /* The section is taller than the screen and the stage inside it is sticky,
     so the canvas pins while the page scrolls through. main.js sets the
     height to hold + rail travel + blind, and maps that distance onto the
     rail and the blind bands. */
  #edo{
    --m-div:4;--m-rh:calc(100vh/7);--m-rh:calc(var(--vh)/7);
    /* --m-cw can't be reused for the card width: it's declared on :root, so
       its var(--m-div) was already substituted there with the root value of
       3 and inherits down as a fixed third-of-screen. Declaring the quarter
       here resolves against this element instead. */
    --edo-col:calc((100vw - var(--m-pad)*2)/4);
    display:block;position:relative;z-index:2;height:200vh;
    /* It used to be transparent so the stage's blind could uncover #speed
       sitting behind it, and a background here would have sat between the
       two. That has not been true for a while: .edo-stage is display:none at
       every width now and #speed with it, so there is no mask and nothing
       behind to reveal. The old note is removed rather than left standing,
       because a warning that no longer applies is worse than none.

       THE GROUND UNDER INDUSTRIES, NOT THE PAGE'S.
     #edo was transparent, so wherever Industries had not yet covered
     it — the strip above its sticky panel as it rises, and the space
     the Works slab uncovers — what showed through was the body's warm
     cream against Industries' neutral grey. That is the band in the
     top corner. It carries Industries' own colour now, so the two are
     the same surface and the join cannot be seen.
     Declared as the variable as well as used, so #industries inherits
     one base value; the driver still overrides it on #industries alone
     when the panels swap. */
  --ind-bg:#ececeb;background:var(--ind-bg);

  }
  /* the -100dvh pulls it back over #speed, which occupies the screen above
     it in flow; both then stick to top:0 and pin as one */
  .edo-stage{
    position:sticky;top:0;z-index:2;
    margin-top:-100vh;margin-top:calc(var(--vh)*-1);
    height:100vh;height:var(--vh);overflow:hidden;
    display:grid;grid-template-columns:repeat(4,minmax(0,1fr));
    grid-auto-rows:var(--m-rh);padding:0 var(--m-pad);gap:0;
    background:var(--bg) url("../../Images/bg.jpg") center/cover no-repeat;
  }
  /* The blind is a mask on the stage rather than black bars over it, so what
     shows through the gaps is the real #speed section sitting behind — not a
     flat black fill. Seven mask layers, one per band; --r0..--r6 run 0 → 1
     and each eats its band from the top down. Layers composite additively,
     so the union of the seven is what stays visible. */
  .edo-blinds{display:none;}
  .edo-stage{
    --r0:0;--r1:0;--r2:0;--r3:0;--r4:0;--r5:0;--r6:0;
    -webkit-mask-image:
      linear-gradient(to bottom,transparent calc((0 + var(--r0))*100%/7),#000 calc((0 + var(--r0))*100%/7),#000 calc(1*100%/7),transparent calc(1*100%/7)),
      linear-gradient(to bottom,transparent calc((1 + var(--r1))*100%/7),#000 calc((1 + var(--r1))*100%/7),#000 calc(2*100%/7),transparent calc(2*100%/7)),
      linear-gradient(to bottom,transparent calc((2 + var(--r2))*100%/7),#000 calc((2 + var(--r2))*100%/7),#000 calc(3*100%/7),transparent calc(3*100%/7)),
      linear-gradient(to bottom,transparent calc((3 + var(--r3))*100%/7),#000 calc((3 + var(--r3))*100%/7),#000 calc(4*100%/7),transparent calc(4*100%/7)),
      linear-gradient(to bottom,transparent calc((4 + var(--r4))*100%/7),#000 calc((4 + var(--r4))*100%/7),#000 calc(5*100%/7),transparent calc(5*100%/7)),
      linear-gradient(to bottom,transparent calc((5 + var(--r5))*100%/7),#000 calc((5 + var(--r5))*100%/7),#000 calc(6*100%/7),transparent calc(6*100%/7)),
      linear-gradient(to bottom,transparent calc((6 + var(--r6))*100%/7),#000 calc((6 + var(--r6))*100%/7),#000 calc(7*100%/7),transparent calc(7*100%/7));
            mask-image:
      linear-gradient(to bottom,transparent calc((0 + var(--r0))*100%/7),#000 calc((0 + var(--r0))*100%/7),#000 calc(1*100%/7),transparent calc(1*100%/7)),
      linear-gradient(to bottom,transparent calc((1 + var(--r1))*100%/7),#000 calc((1 + var(--r1))*100%/7),#000 calc(2*100%/7),transparent calc(2*100%/7)),
      linear-gradient(to bottom,transparent calc((2 + var(--r2))*100%/7),#000 calc((2 + var(--r2))*100%/7),#000 calc(3*100%/7),transparent calc(3*100%/7)),
      linear-gradient(to bottom,transparent calc((3 + var(--r3))*100%/7),#000 calc((3 + var(--r3))*100%/7),#000 calc(4*100%/7),transparent calc(4*100%/7)),
      linear-gradient(to bottom,transparent calc((4 + var(--r4))*100%/7),#000 calc((4 + var(--r4))*100%/7),#000 calc(5*100%/7),transparent calc(5*100%/7)),
      linear-gradient(to bottom,transparent calc((5 + var(--r5))*100%/7),#000 calc((5 + var(--r5))*100%/7),#000 calc(6*100%/7),transparent calc(6*100%/7)),
      linear-gradient(to bottom,transparent calc((6 + var(--r6))*100%/7),#000 calc((6 + var(--r6))*100%/7),#000 calc(7*100%/7),transparent calc(7*100%/7));
  }
  #edo .hero-frame{
    display:block;position:absolute;inset:0 var(--m-pad);
    z-index:5;pointer-events:none;
    border:1px solid rgba(199,164,93,.26);
    background-origin:border-box;
    background-image:
      linear-gradient(to right,rgba(199,164,93,.19) 1px,transparent 1px),
      linear-gradient(to bottom,rgba(199,164,93,.19) 1px,transparent 1px);
    background-size:calc(100%/var(--m-div)) 100%,100% var(--m-rh);
  }
  /* three-step intro, all sequenced off .edo-in (added by main.js when the
     section comes into view):
       1. the gradient pane wipes open from the bottom-left corner
       2. VIREVO types in, one letter at a time
       3. Introduces rises and fades up underneath it */
  .edo-wipe{
    grid-column:3;grid-row:1;z-index:3;
    align-self:stretch;justify-self:stretch;
    background:linear-gradient(180deg,rgba(18,30,22,.86),rgba(18,30,22,.28));
    backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);
    clip-path:inset(100% 100% 0 0);
    transition:clip-path 1s cubic-bezier(.77,0,.18,1);
  }
  .edo-in .edo-wipe{clip-path:inset(0 0 0 0);}
  .edo-kicker{
    grid-column:3;grid-row:1;align-self:center;justify-self:center;z-index:4;
    font-family:'Poppins',var(--font-display);font-weight:700;
    font-size:10px;line-height:1.5;letter-spacing:.14em;text-transform:uppercase;
    color:var(--cream);text-align:center;
  }
  .ek-brand{display:block;}
  .ek-brand i{
    display:inline-block;font-style:normal;opacity:0;
    transition:opacity .26s linear;
  }
  .edo-in .ek-brand i{opacity:1;}
  .edo-in .ek-brand i:nth-child(1){transition-delay:.95s}
  .edo-in .ek-brand i:nth-child(2){transition-delay:1.04s}
  .edo-in .ek-brand i:nth-child(3){transition-delay:1.13s}
  .edo-in .ek-brand i:nth-child(4){transition-delay:1.22s}
  .edo-in .ek-brand i:nth-child(5){transition-delay:1.31s}
  .edo-in .ek-brand i:nth-child(6){transition-delay:1.40s}
  .ek-intro{
    display:block;color:#f7dbac;font-weight:700;
    opacity:0;transform:translateY(14px);
    transition:opacity .7s cubic-bezier(.22,1,.36,1),transform .7s cubic-bezier(.22,1,.36,1);
  }
  .edo-in .ek-intro{opacity:1;transform:none;transition-delay:1.7s;}
  /* EDO runs as a right-to-left marquee across the top-right box */
  .edo-do{
    grid-column:4;grid-row:2;align-self:end;justify-self:stretch;z-index:4;
    display:flex;align-items:flex-end;overflow:hidden;min-width:0;
  }
  .edo-track{
    display:flex;white-space:nowrap;width:max-content;
    animation:wmScroll 25s linear infinite;
  }
  .edo-do .edo-track{
    font-family:'Bebas Neue',var(--font-display);font-weight:700;
    font-size:calc(var(--m-rh)*.8);line-height:.82;letter-spacing:.02em;
    color:#e4dfd3;
    /* Bebas leaves descender space below the caps — pull the track down by it
       so the glyph feet land on the row's bottom border, not above it */
    margin-bottom:-.17em;
  }
  .edo-word{
    z-index:4;align-self:end;min-width:0;
    /* matches the EDO marquee exactly, at full opacity */
    font-family:'Poppins',var(--font-display);color:#e4dfd3;opacity:1;
  }
  .ew-cap{
    display:block;font-weight:700;
    font-size:calc(var(--m-rh)*.62);line-height:.86;letter-spacing:-.02em;
  }
  .ew-rest{
    display:block;font-weight:300;color:#ffffff;
    font-size:14px;line-height:1.1;white-space:nowrap;
  }
  /* Optimisation scrolls right-to-left inside its column */
  .ew-scroll{display:block;overflow:hidden;min-width:0;}
  .ew-scroll .edo-track{animation-duration:12s;}
  .ew-e,.ew-d{display:none;}
  .ew-o{grid-column:4;grid-row:3;}
  .ew-o .ew-cap{display:none;}
  /* sits above the Optimisation marquee — set at the nav logo's 18px, in the
     green used for the hero "Imagine" card panel */
  .ew-lead{
    display:block;font-family:'Poppins',var(--font-display);font-weight:700;
    font-size:18px;line-height:1.1;color:#f7dbac;margin-bottom:4px;
  }
  /* the marquee matches it — same family, weight and size */
  .ew-o .ew-rest{
    font-family:'Poppins',var(--font-display);font-weight:700;font-size:18px;
  }
  /* z-index 0 — bottom layer, everything else reads over it. It spans the
     whole canvas so the figure can run down behind the card rail. */
  /* Taken out of the grid and positioned against the stage instead, so the
     figure's size is driven by the two rules that matter rather than by
     whatever rows it happens to span:
       · its foot sits 50px above the canvas bottom
       · 40% of its height falls below the card rail's top line (2 rows up)
     That gives height = (2 rows - 50px) / 0.4, then 30% larger on top of it
     — so × 3.25. Width is auto, so it grows by the same 30%. */
  /* bottom is 50px + a 70px lift. Raising the offset rather than nudging with
     a transform keeps the image's own height calc (which subtracts that same
     50px baseline) untouched, so it moves without rescaling. */
  .edo-img{
    position:absolute;left:0;right:0;bottom:120px;top:auto;z-index:0;
    height:auto;
    display:flex;align-items:flex-end;justify-content:center;
    pointer-events:none;
  }
  /* the size lives on the <img> itself, not the wrapper — a height:100% on
     the image was the weak link, so nothing was actually growing */
  .edo-img img{
    display:block;
    /* 3.25 × 1.15 — the multiplier is the scale, so raising it grows the
       figure from its bottom anchor and it extends upward */
    height:calc((200vh/7 - 50px)*3.74);
    height:calc((var(--vh)*2/7 - 50px)*3.74);
    width:auto;max-width:none;max-height:none;min-height:0;
    object-fit:contain;object-position:center bottom;
    transform:translateX(-20px) scaleX(-1);
  }
  /* cards : chip + title on row 6, body on row 7 */
  .edo-card{
    z-index:4;min-width:0;
    /* same padding as the hero's "Imagine" card */
    padding:10px 8px 10px 10px;
    /* same glass as the nav bar */
    background:rgba(42,56,47,.36);
    backdrop-filter:blur(16px) saturate(150%);
    -webkit-backdrop-filter:blur(16px) saturate(150%);
    display:grid;grid-template-columns:minmax(0,1fr);
    grid-template-rows:minmax(0,1fr) minmax(0,1fr);row-gap:0;
  }
  /* rail across the bottom two rows — one column per card, spacers keep the
     first card on the column-2 line. It auto-scrolls once the section comes
     into view (driven from main.js) and stays swipeable by hand. */
  .edo-rail{
    grid-column:1/-1;grid-row:5/7;z-index:4;min-width:0;
    display:flex;align-items:stretch;gap:30px;
    /* hidden, not auto — the rail is driven by page scroll, so a second
       scrollable surface here would just compete with it */
    overflow:hidden;scrollbar-width:none;
  }
  .edo-rail::-webkit-scrollbar{display:none;}
  /* the leading spacer is one column less the gap, so card 1 still starts
     exactly on the column-2 line */
  .edo-pad{flex:0 0 calc(var(--edo-col) - 30px);}
  /* two columns wide; width AND max-width are both pinned because flex-basis
     alone loses to the content's min-content size */
  /* --foc is written per frame by main.js: 1 when the card is centred in the
     rail, falling to 0 as it moves off. Everything that marks a card as "in
     focus" is interpolated from it — the glow beneath, the darker ground, a
     brighter edge and a slight lift — so focus reads as a continuous state
     rather than a class that snaps on.
     The transition is short on purpose: the value is rewritten every frame,
     which restarts the transition each time, so a long one would never
     arrive. JS already smooths the underlying scroll. */
  .edo-card{
    --edo-cardw:calc(var(--edo-col)*2);
    --foc:0;
    flex:0 0 var(--edo-cardw);width:var(--edo-cardw);max-width:var(--edo-cardw);
    min-width:0;overflow:hidden;border-radius:6px;
    /* own compositing layer keeps the blur clipped to the radius while the
       card is being transformed */
    isolation:isolate;will-change:transform;
    border:1px solid rgba(246,242,233,calc(.2 + var(--foc)*.14));
    background:rgba(22,34,26,calc(var(--foc)*.42));
    transform:scale(calc(1 + var(--foc)*.03));
    box-shadow:0 8px 26px rgba(12,22,15,.26),
               0 20px 44px rgba(8,16,11,calc(var(--foc)*.52)),
               0 0 32px rgba(8,16,11,calc(var(--foc)*.40));
    transition:transform .18s linear,
               box-shadow .18s linear,
               background .18s linear;
  }
  /* hover darkens rather than lightens — a white wash was washing the copy
     out. Embossed edge: light inset along the top, dark along the bottom,
     with a soft dark glow around the whole card. */
  /* z-index 4, not 6 — above its siblings but still under .hero-frame (5),
     so the grid nodes keep drawing over a hovered card */
  .edo-card:hover,.edo-card:focus-within{
    transform:scale(1.05);z-index:4;
    /* restated because a backdrop-filter under a transform is prone to
       painting its own square corners over the rounded box */
    border-radius:6px;
    background:rgba(22,34,26,.62);
    border-color:rgba(246,242,233,.34);
    box-shadow:0 18px 40px rgba(8,16,11,.5),
               0 0 26px rgba(8,16,11,.38),
               inset 0 1px 0 rgba(255,255,255,.3),
               inset 0 -1px 0 rgba(8,16,11,.45);
  }
  /* both cards are a single full-width column so the body fills its whole
     box instead of being squeezed against the left edge */
  .ec1,.ec2{grid-template-columns:minmax(0,1fr);}
  .ec1 > *,.ec2 > *{grid-column:1;justify-self:stretch;}
  .ec1 .edo-chip,.ec2 .edo-chip{justify-self:start;}
  .ec1 .edo-body,.ec2 .edo-body{width:100%;min-width:0;}
  .ec1 .edo-body{font-size:9.1px;letter-spacing:1.5px;}
  /* type matched to the hero's "Imagine" card */
  .edo-chip{
    grid-row:1;align-self:start;justify-self:start;
    background:linear-gradient(90deg,#3a2f14,#c7a45d);
    border-radius:20px;padding:3px 8px;
    font-family:var(--font-mono);font-size:7.2px;letter-spacing:.1em;
    text-transform:uppercase;color:var(--cream);max-width:100%;font-weight:600;}
  .edo-chip b{font-weight:600;letter-spacing:.16em;}
  /* 26px clears the chip above it and leaves the asked-for 10px gap */
  .edo-title{
    grid-row:1;align-self:start;margin:26px 0 0;
    font-family:'Poppins',var(--font-display);font-weight:700;
    font-size:.65rem;line-height:1.16;text-transform:uppercase;color:var(--cream);
  }
  .edo-title .it{font-style:normal;font-weight:700;color:#f7dbac;}
  .edo-body{
    grid-row:2;align-self:start;margin:5px 0 0;
    font-family:'Poppins',var(--font-display);font-weight:300;
    font-size:10.1px;line-height:1.24;letter-spacing:.5px;
    text-transform:uppercase;color:var(--cream);
  }
  /* "Not just ERP" tightened a further 1px of letter- and 2px of word-spacing */
  .ec2 .edo-chip{letter-spacing:-.28px;word-spacing:-2px;}
  .ec2 .edo-chip b{letter-spacing:.15px;}
  .edo-body strong{color:#f7dbac;font-weight:700;}
  .edo-body em{font-style:italic;font-weight:700;color:var(--cream);}

  /* ---------- EDO STRIP : scrolling band under the section ----------
     the track holds the set twice over and slides exactly half its width,
     so the loop is seamless */
  .edo-strip{
    display:block;overflow:hidden;position:relative;
    grid-column:1/-1;grid-row:7;align-self:center;z-index:4;min-width:0;
    background:linear-gradient(90deg,#8b8071,#a99d8b 55%,#bdb1a0);
    border-top:1px solid rgba(246,242,233,.10);
    border-bottom:1px solid rgba(12,22,15,.18);
    padding:11px 0;
  }
  .strip-track{
    display:flex;width:max-content;
    animation:wmScroll 46s linear infinite;
  }
  .strip-item{
    display:flex;flex-direction:column;align-items:center;
    padding:0 26px;white-space:nowrap;text-align:center;
    line-height:1.36;
  }
  .strip-item b{
    font-family:'Poppins',var(--font-display);font-weight:700;
    font-size:15px;letter-spacing:.16em;text-transform:uppercase;color:#f7dbac;
  }
  .strip-item i{
    font-style:normal;font-family:'Poppins',var(--font-display);font-weight:300;
    font-size:11px;letter-spacing:.02em;color:#efeae4;margin-top:2px;
  }

  /* ---------- SPEED : 7 rows × 4 columns, black canvas ----------
     row 1    : "Decided" marquee (col 1) · On (col 2) · AI (col 3) ·
                "stuck on" bottom-aligned (col 4)
     row 2    : "How" marquee, top-aligned (col 4)
     row 3    : bracketed quote — parens in cols 1 and 4, copy across 2-3
     row 4    : "I already have…" (col 3) · platform marquee (cols 1-2)
     row 5    : "Why Do I Need" top-aligned · "More" marquee (col 3, bottom)
     row 6    : two panels, columns 1 and 4
     row 7    : centred closing line
     The frame is redrawn in white here rather than the gold used elsewhere. */
  /* Lives inside #edo as the first child, so it and the stage are siblings
     that both stick to top:0 and pin together for the whole section. That
     removes the offset arithmetic entirely — there is no distance to get
     wrong, so the two can no longer drift apart or interleave. Earlier in
     the DOM and z-index 1, so the stage paints over it until the blind
     opens. */
  /* HIDDEN, AND THE BLIND NOW OPENS ONTO INDUSTRIES INSTEAD.
     This is the black "Decided On AI / stuck on How" screen. It sat at
     z-index 1, between the card stage above it and Industries below, so the
     blind ate the stage to reveal this and a second phase then lifted it away
     to reveal Industries.
     Taking it out with display:none is enough on its own precisely because of
     that stacking: the blind is a mask on the STAGE, not a curtain in front of
     #speed, so whatever is behind shows through — and with this gone that is
     Industries. The reveal keeps working, it simply arrives one screen sooner.
     Nothing in the scroll maths needs touching: the phase that used to lift
     this section still runs and still drives the Industries fade and its card
     animation; its transform just lands on a hidden element and does nothing.
     Deleting the display line brings the section back exactly as it was. */
  #speed{
    display:none;
    --m-div:4;--m-rh:calc(100vh/7);--m-rh:calc(var(--vh)/7);
    position:sticky;top:0;z-index:1;
    /* pulled back over #industries, which sits above it in flow and is now
       exactly one screen tall */
    margin-top:-100vh;margin-top:calc(var(--vh)*-1);
    height:100vh;height:var(--vh);
    grid-template-columns:repeat(4,minmax(0,1fr));grid-auto-rows:var(--m-rh);
    padding:0 var(--m-pad);gap:0;overflow:hidden;background:#000;
  }

  #speed .hero-frame{
    display:block;position:absolute;inset:0 var(--m-pad);
    z-index:5;pointer-events:none;
    border:1px solid rgba(255,255,255,.154);
    background-origin:border-box;
    background-image:
      linear-gradient(to right,rgba(255,255,255,.112) 1px,transparent 1px),
      linear-gradient(to bottom,rgba(255,255,255,.112) 1px,transparent 1px);
    background-size:calc(100%/var(--m-div)) 100%,100% var(--m-rh);
  }
  #speed .hero-frame > *{display:none;}
  #speed .hero-frame::after{
    content:"";position:absolute;
    left:-3.25px;top:-3.25px;width:calc(100% + 6.5px);height:calc(100% + 6.5px);
    background-image:linear-gradient(to right,rgba(255,255,255,.49) 4.5px,transparent 4.5px);
    background-size:calc((100% - 4.5px)/var(--m-div)) var(--m-rh);
    -webkit-mask-image:linear-gradient(to bottom,#000 4.5px,transparent 4.5px);
            mask-image:linear-gradient(to bottom,#000 4.5px,transparent 4.5px);
    -webkit-mask-size:100% var(--m-rh);mask-size:100% var(--m-rh);
  }
  .sp-word,.sp-marquee{
    z-index:4;min-width:0;align-self:center;
    font-family:'Bebas Neue',var(--font-display);font-weight:700;
    /* same scale as the "Why Do I Need" headline */
    font-size:calc(var(--m-rh)*.51);line-height:.86;letter-spacing:.01em;
    text-transform:uppercase;color:#fff;
  }
  .sp-marquee{display:block;overflow:hidden;}
  .sp-track{
    display:flex;white-space:nowrap;width:max-content;
    animation:wmScroll 28s linear infinite;
  }
  /* row 1 : Decided (marquee) · On · AI · "stuck on".
     Bebas sits low in its line box, so a small negative top margin lifts the
     caps onto the row's top border rather than leaving them floating below it. */
  /* plain 2px — the earlier -.1em leading compensation was ~6px, so adding
     2px to it still netted a negative offset and pulled the caps up past
     the border instead of clearing it */
  /* bottom-aligned with 1px clearance. Bebas leaves descender space under its
     caps, so the negative bottom margin cancels that before the 1px is
     applied — without it the glyph feet float well above the row border. */
  .sp-decided,.sp-on,.sp-ai{
    align-self:end;margin-top:0;margin-bottom:calc(1px - .17em);
  }
  .sp-decided{grid-column:1;grid-row:1;display:flex;align-items:flex-end;}
  .sp-on{grid-column:2;grid-row:1;text-align:right;}
  .sp-ai{grid-column:3;grid-row:1;text-align:right;color:#f7dbac;}
  .sp-stuck{
    grid-column:4;grid-row:1;align-self:end;z-index:4;min-width:0;
    font-family:'Poppins',var(--font-display);font-style:italic;font-weight:300;
    font-size:calc(var(--m-rh)*.15);line-height:1.1;color:#fff;
    text-align:right;padding-bottom:6px;white-space:nowrap;
  }
  /* row 2 : HOW marquee, on the row's top line */
  .sp-how{grid-column:4;grid-row:2;align-self:start;}
  /* ---- row 3 : bracketed quote ---- */
  .sp-paren{
    grid-row:3;z-index:4;align-self:center;justify-self:center;
    /* a thin serif at its lightest weight — Poppins has no hairline cut, so
       its parenthesis stays chunky however light the weight is set */
    font-family:'Times New Roman',Georgia,serif;font-weight:100;
    /* scaled tall and narrow so the stroke thins out further */
    transform:scaleX(.55);
    font-size:calc(var(--m-rh)*.95);line-height:.85;color:rgba(255,255,255,.34);
  }
  .sp-paren-l{grid-column:1;}
  .sp-paren-r{grid-column:4;}
  .sp-quote{
    grid-column:2/4;grid-row:3;z-index:4;min-width:0;
    align-self:center;text-align:center;
  }
  /* matched to the EDO card scroller : titles at .65rem/1.16, body at
     10.1px/1.24 with .5px tracking, both Poppins */
  .sq-lead{
    font-family:'Poppins',var(--font-display);font-style:italic;font-weight:700;
    font-size:.65rem;line-height:1.16;color:#fff;margin:0 0 .9em;
  }
  .sq-lead b{font-weight:700;color:#f7dbac;}
  .sq-body{
    font-family:'Poppins',var(--font-display);font-weight:300;
    font-size:9.1px;line-height:1.24;letter-spacing:.5px;color:#fff;margin:0;
  }
  .sq-body b{font-weight:700;color:#f7dbac;}

  /* ---- row 4 : reply line + platform marquee ---- */
  .sp-already{
    grid-column:3;grid-row:4;z-index:4;align-self:start;margin:0;
    padding-top:calc(var(--m-rh)*.28);text-align:right;
    font-family:'Poppins',var(--font-display);font-style:italic;font-weight:700;
    font-size:.65rem;line-height:1.16;color:#fff;
  }
  .sp-already b{font-weight:700;color:#f7dbac;}
  .sp-logos{
    grid-column:1/3;grid-row:4;z-index:4;align-self:end;min-width:0;
    overflow:hidden;background:rgba(255,255,255,.06);
    border-radius:6px;padding:8px 0;margin-bottom:6px;
  }
  .sp-logo-track{
    display:flex;width:max-content;
    animation:wmScroll 34s linear infinite;
  }
  .sp-logo{
    display:flex;flex-direction:column;align-items:center;gap:5px;
    padding:0 12px;white-space:nowrap;
    font-family:'Poppins',var(--font-display);font-weight:400;
    font-size:9px;color:rgba(255,255,255,.72);
  }
  /* placeholder marks — greyscale, swap in real logo files when available */
  .sp-logo .lg{
    width:22px;height:22px;display:block;border-radius:50%;
    background:rgba(255,255,255,.16);
    border:1px solid rgba(255,255,255,.3);
    position:relative;
  }
  .sp-logo .lg::after{
    content:"";position:absolute;inset:6px;background:rgba(255,255,255,.75);
  }
  .lg-1::after{border-radius:50%;}
  .lg-2::after{clip-path:polygon(50% 0,100% 50%,50% 100%,0 50%);}
  .lg-3::after{clip-path:polygon(50% 0,65% 35%,100% 50%,65% 65%,50% 100%,35% 65%,0 50%,35% 35%);}
  .lg-4::after{border-radius:50%;inset:5px;background:none;border:2px solid rgba(255,255,255,.75);}
  .lg-5::after{clip-path:polygon(0 0,100% 0,100% 70%,50% 100%,0 70%);}
  .lg-6::after{border-radius:50%;inset:5px;background:none;border:2px solid rgba(255,255,255,.75);clip-path:inset(0 0 0 0 round 50%);}
  .lg-7::after{clip-path:polygon(50% 0,61% 39%,100% 50%,61% 61%,50% 100%,39% 61%,0 50%,39% 39%);}
  .lg-8::after{clip-path:polygon(50% 0,100% 25%,100% 75%,50% 100%,0 75%,0 25%);}

  /* ---- row 5 : headline + MORE marquee ---- */
  .sp-head{
    grid-column:1/-1;grid-row:5;align-self:start;z-index:4;margin:0;
    font-family:'Bebas Neue',var(--font-display);font-weight:700;
    font-size:calc(var(--m-rh)*.51);line-height:.86;letter-spacing:.01em;
    text-transform:uppercase;text-align:center;
  }
  .sh-a{color:#c9bdb6;}
  .sh-b{color:#f7e2bc;}
  .sp-more{
    grid-column:3;grid-row:5;align-self:end;
    font-size:calc(var(--m-rh)*.51);
    /* Bebas leaves descender space under its caps — pull the track down by
       it so the glyph feet sit on the row's bottom border */
    display:flex;align-items:flex-end;
  }
  .sp-more .sp-track{color:#e8e8e8;margin-bottom:-.17em;}

  /* ---- row 6 : the two panels ---- */
  .sp-panel{
    grid-row:6;z-index:4;min-width:0;max-width:100%;overflow:hidden;
    padding:10px 8px;
    display:flex;flex-direction:column;justify-content:center;gap:7px;
  }
  /* mirrored gradients — each panel fades from near-black on the outer edge
     of the canvas to a light grey on the edge facing the middle */
  .sp-panel-l{
    grid-column:1;text-align:right;
    background:linear-gradient(100deg,
      rgba(255,255,255,.012) 0%,
      rgba(255,255,255,.055) 48%,
      rgba(255,255,255,.17) 100%);
  }
  .sp-panel-r{
    grid-column:4;text-align:left;
    background:linear-gradient(260deg,
      rgba(255,255,255,.012) 0%,
      rgba(255,255,255,.055) 48%,
      rgba(255,255,255,.17) 100%);
  }
  .sp-panel h4{
    margin:0;font-family:'Poppins',var(--font-display);font-weight:700;
    font-size:.5rem;line-height:1.15;letter-spacing:.03em;color:#f7dbac;
    overflow-wrap:anywhere;
  }
  .sp-panel-l h4{font-style:italic;text-transform:uppercase;}
  .sp-panel p{
    margin:0;font-family:'Poppins',var(--font-display);font-weight:300;
    font-size:8px;line-height:1.2;color:#efeae4;overflow-wrap:anywhere;
  }
  .sp-panel p.accent{font-weight:700;font-size:8.6px;color:#f7dbac;}

  /* ---- row 7 : closing line ---- */
  .sp-close{
    grid-column:1/-1;grid-row:7;align-self:center;z-index:4;margin:0;
    min-width:0;width:auto;height:auto;position:static;
    text-align:center;padding:0 12px;
    font-family:'Poppins',var(--font-display);font-weight:300;
    font-size:10.5px;line-height:1.55;color:#ddd8d2;
  }

  /* ---------- SHOWCASE : same 4×4 canvas as the hero ----------
       rows 1-4 : video, filling the whole canvas
       row 1 col 1 : "Beyond Business Intelligence."
       row 4 col 4 : "Virevo Promise…"
     The Tojo cut-out is hidden here — the video carries the whole frame. */
  #showcase{
    /* same row metric as the hero — four rows to one screen */
    --m-div:4;--m-rh:25vh;--m-rh:calc(var(--vh)/4);
    height:auto;padding:0 var(--m-pad);
    display:grid;grid-template-columns:repeat(4,minmax(0,1fr));grid-auto-rows:var(--m-rh);
  }
  #showcase .hero-scene{
    position:relative;inset:auto;margin:0;border-radius:0;height:auto;
    grid-column:1/-1;grid-row:1/5;overflow:hidden;z-index:1;
  }
  #showcase .hero-video{
    position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
    width:100%;height:100%;max-width:none;object-fit:cover;
    -webkit-mask-image:none;mask-image:none;
  }
  #showcase .hero-shade{display:none;}
  /* z-index 4 keeps the card beneath .hero-frame (z-index 5) so the grid
     lines and nodes draw over it, as they do on desktop */
  /* the card itself goes transparent — the frosted panel moves onto the two
     text blocks so each reads as its own pane on the canvas */
  /* the card overlays the whole canvas and stays transparent — only the two
     text blocks carry a panel, each pinned to its own corner cell */
  .show-card{
    position:relative;inset:auto;left:auto;right:auto;top:auto;bottom:auto;
    grid-column:1/-1;grid-row:1/5;margin:0;padding:0;z-index:4;
    background:none;backdrop-filter:none;-webkit-backdrop-filter:none;
    display:grid;grid-template-columns:repeat(4,minmax(0,1fr));
    grid-template-rows:repeat(4,minmax(0,1fr));
    align-items:stretch;gap:0;min-height:0;
  }
  .show-name,.show-caption{
    background:rgba(246,242,233,.4);
    backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);
    padding:12px 9px;margin:0;
    align-self:stretch;justify-self:stretch;min-width:0;z-index:2;
    display:flex;flex-direction:column;justify-content:center;
  }
  .show-name{grid-column:1;grid-row:1;font-size:12.7px;line-height:1.2;font-weight:700;}
  .show-name .it{font-weight:700;font-size:10.5px;}
  .show-caption{grid-column:4;grid-row:4;font-size:9px;line-height:1.7;letter-spacing:.1em;max-width:none;}
  .show-thumb{display:none;}
  .show-card .btn{grid-column:2/4;grid-row:4;align-self:center;justify-self:center;}

  /* ---------- VISION : 4-column canvas ----------
     rows 1-2 : "A" (col 1) and "Vision" (cols 2-4) on the top line, with the
                Evolves. marquee pinned to the bottom of row 2.
                "New" is dropped on mobile — the phrase reads "A Vision".
     rows 3-4 : the three question cards in columns 1, 3 and 4 */
  #vision{
    /* same row metric as the hero — four rows to one screen */
    --m-div:4;--m-rh:25vh;--m-rh:calc(var(--vh)/4);
    height:auto;padding:0 var(--m-pad);gap:0;
    display:grid;grid-template-columns:repeat(4,minmax(0,1fr));grid-auto-rows:var(--m-rh);
  }
  .vision-top{
    position:relative;left:auto;right:auto;top:auto;height:auto;padding:0;
    grid-column:1/-1;grid-row:1/3;overflow:hidden;
    /* below .hero-frame (z-index 5) so the row line and its nodes draw
       over the type rather than being buried under it */
    z-index:4;
    display:grid;grid-template-columns:repeat(4,minmax(0,1fr));
    grid-template-rows:auto minmax(0,1fr);
  }
  .vision-word{
    position:static;left:auto;right:auto;top:auto;bottom:auto;
    width:auto;margin:0;grid-row:1;align-self:start;
    font-size:calc(var(--m-cw)*.88);line-height:.82;
  }
  .vw-a{grid-column:1;text-align:left;}
  .vw-new{display:none;}
  .vw-vision{grid-column:2/5;text-align:left;}
  /* same row, bottom-aligned on its lower line */
  .vision-marquee{
    position:static;left:auto;right:auto;bottom:auto;top:auto;width:auto;height:auto;margin:0;
    grid-column:1/-1;grid-row:2;align-self:end;overflow:hidden;
  }
  .vm-track{font-size:calc(var(--m-cw)*1.77);}
  /* one card per column, one row tall — the num stacks above the title
     instead of sitting beside it, and type/padding step down so a whole
     question fits inside a single cell */
  /* min-width:0 + justify/align-self:stretch make the card fill its cell
     exactly — a grid item's default min-width:auto floors it at its
     content's min-content width, which is what was pushing the cards
     wider than a third of the screen and off the right edge */
  .q-card{
    position:relative;left:auto;right:auto;top:auto;bottom:auto;margin:0;
    grid-row:3/5;z-index:4;min-width:0;width:auto;max-width:100%;
    justify-self:stretch;align-self:stretch;align-content:start;
    padding:8px 6px;grid-template-columns:minmax(0,1fr);gap:2px;text-align:center;
  }
  /* the desktop .q-card.q1/.q2/.q3 rules set left/right and carry a higher
     specificity (two classes) than the plain .q-card reset above, so those
     offsets survived and — now that the cards are position:relative grid
     items — were shifting each one to the right of its own cell by its old
     desktop offset. They have to be cleared at matching specificity. */
  .q-card.q1,.q-card.q2,.q-card.q3{left:auto;right:auto;top:auto;bottom:auto;}
  /* column 2 stays blank */
  .q-card.q1{grid-column:1;margin-top:0;}
  .q-card.q2{grid-column:3;}
  .q-card.q3{grid-column:4;}
  /* the number becomes a large Bebas watermark sitting behind the copy,
     out of flow so the title/body start at the top of the cell */
  /* at line-height .82 Bebas caps sit flush on the em-box top, so top:0
     lands the digits on the cell's top line and the size is set from the
     row height (not the column) to fill the box vertically */
  .q-num{
    position:absolute;left:0;right:0;top:0;bottom:auto;grid-row:auto;padding:0;
    font-family:'Bebas Neue',var(--font-display);font-style:normal;font-weight:700;
    font-size:calc(var(--m-rh)*1.32);line-height:.82;letter-spacing:-4px;
    color:rgba(30,55,39,.13);text-align:center;
    /* slide left so the seam between the two digits lands near the middle
       of the cell — the right half of the "0" and the leading half of the
       second digit stay in frame, the rest bleeds out of the card */
    transform:translateX(calc(-85% - 50px));
    pointer-events:none;user-select:none;z-index:0;
  }
  .q-title,.q-body{position:relative;z-index:1;}
  /* header sits on the box's top line, body copy follows directly under it;
     both step down so a whole question fits the single row */
  .q-title{font-size:15.6px;letter-spacing:.04em;padding-top:0;line-height:1.26;overflow-wrap:anywhere;}
  .q-title::after{width:14px;height:1.5px;margin:3px auto 0;}
  .q-body{font-size:13.7px;line-height:1.38;}

  /* ---------- CROSSROAD : 4-column canvas, two rows ----------
     row 1 : portrait in columns 1-2, "01" card in columns 3-4
     row 2 : "02" card in columns 1-2, the verdict in columns 3-4 */
  #crossroad .hero-frame,#delivery .hero-frame{
    display:block;position:absolute;inset:0 var(--m-pad);
    z-index:5;pointer-events:none;
    border:1px solid rgba(199,164,93,.26);
    background-origin:border-box;
    background-image:
      linear-gradient(to right,rgba(199,164,93,.19) 1px,transparent 1px),
      linear-gradient(to bottom,rgba(199,164,93,.19) 1px,transparent 1px);
    background-size:calc(100%/var(--m-div)) 100%,100% var(--m-rh);
  }
  #crossroad .hero-frame > *,#delivery .hero-frame > *{display:none;}
  #crossroad{
    --m-div:4;--m-rh:calc(var(--m-cw)*1.9);
    height:auto;padding:0 var(--m-pad);gap:0;
    display:grid;grid-template-columns:repeat(4,minmax(0,1fr));grid-auto-rows:var(--m-rh);
  }
  .cross-img{
    position:relative;inset:auto;left:auto;right:auto;top:auto;bottom:auto;
    grid-column:1/3;grid-row:1/2;height:auto;min-width:0;overflow:hidden;z-index:1;
  }
  .cross-img img{width:100%;height:100%;object-fit:cover;object-position:center top;}
  .cross-card{
    position:relative;inset:auto;left:auto;right:auto;top:auto;bottom:auto;
    margin:0;z-index:4;min-width:0;padding:14px 10px;overflow:hidden;
  }
  /* the desktop .cross-card.c1/.c2/.c3 rules set left/right/top/bottom and
     the earlier mobile block gives c1/c2 side margins — all of those carry
     two-class specificity, so they beat the plain .cross-card reset above
     and were shifting each card out of its own cell. Clear them here at
     matching specificity. */
  .cross-card.c1,.cross-card.c2,.cross-card.c3{
    left:auto;right:auto;top:auto;bottom:auto;margin:0;padding:14px 10px;
  }
  .cross-card.c1{grid-column:3/5;grid-row:1/2;background:#e4dfd3;}
  .cross-card.c2{grid-column:1/3;grid-row:2/3;background:#c5bfb1;}
  .cross-card.c3{grid-column:3/5;grid-row:2/3;}
  /* each of these cards straddles two canvas columns, so split it internally
     on the same line: rule + number + label in the first, quote in the second */
  .cross-card.c1,.cross-card.c2{
    display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);
    grid-template-rows:auto auto auto minmax(0,1fr);
    column-gap:10px;align-content:start;
  }
  .cross-card.c1 .cx-rule,.cross-card.c2 .cx-rule{grid-column:1;grid-row:1;}
  .cross-card.c1 .cx-num,.cross-card.c2 .cx-num{grid-column:1;grid-row:2;font-size:2.5rem;}
  .cross-card.c1 .cx-label,.cross-card.c2 .cx-label{grid-column:1;grid-row:3;font-size:13.7px;}
  /* quote sits in the second column, pushed to the bottom of the card */
  .cross-card.c1 .cx-quote,.cross-card.c2 .cx-quote{
    grid-column:2;grid-row:1/5;align-self:end;margin-top:0;font-size:14.4px;
  }

  /* the verdict card splits the same way: headline left, body right */
  .cross-card.c3{
    display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);
    grid-template-rows:auto minmax(0,1fr);column-gap:10px;align-content:start;
  }
  .cross-card.c3 .cx-head{grid-column:1;grid-row:1;}
  .cross-card.c3 .cx-rule{grid-column:1;grid-row:2;align-self:start;margin:10px 0 0 auto;}
  .cross-card.c3 .cx-final-body{
    grid-column:2;grid-row:1/3;align-self:end;margin-top:0;
    font-size:14.4px;color:#ebe1d3;
  }

  /* ---------- DELIVERY : mirror of crossroad on the same canvas ----------
     rows 1-2 : statement card in columns 1-2, portrait in columns 3-4 */
  #delivery{
    --m-div:4;--m-rh:calc(var(--m-cw)*1.9);
    height:auto;padding:0 var(--m-pad);gap:0;
    display:grid;grid-template-columns:repeat(4,minmax(0,1fr));grid-auto-rows:var(--m-rh);
  }
  .point-img{
    position:relative;inset:auto;left:auto;right:auto;top:auto;bottom:auto;
    grid-column:1/-1;grid-row:1/3;height:auto;min-width:0;overflow:hidden;z-index:1;
  }
  .point-img img{width:100%;height:100%;object-fit:cover;object-position:center bottom;}
  /* position:relative is load-bearing — an earlier mobile block sets
     .cross-card.point-card{position:static}, which beats the plain
     .cross-card reset on specificity. Without it the ::before pane below
     resolves its percentages against the section instead of the card and
     lands in the wrong grid box entirely. */
  .cross-card.point-card{
    position:relative;
    left:auto;right:auto;top:auto;bottom:auto;margin:0;padding:14px 10px;
    grid-column:1/3;grid-row:1/3;z-index:4;min-width:0;
    display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);
    grid-template-rows:auto minmax(0,1fr);column-gap:10px;align-content:start;
    /* fully transparent — the image behind it carries the whole cell */
    background:none;
    backdrop-filter:none;-webkit-backdrop-filter:none;
  }
  .point-card .cx-rule{grid-column:1;grid-row:1;margin-bottom:10px;}
  .pt-head{grid-column:1;grid-row:2;align-self:start;font-size:1.2rem;line-height:1.2;}
  .pt-head .it{color:#ebe1d3;}
  /* frosted pane filling the bottom-right box (row 2, column 2) — done as a
     pseudo-element because backdrop-filter can't be masked to part of an
     element the way a plain background can */
  .cross-card.point-card::before{
    content:"";position:absolute;left:50%;right:0;top:50%;bottom:0;z-index:0;
    background:rgba(255,255,255,.06);
    backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);
    pointer-events:none;
  }
  .point-card > *{position:relative;z-index:1;}
  .pt-body{
    grid-column:2;grid-row:1/3;align-self:end;margin:0;padding:0 10px;
    font-size:14.4px;line-height:1.45;color:#ebe1d3;
  }
  .pt-body strong{color:#ebe1d3;}
  .pt-note{grid-column:2;grid-row:3;align-self:end;margin:8px 0 0;padding:0 10px 12px;font-size:12px;color:#ebe1d3;}

  /* ---------- MANIFESTO : same 4-column canvas ----------
     row 1 : "The Virevo Idea" + first half of the statement (col 1),
             second half of the statement (col 2)
     row 2 : Philosophy label + lead (col 1), its body copy (col 2)
     row 3 : The Prize label + lead (col 2), its body split across cols 3-4
     .shell and .manifesto-side both go display:contents so their children
     become direct items of the section grid and can be placed by cell. */
  #manifesto .hero-frame{
    display:block;position:absolute;inset:0 var(--m-pad);
    z-index:5;pointer-events:none;
    border:1px solid rgba(199,164,93,.26);
    background-origin:border-box;
    background-image:
      linear-gradient(to right,rgba(199,164,93,.19) 1px,transparent 1px),
      linear-gradient(to bottom,rgba(199,164,93,.19) 1px,transparent 1px);
    background-size:calc(100%/var(--m-div)) 100%,100% var(--m-rh);
  }
  #manifesto .hero-frame > *{display:none;}
  #manifesto{
    --m-div:4;--m-rh:calc(var(--m-cw)*1.9);
    position:relative;height:auto;padding:0 var(--m-pad);gap:0;border-bottom:0;
    display:grid;grid-template-columns:repeat(4,minmax(0,1fr));grid-auto-rows:var(--m-rh);
  }
  #manifesto .shell{display:contents;}
  #manifesto .sec-head{
    grid-column:1;grid-row:1;align-self:start;margin:0;padding:10px 8px 0;
    display:block;z-index:4;
  }
  #manifesto .sec-index{display:none;}
  #manifesto .kicker{
    font-family:'Bebas Neue',var(--font-display);
    font-size:11.7px;letter-spacing:.18em;
  }
  #manifesto .kicker::before{width:16px;}
  .manifesto-text{
    max-width:none;margin:0;padding:8px;z-index:4;
    font-size:15px;line-height:1.3;
  }
  .mf-a{grid-column:2;grid-row:1;align-self:end;}
  .mf-b{grid-column:3;grid-row:1;align-self:end;}
  .manifesto-side{display:contents;}
  /* each half-row block straddles two cells: label + lead left, copy right */
  .manifesto-side > div{
    margin:0;padding:8px;min-width:0;z-index:4;
    display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);
    grid-template-rows:auto minmax(0,1fr);column-gap:8px;align-content:start;
  }
  .manifesto-side > div:nth-child(1){grid-column:1/3;grid-row:2;}
  /* spans the full row: label, lead and the short line stack in column 1,
     the long paragraph sits away in column 4 */
  .manifesto-side > div:nth-child(2){
    grid-column:1/5;grid-row:3;
    grid-template-columns:repeat(4,minmax(0,1fr));
    grid-template-rows:auto auto minmax(0,1fr);
  }
  .manifesto-side .lbl{grid-column:1;grid-row:1;font-size:8.5px;letter-spacing:.16em;margin-bottom:6px;}
  .manifesto-side .mf-lead{
    grid-column:1;grid-row:2;align-self:start;
    font-size:.95rem;line-height:1.22;padding:8px 10px;border-left-width:2px;
  }
  .manifesto-side p{font-size:11px;line-height:1.45;margin-top:8px;}
  /* .mf-lead is itself a <p>, so it counts as nth-of-type(1) — the body
     paragraphs are types 2 and 3, not 1 and 2 */
  .manifesto-side > div:nth-child(1) p:nth-of-type(2){grid-column:2;grid-row:1;align-self:start;margin-top:0;}
  .manifesto-side > div:nth-child(1) p:nth-of-type(3){grid-column:2;grid-row:2;align-self:start;margin-top:8px;}
  /* The Prize : label in col 1 · header then its short line in col 3 ·
     the long paragraph in col 4 */
  .manifesto-side > div:nth-child(2) .mf-lead{grid-column:3;grid-row:1;align-self:start;}
  .manifesto-side > div:nth-child(2) p:nth-of-type(2){grid-column:3;grid-row:2;align-self:start;margin-top:8px;}
  .manifesto-side > div:nth-child(2) p:nth-of-type(3){grid-column:4;grid-row:1/4;align-self:end;margin-top:0;}

  /* the strip marquee between manifesto and solutions is desktop-only */
  .marquee{display:none;}

  /* ---------- scroll reveal ----------
     a slower, shorter-travel version of the desktop fade so content settles
     into its cell rather than snapping in */
  .fade-up{
    transform:translateY(26px);
    transition:opacity 1.15s cubic-bezier(.22,1,.36,1),transform 1.15s cubic-bezier(.22,1,.36,1);
  }
  .cx-num{font-size:1.6rem;}
  .cx-label{font-size:9.5px;}
  .cx-quote,.cx-final-body{font-size:12px;line-height:1.45;margin-top:10px;}
  .cx-head{font-size:1.2rem;line-height:1.2;}
  .cx-rule{margin-bottom:10px;}
}

/* ============================================================
   INDUSTRIES — 10 rows × 4 columns, mobile only
   row 1   : "Industries"        row 2    : "We Serve"
   row 3   : intro copy          rows 4-5 : three cards
   row 6   : section heading, with the discipline labels marqueeing along
             the row's bottom line
   row 8   : closing split — copy + link left, suite card right
   rows 7, 9-10 : free
   Reveals by opacity as it rises into view, so it reads as a layer sitting
   under the black section rather than a new page arriving.
   ============================================================ */
/* ============================================================
   INDUSTRIES — no longer phone-only.
   This block was wrapped in @media (max-width:980px) and held every
   rule the section has, so on a wide screen the markup arrived with
   no layout at all. The wrapper is simply gone: the block contained
   nothing BUT Industries — 44 rules, checked one by one — so removing
   it changes the scope of this section and of nothing else. The
   desktop block at the end of the file restates only the values that
   were sized for a hand.
   ============================================================ */
/* The bottom of three stacked layers inside #edo — it pins at top:0 and
   simply waits there while the black section slides up off it, so it is
   genuinely already in place rather than scrolling in behind. */
#industries{
  /* Nine rows in exactly one screen — a viewport ninth each. The section
     is therefore never taller than the pin, so row 9 (blank, under the
     scroller) is on screen for the whole sequence instead of depending on
     leftover scroll to bring it up. */
  --m-div:4;--m-rh:calc(100vh/9);--m-rh:calc(var(--vh)/9);
  display:grid;position:sticky;top:0;z-index:0;
  height:100vh;height:var(--vh);
  grid-template-columns:repeat(4,minmax(0,1fr));grid-auto-rows:var(--m-rh);
  /* only the header strip above .ind-body shows this — each panel paints
     its own ground. JS flips it once the track is past halfway so the
     header sits on the incoming industry's colour. */
  padding:0 var(--m-pad);gap:0;background:var(--ind-bg,#ececeb);overflow:hidden;
  opacity:var(--ind-op,.2);
}
#industries .hero-frame{
  display:block;position:absolute;inset:0 var(--m-pad);
  /* full-opacity frame with the lines themselves at 10% ink — that reads as
     a 10% grid. Dimming the frame instead multiplied against already-faint
     colours and left it all but invisible. */
  z-index:5;pointer-events:none;opacity:0;
  border:1px solid rgba(30,55,39,.10);
  background-origin:border-box;
  background-image:
    linear-gradient(to right,rgba(30,55,39,.10) 1px,transparent 1px),
    linear-gradient(to bottom,rgba(30,55,39,.10) 1px,transparent 1px);
  background-size:calc(100%/var(--m-div)) 100%,100% var(--m-rh);
}
#industries .hero-frame > *{display:none;}
#industries .hero-frame::after{
  content:"";position:absolute;
  left:-3.25px;top:-3.25px;width:calc(100% + 6.5px);height:calc(100% + 6.5px);
  background-image:linear-gradient(to right,rgba(30,55,39,.10) 4.5px,transparent 4.5px);
  background-size:calc((100% - 4.5px)/var(--m-div)) var(--m-rh);
  -webkit-mask-image:linear-gradient(to bottom,#000 4.5px,transparent 4.5px);
          mask-image:linear-gradient(to bottom,#000 4.5px,transparent 4.5px);
  -webkit-mask-size:100% var(--m-rh);mask-size:100% var(--m-rh);
}

/* --- rows 1-2 : the two-tone headline, same scale as "Decided on AI" --- */
.ind-a,.ind-b{
  z-index:4;margin:0;
  font-family:'Bebas Neue',var(--font-display);font-weight:700;
  /* .57 against a ninth-of-screen row keeps the same rendered size the
     .51 gave against an eighth */
  font-size:calc(var(--m-rh)*.57);line-height:.86;letter-spacing:.01em;
  text-transform:uppercase;white-space:nowrap;
}
/* Bebas leaves space below its caps and above them, so the two nudges pull
   each line onto its row border rather than leaving it floating */
/* Both headings share row 1 — in the reference they sit together above the
   first node line, "Industries" on the row's top edge and "We Serve" on
   its bottom. The em nudges cancel Bebas' leading above and below the caps
   so each meets its edge. */
.ind-a{
  grid-column:1/4;grid-row:1;color:var(--ink);
  /* reference sets the caps 1% of the frame down from the top, not flush
     against it — .09 of a row, less Bebas' leading */
  align-self:start;margin-top:calc(var(--m-rh)*.09 - .07em);
}
.ind-b{
  grid-column:2/5;grid-row:1;color:#fff;text-align:right;
  align-self:end;margin-bottom:calc(2px - .16em);
}

/* --- row 2 : intro, centred in the row as in the reference --- */
.ind-intro{
  grid-column:1/-1;grid-row:2;z-index:4;align-self:center;margin:0;
  text-align:center;padding:0 10px;
  font-family:'Poppins',var(--font-display);font-weight:300;
  font-size:9.5px;line-height:1.7;color:rgba(30,55,39,.72);
}

/* --- rows 3-9 : the industry body ---------------------------------
   The header (rows 1-2) is a plain grid item and never moves. Everything
   below it lives in this window, which clips at the row-3 line — so the
   outgoing panel reads as sliding up *under* the header rather than
   overlapping it. Two panels sit stacked in the track; JS slides the track
   up by exactly one panel to swap Healthcare for Hospitality. */
.ind-body{
  grid-column:1/-1;grid-row:3/10;z-index:4;
  min-width:0;position:relative;overflow:hidden;
}
.ind-track{
  position:absolute;inset:0 0 auto 0;height:200%;
  display:grid;grid-template-rows:repeat(2,1fr);
  transform:translate3d(0,calc(var(--ind-y,0) * -50%),0);
  will-change:transform;
}
/* Each panel re-declares the seven rows it occupies so its children keep
   the same row heights they had when they were direct children of the
   section — the reference spacing is unchanged, only the nesting is. */
.ind-panel{
  display:grid;position:relative;min-width:0;
  grid-template-columns:repeat(4,minmax(0,1fr));
  grid-template-rows:repeat(7,var(--m-rh));
}
/* The incoming panel still carries a ground of its own — that is what lets
   it cover the outgoing one completely as it rises, and nothing here makes
   either of them any less opaque.

   What changes is that both read the SAME value while the swap is running.
   Two different solids meeting at a moving edge is a visible seam: the
   colour arrives all at once, at whatever height the join happens to be.
   Sharing one interpolated value makes the join disappear, so the whole
   ground moves from one colour to the other together, and the panel is
   simply a panel rather than a line the colour changes across.

   The fallbacks are per panel and deliberately different, so with no
   script running each still paints its own proper colour. */
.ip-health{background:var(--ind-bg,#ececeb);}
.ip-hosp{background:var(--ind-bg,#c4c4bc);}

/* --- rows 3-5 : three equal cards, side by side within the canvas --- */
.ind-cards{
  grid-column:1/-1;grid-row:1/4;z-index:4;min-width:0;
  /* measured off the reference: cards open .55 of a row into row 3 and
     close .66 of a row before the end of row 5 */
  display:flex;align-items:stretch;gap:10px;
  padding:calc(var(--m-rh)*.55) 4px calc(var(--m-rh)*.66);
  /* the depth the cards turn through — set on the row, not each card, so
     all three share one vanishing point and read as one arrangement */
  perspective:820px;perspective-origin:50% 50%;
}
.ind-card{
  flex:1 1 0;   /* equal thirds of the row */
  min-width:0;overflow:hidden;border-radius:2px;
  margin:0;display:flex;flex-direction:column;
  /* hinged along the top edge, so the card rolls down into place rather
     than swinging in from a side */
  transform-origin:50% 0%;
  backface-visibility:hidden;
}
/* Each card rolls down into place on its own axis, hinged along its top
   edge. The turn is not a transition or a keyframe — main.js writes the
   transform every frame from the reveal's own progress, so the cards are
   literally geared to how much of the black section has slid away. Hold
   the scroll half way and they hold half way; reverse it and they unwind.
   A triggered animation could only ever play at its own pace, unaware of
   the scroll that set it off. The base state here is what shows before the
   first frame lands. */
.js .ind-card{opacity:0;}

.ic1{background:#2b2b2b;}
.ic1 img,.ic1 video{width:100%;height:100%;object-fit:cover;display:block;}
.ic2{
  align-items:center;justify-content:space-between;
  padding:12px 8px;background:linear-gradient(180deg,#f7f6f4,#dedcd8);
}
.ic3{background:#2b2b2b;padding:10px 8px;gap:8px;}
.ic3 img{width:100%;flex:1 1 auto;min-height:0;object-fit:cover;border-radius:1px;}
.ic-kicker{
  font-family:'Poppins',var(--font-display);font-weight:700;
  font-size:7.5px;letter-spacing:.1em;text-transform:uppercase;
}
.ic2 .ic-kicker{color:rgba(30,55,39,.8);}
.ic3 .ic-kicker{color:#fff;text-align:right;}
.ic-icon{
  width:52px;height:52px;border-radius:50%;background:#fff;color:#5a5a5a;
  display:flex;align-items:center;justify-content:center;
}
.ic-icon svg{width:26px;height:26px;}
.ic-note{
  font-family:'Poppins',var(--font-display);font-weight:300;
  font-size:7px;line-height:1.45;text-align:center;color:rgba(30,55,39,.65);
}
.ic-foot{
  display:flex;align-items:flex-end;justify-content:space-between;gap:6px;
  font-family:'Poppins',var(--font-display);color:#fff;
}
.ic-foot b{font-weight:600;font-size:7.5px;line-height:1.35;}
.ic-foot em{font-style:normal;font-weight:700;font-size:19px;line-height:1;}
.ic-foot sup{font-size:.5em;vertical-align:super;}

/* --- row 6 : heading, lifted 10%, with the labels riding the row's
       bottom line as a marquee --- */
/* Three equal gaps down the row — above the heading, between heading and
   marquee, and below the marquee. Raising both paddings to 16px lifts the
   marquee off the bottom line and closes the middle gap to match; at 12px
   the middle one was the widest of the three. */
.ind-head{
  grid-column:1/-1;grid-row:4;z-index:4;align-self:start;margin:0;
  text-align:center;padding-top:calc(var(--m-rh)*.19);
  font-family:'Poppins',var(--font-display);font-weight:700;
  font-size:13px;letter-spacing:.01em;text-transform:uppercase;color:#4a4a4a;
}
.ind-tags{
  grid-column:1/-1;grid-row:4;z-index:4;align-self:end;min-width:0;
  overflow:hidden;padding-bottom:calc(var(--m-rh)*.10);
}
.ind-tag-track{
  display:flex;width:max-content;
  animation:wmScroll 37s linear infinite;
}
.ind-tag-track span{
  padding:0 18px;white-space:nowrap;text-align:center;
  font-family:'Poppins',var(--font-display);font-weight:300;
  font-size:8px;line-height:1.35;letter-spacing:.04em;color:rgba(30,55,39,.7);
}

/* --- row 8 : closing split --- */
.ind-foot-l{
  grid-column:1/3;grid-row:8;z-index:4;min-width:0;
  display:flex;flex-direction:column;justify-content:center;gap:10px;
  padding:0 10px;
}
.ind-foot-l p{
  margin:0;font-family:'Poppins',var(--font-display);font-weight:300;
  font-size:11px;line-height:1.35;color:#3a3a3a;
}
.ind-link{
  font-family:'Poppins',var(--font-display);font-weight:400;
  font-size:7.5px;letter-spacing:.14em;text-transform:uppercase;color:#3a3a3a;
  border-bottom:1px solid rgba(30,55,39,.35);padding-bottom:4px;
  display:inline-flex;align-items:center;gap:8px;align-self:flex-start;
}
.ind-foot-r{
  grid-column:3/5;grid-row:8;z-index:4;min-width:0;
  display:flex;flex-direction:column;justify-content:center;gap:6px;
  padding:0 10px;
}
.ind-shot{position:relative;display:block;overflow:hidden;}
.ind-shot img{width:100%;height:74px;object-fit:cover;display:block;filter:blur(1px) brightness(.85);}
.ind-shot b{
  position:absolute;left:8px;bottom:6px;color:#fff;
  font-family:'Poppins',var(--font-display);font-weight:700;
  font-size:14px;line-height:1.05;text-transform:uppercase;
}
.ind-agent{
  font-family:'Poppins',var(--font-display);font-weight:400;
  font-size:11px;color:#3a3a3a;
}
.ind-agent-note{
  font-family:'Poppins',var(--font-display);font-weight:300;
  font-size:7.5px;line-height:1.45;color:rgba(30,55,39,.62);
}

/* ============================================================
   SELECTED WORK — pinned horizontal rail, mobile only
   The section is taller than the screen; the stage inside pins while the
   page scrolls through, and that distance drives the rail sideways. Same
   mechanism as the EDO card rail, so it needs no wheel/touch interception
   and rewinds cleanly on the way back up. Section height is set in main.js
   from the rail's actual travel.
   ============================================================ */
/* ---- and its second half. The section's rules sat in TWO separate
   phone media blocks, which is what defeated the first attempt at this:
   unwrapping one left the other still phone-only, so the cards had a
   grid but the heading strip and the rail beneath them did not. This
   block is 29 rules, all Industries, checked the same way. ---- */
/* row 7 : the heading · rows 8-9 : the rail. Both live on the Industries
   canvas rather than in a section of their own. */
.works-head{
  grid-column:1/-1;grid-row:7;z-index:4;align-self:center;
  padding:0 calc(var(--m-pad) + 10px);
}
.wk-kicker{
  display:block;margin-bottom:8px;
  font-family:var(--font-mono);font-size:8px;letter-spacing:.28em;
  text-transform:uppercase;color:rgba(30,55,39,.5);font-weight:600;}
.works-head h2{
  margin:0;font-family:'Poppins',var(--font-display);font-weight:300;
  font-size:1.5rem;line-height:1.12;color:#2c2c2c;
}
.works-head h2 i{font-style:normal;color:rgba(30,55,39,.42);}

/* rows 7-9. The hairline is a pseudo-element rather than a border because
   the reference puts it .53 of a row into row 7, with the cards starting
   a further .45 of a row below — a border would have to sit flush at the
   rail's top edge. */
/* No gap and no leading pad: each card is exactly half the rail, so the
   first two split the screen evenly and every card boundary lands on a
   canvas column line. The breathing room that the gap used to provide is
   now padding inside each card, which keeps the boundaries exact. */
.works-rail{
  grid-column:1/-1;grid-row:5/8;z-index:4;min-width:0;position:relative;
  display:flex;align-items:stretch;gap:0;
  padding:var(--m-rh) 0 18px 0;
  overflow:hidden;   /* driven by page scroll, not by its own scrollbar */
}
/* The rule lives on the panel, not the rail. An absolutely-positioned child
   of a scroll container travels with its content, so a rule drawn on the
   rail slid away the moment the cards moved and left the right-hand side
   bare. On the panel it is outside the scroll and spans the full width for
   the whole run — 4 rows down to the rail's top, then .53 of a row in. */
.ind-panel::after{
  content:"";position:absolute;left:0;right:0;z-index:5;
  top:calc(var(--m-rh)*4.53);height:1px;
  background:rgba(30,55,39,.18);pointer-events:none;
}
.wk-pad{flex:0 0 calc(var(--m-pad) + 10px);}
/* two canvas columns wide; height comes from the two rows it sits in */
/* divider between cards : a hairline down the middle of the gap with a
   plus at its head. Drawn on the card that follows, so the first one has
   none and the trailing spacer never picks one up. */
.wk-card + .wk-card::before,
.wk-card + .wk-card::after{
  content:"";position:absolute;left:0;pointer-events:none;
}
/* runs up past the card's own top to meet the rail's hairline — the cards
   sit a row below the rail's top edge and the rule .53 of a row in, so the
   overshoot is the difference */
.wk-card + .wk-card::before{
  top:calc(var(--m-rh)*-.47);bottom:0;width:1px;background:rgba(30,55,39,.18);
}
/* centred on the rule rather than hung a fixed distance above it, so it
   stays on the line at any row height. The extra pixel offsets the glyph,
   which sits slightly high in its em box. */
.wk-card + .wk-card::after{
  content:"+";top:calc(var(--m-rh)*-.47);
  transform:translate(-50%,calc(-50% + 1px));
  font-family:'Poppins',var(--font-display);font-weight:300;
  font-size:15px;line-height:1;color:rgba(30,55,39,.4);
}
.wk-card{
  position:relative;
  flex:0 0 calc((100vw - var(--m-pad)*2)/2);
  padding:0 13px;
  min-width:0;display:flex;flex-direction:column;gap:8px;
}
/* the card box itself stays put so its divider and node do — it is the
   contents main.js eases in from the bottom-right */
.wk-card > *{will-change:transform,opacity;}
/* card 1 : centred statement, no image */
.wk-plain{justify-content:center;}
.wk-plain .wk-meta{align-items:center;text-align:center;gap:18px;padding:0 8px;}
.wk-plain .wk-link{align-self:center;}
.wk-statement{
  margin:0;font-family:'Poppins',var(--font-display);font-weight:300;
  font-size:15px;line-height:1.32;color:#3a3a3a;
}
.wk-shot{
  position:relative;overflow:hidden;border-radius:6px;
  flex:1 1 auto;min-height:0;background:#dcdad6;
}
.wk-shot img{width:100%;height:100%;object-fit:cover;display:block;}
.wk-shot.dark img{filter:brightness(.55) saturate(.9);}
/* the inner hairline frame is gone — it read as a border drawn on top of
   the artwork rather than as part of it, and these pictures carry their own
   composition. The rule is removed rather than transparent so nothing is
   painted over the image at all. */
.wk-tag{
  position:absolute;left:20px;top:20px;z-index:2;
  font-family:'Poppins',var(--font-display);font-weight:400;
  font-size:8.5px;line-height:1.45;letter-spacing:.12em;
  text-transform:uppercase;color:#fff;
  text-shadow:0 1px 6px rgba(0,0,0,.45);
}
.wk-big{
  position:absolute;left:16px;bottom:14px;right:16px;z-index:2;
  font-family:'Poppins',var(--font-display);font-weight:700;
  font-size:1.15rem;line-height:1.05;color:#fff;text-transform:uppercase;
  text-shadow:0 2px 14px rgba(0,0,0,.4);
}
.wk-big.serif{
  font-family:Georgia,'Times New Roman',serif;font-weight:400;
  text-transform:none;
}
.wk-meta{display:flex;flex-direction:column;gap:4px;padding-right:6px;}
.wk-meta h3{
  margin:0;font-family:'Poppins',var(--font-display);font-weight:400;
  font-size:.68rem;color:#2c2c2c;
}
.wk-meta p{
  margin:0;font-family:'Poppins',var(--font-display);font-weight:300;
  font-size:8.5px;line-height:1.45;color:rgba(30,55,39,.62);
}
.wk-link{
  margin-top:4px;align-self:flex-start;
  display:inline-flex;align-items:center;gap:10px;
  padding-bottom:5px;border-bottom:1px solid rgba(30,55,39,.3);
  font-family:var(--font-mono);font-size:8px;letter-spacing:.22em;
  text-transform:uppercase;color:#2c2c2c;font-weight:600;}
.wk-link i{font-style:normal;transition:transform .4s;}
.wk-link:hover i{transform:translateX(5px);}

/* ============================================================
   MOBILE : page ends after the "Decided on AI" section
   Everything below it is hidden rather than deleted — the markup is intact,
   so the desktop layout is unaffected and the sections are available as
   reference while they're rebuilt. Delete this block to bring them back.
   ============================================================ */
/* ============================================================
   INTRO — the plus-to-square sequence, 7 rows x 4 columns, mobile only
   One box carries the entire animation. Its corners start at the screen's
   corners, collapse to the centre so the four nodes stack into a single
   plus, open back out to the square, and finally grow past the screen edge
   dissolving everything and flooding #b6aa9c. Because the nodes are pinned to that
   box's corners and never scaled, they travel without ever changing size —
   which a transform:scale() on the box could not have given.
   ============================================================ */
#intro{display:none;}
/* ------------------------------------------------------------
   Keyframes hoisted to document level.
   They were declared inside the phone media query, which meant the
   desktop build could name them but never find them — an animation
   pointing at an undefined keyframe simply does not run. Definitions are
   inert on their own; what decides who animates is the rules that
   reference them, and those are still gated per width. Moving them costs
   the phone nothing and lets both builds share one source.
   ------------------------------------------------------------ */
@keyframes inSeq{
    /* the screen's corners, held just inside so the nodes are not clipped */
    0%       {left:20px;right:20px;top:20px;bottom:20px;
              animation-timing-function:cubic-bezier(.55,0,.25,1);}
    17%,21%  {left:50%;right:50%;top:50%;bottom:50%;
              animation-timing-function:cubic-bezier(.16,1,.3,1);}
    37%,63%  {left:var(--iq);right:var(--iq);top:var(--iq);bottom:var(--iq);
              animation-timing-function:cubic-bezier(.7,0,.3,1);}
    81%,100% {left:0;right:0;top:0;bottom:0;}
  }
@keyframes inNode{ 0%{opacity:0} 6%,66%{opacity:1} 81%,100%{opacity:0} }
@keyframes inDrawX{
    0%,21%   {transform:scaleX(0);opacity:0;}
    24%      {opacity:1;}
    37%,66%  {transform:scaleX(1);opacity:1;}
    81%,100% {transform:scaleX(1);opacity:0;}
  }
@keyframes inDrawY{
    0%,21%   {transform:scaleY(0);opacity:0;}
    24%      {opacity:1;}
    37%,66%  {transform:scaleY(1);opacity:1;}
    81%,100% {transform:scaleY(1);opacity:0;}
  }
@keyframes inTrace{ 0%,35%{opacity:0} 40%,66%{opacity:1} 81%,100%{opacity:0} }
@keyframes inChase{ to{stroke-dashoffset:-100;} }
@keyframes inStroke{
    0%,37%   {opacity:0;transform:translateY(-50%) scale(1);}
    46%,63%  {opacity:1;transform:translateY(-50%) scale(1);}
    81%,100% {opacity:0;transform:translateY(-50%) scale(1.75);}
  }
@keyframes inFillText{
    0%,46%   {clip-path:inset(0 100% 0 0);}
    58%,100% {clip-path:inset(0 0 0 0);}
  }
/* THE FLOOD LEAVES BY CHANGING COLOUR, NOT BY BEING TAKEN AWAY.
   It used to ramp opacity 0 → 1 → 0 on a linear clock, and both of those
   are why the end read as a jolt. Linear means the corner at 81% is a
   dead stop and an instant reversal — the fill arrives at full strength
   and turns round in the same frame, with no slowing into it. And fading
   a solid taupe to nothing over white is not the same thing as that taupe
   BECOMING white: opacity takes the colour out in even steps while the eye
   reads the last of it disappearing all at once.

   So the exit now does both at the same time. Through the closing beat the
   fill blends toward the ground colour as well as thinning, which means the
   final moments are a near-white at low opacity rather than a solid panel
   that vanishes. The two curves also give it somewhere to slow down: eased
   into the peak, eased out of it, so there is no corner left to see.

   #ffffff is the section's own background at both widths — the fill is
   dissolving INTO the ground, which is what makes the hand-off invisible
   rather than merely quick. */
@keyframes inFlood{
  0%,63%   {opacity:0;background-color:#b6aa9c;
            animation-timing-function:cubic-bezier(.42,0,.30,1);}
  81%      {opacity:1;background-color:#b6aa9c;
            animation-timing-function:cubic-bezier(.28,0,.32,1);}
  92%,100% {opacity:0;background-color:#ffffff;}
}
@keyframes inRise{
    0%,40%   {opacity:0;transform:translateY(10px) scale(1);}
    50%,63%  {opacity:1;transform:translateY(0) scale(1);}
    81%,100% {opacity:0;transform:translateY(0) scale(1.75);}
  }
@keyframes inRise2{
    0%,44%   {opacity:0;transform:translateY(10px) scale(1);}
    54%,63%  {opacity:1;transform:translateY(0) scale(1);}
    81%,100% {opacity:0;transform:translateY(0) scale(1.75);}
  }
@keyframes wdType{
    from{opacity:1;clip-path:inset(0 100% 0 0);}
    to  {opacity:1;clip-path:inset(0 0 0 0);}
  }
@keyframes wdFade{
    from{opacity:0;transform:translateY(9px);}
    to  {opacity:1;transform:translateY(0);}
  }
@keyframes wdRule{ from{transform:scaleX(0);} to{transform:scaleX(1);} }
@keyframes wdNode{ from{opacity:0;} to{opacity:1;} }
@keyframes wdLineL2R{
    0%        {opacity:1;transform:scaleX(1);transform-origin:center;}
    9%        {opacity:0;transform:scaleX(1);transform-origin:center;}
    10%       {opacity:0;transform:scaleX(0);transform-origin:left;}
    11%       {opacity:1;transform:scaleX(0);transform-origin:left;
               animation-timing-function:cubic-bezier(.45,0,.25,1);}
    52%,100%  {opacity:1;transform:scaleX(1);transform-origin:left;}
  }
@keyframes wdLineR2L{
    0%        {opacity:1;transform:scaleX(1);transform-origin:center;}
    9%        {opacity:0;transform:scaleX(1);transform-origin:center;}
    10%       {opacity:0;transform:scaleX(0);transform-origin:right;}
    11%       {opacity:1;transform:scaleX(0);transform-origin:right;
               animation-timing-function:cubic-bezier(.45,0,.25,1);}
    52%,100%  {opacity:1;transform:scaleX(1);transform-origin:right;}
  }
@keyframes wdTripL2R{
    0%,6%     {opacity:1;transform:translate(-50%,-50%) translateX(0);
               animation-timing-function:cubic-bezier(.45,0,.25,1);}
    52%       {opacity:1;
               transform:translate(-50%,-50%) translateX(var(--wd-travel));}
    55%,100%  {opacity:0;
               transform:translate(-50%,-50%) translateX(var(--wd-travel));}
  }
@keyframes wdTripR2L{
    0%,6%     {opacity:1;
               transform:translate(-50%,-50%) translateX(var(--wd-travel));
               animation-timing-function:cubic-bezier(.45,0,.25,1);}
    52%       {opacity:1;transform:translate(-50%,-50%) translateX(0);}
    55%,100%  {opacity:0;transform:translate(-50%,-50%) translateX(0);}
  }
@keyframes wdSpinCW{  0%,6%{transform:rotate(0deg);}  52%,100%{transform:rotate(1080deg);}  }
@keyframes wdSpinCCW{ 0%,6%{transform:rotate(0deg);}  52%,100%{transform:rotate(-1080deg);} }
@keyframes wdGround{ from{opacity:0;} to{opacity:1;} }
@keyframes wdTypeIn{
    from{opacity:1;clip-path:inset(0 100% 0 0);transform:translateX(-38px);}
    to  {opacity:1;clip-path:inset(0 0 0 0);transform:translateX(0);}
  }
@keyframes wdSlideIn{
    from{opacity:0;transform:translateX(-38px);}
    to  {opacity:1;transform:translateX(0);}
  }

@media (max-width:980px){
  #intro{
    --m-div:4;--m-rh:calc(var(--vh)/7);
    /* the square's side, and from it everything sized against it */
    --ib:59vw;
    /* the inset that makes the box exactly --ib square: horizontally 50% of
       the width, vertically 50% of the height, less half a side either way */
    --iq:calc(50% - var(--ib)/2);
    /* one clock for the whole sequence — every percentage below is a moment
       on it, so the beats stay in step if the duration is retimed */
    --D:5s;
    --ln:rgba(30,55,39,.46);   /* the square's own line */
    --lnd:rgba(30,55,39,.88);  /* the segment chasing round it */
    --po:9px;                  /* nodes stand this far off the corners */
    display:grid;position:relative;z-index:1;
    height:var(--vh);
    /* rows declared explicitly: "1/-1" counts against the explicit grid, and
       with only grid-auto-rows that grid has a single line, so -1 resolves
       back to line 1 and a full-height span collapses into row 1 */
    grid-template-columns:repeat(4,minmax(0,1fr));
    grid-template-rows:repeat(7,var(--m-rh));
    padding:0 var(--m-pad);gap:0;background:#ffffff;overflow:hidden;
  }
  #intro .hero-frame{
    display:block;position:absolute;inset:0 var(--m-pad);
    z-index:5;pointer-events:none;opacity:0;
    border:1px solid rgba(30,55,39,.10);
    background-origin:border-box;
    background-image:
      linear-gradient(to right,rgba(30,55,39,.10) 1px,transparent 1px),
      linear-gradient(to bottom,rgba(30,55,39,.10) 1px,transparent 1px);
    background-size:calc(100%/var(--m-div)) 100%,100% var(--m-rh);
  }
  #intro .hero-frame > *{display:none;}

  /* above the copy, so the fill sweeping out at the end covers it */
  .in-stage{position:absolute;inset:0;z-index:6;pointer-events:none;}
  .in-frame{
    position:absolute;
    /* resting state before the sequence is armed: the square */
    left:var(--iq);right:var(--iq);top:var(--iq);bottom:var(--iq);
  }
  /* the ground that floods the screen on the last beat */
  .in-frame::before{
    content:"";position:absolute;inset:0;background:#b6aa9c;opacity:0;z-index:0;
  }

  .in-plus{position:absolute;width:22px;height:22px;opacity:0;z-index:2;}
  .in-plus::before,.in-plus::after{content:"";position:absolute;background:var(--ln);}
  .in-plus::before{left:0;right:0;top:50%;height:1px;margin-top:-.5px;}
  .in-plus::after{top:0;bottom:0;left:50%;width:1px;margin-left:-.5px;}
  /* held clear of the corners by --po, as in the reference — each pushed out
     along its own diagonal so the gap is even on both sides */
  .in-plus.tl{left:0;top:0;transform:translate(calc(-50% - var(--po)),calc(-50% - var(--po)));}
  .in-plus.tr{right:0;top:0;transform:translate(calc(50% + var(--po)),calc(-50% - var(--po)));}
  .in-plus.bl{left:0;bottom:0;transform:translate(calc(-50% - var(--po)),calc(50% + var(--po)));}
  .in-plus.br{right:0;bottom:0;transform:translate(calc(50% + var(--po)),calc(50% + var(--po)));}

  /* The chase. Stroked on an SVG rather than the four divs because only a
     path can carry a dash that travels round a corner. pathLength="100"
     restates the perimeter as 100 units whatever the box's real size, so the
     dash keeps its proportions as the box grows. */
  .in-trace{
    position:absolute;inset:0;width:100%;height:100%;
    z-index:2;opacity:0;overflow:visible;
  }
  .in-trace rect{
    fill:none;stroke:var(--lnd);stroke-width:1;
    vector-effect:non-scaling-stroke;
    stroke-dasharray:24 76;
  }

  .in-edge{position:absolute;background:var(--ln);opacity:0;z-index:1;}
  .in-edge.top{left:0;right:0;top:0;height:1px;transform:scaleX(0);}
  .in-edge.bottom{left:0;right:0;bottom:0;height:1px;transform:scaleX(0);}
  .in-edge.left{top:0;bottom:0;left:0;width:1px;transform:scaleY(0);}
  .in-edge.right{top:0;bottom:0;right:0;width:1px;transform:scaleY(0);}

  /* Outline first, then filled. The element itself carries only the stroke;
     its ::after is the same word solid, wiped in left to right over the top.
     .215 of the square is the size at which six letters span about 80% of it. */
  .in-mark{
    position:absolute;left:0;right:0;top:50%;transform:translateY(-50%);
    text-align:center;z-index:3;opacity:0;
    font-family:'Poppins',var(--font-display);font-weight:700;
    font-size:calc(var(--ib)*.215);line-height:1;letter-spacing:.005em;
    color:transparent;-webkit-text-stroke:1px var(--ln);
  }
  .in-mark::after{
    content:attr(data-w);
    position:absolute;left:0;right:0;top:0;
    color:var(--ln);-webkit-text-stroke:0;
    clip-path:inset(0 100% 0 0);
  }

  .in-lead{
    grid-column:1/-1;grid-row:5;z-index:4;margin:0;
    align-self:end;padding:0 12px calc(var(--m-rh)*.06);
    text-align:center;opacity:0;
    font-family:'Poppins',var(--font-display);font-weight:400;
    font-size:13px;line-height:1.3;letter-spacing:.02em;
    text-transform:uppercase;color:#3a3a3a;
  }
  .in-sub{
    grid-column:1/-1;grid-row:6;z-index:4;margin:0;
    align-self:center;padding:0 12px;
    text-align:center;opacity:0;
    font-family:'Poppins',var(--font-display);font-weight:300;
    font-size:10.5px;line-height:1.55;letter-spacing:.02em;
    text-transform:uppercase;color:#a5a5a5;
  }

  /* ---- the WHAT WE DO panel : revealed as the flood clears ----
     Laid out as a flow rather than on the seven-row grid the rest of the
     canvas uses. Grid rows are a fixed share of the screen, so on a shorter
     viewport — an iPhone with the browser chrome showing, say — two lines of
     Bebas no longer fit the row holding them and the copy ran straight
     through the heading beneath it. Stacked in flow, items can only push
     each other down; overlapping is not a state this can reach. */
  .wd{
    position:absolute;inset:0;z-index:3;
    display:flex;flex-direction:column;
    padding:0 var(--m-pad);overflow:hidden;
  }
  .wd > *:not(.wd-steps){opacity:0;flex:0 0 auto;}
  /* THE ROW IS GATED TOO.
     Every other child of .wd starts at zero and is brought in by the load
     sequence — .wd-steps was the exception, so any moment the play class
     was off it, the step panels were the ONLY thing on screen. Coming back
     to the section that is exactly what happened: the column was already
     there, and the rule then drew itself in underneath it. Gating the row
     the same way makes the order structural rather than a race. */
  .wd-steps{opacity:0;}
  #intro.play .wd-steps{animation:wdFade .8s ease-out 5.62s both;}

  .wd-a,.wd-b{
    margin:0;
    font-family:'Bebas Neue',var(--font-display);font-weight:400;
    font-size:15vw;line-height:.90;letter-spacing:.015em;
    text-transform:uppercase;white-space:nowrap;
  }
  .wd-a{margin:calc(var(--m-rh)*.16) 0 0 8%;color:#8a9a8c;}
  .wd-b{margin:.04em 0 0 28%;color:#c5c0bc;-webkit-text-stroke:0;}

  .wd-intro{
    margin:calc(var(--m-rh)*.44) 0 0;padding:0 9%;
    font-family:'Poppins',var(--font-display);font-weight:400;
    font-size:10.5px;line-height:1.55;letter-spacing:-.015em;color:#4a4a4a;
  }
  .wd-intro p+p{margin-top:.95em;}
  .wd-intro b{font-weight:700;color:#2f2f2f;}

  /* Grows from its own centre outward. The 10px is measured from the screen
     edge, so the canvas padding is taken back off it. */
  .wd-rule{
    margin:calc(var(--m-rh)*.46) 0 0;position:relative;height:1px;z-index:2;
    /* The line runs from (10px - pad) to (10px - pad), so its width — and
       therefore the traveller's journey — is NOT 100vw - 20px. That was the
       old figure and it left the node stopping short of the post it was
       supposed to land on, so the two never quite met. */
    --wd-travel:calc(100vw - 20px + var(--m-pad) * 2);
  }
  .wd-line{
    position:absolute;top:0;height:1px;
    left:calc(10px - var(--m-pad));right:calc(10px - var(--m-pad));
    background:rgba(30,55,39,.34);
    transform:scaleX(0);transform-origin:center;
  }
  .wd-p{position:absolute;top:0;width:15px;height:15px;opacity:0;}
  .wd-p::before,.wd-p::after{content:"";position:absolute;background:rgba(30,55,39,.5);}
  .wd-p::before{left:0;right:0;top:50%;height:1px;margin-top:-.5px;}
  .wd-p::after{top:0;bottom:0;left:50%;width:1px;margin-left:-.5px;}
  /* THE POSTS ARE VISIBLE BY DEFAULT, not by animation fill.
     They used to get their opacity solely from "#intro.play .wd-p", which
     meant the moment the play class came off — and it comes off the instant
     the section drops below 55% on screen, i.e. exactly as the next section
     arrives — both ends blinked out. Their resting state now lives in the
     base rule, so losing the class can no longer blank them; the load
     sequence still hides them until 5.2s through its backwards fill. */
  .wd-p.l{left:calc(10px - var(--m-pad));transform:translate(-50%,-50%);opacity:1;}
  .wd-p.r{right:calc(10px - var(--m-pad));transform:translate(50%,-50%);opacity:1;}
  /* the traveller sits on the left post and is invisible until it sets off */
  .wd-p.t{left:calc(10px - var(--m-pad));transform:translate(-50%,-50%);opacity:0;}

  /* takes whatever height is left, which is what keeps the step content off
     the rule above it however tall the copy runs */
  .wd-steps{
    flex:1 1 auto;min-height:0;position:relative;
    margin:0 calc(var(--m-pad)*-1);
  }
  .wd-step{
    position:absolute;inset:0;
    display:flex;flex-direction:column;
    padding:0 var(--m-pad);
    opacity:0;visibility:hidden;
    transition:opacity .38s ease-out,visibility 0s linear .38s;
  }
  .wd-step.on{opacity:1;visibility:visible;transition:opacity .38s ease-out;}
  .wd-step > *{position:relative;z-index:1;flex:0 0 auto;}
  .wd-dark::before{
    content:"";position:absolute;z-index:0;opacity:0;inset:0;
    left:calc(var(--m-pad)*-1);right:calc(var(--m-pad)*-1);
    background:var(--ground,#c3bbb0);
  }
  .wd-s2{--ground:#b5ada0;}
  .wd-s3{--ground:#a49c90;}
  .wd-s4{--ground:#948d81;}
  .wd-s5{--ground:#847f77;}
  .wd-dark .wd-kicker,
  .wd-dark .wd-head,
  .wd-dark .wd-body,
  .wd-dark .wd-foot{color:#ffffff;}

  .wd-kicker{
    margin:calc(var(--m-rh)*.30) 0 0;padding:0 0 0 9.5%;
    font-family:'Poppins',var(--font-display);font-weight:600;
    font-size:10px;line-height:1.2;letter-spacing:.09em;
    text-transform:uppercase;color:#3a3a3a;
  }
  .wd-head .wd-br{display:none;}
  .wd-head{
    margin:calc(var(--m-rh)*.24) 0 0;padding:0 6% 0 9%;
    font-family:'Poppins',var(--font-display);font-weight:400;
    font-size:14px;line-height:1.35;letter-spacing:0;
    text-transform:uppercase;color:#6a6a6a;
  }
  .wd-body{
    margin:calc(var(--m-rh)*.26) 0 0;padding:0 6% 0 9%;
    font-family:'Poppins',var(--font-display);font-weight:400;
    font-size:10px;line-height:1.55;letter-spacing:-.01em;color:#565656;
  }
  .wd-body p+p{margin-top:.85em;}

  /* the image absorbs whatever slack is left, so a longer step shrinks the
     picture instead of pushing the footer off the bottom */
  .wd-shot{
    flex:1 1 auto;min-height:0;
    margin:calc(var(--m-rh)*.22) 9% 0;position:relative;overflow:hidden;
  }
  .wd-shot img,
  .wd-shot video{width:100%;height:100%;object-fit:cover;display:block;}
  .wd-foot{
    margin:calc(var(--m-rh)*.14) 0 calc(var(--m-rh)*.16);padding:0 0 0 9.5%;
    font-family:'Poppins',var(--font-display);font-weight:600;
    font-size:18px;line-height:1.2;letter-spacing:0;
    text-transform:uppercase;color:#5a5a5a;
  }

  /* ---- the sequence, armed by .play from main.js ----
       0 -  17%  (0.00-0.85s) four nodes travel in from the screen corners
      17 -  21%  (0.85-1.05s) stacked at the centre, reading as one plus
      21 -  37%  (1.05-1.85s) they open back out, the edges drawing with them
      37 -  46%  (1.85-2.30s) the wordmark strokes in as outline
      46 -  58%  (2.30-2.90s) it fills, left to right, in the line colour
      58 -  63%  (2.90-3.15s) everything holds
      63 -  81%  (3.15-4.05s) every element enlarges and dissolves to nothing
                              while the ground floods #b6aa9c
      81 -  92%  (4.05-4.60s) the flood clears and the ground is restored
     then       (4.30-5.82s) the panel types itself in over it            */
  #intro.play .in-frame{animation:inSeq var(--D) both;}
  #intro.play .in-frame::before{animation:inFlood var(--D) linear both;}
  #intro.play .in-plus{animation:inNode var(--D) linear both;}
  #intro.play .in-edge.top,
  #intro.play .in-edge.bottom{animation:inDrawX var(--D) cubic-bezier(.22,1,.36,1) both;}
  #intro.play .in-edge.left,
  #intro.play .in-edge.right{animation:inDrawY var(--D) cubic-bezier(.22,1,.36,1) both;}
  #intro.play .in-trace{animation:inTrace var(--D) linear both;}
  #intro.play .in-trace rect{animation:inChase 1.55s linear infinite;}
  #intro.play .in-mark{animation:inStroke var(--D) linear both;}
  #intro.play .in-mark::after{animation:inFillText var(--D) linear both;}
  #intro.play .in-lead{animation:inRise var(--D) linear both;}
  #intro.play .in-sub {animation:inRise2 var(--D) linear both;}

  

  

  
  /* up to full as the box sweeps out, then straight back off — the beat
     where the ground is restored and the panel arrives */

  

  /* ---- the panel typing in ----
     Single lines are wiped left to right on a stepped timing function, which
     advances the reveal in discrete jumps rather than smoothly — that is what
     reads as typing, without a character per element. Paragraph blocks fade
     and rise instead: a wipe across several lines uncovers them all at once
     and looks nothing like typing. */
  /* Order matters more than pace here: the two headline lines, then the
     opening copy, then the rule drawing itself out, then everything under
     it. Each reveal is unhurried in its own right — half a second or more —
     but they overlap heavily, so the whole panel lands in about 1.5s
     (4.30s to 5.82s) rather than queueing up nose to tail. */
  #intro.play .wd-a     {animation:wdType .55s steps(18,end) 4.30s both;}
  #intro.play .wd-b     {animation:wdType .55s steps(18,end) 4.46s both;}
  #intro.play .wd-intro {animation:wdFade .55s ease-out   4.62s both;}
  #intro.play .wd-rule  {animation:wdNode .01s linear      4.80s both;}
  #intro.play .wd-line  {animation:wdRule .65s cubic-bezier(.16,1,.3,1) 4.80s both;}
  #intro.play .wd-p     {animation:wdNode .35s ease-out    5.20s both;}

  

  /* ---- the step change, on a 3.2s clock ----
     Specificity note: these are prefixed with #intro deliberately. The
     loading sequence sets the same properties through "#intro.play .wd-line"
     and "#intro.play .wd-p", and an ID beats any number of classes — so the
     unprefixed versions of these rules lost every time and the hand-off
     simply never ran.

     A SEPARATE NODE MAKES THE TRIP. It used to be the left-hand node itself
     that travelled, which meant the end it left stood empty for a second and
     a half and the end it arrived at had nothing to arrive AT — the node got
     there, blinked out, and snapped home. Now the two ends are posts that
     never move, and a third node crosses between them and merges into the
     far post as it lands. Neither end is ever without a plus.
     The spin follows the direction of travel, which is what makes it read as
     rolling rather than merely revolving.

       0.00-0.32s  the current line dissolves
       0.19-1.66s  the traveller crosses, spinning, the new line behind it
       1.66-1.76s  it dissolves into the far post, which is already there
       0.35-1.55s  the ground fades up behind it
       0.55-1.85s  the new copy slides in alongside the line               */
  #intro .wd-rule.swap  .wd-line{animation:wdLineL2R 3.2s linear both;}
  #intro .wd-rule.swap  .wd-p.t {animation:wdTripL2R 3.2s linear both;}
  #intro .wd-rule.swap  .wd-p.t::before,
  #intro .wd-rule.swap  .wd-p.t::after{animation:wdSpinCW 3.2s linear both;}
  #intro .wd-rule.swapb .wd-line{animation:wdLineR2L 3.2s linear both;}
  #intro .wd-rule.swapb .wd-p.t {animation:wdTripR2L 3.2s linear both;}
  #intro .wd-rule.swapb .wd-p.t::before,
  #intro .wd-rule.swapb .wd-p.t::after{animation:wdSpinCCW 3.2s linear both;}
  /* and the posts stay put through all of it, whatever else is running */
  #intro .wd-rule.swap  .wd-p.l,#intro .wd-rule.swap  .wd-p.r,
  #intro .wd-rule.swapb .wd-p.l,#intro .wd-rule.swapb .wd-p.r{
    animation:none;opacity:1;
  }

  /* The line is drawn BEHIND the travelling node, not after it arrives.
     Anchored at the end the node departs from, it shares the node's exact
     window and easing — and because the rule's width equals the node's
     travel, matching scaleX to the eased position keeps the line's leading
     edge under the node for the whole trip rather than merely near it. */

  /* Travel and spin are split across two elements. One element can only hold
     one transform, and the journey wants an eased translate with a dead-even
     rotation — combining them meant the eased timing dragged on the spin too,
     so it visibly slowed as it arrived. The node itself therefore only moves;
     the two bars drawn inside it do the turning, on their own linear clock.
     THERE IS NO RETURN LEG ANY MORE. The traveller lands exactly on the far
     post and dissolves into it over three hundredths of the clock — two
     identical plusses on the same pixel, so the hand-over cannot be seen. It
     then stays put and invisible; dropping the class returns it to the left
     for the next trip, at zero opacity, off the clock entirely. */

  /* Three whole turns across the travel, held flat either side of it. Both
     bars share the element's centre, so turning each by the same angle spins
     the plus about its own middle. Landing on a multiple of 360 means it
     comes to rest as an upright plus, not mid-turn. */

  /* the ground arrives last, and slowly */

  /* The content travels WITH the node, not after it. The node crosses from
     0.19s to 1.66s; the copy now runs from 0.55s to about 1.85s, so it is
     sliding in while the line is still being drawn rather than waiting for
     the whole hand-off to finish before anything appears.
     The ground leads the copy slightly on purpose — white type arriving over
     the outgoing pale panel would be unreadable for the moment before the
     taupe catches up. */
  .wd-step.on.wd-dark::before{animation:wdGround 1.2s ease-out  .35s both;}
  .wd-step.on .wd-kicker{animation:wdTypeIn .55s steps(14,end)  .55s both;}
  .wd-step.on .wd-head  {animation:wdSlideIn .60s cubic-bezier(.16,1,.3,1) .72s both;}
  .wd-step.on .wd-body  {animation:wdSlideIn .60s cubic-bezier(.16,1,.3,1) .90s both;}
  .wd-step.on .wd-shot  {animation:wdSlideIn .60s cubic-bezier(.16,1,.3,1) 1.08s both;}
  .wd-step.on .wd-foot  {animation:wdTypeIn .55s steps(18,end) 1.24s both;}

  /* ==========================================================
     NO GROUNDS ON THE PHONE EITHER

     Each step carried its own taupe panel, deepening step by step. The
     desktop build dropped them a while back; this brings the phone into
     line, so the section is one continuous white from the heading to the
     last step rather than five tinted cards.

     The colour reset is not optional bookkeeping — it is the whole job.
     .wd-dark sets the type to #ffffff BECAUSE it sat on the taupe. Take
     the panel away and leave that rule standing and every one of these
     steps is white type on a white section, i.e. blank. Same values the
     desktop block uses, so both widths read identically.

     The ::before also drove wdGround, the animation that faded the panel
     in ahead of the copy. display:none on the element stops it running,
     so there is nothing left to cancel separately. */
  .wd-dark::before{display:none;}
  .wd-dark .wd-kicker{color:#3a3a3a;}
  .wd-dark .wd-head  {color:#6a6a6a;}
  .wd-dark .wd-body  {color:#565656;}
  .wd-dark .wd-foot  {color:#5a5a5a;}

  /* The clip moved above the copy in the markup, so the two arrival delays
     were running in the old order — the picture appearing after the text
     that now sits below it. Swapped to match what the eye reads. */
  .wd-step.on .wd-shot{animation:wdSlideIn .60s cubic-bezier(.16,1,.3,1) .90s both;}
  .wd-step.on .wd-body{animation:wdSlideIn .60s cubic-bezier(.16,1,.3,1) 1.08s both;}

}

/* ============================================================
   DESIGN IN MOTION — curved WebGL gallery
   A tall section with a pinned stage inside it. The pin is CSS sticky, not
   ScrollTrigger's: ScrollTrigger's pin wraps the target in a pin-spacer
   div, which broke the grid layout the last time this build reached for it.
   Sticky pins with no DOM change, and progress is read from the section's
   own rect, so reverse scrolling is exact rather than nearly right.
   ============================================================ */
#chapter{display:none;}
/* Desktop only, as asked. Off by default so the phone never pays for it —
   a pinned horizontal rail has no honest small-screen equivalent, and the
   images alone would be several megabytes on a phone connection. */
#works{display:none;}
/* The old EDO canvas is off at EVERY width. It was switched off inside the
   phone's media query, which was right while #edo was phone-only and became
   wrong the moment #edo was opened on desktop for Industries — a rule scoped
   to max-width simply stops applying above it, and the canvas reappeared.
   Not deleted from the markup: main.js's #edo driver is gated on .edo-rail
   existing, and that rail lives inside this stage. */
.edo-stage{display:none;}
@media (max-width:980px){
  /* ---- DESIGN IN MOTION ----------------------------------------------
     Long enough that three rounds of the hoop and the arrival all have room
     to read; much shorter and the ribbon crosses the frame too fast. */
  #chapter{
    --m-div:4;--m-rh:calc(var(--vh)/7);
    /* The same three numbers the desktop build places its title from, resolved
       for a narrow screen. Bebas is condensed, so OPTIMISATION at this size
       comes to about 83% of the viewport — wide enough to carry the line with
       room to sit centred. Height would allow more here; WIDTH is what binds
       on a phone, which is the opposite of the desktop case. */
    --tf:min(calc(var(--m-rh)*.62),16.5vw);
    --ef:clamp(10px,2.8vw,14px);
    --tmid:47%;
    display:block;position:relative;z-index:1;
    height:700vh;background:#c3c3c3;
  }
  .ch-stage{position:sticky;top:0;height:var(--vh);overflow:hidden;background:#c3c3c3;}

  /* THE TYPOGRAPHY IS TWO DEPTH PLANES, NOT ONE.
     The ribbon used to pass in front of every word, which flattened it: a
     card can only read as being in the room if some of the room is in front
     of it. EXECUTIVE therefore sits BEHIND the canvas and OPTIMISATION and
     DECISION sit IN FRONT of it, so the band crosses over the first and
     disappears under the other two.
     That mapping is not arbitrary. The hoop is tipped, so its near side runs
     through the upper part of the frame and its far side through the lower —
     the big near cards meet EXECUTIVE and the small far ones meet the words
     below it. Layering by word therefore matches the geometry: the ribbon
     threads between them.
     .ch-overlay must NOT carry a z-index. A positioned element with one
     becomes a stacking context and traps its children's layering inside it,
     so the words could never straddle the canvas. */
  .ch-canvas{position:absolute;inset:0;width:100%;height:100%;z-index:4;display:block;}
  .ch-overlay{position:absolute;inset:0;pointer-events:none;}

  #chapter .hero-frame{
    display:block;position:absolute;inset:0 var(--m-pad);
    /* above the near typography too, so the lattice still reads as glass laid
       over the whole scene rather than something the words punch through */
    z-index:6;pointer-events:none;opacity:1;
    border:1px solid rgba(30,40,32,.10);
    background-origin:border-box;
    background-image:
      linear-gradient(to right,rgba(30,40,32,.10) 1px,transparent 1px),
      linear-gradient(to bottom,rgba(30,40,32,.10) 1px,transparent 1px);
    background-size:calc(100%/var(--m-div)) 100%,100% var(--m-rh);
  }
  #chapter .hero-frame::after{
    content:"";position:absolute;
    left:-3.25px;top:-3.25px;width:calc(100% + 6.5px);height:calc(100% + 6.5px);
    background-image:linear-gradient(to right,rgba(30,40,32,.18) 4.5px,transparent 4.5px);
    background-size:calc((100% - 4.5px)/var(--m-div)) var(--m-rh);
    -webkit-mask-image:linear-gradient(to bottom,#000 4.5px,transparent 4.5px);
            mask-image:linear-gradient(to bottom,#000 4.5px,transparent 4.5px);
    -webkit-mask-size:100% var(--m-rh);mask-size:100% var(--m-rh);
  }
  #chapter .hero-frame > *{display:none;}

  .ch-lede{
    position:absolute;left:50%;transform:translateX(-50%);
    top:calc(11.2% - 15px);
    margin:0;width:min(370px,80%);text-align:center;
    font-family:'Poppins',var(--font-display);font-weight:400;
    font-size:3.5vw;line-height:1.2;color:rgba(30,40,32,.86);
  }
  .ch-title{
    position:absolute;inset:0;margin:0;
    /* Bebas Neue at 400, as on desktop. The tracking goes with the face: the
       -.035em was set for Poppins, whose caps are wide and want pulling in,
       and the same figure closes Bebas into itself. */
    font-family:'Bebas Neue',var(--font-display);font-weight:400;
    font-size:var(--tf);line-height:1;letter-spacing:-.005em;
    text-transform:uppercase;color:rgba(46,50,45,.82);
  }
  /* the two halves travel independently: one in from the left, one from the
     right, then out the same way — so each needs its own transform */
  /* Centred by LAYOUT, not by transform — the driver writes transform on
     these every frame to carry them across the screen, and a centring
     transform would be wiped on the first tick. */
  .ch-w{
    position:absolute;left:0;right:0;display:block;white-space:nowrap;
    text-align:center;will-change:transform,opacity;
  }
  /* The three lines are set as ONE block, closed up. The tops moved together
     rather than individually so the group keeps the optical centre it had at
     47% of the stage — tightening only the gaps would have walked the whole
     lockup upwards. */
  /* behind the ribbon: the near cards ride over it */
  /* placed by the same arithmetic as desktop rather than by hand-picked
     percentages, so the gaps either side of the caption stay equal at any
     phone size — and neither half carries a z-index now, so both pass under
     the ribbon as they do on the wide build */
  .ch-w1{top:calc(var(--tmid) - var(--tf) * 1.10 - var(--ef) * .71);}
  /* in front of the ribbon: the far cards run under it */
  .ch-w2{top:calc(var(--tmid) + var(--tf) * .20 + var(--ef) * .71);}

  .ch-eyebrow{
    /* one word now, not two lines: re-centred in the gap the title leaves.
       In front of the ribbon, with OPTIMISATION — it sits in the same lower
       band of the frame that the far side of the hoop sweeps through. */
    position:absolute;left:0;right:0;text-align:center;
    top:calc(var(--tmid) - var(--ef) * .71);
    font-family:var(--font-mono);
    font-size:var(--ef);line-height:1.42;letter-spacing:.13em;
    text-transform:uppercase;color:rgba(30,40,32,.66);will-change:opacity;font-weight:600;}
  .ch-cta{
    position:absolute;left:50%;transform:translateX(-50%);top:87.2%;
    display:inline-flex;align-items:center;gap:10px;
    padding-bottom:7px;border-bottom:1px solid rgba(30,40,32,.4);
    pointer-events:auto;white-space:nowrap;
    font-family:var(--font-mono);
    font-size:2.3vw;letter-spacing:.16em;text-transform:uppercase;
    color:rgba(30,40,32,.9);font-weight:600;}
  .ch-cta i{font-style:normal;}

  .ch-a11y{
    position:absolute;width:1px;height:1px;overflow:hidden;
    clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;
  }
  /* Sits above the canvas and the lattice so nothing swallows the tap. Each
     button is placed by the engine, in page pixels, on the card it belongs to.
     pointer-events go on the buttons rather than the layer, so the empty
     space between cards still scrolls the page normally. */
  .ch-hits{position:absolute;inset:0;z-index:7;pointer-events:none;}
  .ch-hits button{
    position:absolute;margin:0;padding:0;border:0;background:transparent;
    cursor:pointer;pointer-events:auto;opacity:0;
    -webkit-tap-highlight-color:transparent;
    transition:opacity .25s ease;
  }
  .ch-hits.live button{opacity:1;}
  .ch-hits button:focus-visible{
    outline:2px solid rgba(30,40,32,.85);outline-offset:3px;
  }
  /* the grid is not interactive until it has actually formed */
  .ch-hits:not(.live) button{pointer-events:none;}

  .ch-loader{
    position:absolute;inset:0;z-index:8;background:#c3c3c3;
    display:flex;align-items:center;justify-content:center;
    font-family:var(--font-mono);
    font-size:10px;letter-spacing:.24em;text-transform:uppercase;
    color:rgba(30,40,32,.55);transition:opacity .6s ease;font-weight:600;}
  .ch-loader.gone{opacity:0;pointer-events:none;}
}

@media (prefers-reduced-motion:reduce){
  #chapter{height:auto!important;}
  .ch-stage{position:relative!important;height:auto!important;padding:14vh 0;}
  .ch-canvas,.ch-loader{display:none!important;}
  .ch-overlay{position:relative;inset:auto;padding:0 7%;}
  .ch-lede,.ch-title,.ch-eyebrow,.ch-cta,.ch-w{
    position:relative!important;left:auto!important;right:auto!important;
    top:auto!important;transform:none!important;opacity:1!important;
    text-align:left;width:auto;
  }
  .ch-title{font-size:13vw;margin:6vh 0;}
}

/* ============================================================
   CREW — "Who We Are / The Team", 10 rows × 4 columns, mobile only
   row 2      : "Who We Are"        row 3 : "The Team" (outlined)
   row 4      : the pull quote
   rows 5-6   : four stat tiles, one per column, captions beneath
   rows 7-10  : the member split — photo + role left, bio and role
                list right, divided on the row-6/7 line and the
                column-2 line with a node where they cross
   ============================================================ */
#crew{display:none;}
/* ============================================================
   WHO WE ARE — no longer phone-only.
   45 rules, every one of them #crew or .cw-, checked individually
   before the wrapper came off. Same unwrap as Industries: the block
   held nothing else, so removing the media query changes the scope of
   this section and of no other.
   ============================================================ */
/* The section is a screen plus the swap distance; the canvas inside pins to
   the top and rides it out, so the headline, quote and stat marquee hold
   still while only the member window below them moves. */
#crew{
  --m-div:4;--m-rh:calc(100vh/10);--m-rh:calc(var(--vh)/10);
  --cw-item:calc((100vw - var(--m-pad)*2)/4);
  display:block;position:relative;z-index:1;
  /* one screen to read, plus 1.4 screens of scroll per arrival. --cw-swaps
     is written by main.js from the number of .cw-member panels actually in
     the markup, so adding or removing a member needs no change here; the 2
     is only the fallback for the moment before the script runs. */
  height:calc(100vh + 140vh);height:calc(var(--vh) * (1 + 1.4 * var(--cw-swaps,2)));
  background:#f1f1f0;
}
.cw-stage{
  display:grid;position:sticky;top:0;
  height:100vh;height:var(--vh);
  grid-template-columns:repeat(4,minmax(0,1fr));grid-auto-rows:var(--m-rh);
  padding:0 var(--m-pad);gap:0;background:#f1f1f0;overflow:hidden;
}
/* lattice kept in the DOM at zero so it can be dialled back up later —
   the reference shows only the two rules drawn explicitly below */
#crew .hero-frame{
  display:block;position:absolute;inset:0 var(--m-pad);
  z-index:5;pointer-events:none;opacity:0;
  border:1px solid rgba(30,55,39,.10);
  background-origin:border-box;
  background-image:
    linear-gradient(to right,rgba(30,55,39,.10) 1px,transparent 1px),
    linear-gradient(to bottom,rgba(30,55,39,.10) 1px,transparent 1px);
  background-size:calc(100%/var(--m-div)) 100%,100% var(--m-rh);
}
#crew .hero-frame > *{display:none;}

/* --- rows 2-3 : the two-tone headline, solid over outlined --- */
.cw-a,.cw-b{
  z-index:4;margin:0;
  font-family:'Bebas Neue',var(--font-display);font-weight:700;
  font-size:calc(var(--m-rh)*.65);line-height:.86;letter-spacing:.01em;
  text-transform:uppercase;white-space:nowrap;
}
.cw-a{
  grid-column:1/4;grid-row:2;color:#a3a3a3;
  align-self:end;margin-bottom:calc(var(--m-rh)*.03 - .16em);
}
.cw-b{
  grid-column:2/5;grid-row:3;text-align:right;color:#ffffff;
  align-self:start;margin-top:calc(var(--m-rh)*.31 - .07em);
}

/* --- row 4 : the pull quote --- */
.cw-quote{
  grid-column:1/-1;grid-row:4;z-index:4;margin:0;
  align-self:start;padding:calc(var(--m-rh)*.29) 10px 0;
  text-align:center;
  font-family:'Poppins',var(--font-display);font-weight:300;
  font-size:10px;line-height:1.55;color:#4d4d4d;
}

/* --- rows 5-6 : four stat tiles, one centred per column --- */
.cw-stats{
  grid-column:1/-1;grid-row:5/7;z-index:4;min-width:0;
  /* the marquee bleeds past the canvas padding so tiles enter and leave at
     the screen edge rather than popping in a column short of it */
  margin:0 calc(var(--m-pad)*-1);overflow:hidden;
  padding-top:calc(var(--m-rh)*.26);
}
.cw-stat-track{
  display:flex;width:max-content;align-items:flex-start;
  /* THE LOOP TRAVELS AN EXACT NUMBER OF TILES, NOT A PERCENTAGE.
     Ten tiles, five of them duplicates, so a translation of -50% ought to
     land the copy precisely where the original was. It does not, quite: the
     track is max-content over tiles sized in vw, so its width is fractional
     and half of it can fall on a part-pixel — the copy lands a hair off and
     the seam shows as a tick, once per lap. Five tile-widths is the same
     distance expressed in a unit that cannot round against the total.
     translate3d, so the browser composites it rather than repainting. */
  animation:cwScroll 40s linear infinite;
  will-change:transform;
}
.cw-stats:hover .cw-stat-track{animation-play-state:paused;}
/* one tile per column, so the loop reads at the same rhythm as the grid */
.cw-stat{
  flex:0 0 auto;width:var(--cw-item);
  display:flex;flex-direction:column;align-items:center;min-width:0;
}
.cw-box{
  width:44%;aspect-ratio:1;border-radius:12px;background:#fff;
  box-shadow:0 2px 10px rgba(0,0,0,.05);
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:3px;padding:0 5px;
}
/* sized down so the longest label — "Industries" — clears the tile's inner
   padding instead of running past its edge */
.cw-box b{
  font-family:'Poppins',var(--font-display);font-weight:600;
  font-size:15px;line-height:1;color:#2f2f2f;white-space:nowrap;
}
.cw-box b sup{font-size:.5em;vertical-align:super;}
.cw-box em{
  font-style:normal;font-family:'Poppins',var(--font-display);font-weight:400;
  font-size:6.5px;line-height:1;color:#2f2f2f;white-space:nowrap;
}
.cw-cap{
  padding-top:calc(var(--m-rh)*.10);text-align:center;
  font-family:'Poppins',var(--font-display);font-weight:300;
  font-size:9px;line-height:1.35;color:#3d3d3d;
}

/* --- rows 7-10 : the member window ---------------------------------
   The frame furniture — the rule on the row-6/7 line, the column-2 line
   and the node where they cross — is drawn here and never moves. The two
   member panels ride a track inside, clipped at the rule so the outgoing
   one reads as sliding up under it. */
/* --cw-row is the row the panel starts on, and it is a variable because TWO
   things have to agree on it: this panel, and the node that marks where its
   top rule crosses the column line. They were separate numbers before, the
   phone moved the panel up a row without moving the node, and the node was
   left floating below the crossing. One source now. */
.cw-body{
  grid-column:1/-1;grid-row:var(--cw-row,7)/11;z-index:4;min-width:0;
  margin-top:var(--cw-off,0px);
  position:relative;overflow:hidden;
}
/* the rule runs the full bleed, past the canvas padding, as in the reference */
.cw-body::before{
  content:"";position:absolute;top:0;height:1px;z-index:3;
  left:calc(var(--m-pad)*-1);right:calc(var(--m-pad)*-1);
  background:rgba(0,0,0,.13);
}
/* and the column-2 line drops from it to the foot of the section */
.cw-body::after{
  content:"";position:absolute;top:0;bottom:0;left:50%;width:1px;z-index:3;
  background:rgba(0,0,0,.13);
}
/* Sits on the stage, not inside .cw-body — the window has to clip its track,
   and a node centred on the window's top edge would lose its upper half to
   that clip.

   It used to hold its place with a pixel offset — six rows down — which is a
   restatement of where the panel starts rather than a reference to it, and it
   stopped being true the moment the phone moved the panel to row 6. It is a
   GRID ITEM now, put on the same starting row as the panel and pinned to that
   row's leading edge: align-self:start lands it exactly on the top rule,
   justify-self:center on the column line. Both come from the grid, so no
   arithmetic here can drift out of step with the panel again. */
.cw-plus{
  position:relative;z-index:6;
  grid-column:1/-1;grid-row:var(--cw-row,7);
  align-self:start;justify-self:center;
  /* the same nudge the panel takes, or the node would sit on the bare row
     edge while the rule it marks had already moved down by it */
  margin-top:var(--cw-off,0px);
  transform:translateY(calc(-50% + 1px));
  font:300 15px/1 'Poppins',var(--font-display);color:rgba(0,0,0,.13);
}
/* The two members are stacked in place rather than queued end to end: the
   first one stays exactly where it is and the second slides up over it,
   like a sheet dealt on top. Only the incoming panel moves. */
.cw-track{position:absolute;inset:0;}
.cw-member{
  position:absolute;inset:0;
  min-width:0;min-height:0;display:grid;
  grid-template-columns:minmax(0,1fr) minmax(0,1fr);
  grid-template-rows:minmax(0,1fr);grid-auto-rows:0;
  background:#f1f1f0;
}
/* THE ARRIVAL IS A QUEUE, NOT A SINGLE PANEL.
   This used to live on .cw-flip, which conflated two unrelated things: the
   mirrored layout and the fact that the panel travels. With a third member
   that breaks — the third is not mirrored but still has to arrive. .cw-in
   now means "this panel arrives", and --k is its place in the queue, so the
   markup alone decides how many there are.

   --cw-y counts arrivals: 0 at rest on member 1, 1 when member 2 has landed,
   2 when member 3 has. Each panel is a full window low until its own turn
   comes round, then climbs to rest. The clamp is what keeps it parked: a
   panel whose turn has passed would otherwise carry on into negative space,
   and one whose turn has not come would sit further and further below. */
.cw-in{
  z-index:calc(1 + var(--k,1));
  transform:translate3d(0,clamp(0%,calc((var(--k,1) - var(--cw-y,0)) * 100%),100%),0);
  box-shadow:0 -12px 34px rgba(0,0,0,.07);
  will-change:transform;
}
/* Both halves are placed explicitly, in both members. Left to auto-place,
   the mirrored panel put its copy in row 2 — the cursor walks forward only,
   so once the photo had taken column 2 of row 1 a column-1 item could not
   go back for the empty cell beside it and opened a new row instead. */
.cw-left{grid-column:1;grid-row:1;}
.cw-side{grid-column:2;grid-row:1;}
/* The incoming panel carries its own leading rule and node, so its top edge
   reads as the boundary between it and the panel beneath while it travels —
   and lands exactly on the fixed rule and node once it comes to rest. On
   .cw-in rather than .cw-flip, so every arriving panel brings its own. */
.cw-in::before{
  content:"";position:absolute;top:0;height:1px;z-index:3;
  left:calc(var(--m-pad)*-1);right:calc(var(--m-pad)*-1);
  background:rgba(0,0,0,.13);
}
.cw-in::after{
  content:"+";position:absolute;left:50%;top:0;z-index:3;
  transform:translate(-50%,calc(-50% + 1px));
  font:300 15px/1 'Poppins',var(--font-display);color:rgba(0,0,0,.13);
}
/* member 2 : a true mirror — every inset swapped left for right.
   Both halves are pinned to row 1 explicitly. Swapping only the columns
   left the copy in row 2: auto-placement walks forward, so once the photo
   had taken column 2 of row 1 the cursor could not go back for a column-1
   item and started a new row, dropping the role list off the panel. */
.cw-flip .cw-left{grid-column:2;grid-row:1;}
.cw-flip .cw-side{grid-column:1;grid-row:1;padding-left:8px;padding-right:15%;}
/* THE ROLE HANGS OFF THE PICTURE'S EDGE, NOT THE COLUMN'S.
   The flipped member mirrors the layout, and the role had been mirrored
   with it — pushed to the right margin and set flush right, which put it
   at the far side of its own column with the photo stranded on the left.
   Both now measure from the same inset, so the title starts exactly where
   the picture starts. One variable rather than the number twice: they are
   the same edge, and writing it once is what stops them drifting apart
   the next time one of them is adjusted. */
.cw-flip{--cw-in:15%;}
.cw-flip .cw-photo{margin-left:var(--cw-in);margin-right:14%;}
.cw-flip .cw-role{margin-left:var(--cw-in);margin-right:0;text-align:left;}
.cw-flip .cw-name{left:auto;right:8px;text-align:right;}

.cw-left{position:relative;min-width:0;min-height:0;}
.cw-photo{
  position:relative;width:71%;margin:calc(var(--m-rh)*.72) 0 0 14%;
  aspect-ratio:382/330;border-radius:12px;overflow:hidden;
}
.cw-photo img{width:100%;height:100%;object-fit:cover;display:block;}
@media (min-width:981px){
  .cw-photo img{object-position:center 26%;}
}
.cw-name{
  position:absolute;left:8px;bottom:6px;color:#fff;
  font-family:'Poppins',var(--font-display);font-weight:300;
  font-size:11px;line-height:1.15;letter-spacing:.01em;
}
.cw-name b{
  display:block;font-weight:700;font-size:13px;letter-spacing:.005em;
  text-transform:uppercase;
}
.cw-role{
  /* display:block, and it matters more than it looks. <a> is inline by
     default, and this was only made a block inside the desktop query — so
     below 981px it stayed inline, where margin-left applies to the START OF
     THE FIRST LINE BOX rather than to the element. The title was indented
     14% while the block-level second line broke out to the container edge:
     first letter at 23.6px, "Officer" and the arrow at 2px. That was the
     misalignment, not anything about the second line's own styling. */
  display:block;
  margin:calc(var(--m-rh)*.18) 0 0 14%;
  font-family:'Poppins',var(--font-display);font-weight:400;
  font-size:10.5px;letter-spacing:-.015em;
  line-height:1.25;color:#2f2f2f;text-transform:uppercase;
}
/* THE WHOLE TITLE ON ONE LINE, AND SIZED FOR THE MIRRORED SIDE.
   The measure that matters is the longer of the two names — CHIEF EVOLUTION
   OFFICER — against the narrower of the two positions. 13px wanted 163px and
   wrapped. 11px wanted 138px against 131px, which I first read as a harmless
   6px into the gutter; it is not, because the second member is mirrored and
   its title sits in the RIGHT column, where there is no gutter to spill into
   — only the edge of the screen, which cut the last letter off.
   10.5px with a touch of negative tracking measures inside the column on
   both sides. */
.cw-role .cw-t{display:block;white-space:nowrap;}
.cw-role .cw-m{
  display:block;font-size:9.5px;font-weight:300;letter-spacing:.04em;
  color:#6a6a6a;white-space:nowrap;
}
/* the arrow travels with "Learn more" rather than being pinned to a corner,
   so the two read as one control at any width */
.cw-role .cw-arw{display:inline-block;margin-left:.5em;}

.cw-side{
  position:relative;min-width:0;min-height:0;
  padding:calc(var(--m-rh)*.72) 8px 0 15%;
}
.cw-bio{
  margin:0;font-family:'Poppins',var(--font-display);font-weight:300;
  font-size:10px;line-height:1.5;color:#2f2f2f;
}
.cw-lbl{
  margin:calc(var(--m-rh)*.18) 0 0;
  font-family:'Poppins',var(--font-display);font-weight:300;
  font-size:9.5px;line-height:1;color:#9a9a9a;
}
.cw-list{list-style:none;margin:calc(var(--m-rh)*.12) 0 0;padding:0;}
.cw-list li{
  padding:7px 0;border-bottom:1px solid rgba(0,0,0,.10);
  font-family:'Poppins',var(--font-display);font-weight:300;
  font-size:11px;line-height:1.2;color:#2f2f2f;
}

/* ============================================================
   FOOTER — 7 rows x 4 columns, mobile only
   rows 1-3 : the nav list left, the contact block right
   rows 4-6 : the wordmark running as a marquee, socials at its foot
   row 7    : the legal strip, also marqueeing
   The video sits behind everything with the brand green laid over it, so
   the film reads as texture rather than as a picture competing with type.
   ============================================================ */
#foot{display:none;}
/* ============================================================
   FOOTER — no longer phone-only.
   36 rules, every one of them #foot or .ft-, checked before the
   wrapper came off. Same unwrap as Industries and Crew.
   ============================================================ */

/* the film plays untouched; legibility is handled by the blurred panels
   under each block of copy rather than by dimming the whole picture */

/* the panel behind a block of copy: dark enough to read against, blurred
   so the footage still moves through it */

/* the nodes at each intersection, same construction as the other canvases:
   a tiled square masked to its own top strip, offset back by half its size
   so it straddles the crossing rather than hanging off it */

#foot .hero-frame > *{display:none;}

/* The list is split across two cells on a diagonal. display:contents on
   the nav lets its two groups become grid items in their own right while
   the element itself stays in the document for what it means — a wrapper
   that took part in the grid would have had to hold both cells and could
   not place them independently. */

/* the two halves meet across the row line, so each carries half the gap —
   the rhythm of the list continues unbroken through the join */

/* the wordmark: clipped to the row band and running edge to edge, the
   duplicated run making the loop seamless */
/* Seated on the bottom line of row 6. Bebas reserves room under its
   baseline for descenders it does not have, so the box is dropped to bring
   the caps themselves onto the line — but only by .13em: the earlier .17em
   was tuned for type at .92 of a row and overshot once the wordmark grew,
   pushing the letters through the line instead of onto it.
   The size lives here rather than on the track so that em resolves against
   it, and the nudge stays correct if the size is ever changed. */

/* ============================================================
   THE TWO CALLS TO ACTION, WITH A LETTER ROLL ON HOVER
   ------------------------------------------------------------
   Each letter is a one-em window holding TWO copies of itself, stacked.
   Hovering slides the pair up by exactly one em: the top copy leaves
   through the ceiling as the lower one arrives in its place, so the letter
   appears to turn over rather than to fade.

   The stagger is the whole effect. Every letter carries its index and the
   delay is computed from it, so the roll runs left to right across the
   phrase instead of the word flipping as a block — and a longer label
   inherits the rhythm without a new rule.

   overflow:hidden on the window is what makes it a roll rather than two
   letters sliding about; the height must be an exact em or the second copy
   shows a sliver of itself at rest.
   ============================================================ */

/* the second line is the quieter of the two */

/* THE WORDMARK IS A WORD NOW, NOT A MARQUEE.
   It ran as a scrolling pair of copies, which cannot carry a per-letter
   reveal: a letter that is being animated and translated at once has two
   claims on its transform, and the marquee wins because it never stops. */

/* the section already sets .in when it comes into view; the letters simply
   join the sequence the panels above them are already running */

/* ---- the two blocks wiping in as the character walks on ----
   Both open from the same physical point — where column 1 meets column 2
   on row 2's bottom line for the links, and the far corner of row 5 for
   the contacts — and expand up and to the left from there. inset() with
   the top and left edges at 100% collapses the box to that single corner,
   so animating back to inset(0) is the wipe.

   Transitions, not keyframes, and that is the point: a keyframe only ever
   plays forwards, so leaving the section snapped the panels shut with no
   motion at all. A transition retraces its own path, so scrolling back up
   wipes them away into the same corner they came out of.

   Gated on .js so the panels are never left invisible if the script does
   not run. */

/* Arriving: held .9s first, which is what gives the footage time to walk
   the character into frame before anything appears over it. The lower half
   goes first, so the wipe reads as travelling up out of that corner. */

/* Leaving: the order reverses and the hold is dropped — there is no
   character to wait for on the way out, and an unwind that dawdles reads
   as a fault rather than as a choice. */

@media (max-width:980px){
  #showcase,#vision,#crossroad,#delivery,#manifesto,.marquee,
  #solutions,#metrics,#story,#morning,#morning4,#execdec,#team,
  #quote,#cta,footer{display:none;}
}

@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.001s!important;transition-duration:.001s!important;}
}

/* ============================================================
   WEB VIEW — an independent desktop design
   ------------------------------------------------------------
   Everything below is inside min-width:981px and nothing above it is
   edited. The phone design lives entirely in max-width:980px blocks, so the
   two can never reach each other — that separation is what went wrong last
   time, when the mobile blocks were widened to cover all widths and every
   desktop tweak became a mobile risk.

   Same markup, same content, different layout: a conventional long scroll,
   with the rail-type sections running horizontally inside it.
   ============================================================ */
/* Bebas Neue, served from /fonts. It keeps the real family name, and Bebas
   has been dropped from the Google Fonts request in the head so this is the
   only source for it — two declarations of one family name would have left it
   ambiguous which file the browser actually used. Declared outside the media
   query because @font-face is a document-level rule; nothing downloads it
   unless something asks for it. */
@font-face{
  font-family:'Bebas Neue';
  /* The query string is deliberate. This URL was referenced for several
     reloads BEFORE the file existed, so the browser holds a cached 404 for
     it, and an ordinary reload will happily reuse that failure. Changing the
     URL is the only way to make it ask again. */
  src:url("../../fonts/BebasNeue-Regular.ttf?v=1") format("truetype");
  /* The file ships one weight, but the range is declared 100-900 so a request
     for 700 matches THIS face and is synthesised from it, rather than being
     treated as no match and falling through to the next family in the list —
     which is what put a plain grotesque on screen. */
  font-weight:100 900;font-style:normal;font-display:block;
}

@media (min-width:981px){
  /* the switch: the legacy desktop sections step aside, the canvas sections
     that were phone-only become the desktop build too */
  #showcase,#vision,#crossroad,#delivery,#manifesto,.marquee,
  #solutions,#metrics,#story,#morning,#morning4,#execdec,#team,
  #quote,#cta,footer{display:none!important;}

  #intro,#crew,#foot,#chapter{display:block;}

  /* ---- HERO ------------------------------------------------------------
     Read off the reference's own lattice: node squares at five x positions
     and three y positions, so four columns by two rows, the frame meeting
     the window edge. The film runs behind all of it.

       row 1        the scroll cue, low in column 1
       row 2 c1-2   the wordmark, clipped and looping
       row 2 c3     the strap line
       row 2 c4     the copy block on its frosted panel

     --m-rh is retargeted to half a viewport here rather than a seventh. It
     drives the lattice painted on .hero-frame as well as the row tracks, so
     both have to move together or the lines drawn would not be the lines the
     content sits against. */
  #hero{
    --m-pad:10px;--m-rh:calc(var(--vh)/2);height:auto;
    /* Flat, and it matches the film's own backdrop rather than being a
       picture of its own — the clip is contained now, so what shows either
       side of it has to read as the same room, not as a different image.
       Measured from the clip's edge pixels: median #B8AA9A across the
       fourteen seconds. JS then keeps it exact, because that edge is not one
       colour — it runs from #D3CBC2 at the open down to about #B2A594, a
       drift of 47/255 in blue, which a fixed value cannot follow. This is the
       value used if the script does not run. */
    background:#b8aa9a;
  }
  #hero .hero-sticky{
    position:relative;height:var(--vh);min-height:var(--vh);overflow:hidden;
    display:grid;grid-template-columns:repeat(4,minmax(0,1fr));
    /* the tracks lose the padding at both ends, so the two rows still add up
       to the screen with the frame inset inside it */
    grid-template-rows:repeat(2,calc((var(--vh) - var(--m-pad)*2)/2));
    padding:var(--m-pad);
  }
  /* FULL BLEED, BECAUSE THE CUT IS 16:9 AND SO IS THE SCREEN.
     The film used to sit in columns 2 and 3, which is right for a portrait
     clip — the box there is about 0.9 wide to tall. The desktop cut is 2562
     by 1440, so putting it in that same box would throw away nearly half the
     frame down the sides. Across the whole grid it meets the viewport's own
     proportions and cover barely has to crop at all.
     The negative margin takes it out through .hero-sticky's padding as well,
     or a ten-pixel band of ground would show round all four edges. */
  #hero .hero-scene{
    display:block;position:relative;z-index:0;overflow:hidden;
    left:auto;right:auto;top:auto;bottom:auto;inset:auto;
    grid-column:1/-1;grid-row:1/-1;
    margin:calc(var(--m-pad) * -1);
  }
  /* cover again. contain was right while the clip was portrait in a squarish
     box — it kept the whole frame and let the ground show either side. The
     desktop cut matches the screen's shape, so cover now fills it edge to edge
     and takes only a sliver off whichever dimension is long. */
  #hero .hero-video{
    position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
    width:100%;height:100%;max-width:none;
    object-fit:cover;object-position:center center;
    -webkit-mask-image:none;mask-image:none;
  }
  #hero .hero-shade{
    display:block;position:absolute;inset:0;z-index:1;pointer-events:none;
    background:linear-gradient(180deg,rgba(20,30,23,.28) 0%,
               rgba(20,30,23,0) 34%,rgba(20,30,23,.30) 100%);
  }
  #hero .hero-frame{
    display:block;position:absolute;inset:var(--m-pad);z-index:5;pointer-events:none;
    /* the lattice at 15% — the border and both line sets move together, or
       the frame's edge would read heavier than the lines inside it */
    border:1px solid rgba(246,242,233,.15);
    background-origin:border-box;
    background-image:
      linear-gradient(to right,rgba(246,242,233,.15) 1px,transparent 1px),
      linear-gradient(to bottom,rgba(246,242,233,.15) 1px,transparent 1px);
    background-size:25% 100%,100% calc((var(--vh) - var(--m-pad)*2)/2);
  }
  #hero .hero-frame > *{display:none;}

  /* A PLUS AT EACH INTERSECTION, NOT A SQUARE.

     It takes both pseudo-elements. Each of these draws ONE arm: a bar of
     background clipped by a mask running the other way, which is how the
     square was made — a 7px bar cut to 7px tall. A plus is two such marks
     crossing, and one element can carry only one background/mask pair, so
     ::after takes the horizontal arm and ::before the vertical.

     The offsets are half the arm's own thickness, so each mark straddles
     the line rather than hanging off it: 4.5px back on the long axis, 0.5px
     on the short one. */
  #hero .hero-frame::after{
    content:"";position:absolute;left:-4.5px;top:-.5px;
    /* +5px, not +1px. The mask paints a 1px band every row, and the last of
       those lands exactly on the element's own bottom edge — at +1px the
       band and the edge coincide and sub-pixel rounding drops it, which is
       why the bottom line had ticks but no crossbar. The extra height is
       free: the mask stops painting past its band regardless.
       The WIDTH must not change — the column spacing is derived from it. */
    width:calc(100% + 9px);height:calc(100% + 5px);
    background-image:linear-gradient(to right,rgba(246,242,233,.85) 9px,transparent 9px);
    background-size:calc((100% - 9px)/4) calc((var(--vh) - var(--m-pad)*2)/2);
    -webkit-mask-image:linear-gradient(to bottom,#000 1px,transparent 1px);
            mask-image:linear-gradient(to bottom,#000 1px,transparent 1px);
    -webkit-mask-size:100% calc((var(--vh) - var(--m-pad)*2)/2);
            mask-size:100% calc((var(--vh) - var(--m-pad)*2)/2);
  }
  #hero .hero-frame::before{
    content:"";position:absolute;left:-.5px;top:-4.5px;
    width:calc(100% + 1px);height:calc(100% + 9px);
    background-image:linear-gradient(to right,rgba(246,242,233,.85) 1px,transparent 1px);
    background-size:calc((100% - 1px)/4) calc((var(--vh) - var(--m-pad)*2)/2);
    -webkit-mask-image:linear-gradient(to bottom,#000 9px,transparent 9px);
            mask-image:linear-gradient(to bottom,#000 9px,transparent 9px);
    -webkit-mask-size:100% calc((var(--vh) - var(--m-pad)*2)/2);
            mask-size:100% calc((var(--vh) - var(--m-pad)*2)/2);
  }

  /* THE PILL HANGS FROM THE FRAME'S TOP LINE.
     It was written as top:var(--m-pad), but --m-pad is declared on #hero and
     #nav is not inside #hero — it is a sibling. The variable was therefore
     unresolvable, the declaration invalid, and top fell back to auto, which
     for a fixed element means its static position: the very top of the page,
     ten pixels ABOVE the line it was meant to start from. Written as a plain
     length here, matched to --m-pad by hand. */
  #nav,#nav.solid{
    /* 15% narrower than it was */
    width:min(28.05vw,561px);top:10px;height:62px;
    grid-template-columns:auto 1fr auto;padding:0 30px;
    /* Nothing left of the panel: no fill, no blur, no outline. The blur has
       to go with the fill — left in, backdrop-filter would keep smearing what
       is behind the bar and the panel would still read as visible. The border
       and the shadow come from the base #nav rule and are cleared here, or a
       faint gold pill outline would remain around nothing. */
    background:transparent;
    -webkit-backdrop-filter:none;backdrop-filter:none;
    border:0;box-shadow:none;border-radius:0;
  }
  #nav .logo{font-size:19px;letter-spacing:.3em;justify-self:center;}
  #nav .nav-link{font-size:11px;letter-spacing:.1em;}

  .scroll-cue{
    display:flex;align-items:center;gap:13px;
    grid-column:1;grid-row:1;align-self:end;justify-self:start;
    position:relative;left:auto;bottom:auto;z-index:4;
    padding:0 0 calc(var(--m-rh)*.1) 42px;
    font-family:var(--font-mono);font-size:10px;letter-spacing:.22em;
    text-transform:uppercase;color:rgba(246,242,233,.65);font-weight:600;}
  .scroll-cue .line{background:rgba(246,242,233,.4);}

  /* Two columns of row 2, clipped at the column line so the loop runs out of
     frame exactly as it does in the reference. The type is set at .62 of the
     row — Bebas caps are about .72em, which lands them at .45 of the row,
     the proportion the reference carries. */
  /* One column, not two: the film now occupies column 2, so the marquee ends
     where the film begins. The wordmark inside is far wider than the cell and
     runs on a seamless -50% loop of two identical spans, so the type is cut
     by both edges of the column at every moment rather than ever ending short
     of one — which is what keeps it gapless left and right. */
  /* The clip is the WHOLE cell now — stretched both ways, no padding — so the
     marquee is bounded by the four grid lines rather than sitting in a strip
     along the bottom of the box. */
  /* THE 1.5x HORIZONTAL SCALE IS GONE.
     It was added to "widen the marquee", and it did — by stretching the
     glyphs. Bebas is a CONDENSED face; scaling it 50% wider turns it into a
     normal-width one, which is why the O read as round rather than narrow and
     why it never matched the reference however the weight and size were
     tuned. The letterforms are now drawn at their true proportions. */
  .hero-wordclip{
    display:flex;align-items:flex-end;
    position:relative;left:auto;bottom:auto;
    grid-column:1;grid-row:2;align-self:stretch;justify-self:stretch;
    /* HEIGHT:AUTO IS THE WHOLE FIX.
       The base rule sets height:44vh, from the days when this was an
       absolutely-placed strip. A grid item with a fixed height cannot be
       stretched — align-self:stretch is simply ignored — so the clip was
       44vh tall inside a row of about 50vh and sat at the TOP of it, ending
       some six vh above the bottom line. That is the band of empty space, and
       the flat cut is its lower edge.
       It also explains why pushing the type down only made it worse: the
       clip's bottom was fixed short of the line, so every extra pixel of
       margin fed more of the letter into the part being trimmed away. */
    /* Both dimensions have to be released, not just the height. The base rule
       is width:calc(50% - 16px) and height:44vh, from when this was an
       absolutely-placed strip. The width override went out with the scaleX it
       was written for, which handed the box straight back to that 50% — and
       50% of the column, less 16px, is exactly the 45% of the cell the type
       was reaching. It was never the track running short; the clip it sits in
       was half a column wide. */
    width:auto;height:auto;
    z-index:4;overflow:hidden;min-width:0;padding:0;
  }
  /* THE GAP CAME FROM SHRINK-TO-FIT, AND ABSOLUTE POSITIONING IS WHY.
     An absolutely positioned box with only `left` set takes a shrink-to-fit
     width, and shrink-to-fit is CAPPED BY ITS CONTAINING BLOCK. While the clip
     was two columns wide the cap never bit; at one column it did. The track
     collapsed to the width of the cell, its two spans were squeezed inside it,
     and the -50% loop then stepped by half a squashed width instead of half
     the real one — leaving the empty stretch on the right.
     Taking it out of absolute positioning removes the cap at the source: as a
     flex item with flex:0 0 auto its width is simply its content, no ceiling
     involved, and the clip's overflow does the trimming. Half of that width is
     exactly one span again, so the loop is seamless whatever the type size.
     VERTICALLY IT IS TRIMMED, NOT NUDGED. Successive pushes had accumulated
     into a single -90px margin, and that is what was cutting the letters:
     line-height .82 makes the line box SHORTER than the text it carries, so
     the glyphs already overhang it top and bottom. Pulling the box down past
     the clip put those overhanging bottoms outside it, and overflow:hidden
     removed them — while the blank band left above the grid line was simply
     the rest of the short line box, still reserving space it was not using.
     Chasing it with more margin could only ever cut more off.
     line-height:1 lets the line box hold the whole glyph, and the trim then
     ends the box exactly at the baseline, so flex-end puts the caps ON the
     bottom line with no offset at all. */
  #hero .hero-wordmark{
    position:relative;left:auto;right:auto;top:auto;bottom:auto;
    /* AN EXPLICIT WIDTH, NOT AN INTRINSIC ONE.
       max-content kept resolving to the width of the cell, which left the
       track only as wide as the box it sits in — so translateX(-50%) stepped
       half a cell and the right-hand half of the column was simply past the
       end of the run. This width is measured, not guessed: the advance widths
       of "Virevo Virevo Virevo " read straight out of BebasNeue-Regular.ttf
       come to 6.846em, and the tracking here takes off .03em on each of 21
       characters and .14em on each of 3 spaces, leaving 5.796em per
       repetition. There are four repetitions, hence 23.184em.
       Because the figure is exactly four blocks, -50% is still exactly two of
       them and the loop stays seamless. */
    flex:0 0 auto;width:23.184em;max-width:none;
    /* Read from the file itself: 1000 upm, ascent .9em, descent .3em, no line
       gap — so line-height:1 leaves exactly .2em below the baseline. */
    margin-bottom:-.2em;
    font-family:"Bebas Neue",sans-serif;
    /* 400, the weight the file actually carries. Anything heavier is drawn by
       thickening these outlines, which widens the letters and closes up the
       counters — the shapes stop being Bebas. */
    font-weight:400;
    font-size:307.636px;line-height:1;color:#f6f2e9;
    letter-spacing:-.03em;word-spacing:-.14em;
    animation-duration:130s;      /* twice the track, so twice the clock */
  }
  /* Two more repetitions, added here so the phone's markup is untouched. Four
     identical blocks instead of two: the run is twice as long, the -50% loop
     still lands on an exact block boundary, and the cell is filled whatever
     the track's measured width turns out to be — the items cannot shrink. */
  #hero .hero-wordmark::before,
  #hero .hero-wordmark::after{
    content:"Virevo\00a0Virevo\00a0Virevo\00a0";
    flex:0 0 auto;
  }
  #hero .hero-wordmark > span{flex:0 0 auto;}
  /* The text-box trim that used to sit here has been removed. Chrome PARSES
     the property, so @supports reported true and the rule took effect —
     zeroing the margin — but the box was not actually being trimmed, which
     left the caps floating by exactly the descent the margin was there to
     remove. A test that passes while the effect does not arrive is worse than
     no test at all. The margin below is measured and unconditional. */

  /* Centred on the SCREEN and sitting on the bottom line. It was in column 3,
     which centred it a quarter of the way right of the middle — fine while
     the film occupied columns 2 and 3 and the strap belonged to it, but the
     film is the whole frame now and the strap belongs to the frame. Spanning
     every column puts it on the true centre line, and align-self:end drops it
     to the foot of the row rather than floating in the middle of it. */
  .hero-foot{
    display:block;
    grid-column:1/-1;grid-row:2;align-self:end;z-index:4;
    text-align:center;padding:0 18px calc(var(--m-rh)*.11);
  }
  .hf-lead{
    font-family:var(--font-display);font-weight:300;color:#fff;
    font-size:clamp(13px,1vw,18px);line-height:1.25;
    /* THE RULE IS EXACTLY TWO COLUMNS WIDE.
       It is drawn as this element's bottom border, so it ran the full width of
       .hero-foot — and .hero-foot now spans every column. Four columns measure
       100vw less the padding either side, so two of them come to 50vw less one
       padding. Auto margins centre it, and because the padding is symmetric
       that centre is the viewport's centre — which is exactly where the line
       between columns 2 and 3 falls. So the rule starts on the column-2 border
       and ends on the column-3 one. */
    width:calc(50vw - var(--m-pad));
    padding-bottom:11px;margin:0 auto 10px;
    border-bottom:1px solid rgba(246,242,233,.45);
    text-shadow:0 1px 5px rgba(18,30,22,.5);
  }
  .hf-sub{
    font-family:var(--font-display);font-style:italic;font-weight:300;color:#fff;
    font-size:clamp(10px,.72vw,13px);letter-spacing:.05em;
    text-shadow:0 1px 5px rgba(18,30,22,.5);
  }

  /* THE PANEL THAT OVERHUNG ON THREE SIDES WAS A SECOND, OLDER ONE.
     .hero-copybg is a separate absolutely-placed sheet of glass from before
     the grid existed, positioned with left:calc(75% - 7px), right:16px and
     bottom:0 — percentages of the whole hero and a 16px inset, where the grid
     works off a 10px padding and columns measured inside it. The two could
     never line up. The phone build already hid it; desktop never did, so it
     was painting behind .hero-copy's own glass and sticking out past the
     lines on the left, the right and the bottom. .hero-copy::before is the
     grid-aligned one and is all that is needed. */
  #hero .hero-copybg{display:none;}

  /* the copy fills its cell, on the same glass as the nav */
  .hero-copy{
    position:relative;inset:auto;left:auto;right:auto;top:auto;bottom:auto;
    grid-column:4;grid-row:2;z-index:4;margin:0;padding:0;
    align-self:stretch;justify-self:stretch;overflow:hidden;min-height:0;
  }
  .hero-copy::before{
    content:"";position:absolute;inset:0;z-index:0;pointer-events:none;
    background:rgba(42,56,47,.36);
    backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);
  }
  /* static block on desktop, as chosen — the cycling chapters stay off */
  .hero-phases{display:none;}
  .hero-mcopy{
    display:flex;flex-direction:column;position:relative;z-index:1;height:100%;
    padding:clamp(26px,2.4vw,48px);
  }
  .hm-chip{
    align-self:flex-start;margin:0 0 auto;
    font-size:clamp(9px,.64vw,12px);letter-spacing:.2em;padding:6px 13px;
    text-transform:uppercase;
  }
  .hm-title{
    font-family:var(--font-display);font-weight:300;color:#f6f2e9;
    font-size:clamp(24px,2.2vw,42px);line-height:1.18;
    margin:0 0 clamp(14px,1.5vw,28px);
  }
  .hm-body{
    font-family:var(--font-mono);color:rgba(246,242,233,.72);
    font-size:clamp(9px,.7vw,13px);line-height:1.9;letter-spacing:.07em;
    text-transform:uppercase;margin:0;font-weight:600;}
  .hm-body strong{color:#f6f2e9;font-weight:500;}
  /* The card is ~316px of content width, so the page's 18px/36px button
     would very nearly span it and stop reading as a button at all. Scaled
     to the panel and left-aligned so it hangs off the copy above it rather
     than stretching to a full-width bar. */
  .hm-cta{
    align-self:flex-start;
    margin:clamp(16px,1.6vw,26px) 0 0;
    padding:clamp(11px,1vw,15px) clamp(20px,1.8vw,30px);
    font-size:clamp(9px,.62vw,11px);letter-spacing:.18em;
  }

  /* ==========================================================
     WHAT WE DO — the phone build, recomputed for a wide screen

     Same markup, same components, same content, same sequence. Only the
     measurements change, and they change rather than scale: the phone sizes
     everything off vw because on a phone the width is the scarce dimension,
     while on a desktop it is the HEIGHT that runs out first. So the square,
     the headings and the rhythm are driven off --m-rh — a seventh of the
     viewport height — with vw only ever acting as a cap.

     The seven-row canvas is kept deliberately, even though the hero above
     uses two. It is what the copy's vertical rhythm is measured in, and
     re-cutting it would mean re-tuning every margin in the block.
     ========================================================== */
  #intro{
    --m-pad:10px;--m-div:4;--m-rh:calc(var(--vh)/7);
    /* the square: bounded by height first, with a width cap so it cannot
       sprawl on an ultrawide monitor */
    --ib:min(46vh,30vw);
    --iq:calc(50% - var(--ib)/2);
    --D:5s;
    --ln:rgba(30,55,39,.46);
    --lnd:rgba(30,55,39,.88);
    --po:11px;
    display:grid;position:relative;z-index:1;
    height:var(--vh);
    grid-template-columns:repeat(4,minmax(0,1fr));
    grid-template-rows:repeat(7,var(--m-rh));
    padding:0 var(--m-pad);gap:0;background:#ffffff;overflow:hidden;
  }
  #intro .hero-frame{
    display:block;position:absolute;inset:0 var(--m-pad);
    z-index:5;pointer-events:none;opacity:0;
    border:1px solid rgba(30,55,39,.10);
    background-origin:border-box;
    background-image:
      linear-gradient(to right,rgba(30,55,39,.10) 1px,transparent 1px),
      linear-gradient(to bottom,rgba(30,55,39,.10) 1px,transparent 1px);
    background-size:calc(100%/var(--m-div)) 100%,100% var(--m-rh);
  }
  #intro .hero-frame > *{display:none;}

  #intro .in-stage{position:absolute;inset:0;z-index:6;pointer-events:none;}
  #intro .in-frame{
    position:absolute;
    left:var(--iq);right:var(--iq);top:var(--iq);bottom:var(--iq);
  }
  #intro .in-frame::before{
    content:"";position:absolute;inset:0;background:#b6aa9c;opacity:0;z-index:0;
  }
  #intro .in-plus{position:absolute;width:26px;height:26px;opacity:0;z-index:2;}
  #intro .in-plus::before,
  #intro .in-plus::after{content:"";position:absolute;background:var(--ln);}
  #intro .in-plus::before{left:0;right:0;top:50%;height:1px;margin-top:-.5px;}
  #intro .in-plus::after{top:0;bottom:0;left:50%;width:1px;margin-left:-.5px;}
  #intro .in-plus.tl{left:0;top:0;transform:translate(calc(-50% - var(--po)),calc(-50% - var(--po)));}
  #intro .in-plus.tr{right:0;top:0;transform:translate(calc(50% + var(--po)),calc(-50% - var(--po)));}
  #intro .in-plus.bl{left:0;bottom:0;transform:translate(calc(-50% - var(--po)),calc(50% + var(--po)));}
  #intro .in-plus.br{right:0;bottom:0;transform:translate(calc(50% + var(--po)),calc(50% + var(--po)));}

  /* the wordmark inside the square rides off --ib, so it follows the box
     rather than needing its own breakpoint */
  #intro .in-lead{
    grid-column:1/-1;grid-row:5;z-index:4;margin:0;
    align-self:end;padding:0 12px calc(var(--m-rh)*.06);
    text-align:center;opacity:0;
    font-family:'Poppins',var(--font-display);font-weight:400;
    /* 28% down from the original clamp(15px,1.25vw,22px) */
    font-size:clamp(10.8px,.9vw,15.84px);line-height:1.3;letter-spacing:.02em;
    text-transform:uppercase;color:#3a3a3a;
  }
  #intro .in-sub{
    grid-column:1/-1;grid-row:6;z-index:4;margin:0;
    align-self:center;padding:0 12px;
    text-align:center;opacity:0;
    font-family:'Poppins',var(--font-display);font-weight:300;
    font-size:clamp(12px,.95vw,17px);line-height:1.55;letter-spacing:.02em;
    text-transform:uppercase;color:#a5a5a5;
  }

  /* A GRID, NOT A COLUMN — so the two halves of the heading can share a line.
     The markup gives them as two separate <h2> elements, which a flex column
     will always stack; placing them in the same grid row puts them side by
     side without touching the markup the phone build also reads. The last
     track is 1fr so the step area still absorbs whatever height is left. */
  #intro .wd{
    position:absolute;inset:0;z-index:3;
    display:grid;grid-template-columns:auto 1fr;
    grid-template-rows:auto auto auto 1fr;
    align-content:start;
    padding:0 var(--m-pad);overflow:hidden;
  }
  #intro .wd > *:not(.wd-steps){opacity:0;}
  #intro .wd-a{grid-column:1;grid-row:1;}
  #intro .wd-b{grid-column:2;grid-row:1;justify-self:start;align-self:end;}
  #intro .wd-intro{grid-column:1/-1;grid-row:2;}
  #intro .wd-rule{grid-column:1/-1;grid-row:3;}
  #intro .wd-steps{grid-column:1/-1;grid-row:4;}

  /* 15vw would be over 200px of cap on a wide monitor and the two lines would
     eat the section. Bounded by height, capped by width. */
  #intro .wd-a,#intro .wd-b{
    margin:0;
    font-family:'Bebas Neue',var(--font-display);font-weight:400;
    font-size:min(calc(var(--m-rh)*1.18),9.5vw);
    line-height:.95;letter-spacing:.015em;
    text-transform:uppercase;white-space:nowrap;
  }
  /* one line, so the indent that pushed "We Do" onto its own row goes */
  #intro .wd-a{margin:calc(var(--m-rh)*.16) 0 0 8%;color:#8a9a8c;}
  #intro .wd-b{margin:calc(var(--m-rh)*.16) 0 0 .28em;color:#c5c0bc;-webkit-text-stroke:0;}

  #intro .wd-intro{
    margin:calc(var(--m-rh)*.44) 0 0;padding:0 9%;
    font-family:'Poppins',var(--font-display);font-weight:400;
    font-size:clamp(13px,1.05vw,18px);line-height:1.55;letter-spacing:-.015em;color:#4a4a4a;
    max-width:none;
  }
  #intro .wd-intro p+p{margin-top:.95em;}
  #intro .wd-intro b{font-weight:700;color:#2f2f2f;}

  #intro .wd-steps{
    flex:1 1 auto;min-height:0;position:relative;
    margin:0 calc(var(--m-pad)*-1);
  }
  #intro .wd-step{
    position:absolute;inset:0;
    display:flex;flex-direction:column;
    padding:0 var(--m-pad);
    opacity:0;visibility:hidden;
    transition:opacity .38s ease-out,visibility 0s linear .38s;
  }
  #intro .wd-step.on{opacity:1;visibility:visible;transition:opacity .38s ease-out;}
  #intro .wd-step > *{position:relative;z-index:1;flex:0 0 auto;}
  #intro .wd-dark::before{
    content:"";position:absolute;z-index:0;opacity:0;inset:0;
    left:calc(var(--m-pad)*-1);right:calc(var(--m-pad)*-1);
    background:var(--ground,#c3bbb0);
  }
  #intro .wd-kicker{
    margin:calc(var(--m-rh)*.30) 0 0;padding:0 0 0 9.5%;
    font-family:'Poppins',var(--font-display);font-weight:600;
    font-size:clamp(10px,.8vw,14px);line-height:1.2;letter-spacing:.09em;
    text-transform:uppercase;color:#3a3a3a;
  }
  /* Columns two and three break their headline after the "&"; this one has
     no "&" to break on, so it ran to a single line and started its paragraph
     a line higher than its neighbours. The break is authored in the markup
     and shown only here, because at phone width the column is narrow enough
     that the headline wraps on its own and a forced break would strand a
     one-word line. virevo-type-scale.md notes the same manual break in this
     role: "not a natural wrap ... worth keeping as an explicit <br>". */
  #intro .wd-head .wd-br{display:inline;}
  #intro .wd-head{
    margin:calc(var(--m-rh)*.24) 0 0;padding:0 6% 0 9%;
    font-family:'Poppins',var(--font-display);font-weight:400;
    font-size:clamp(20px,1.9vw,34px);line-height:1.20;letter-spacing:0;
    text-transform:uppercase;color:#6a6a6a;
  }
  /* The one place the phone's measurements could not simply be enlarged. Its
     body copy runs the full width because the width is small; here the same
     rule would set a 1600px line, which is unreadable. The column is capped
     and the block keeps its left indent, so the copy still hangs off the same
     line as the kicker and the headline above it. */
  #intro .wd-body{
    margin:calc(var(--m-rh)*.26) 0 0;padding:0 6% 0 9%;
    font-family:'Poppins',var(--font-display);font-weight:400;
    font-size:clamp(12px,.95vw,17px);line-height:1.50;letter-spacing:-.01em;color:#565656;
  }
  #intro .wd-body p{max-width:62ch;}
  #intro .wd-body p+p{margin-top:.85em;}
  #intro .wd-shot{
    flex:1 1 auto;min-height:0;
    margin:calc(var(--m-rh)*.22) 9% 0;position:relative;overflow:hidden;
  }
  #intro .wd-shot img{width:100%;height:100%;object-fit:cover;display:block;}
  #intro .wd-foot{
    margin:calc(var(--m-rh)*.14) 0 calc(var(--m-rh)*.16);padding:0 0 0 9.5%;
    font-family:'Poppins',var(--font-display);font-weight:600;
    font-size:clamp(20px,2.1vw,38px);line-height:1.2;letter-spacing:0;
    text-transform:uppercase;color:#5a5a5a;
  }

  /* ---- components and sequence, copied verbatim from the phone build ----
     Prefixed with #intro so they cannot reach any other section, and
     placed before the size overrides above take effect. Everything here
     is geometry and timing that does not depend on the viewport, so it
     is the same at both widths by definition rather than by coincidence.
     ---------------------------------------------------------------- */
  #intro .in-edge{position:absolute;background:var(--ln);opacity:0;z-index:1;}
  #intro .in-edge.top{left:0;right:0;top:0;height:1px;transform:scaleX(0);}
  #intro .in-edge.bottom{left:0;right:0;bottom:0;height:1px;transform:scaleX(0);}
  #intro .in-edge.left{top:0;bottom:0;left:0;width:1px;transform:scaleY(0);}
  #intro .in-edge.right{top:0;bottom:0;right:0;width:1px;transform:scaleY(0);}
  #intro .in-trace{position:absolute;inset:0;width:100%;height:100%; z-index:2;opacity:0;overflow:visible;}
  #intro .in-trace rect{fill:none;stroke:var(--lnd);stroke-width:1; vector-effect:non-scaling-stroke; stroke-dasharray:24 76;}
  #intro .in-mark{position:absolute;left:0;right:0;top:50%;transform:translateY(-50%); text-align:center;z-index:3;opacity:0; font-family:'Poppins',var(--font-display);font-weight:700; font-size:calc(var(--ib)*.215);line-height:1;letter-spacing:.005em; color:transparent;-webkit-text-stroke:1px var(--ln);}
  #intro .in-mark::after{content:attr(data-w); position:absolute;left:0;right:0;top:0; color:var(--ln);-webkit-text-stroke:0; clip-path:inset(0 100% 0 0);}
  #intro .wd-rule{margin:calc(var(--m-rh)*.46) 0 0;position:relative;height:1px;z-index:2; /* The line runs from (10px - pad) to (10px - pad), so its width — and therefore the traveller's journey — is NOT 100vw - 20px. That was the old figure and it left the node stopping short of the post it was supposed to land on, so the two never quite met. */ --wd-travel:calc(100vw - 20px + var(--m-pad) * 2);}
  #intro .wd-line{position:absolute;top:0;height:1px; left:calc(10px - var(--m-pad));right:calc(10px - var(--m-pad)); background:rgba(30,55,39,.34); transform:scaleX(0);transform-origin:center;}
  #intro .wd-p{position:absolute;top:0;width:15px;height:15px;opacity:0;}
  #intro .wd-p::before, #intro .wd-p::after{content:"";position:absolute;background:rgba(30,55,39,.5);}
  #intro .wd-p::before{left:0;right:0;top:50%;height:1px;margin-top:-.5px;}
  #intro .wd-p::after{content:"";position:absolute;background:rgba(30,55,39,.5);}
  #intro .wd-p.l{left:calc(10px - var(--m-pad));transform:translate(-50%,-50%);opacity:1;}
  #intro .wd-p.r{right:calc(10px - var(--m-pad));transform:translate(50%,-50%);opacity:1;}
  #intro .wd-p.t{left:calc(10px - var(--m-pad));transform:translate(-50%,-50%);opacity:0;}
  #intro .wd-s2{--ground:#b5ada0;}
  #intro .wd-s3{--ground:#a49c90;}
  #intro .wd-s4{--ground:#948d81;}
  #intro .wd-s5{--ground:#847f77;}
  #intro.play .in-frame{animation:inSeq var(--D) both;}
  #intro.play .in-frame::before{animation:inFlood var(--D) linear both;}
  #intro.play .in-plus{animation:inNode var(--D) linear both;}
  #intro.play .in-trace{animation:inTrace var(--D) linear both;}
  #intro.play .in-trace rect{animation:inChase 1.55s linear infinite;}
  #intro.play .in-mark{animation:inStroke var(--D) linear both;}
  #intro.play .in-mark::after{animation:inFillText var(--D) linear both;}
  #intro.play .in-lead{animation:inRise var(--D) linear both;}
  #intro.play .in-sub{animation:inRise2 var(--D) linear both;}
  #intro.play .wd-a{animation:wdType .55s steps(18,end) 4.30s both;}
  #intro.play .wd-b{animation:wdType .55s steps(18,end) 4.46s both;}
  #intro.play .wd-intro{animation:wdFade .55s ease-out 4.62s both;}
  #intro.play .wd-rule{animation:wdNode .01s linear 4.80s both;}
  #intro.play .wd-line{animation:wdRule .65s cubic-bezier(.16,1,.3,1) 4.80s both;}
  #intro.play .wd-p{animation:wdNode .35s ease-out 5.20s both;}
  #intro .wd-rule.swap  .wd-line{animation:wdLineL2R 3.2s linear both;}
  #intro .wd-rule.swap  .wd-p.t{animation:wdTripL2R 3.2s linear both;}
  #intro .wd-rule.swapb .wd-line{animation:wdLineR2L 3.2s linear both;}
  #intro .wd-rule.swapb .wd-p.t{animation:wdTripR2L 3.2s linear both;}
  #intro .wd-step.on.wd-dark::before{animation:wdGround 1.2s ease-out .35s both;}
  #intro .wd-step.on .wd-kicker{animation:wdTypeIn .55s steps(14,end) .55s both;}
  #intro .wd-step.on .wd-head{animation:wdSlideIn .60s cubic-bezier(.16,1,.3,1) .72s both;}
  #intro .wd-step.on .wd-body{animation:wdSlideIn .60s cubic-bezier(.16,1,.3,1) .90s both;}
  #intro .wd-step.on .wd-shot{animation:wdSlideIn .60s cubic-bezier(.16,1,.3,1) 1.08s both;}
  #intro .wd-step.on .wd-foot{animation:wdTypeIn .55s steps(18,end) 1.24s both;}
  #intro.play .in-edge.top,
  #intro.play .in-edge.bottom{animation:inDrawX var(--D) cubic-bezier(.22,1,.36,1) both;}
  #intro.play .in-edge.left,
  #intro.play .in-edge.right{animation:inDrawY var(--D) cubic-bezier(.22,1,.36,1) both;}
  #intro .wd-rule.swap .wd-p.t::before,
  #intro .wd-rule.swap .wd-p.t::after{animation:wdSpinCW 3.2s linear both;}
  #intro .wd-rule.swapb .wd-p.t::before,
  #intro .wd-rule.swapb .wd-p.t::after{animation:wdSpinCCW 3.2s linear both;}
  #intro .wd-rule.swap .wd-p.l,#intro .wd-rule.swap .wd-p.r,
  #intro .wd-rule.swapb .wd-p.l,#intro .wd-rule.swapb .wd-p.r{animation:none;opacity:1;}
  #intro .wd-dark .wd-kicker,
  #intro .wd-dark .wd-head,
  #intro .wd-dark .wd-body,
  #intro .wd-dark .wd-foot{color:#ffffff;}
  /* the only component that had to grow: a 15px node reads as a speck
     on a desktop rule */
  #intro .wd-p{width:19px;height:19px;}

  /* ==========================================================
     THE STEPS AS A THREE-COLUMN TRACK

     The phone stacks all six panels on top of each other and shows one. Here
     they sit side by side, a third of the width each, and the same gesture
     that swapped them now advances along the row — so the governor, the
     reverse-scroll behaviour and the rule's hand-off all carry over unchanged.

     Two numbers drive it, both plain custom properties:
       --i  which panel this is, set once per child
       --o  how far the whole row has slid left, set from data-step

     --o stays at 0 for the first three gestures, so panels arrive into empty
     columns rather than the row moving before it is full. From the fourth it
     increments, and the row travels one column per gesture.

     A panel that has not been reached yet waits below its column at --y:100%
     and rises as it arrives. --y is separate from the transform's horizontal
     term on purpose: one property cannot be overridden in halves, so keeping
     the vertical offset in its own variable lets the arrival rules set it
     without restating the travel.
     ========================================================== */
  #intro .wd-steps{
    position:relative;overflow:hidden;min-height:0;
    margin:calc(var(--m-rh)*.18) calc(var(--m-pad)*-1) 0;
  }
  #intro .wd-step{
    /* right:auto matters — the phone rule uses inset:0, and left+right+width
       together over-constrain an absolute box */
    position:absolute;top:0;bottom:0;left:0;right:auto;width:33.3333%;
    display:flex;flex-direction:column;
    padding:0 clamp(14px,1.5vw,30px);
    opacity:1;visibility:visible;
    transform:translate3d(calc((var(--i,0) - var(--o,0)) * 100%),var(--y,0%),0);
    transition:transform .85s cubic-bezier(.16,1,.3,1),opacity .5s ease-out;
  }
  #intro .wd-step:nth-child(1){--i:0;}
  #intro .wd-step:nth-child(2){--i:1;}
  #intro .wd-step:nth-child(3){--i:2;}
  #intro .wd-step:nth-child(4){--i:3;}
  #intro .wd-step:nth-child(5){--i:4;}
  #intro .wd-step:nth-child(6){--i:5;}

  /* the row holds until the three columns are full, then walks */
  #intro .wd-steps[data-step="0"],
  #intro .wd-steps[data-step="1"],
  #intro .wd-steps[data-step="2"]{--o:0;}
  #intro .wd-steps[data-step="3"]{--o:1;}
  #intro .wd-steps[data-step="4"]{--o:2;}
  #intro .wd-steps[data-step="5"]{--o:3;}

  /* everything past the current step waits below, ready to rise */
  #intro .wd-steps[data-step="0"] .wd-step:nth-child(n+2),
  #intro .wd-steps[data-step="1"] .wd-step:nth-child(n+3),
  #intro .wd-steps[data-step="2"] .wd-step:nth-child(n+4),
  #intro .wd-steps[data-step="3"] .wd-step:nth-child(n+5),
  #intro .wd-steps[data-step="4"] .wd-step:nth-child(n+6){--y:100%;opacity:0;}

  /* Each column carries its own ground, and it is simply on. The phone fades
     it up through the .on class, but .on marks the panel that just arrived —
     here the earlier ones are still on screen beside it and would have lost
     their background the moment the class moved along. */
  #intro .wd-dark::before{
    opacity:1;left:0;right:0;animation:none;
  }
  #intro .wd-step.on.wd-dark::before{animation:none;}

  /* the panel's own parts no longer animate per swap: on a row of three, a
     panel arriving must not restage the two beside it */
  #intro .wd-step.on .wd-kicker,
  #intro .wd-step.on .wd-head,
  #intro .wd-step.on .wd-body,
  #intro .wd-step.on .wd-shot,
  #intro .wd-step.on .wd-foot{animation:none;}

  #intro .wd-step .wd-kicker{margin-top:calc(var(--m-rh)*.34);padding-left:0;}
  #intro .wd-step .wd-head{
    padding:0;font-size:clamp(17px,1.35vw,26px);
    margin-top:calc(var(--m-rh)*.20);
  }
  #intro .wd-step .wd-body{padding:0;margin-top:calc(var(--m-rh)*.18);}
  #intro .wd-step .wd-body p{max-width:none;}
  #intro .wd-step .wd-foot{
    padding-left:0;margin:auto 0 calc(var(--m-rh)*.22);
    font-size:clamp(15px,1.35vw,24px);
  }
  /* held to a band rather than absorbing the slack, so the copy above it
     keeps its rhythm and the picture stays a detail in a narrow column */
  #intro .wd-step .wd-shot{
    flex:0 0 auto;margin:calc(var(--m-rh)*.24) 0 0;
    height:clamp(96px,calc(var(--m-rh)*1.15),190px);
  }

  /* ---- the top block, arriving after the square ----
     The heading types itself on in the phone build. Here it fades, and the
     two lines of copy follow it one at a time, each a beat behind the last. */
  #intro.play .wd-a{animation:wdFade 1.1s ease-out 4.50s both;}
  #intro.play .wd-b{animation:wdFade 1.1s ease-out 4.72s both;}
  #intro.play .wd-intro{animation:wdNode .01s linear 4.90s both;}
  #intro.play .wd-intro p:nth-of-type(1){animation:wdFade 1.15s ease-out 5.05s both;}
  #intro.play .wd-intro p:nth-of-type(2){animation:wdFade 1.15s ease-out 5.85s both;}
  #intro.play .wd-rule{animation:wdNode .01s linear 6.60s both;}
  #intro.play .wd-line{animation:wdRule .8s cubic-bezier(.16,1,.3,1) 6.60s both;}
  #intro.play .wd-p{animation:wdNode .45s ease-out 7.05s both;}

  /* ==========================================================
     SPACING AND SEQUENCE, SET AGAINST THE REFERENCE

     Everything below overrides values set earlier in this same block. Kept
     separate rather than edited in place so the reference-driven numbers can
     be read as one set.
     ========================================================== */

  /* One indent for the whole top block. The heading was at 8% and the copy at
     9%, which is close enough to look like a mistake rather than a decision —
     in the reference both hang off the same line. */
  #intro .wd{--wd-in:7%;}
  #intro .wd-a{margin:calc(var(--m-rh)*.10) 0 0 var(--wd-in);}
  #intro .wd-b{margin:calc(var(--m-rh)*.10) 0 0 .28em;}
  #intro .wd-intro{
    margin:calc(var(--m-rh)*.20) 0 0;
    padding:0 var(--wd-in);
    font-size:clamp(12px,.92vw,15px);line-height:1.55;
  }
  #intro .wd-intro p{max-width:34ch;}   /* two short lines, as the reference */
  #intro .wd-intro p+p{margin-top:.7em;}
  #intro .wd-rule{margin:calc(var(--m-rh)*.26) 0 0;}
  #intro .wd-steps{margin:calc(var(--m-rh)*.14) calc(var(--m-pad)*-1) 0;}

  /* THE RULE IS AS LONG AS THE COLUMNS THAT HAVE ARRIVED.
     It measured the full width before, which read as a line waiting to be
     filled rather than one being drawn. --n is the number of live columns,
     read off the step published on #intro, and the line and its end node both
     follow it. A third of the rule is a column because the rule and the track
     span the same grid area. */
  #intro{--n:1;}
  #intro[data-step="0"]{--n:1;}
  #intro[data-step="1"]{--n:2;}
  #intro[data-step="2"],#intro[data-step="3"],
  #intro[data-step="4"],#intro[data-step="5"]{--n:3;}

  #intro .wd-line{
    left:0;right:auto;width:calc(var(--n) * 33.3333%);
    transform:none;transform-origin:left;
    transition:width .85s cubic-bezier(.16,1,.3,1);
  }
  #intro .wd-p.l{left:0;transform:translate(-50%,-50%);}
  #intro .wd-p.r{
    left:calc(var(--n) * 33.3333%);right:auto;
    transform:translate(-50%,-50%);
    transition:left .85s cubic-bezier(.16,1,.3,1);
  }
  /* the two inner divisions, which only exist once there are columns either
     side of them — the phone build has no use for these, so they are drawn
     with pseudo-elements rather than added to the markup */
  #intro .wd-rule::before,#intro .wd-rule::after{
    content:"";position:absolute;top:0;width:19px;height:19px;
    transform:translate(-50%,-50%);opacity:0;
    transition:opacity .5s ease-out;
    background:
      linear-gradient(rgba(30,55,39,.5),rgba(30,55,39,.5)) center/100% 1px no-repeat,
      linear-gradient(rgba(30,55,39,.5),rgba(30,55,39,.5)) center/1px 100% no-repeat;
  }
  #intro .wd-rule::before{left:33.3333%;}
  #intro .wd-rule::after {left:66.6667%;}
  #intro[data-step="1"] .wd-rule::before,
  #intro[data-step="2"] .wd-rule::before,
  #intro[data-step="3"] .wd-rule::before,
  #intro[data-step="4"] .wd-rule::before,
  #intro[data-step="5"] .wd-rule::before{opacity:1;}
  #intro[data-step="2"] .wd-rule::after,
  #intro[data-step="3"] .wd-rule::after,
  #intro[data-step="4"] .wd-rule::after,
  #intro[data-step="5"] .wd-rule::after{opacity:1;}

  /* The travelling node and the line's redraw belong to the phone's one-panel
     swap. Here the line grows instead, so a node crossing the whole width
     would be describing a journey nothing is making. */
  #intro .wd-p.t{display:none;}
  #intro .wd-rule.swap .wd-line,
  #intro .wd-rule.swapb .wd-line{animation:none;}

  /* THE FIRST PANEL WAITS FOR THE COPY.
     It was on screen from the moment the section was, so the square opened
     onto a step that had already arrived. The track is held back until the
     standfirst has settled, then the whole row rises in from the lower left.
     The container is what moves, not the panels — their own transform is the
     track position, and animating the same property twice would fight. */
  #intro.play .wd-steps{animation:wdRiseIn 1.25s cubic-bezier(.16,1,.3,1) 7.35s both;}

  /* four pixels off every size and a tenth off every gap */
  #intro .wd-step{padding:0 clamp(12px,1.35vw,26px);}
  #intro .wd-step .wd-kicker{
    margin-top:calc(var(--m-rh)*.31);
    /* .09em, not .11em: virevo-type-scale.md measures this role at .0894em
       on desktop and .0907em on mobile and calls it "the same token". */
    font-size:clamp(9px,.72vw,11px);letter-spacing:.09em;
  }
  #intro .wd-step .wd-head{
    margin-top:calc(var(--m-rh)*.18);
    font-size:clamp(14px,1.15vw,22px);line-height:1.24;
  }
  #intro .wd-step .wd-body{
    margin-top:calc(var(--m-rh)*.16);
    font-size:clamp(11px,.85vw,14px);line-height:1.6;
  }
  #intro .wd-step .wd-body p+p{margin-top:.75em;}
  #intro .wd-step .wd-shot{
    margin-top:calc(var(--m-rh)*.22);
    height:clamp(84px,calc(var(--m-rh)*1.0),165px);
  }
  #intro .wd-step .wd-foot{
    margin:auto 0 calc(var(--m-rh)*.20);
    font-size:clamp(13px,1.15vw,20px);
  }

  /* ==========================================================
     INSET, ALIGNMENT AND THE DIVISIONS
     ========================================================== */

  /* The content no longer runs to the frame. One inset, proportional, on all
     four sides — and it is on .wd rather than the section, so the lattice and
     its nodes still meet the window edge as they do in the hero. */
  #intro .wd{
    --wd-gut:clamp(20px,2.2vw,46px);
    padding:calc(var(--m-rh)*.20) clamp(28px,3.4vw,78px) calc(var(--m-rh)*.22);
    /* every indent inside now measures from this edge, so the heading, the
       copy, the rule and the first column all hang off one line — which is
       what the reference does, where all three start at the same x */
    --wd-in:0%;
  }
  #intro .wd-steps{margin:calc(var(--m-rh)*.14) 0 0;}

  /* Full width, as asked — the 34ch cap made it a first-column note when it is
     meant to read across the whole section. Capped only at the point a line
     stops being comfortable to track back from. */
  #intro .wd-intro p{max-width:none;}
  #intro .wd-intro{padding:0;max-width:min(96%,1180px);}

  /* the gutter lives on the right of each column, so column one's text still
     starts on the same line as the heading above it */
  #intro .wd-step{padding:0 var(--wd-gut) 0 0;}

  /* NO GROUNDS. Each step had its own tint, which on a row of three read as
     three unrelated cards rather than one continuous method. With the panels
     transparent the white type they carried has to come back to the light
     palette, or the copy would vanish against the section. */
  #intro .wd-dark::before{display:none;}
  #intro .wd-dark .wd-kicker{color:#3a3a3a;}
  #intro .wd-dark .wd-head{color:#6a6a6a;}
  #intro .wd-dark .wd-body{color:#565656;}
  #intro .wd-dark .wd-foot{color:#5a5a5a;}

  /* THE DIVISIONS, INCLUDING BOTH ENDS OF THE FIRST COLUMN.
     The end nodes were inheriting their opacity from the load sequence, which
     meant they only existed between 7.05s and the moment .play came off. They
     are drawn here in their own right, at the same weight as the inner ones,
     and every one of the four scales up as its column arrives. */
  #intro .wd-p{width:21px;height:21px;}
  #intro .wd-p.l,#intro .wd-p.r{
    opacity:1;animation:wdPlusIn .5s cubic-bezier(.16,1,.3,1) 7.05s both;
  }
  #intro .wd-p.l::before,#intro .wd-p.l::after,
  #intro .wd-p.r::before,#intro .wd-p.r::after{background:rgba(30,55,39,.62);}
  #intro .wd-rule::before,#intro .wd-rule::after{
    width:21px;height:21px;
    background:
      linear-gradient(rgba(30,55,39,.62),rgba(30,55,39,.62)) center/100% 1px no-repeat,
      linear-gradient(rgba(30,55,39,.62),rgba(30,55,39,.62)) center/1px 100% no-repeat;
  }
  #intro[data-step="1"] .wd-rule::before,
  #intro[data-step="2"] .wd-rule::before,
  #intro[data-step="3"] .wd-rule::before,
  #intro[data-step="4"] .wd-rule::before,
  #intro[data-step="5"] .wd-rule::before,
  #intro[data-step="2"] .wd-rule::after,
  #intro[data-step="3"] .wd-rule::after,
  #intro[data-step="4"] .wd-rule::after,
  #intro[data-step="5"] .wd-rule::after{
    opacity:1;animation:wdPlusIn .5s cubic-bezier(.16,1,.3,1) both;
  }

  /* ==========================================================
     THE MISSING STROKE, AND THE TRAVELLING NODE
     ========================================================== */

  /* THE PLUSSES WERE HALF DRAWN.
     A plus is two pseudo-elements: ::before is the bar across, ::after the
     bar down. When I copied these rules over from the phone build I matched
     ".wd-p::after" against the source, and the first thing that matched was
     the tail of the SHARED rule ".wd-p::before,.wd-p::after" — so ::after
     came across carrying the shared declarations and none of its own
     geometry. With no offsets and no width it had no box to paint, which is
     why every node rendered as a lone horizontal dash. */
  #intro .wd-p::after{
    content:"";position:absolute;
    top:0;bottom:0;left:50%;width:1px;margin-left:-.5px;
    background:rgba(30,55,39,.62);
  }
  #intro .wd-p::before{background:rgba(30,55,39,.62);}

  /* THE RIGHT-HAND NODE IS THE TRAVELLER.
     The left plus is a post and never moves. The right one sits on the end of
     the line, so when --n grows it walks a whole column and the line draws
     out behind it — and it turns as it goes, which is what makes it read as
     rolling along the rule rather than being repositioned. One turn per
     column, on the same clock as the line, so the two finish together. */
  #intro .wd-rule.swap  .wd-p.r::before,
  #intro .wd-rule.swap  .wd-p.r::after{
    animation:wdSpin1 .85s cubic-bezier(.16,1,.3,1) both;
  }
  #intro .wd-rule.swapb .wd-p.r::before,
  #intro .wd-rule.swapb .wd-p.r::after{
    animation:wdSpin1r .85s cubic-bezier(.16,1,.3,1) both;
  }

  /* ---- more air, everywhere ---- */
  #intro .wd{
    --wd-gut:clamp(30px,3.2vw,68px);
    padding:calc(var(--m-rh)*.26) clamp(40px,4.6vw,104px) calc(var(--m-rh)*.30);
  }

  /* ==========================================================
     MEASURED OFF THE RECORDING

     Node size: in the reference the divisions sit at x 113, 805 and 1498 on a
     2462px capture — a 692px column — and each stem measures 26px tall. That
     is a 2x display, so the mark is THIRTEEN css pixels, not the twenty-one
     it had here. It is a small detail on a long rule, not a fixture.

     Pace: tracking the rule's right-hand end frame by frame, it crosses one
     column in roughly 3.8 seconds of scrolling, and — this is the telling
     part — it PLATEAUS whenever scrolling stops, holding at 2.04 columns from
     10.5s to 10.9s and at 3.04 from 14.3s to 15.1s. So the reference is
     scrubbed by scroll position rather than played on a timer.

     Ours advances a step per gesture, so it cannot scrub. What it can do is
     take much longer over each move, which is what makes the difference
     legible: every travel below is more than doubled and eased so it leaves
     quickly and arrives slowly, the shape a scrubbed motion has as the reader
     comes off a gesture.
     ========================================================== */
  #intro .wd-p{width:13px;height:13px;}
  #intro .wd-rule::before,#intro .wd-rule::after{width:13px;height:13px;}

  /* the rule and its end node, on one slow clock */
  #intro .wd-line{transition:width 2s cubic-bezier(.22,.61,.36,1);}
  #intro .wd-p.r{transition:left 2s cubic-bezier(.22,.61,.36,1);}
  #intro .wd-rule.swap  .wd-p.r::before,
  #intro .wd-rule.swap  .wd-p.r::after{
    animation:wdSpin1 2s cubic-bezier(.22,.61,.36,1) both;
  }
  #intro .wd-rule.swapb .wd-p.r::before,
  #intro .wd-rule.swapb .wd-p.r::after{
    animation:wdSpin1r 2s cubic-bezier(.22,.61,.36,1) both;
  }

  /* the columns travel on the same clock, so the row and the rule stay
     together rather than one finishing while the other is still moving */
  #intro .wd-step{
    transition:transform 2s cubic-bezier(.22,.61,.36,1),opacity 1.1s ease-out;
  }
  /* the divisions no longer pop — they ease up like everything else */
  #intro .wd-rule::before,#intro .wd-rule::after{transition:opacity 1.1s ease-out;}
  #intro .wd-p.l,#intro .wd-p.r{
    animation:wdPlusIn 1.1s cubic-bezier(.22,.61,.36,1) 7.05s both;
  }
  #intro[data-step="1"] .wd-rule::before,
  #intro[data-step="2"] .wd-rule::before,
  #intro[data-step="3"] .wd-rule::before,
  #intro[data-step="4"] .wd-rule::before,
  #intro[data-step="5"] .wd-rule::before,
  #intro[data-step="2"] .wd-rule::after,
  #intro[data-step="3"] .wd-rule::after,
  #intro[data-step="4"] .wd-rule::after,
  #intro[data-step="5"] .wd-rule::after{
    animation:wdPlusIn 1.1s cubic-bezier(.22,.61,.36,1) both;
  }
  /* and the first row arrives more slowly too */
  #intro.play .wd-steps{animation:wdRiseIn 2s cubic-bezier(.22,.61,.36,1) 8.2s both;}

  #intro .wd-steps{margin:calc(var(--m-rh)*.26) 0 0;}
  #intro .wd-step .wd-kicker{margin-top:calc(var(--m-rh)*.40);}
  #intro .wd-step .wd-head{margin-top:calc(var(--m-rh)*.26);line-height:1.3;}
  #intro .wd-step .wd-body{margin-top:calc(var(--m-rh)*.24);line-height:1.72;}
  #intro .wd-step .wd-body p+p{margin-top:1em;}
  #intro .wd-step .wd-shot{margin-top:calc(var(--m-rh)*.30);}
  #intro .wd-step .wd-foot{margin:auto 0 calc(var(--m-rh)*.30);}

  /* ==========================================================
     ARRIVING AND LEAVING, NOT SLIDING ACROSS

     A panel had only two states — waiting below, or in the row — so once it
     was in the row it stayed at full strength and simply translated. Past the
     third column that reads as a slab crossing the screen and overlapping its
     neighbours, which is what the screenshot shows.
     Three states now. It comes UP AND IN from the lower right, holds while it
     is one of the three live columns, and leaves DOWN AND OUT to the lower
     left. The horizontal term stays the track position; the two extra offsets
     ride on top of it, so entrance and exit are diagonal without disturbing
     where the column actually sits.
     ========================================================== */
  #intro .wd-step{
    transform:translate3d(calc((var(--i,0) - var(--o,0)) * 100% + var(--dx,0%)),
                          var(--y,0%),0);
    transition:transform 2s cubic-bezier(.22,.61,.36,1),opacity 1.4s ease-out;
  }
  /* still to come: below and a little to the right */
  #intro .wd-steps[data-step="0"] .wd-step:nth-child(n+2),
  #intro .wd-steps[data-step="1"] .wd-step:nth-child(n+3),
  #intro .wd-steps[data-step="2"] .wd-step:nth-child(n+4),
  #intro .wd-steps[data-step="3"] .wd-step:nth-child(n+5),
  #intro .wd-steps[data-step="4"] .wd-step:nth-child(n+6){
    --y:100%;--dx:9%;opacity:0;
  }
  /* gone past: dropping away to the lower left. The row offset has already
     carried them off the left edge, so this only has to take them down and
     fade them — otherwise they would still be there, at full strength,
     underneath the columns that replaced them. */
  #intro .wd-steps[data-step="3"] .wd-step:nth-child(-n+1),
  #intro .wd-steps[data-step="4"] .wd-step:nth-child(-n+2),
  #intro .wd-steps[data-step="5"] .wd-step:nth-child(-n+3){
    --y:62%;--dx:-9%;opacity:0;
  }

  /* ---- spacing, weight and measure ---- */

  /* the rule sits evenly between the copy above it and the steps below */
  #intro .wd-steps{margin:0;}
  #intro .wd-step .wd-kicker{margin-top:calc(var(--m-rh)*.26);}

  /* THE COLUMN WIDENS BY 15%, AND THE GUTTER PAYS FOR IT.

     The exact reverse of the earlier move, which took 15% off the measure and
     gave it to the white space. The BOX stays at 33.3333% deliberately: the
     track offsets each column by `--i * 100%` OF ITS OWN WIDTH, so widening
     the box would widen the spacing too and push the third column under
     .wd-steps' overflow. Everything the eye reads as the column is the box
     minus the gutter, so taking it out of the gutter widens the column
     without touching the track.

     Solving  W/3 - g_new = 1.15 * (W/3 - g_old)  across the range gives a
     near-constant 3.85-3.91vw, which is what the middle term is. Both ends
     are set where they stop mattering rather than where they look tidy: the
     floor is reached at 982px, a pixel above the breakpoint, and the cap
     only bites past ~2000px, where it eases off to +11% on columns already
     over 700px wide. Exactly 15% from 1000px to 1920px. */
  #intro .wd{--wd-gut:clamp(38px,3.87vw,78px);}

  /* THE CLOSING LINE IS THE PUNCH, NOT A CAPTION.
     This rule used to say the opposite, and that is what put the same
     element at opposite ends of the hierarchy in the two views: on a phone
     .wd-foot is 18px against .wd-head's 14px — the biggest thing in the
     column — while here it had been shrunk below the body copy.

     Mobile is the intended reading, so desktop follows it. The size is the
     phone's own ratio (18/14 = 1.286) applied term by term to the step
     heading's clamp(14px,1.15vw,22px), so the two stay in proportion at
     every width instead of being two figures that happen to agree at one. */
  #intro .wd-step .wd-foot{
    font-size:clamp(18px,1.48vw,28px);
    /* margin-top no longer auto: auto pinned it to the bottom of the column,
       which made the gap under the image depend on how long the copy above
       happened to run. Equal to the gap above the image instead. */
    margin:calc(var(--m-rh)*.30) 0 0;
  }

  /* Leading per virevo-type-scale.md rather than the tightened values that
     were here: General Copy is 1.55 (the mobile capture's wrapped lines
     settled that — 25px at 16px), and the Tile Heading is 1.20 on desktop. */
  #intro .wd-intro{line-height:1.55;}
  #intro .wd-intro p+p{margin-top:.55em;}
  #intro .wd-step .wd-head{line-height:1.20;}
  #intro .wd-step .wd-body{line-height:1.5;}
  #intro .wd-step .wd-body p+p{margin-top:.8em;}

  /* ==========================================================
     EVERY COLUMN ARRIVES THE SAME WAY, AND IN ORDER

     A panel's offsets live in custom properties, and an UNREGISTERED custom
     property is a raw token — it has no type, so it cannot be interpolated.
     The transform reading it therefore had nothing to animate between and
     each column after the first snapped into place. Registering --y and --dx
     as percentages gives them a type, and with a type they can transition —
     which is what makes the second, third and every later column rise the way
     the first one does.
     (The @property blocks sit at the end of this file, outside the query.)

     The opacity is put on the same 2s clock as the travel. At 1.4s it had
     finished while the column was still moving, so the fade read as a
     separate, faster event rather than part of the arrival. */
  /* THE ARRIVING COLUMN WAS THE ONE THAT COULD NOT ANIMATE.
     ".wd-step.on" comes from the phone build and sets its own transition —
     opacity, .38s, nothing else. It scores one id and two classes, so it beat
     every rule I had written at one id and one class, and `transition` is a
     shorthand: setting it replaces the whole list, transform included. The
     panel that had just been given .on is precisely the one arriving, which
     is why the first column eased and every column after it snapped.
     Restated here against both the plain and the .on panel, so the class can
     no longer strip the list out from under it. */
  #intro .wd-steps .wd-step,
  #intro .wd-steps .wd-step.on{
    transition:transform 2s cubic-bezier(.22,.61,.36,1),
               --y 2s cubic-bezier(.22,.61,.36,1),
               --dx 2s cubic-bezier(.22,.61,.36,1),
               opacity 2s cubic-bezier(.22,.61,.36,1);
  }

  /* AND THE ROW ITSELF HAS TO GLIDE.
     --o is how far the row has walked, and it is read by every panel's
     transform. It was changing in one jump the instant data-step changed, so
     from the fourth column on the whole row teleported a column to the left
     and the new panel appeared already in place — arriving "from the left",
     instantly, however carefully its own offsets were eased.
     Registered as a number and transitioned on the track, it now interpolates,
     and because it inherits, all six panels recompute from it every frame:
     the outgoing column slides off while the incoming one rises into the gap
     it leaves. */
  #intro .wd-steps{transition:--o 2s cubic-bezier(.22,.61,.36,1);}

  /* THE ROW MUST NOT PRECEDE THE RULE.
     .wd-steps is the one child the opening sequence leaves alone, so it was
     visible from the moment the section was — and if the play class arrived
     late, or the reader entered slowly, the columns were on screen before the
     line that is supposed to introduce them. Hiding it at rest means the
     order is guaranteed by the markup rather than by timing: line at 6.60s,
     nodes at 7.05s, and only then the row. */
  #intro .wd-steps{opacity:0;}
  #intro.play .wd-steps{animation:wdRiseIn 2s cubic-bezier(.22,.61,.36,1) 8.2s both;}

  /* ==========================================================
     EXECUTIVE DECISION OPTIMISATION — the phone build, widened

     Same markup, same engine, same eleven beats. What changes is that the
     phone sizes this section off vw — sensible when width is the scarce
     dimension — while on a desktop it is the HEIGHT that runs out first. So
     the type is driven off --m-rh, a seventh of the viewport height, with vw
     only ever acting as a cap.

     The seven-row canvas is kept so the lattice matches the sections above,
     and the stage is pinned with position:sticky rather than ScrollTrigger's
     pin, which injects a spacer div and would break the grid.
     ========================================================== */
  #chapter{
    --m-pad:10px;--m-div:4;--m-rh:calc(var(--vh)/7);
    /* the title's size, 20% up from the last step and still bounded by height
       with width as the cap, so the reduction ratio holds either way */
    --tf:min(calc(var(--m-rh)*.7836),5.8368vw);
    --ef:clamp(11px,.85vw,16px);   /* the caption's */
    --tmid:47%;                    /* where the stack is centred */
    position:relative;z-index:1;
    height:700vh;background:#c3c3c3;
  }
  #chapter .ch-stage{
    position:sticky;top:0;height:var(--vh);overflow:hidden;background:#c3c3c3;

    /* THE BLIND, ARRIVING.
       The same seven-band construction the Works stage uses, so the two
       hand-offs on this page read as one device rather than two ideas.
       Each --crN runs 1 -> 0 and its band fills upward from its own lower
       edge; staggered across the seven, that is a blind opening.

       Its own variable names, not --r0..--r6. Those are already declared
       on .wk-stage, and a custom property inherits — anything setting them
       on a shared ancestor would drive both stages at once. The prefix
       keeps the two independent.

       At rest all seven are 0, which is a fully opaque mask: a browser
       that never runs the script sees the ribbon normally rather than
       seeing nothing at all. */
    --cr0:0;--cr1:0;--cr2:0;--cr3:0;--cr4:0;--cr5:0;--cr6:0;
    -webkit-mask-image:
      linear-gradient(to bottom,transparent calc((0 + var(--cr0))*100%/7),#000 calc((0 + var(--cr0))*100%/7),#000 calc(1*100%/7),transparent calc(1*100%/7)),
      linear-gradient(to bottom,transparent calc((1 + var(--cr1))*100%/7),#000 calc((1 + var(--cr1))*100%/7),#000 calc(2*100%/7),transparent calc(2*100%/7)),
      linear-gradient(to bottom,transparent calc((2 + var(--cr2))*100%/7),#000 calc((2 + var(--cr2))*100%/7),#000 calc(3*100%/7),transparent calc(3*100%/7)),
      linear-gradient(to bottom,transparent calc((3 + var(--cr3))*100%/7),#000 calc((3 + var(--cr3))*100%/7),#000 calc(4*100%/7),transparent calc(4*100%/7)),
      linear-gradient(to bottom,transparent calc((4 + var(--cr4))*100%/7),#000 calc((4 + var(--cr4))*100%/7),#000 calc(5*100%/7),transparent calc(5*100%/7)),
      linear-gradient(to bottom,transparent calc((5 + var(--cr5))*100%/7),#000 calc((5 + var(--cr5))*100%/7),#000 calc(6*100%/7),transparent calc(6*100%/7)),
      linear-gradient(to bottom,transparent calc((6 + var(--cr6))*100%/7),#000 calc((6 + var(--cr6))*100%/7),#000 calc(7*100%/7),transparent calc(7*100%/7));
    mask-image:
      linear-gradient(to bottom,transparent calc((0 + var(--cr0))*100%/7),#000 calc((0 + var(--cr0))*100%/7),#000 calc(1*100%/7),transparent calc(1*100%/7)),
      linear-gradient(to bottom,transparent calc((1 + var(--cr1))*100%/7),#000 calc((1 + var(--cr1))*100%/7),#000 calc(2*100%/7),transparent calc(2*100%/7)),
      linear-gradient(to bottom,transparent calc((2 + var(--cr2))*100%/7),#000 calc((2 + var(--cr2))*100%/7),#000 calc(3*100%/7),transparent calc(3*100%/7)),
      linear-gradient(to bottom,transparent calc((3 + var(--cr3))*100%/7),#000 calc((3 + var(--cr3))*100%/7),#000 calc(4*100%/7),transparent calc(4*100%/7)),
      linear-gradient(to bottom,transparent calc((4 + var(--cr4))*100%/7),#000 calc((4 + var(--cr4))*100%/7),#000 calc(5*100%/7),transparent calc(5*100%/7)),
      linear-gradient(to bottom,transparent calc((5 + var(--cr5))*100%/7),#000 calc((5 + var(--cr5))*100%/7),#000 calc(6*100%/7),transparent calc(6*100%/7)),
      linear-gradient(to bottom,transparent calc((6 + var(--cr6))*100%/7),#000 calc((6 + var(--cr6))*100%/7),#000 calc(7*100%/7),transparent calc(7*100%/7));
  }
  /* the ribbon passes IN FRONT of the typography, and the lattice over both */
  #chapter .ch-canvas{
    position:absolute;inset:0;width:100%;height:100%;z-index:4;display:block;
  }
  #chapter .ch-overlay{position:absolute;inset:0;pointer-events:none;}
  /* The lattice is off here. It is still rendered rather than removed, so the
     canvas keeps the same stacking neighbours and nothing below it shifts —
     opacity:0 also takes the node squares with it, since they are drawn by
     this element's own ::after. */
  #chapter .hero-frame{
    display:block;position:absolute;inset:0 var(--m-pad);
    z-index:6;pointer-events:none;opacity:0;
    border:1px solid rgba(30,40,32,.10);
    background-origin:border-box;
    background-image:
      linear-gradient(to right,rgba(30,40,32,.10) 1px,transparent 1px),
      linear-gradient(to bottom,rgba(30,40,32,.10) 1px,transparent 1px);
    background-size:calc(100%/var(--m-div)) 100%,100% var(--m-rh);
  }
  #chapter .hero-frame::after{
    content:"";position:absolute;
    left:-3.25px;top:-3.25px;width:calc(100% + 6.5px);height:calc(100% + 6.5px);
    background-image:linear-gradient(to right,rgba(30,40,32,.18) 4.5px,transparent 4.5px);
    background-size:calc((100% - 4.5px)/var(--m-div)) var(--m-rh);
    -webkit-mask-image:linear-gradient(to bottom,#000 4.5px,transparent 4.5px);
            mask-image:linear-gradient(to bottom,#000 4.5px,transparent 4.5px);
    -webkit-mask-size:100% var(--m-rh);mask-size:100% var(--m-rh);
  }
  #chapter .hero-frame > *{display:none;}

  #chapter .ch-lede{
    position:absolute;left:50%;transform:translateX(-50%);
    top:calc(11.2% - 15px);margin:0;
    width:min(52ch,54%);text-align:center;
    font-family:'Poppins',var(--font-display);font-weight:400;
    font-size:clamp(14px,1.15vw,21px);line-height:1.45;color:rgba(30,40,32,.86);
  }
  /* 10.5vw is 200px of cap on a wide monitor and OPTIMISATION alone would
     run past both edges. Bounded by the row height, capped by width. */
  #chapter .ch-title{
    position:absolute;inset:0;margin:0;
    /* Bebas Neue, from the local file. Weight 400 because it ships one
       weight — anything heavier is a synthetic bold, which thickens and
       widens the letterforms until they stop being Bebas.
       The tracking is nearly nothing: -.035em was set for Poppins, whose caps
       are wide and want pulling in. Bebas is condensed and already tightly
       fitted, so the same negative value closes the letters into each other. */
    font-family:'Bebas Neue',var(--font-display);font-weight:400;
    font-size:var(--tf);
    line-height:1;letter-spacing:-.005em;
    text-transform:uppercase;color:rgba(46,50,45,.82);
  }
  /* CENTRE-STACKED, AND CENTRED BY LAYOUT RATHER THAN BY TRANSFORM.
     The obvious way to centre these is left:50% with a translateX(-50%) — but
     the driver writes transform on these two elements every frame to slide
     them in and dissolve them out, so a centring transform would be wiped on
     the first tick. Spanning the full width and centring the text instead
     leaves transform free for the motion. */
  #chapter .ch-w{
    position:absolute;left:0;right:0;display:block;white-space:nowrap;
    text-align:center;will-change:transform,opacity;
  }
  /* THE THREE LINES ARE PLACED BY ARITHMETIC, NOT BY THREE PERCENTAGES.
     Fixed percentages only balance at one viewport: the type is sized off the
     row height and the caption is not, so any pair of hand-picked tops drifts
     apart the moment the window changes — which is how the gaps ended up at
     88px above the caption and 23px below it.
     The three are now derived from the same two numbers. Bebas at
     line-height:1 puts its cap top .1em below the box top and its baseline at
     .8em, so with a chosen gap of .30em the placements fall out as:

       caption   mid - half the caption
       first     that, less 1.10em of title
       last      mid + .20em of title + half the caption

     which leaves exactly .30em of clear space above and below the caption at
     every size, by construction rather than by tuning. */
  /* BOTH HALVES SIT UNDER THE RIBBON.
     OPTIMISATION carried z-index:5, which put it above the canvas — that
     was right on the phone, where the film's far side sweeps the lower
     band and the word wants to be in front of it. Here the two words
     cross the frame and the band turns between them, so anything above
     the canvas stops being part of the scene and becomes a caption laid
     over it. Neither declares a z-index now, so both fall below. */
  #chapter .ch-w1{top:calc(var(--tmid) - var(--tf) * 1.10 - var(--ef) * .71);}
  #chapter .ch-w2{
    top:calc(var(--tmid) + var(--tf) * .20 + var(--ef) * .71);
    right:0;
  }
  #chapter .ch-eyebrow{
    position:absolute;left:0;right:0;text-align:center;
    top:calc(var(--tmid) - var(--ef) * .71);
    font-family:var(--font-mono);
    font-size:var(--ef);line-height:1.42;letter-spacing:.13em;
    text-transform:uppercase;color:rgba(30,40,32,.66);will-change:opacity;font-weight:600;}
  #chapter .ch-cta{
    position:absolute;left:50%;transform:translateX(-50%);top:87.2%;
    display:inline-flex;align-items:center;gap:10px;
    padding-bottom:7px;border-bottom:1px solid rgba(30,40,32,.4);
    pointer-events:auto;white-space:nowrap;
    font-family:var(--font-mono);
    font-size:clamp(10px,.72vw,14px);letter-spacing:.16em;
    text-transform:uppercase;color:rgba(30,40,32,.9);font-weight:600;}
  #chapter .ch-cta i{font-style:normal;}
  #chapter .ch-a11y{
    position:absolute;width:1px;height:1px;overflow:hidden;
    clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;
  }
  /* the click targets are placed by the engine, in page pixels, on the cards
     as they land — nothing here needs a width of its own */
  #chapter .ch-hits{position:absolute;inset:0;z-index:7;pointer-events:none;}
  #chapter .ch-hits button{
    position:absolute;margin:0;padding:0;border:0;background:transparent;
    cursor:pointer;pointer-events:auto;opacity:0;
    -webkit-tap-highlight-color:transparent;transition:opacity .25s ease;
  }
  #chapter .ch-hits.live button{opacity:1;}
  #chapter .ch-hits button:focus-visible{
    outline:2px solid rgba(30,40,32,.85);outline-offset:3px;
  }
  #chapter .ch-hits:not(.live) button{pointer-events:none;}
  #chapter .ch-loader{
    position:absolute;inset:0;z-index:8;background:#c3c3c3;
    display:flex;align-items:center;justify-content:center;
    font-family:var(--font-mono);
    font-size:11px;letter-spacing:.24em;text-transform:uppercase;
    color:rgba(30,40,32,.55);transition:opacity .6s ease;font-weight:600;}
  #chapter .ch-loader.gone{opacity:0;pointer-events:none;}

  /* ==========================================================
     THE STEP'S PICTURE IS A CLIP NOW

     .wd-shot was a BAND: full column width at a fixed height, with the
     image cropped to fill it. That works for a still, where the crop is
     just a choice of framing. It does not work for these clips — the
     source is 900x674, the band was roughly 2:1, so cover would have
     taken the top and bottom off and cut the character's head.

     So the box is sized by HEIGHT and takes its width from the source
     ratio, which means no crop at all. align-self is the necessary part:
     a column flex container stretches its children across, and that
     stretch would beat an auto width and re-break the ratio.
     ========================================================== */
  #intro .wd-step .wd-shot{
    /* 0 1 auto, not 0 0 auto — the clip is the block that GIVES WAY. The
       column is an absolutely positioned box with top and bottom both set,
       so its height is definite and flex-shrink genuinely applies; letting
       the clip shrink means a short window costs picture, not a footer
       clipped off by .wd-steps' overflow. Which is what lets the height
       below be the size wanted on a good screen rather than the largest
       size that is safe on a bad one. */
    flex:0 1 auto;min-height:0;
    align-self:flex-start;
    /* +20% on the width. The box is still driven by HEIGHT with the width
       falling out of the ratio, so the figure below is the width increase
       expressed where it can be applied without distorting the source —
       set the width directly and the 900x674 no longer fits, and object-fit
       goes back to cropping the character's head. */
    height:clamp(132px,calc(var(--m-rh)*1.86),263px);
    width:auto;aspect-ratio:900/674;max-width:100%;
    /* trimmed from .28 to buy back some of the height the clip just took,
       so the increase lands on the picture rather than on the shrink */
    margin:calc(var(--m-rh)*.20) 0 0;
    border-radius:4px;overflow:hidden;
  }
  #intro .wd-step .wd-shot img,
  #intro .wd-step .wd-shot video{
    width:100%;height:100%;object-fit:cover;display:block;
  }

  /* The clip sits above the copy, so the copy's top margin is now measuring
     from the picture rather than from the headline — and the headline's
     generous gap under it was tuned for type meeting type. Closed up, and
     the footer keeps its own gap below the copy. */
  #intro .wd-step .wd-body{margin-top:calc(var(--m-rh)*.26);}
}

@keyframes wdSpin1 { from{transform:rotate(0deg);}   to{transform:rotate(360deg);} }
@keyframes wdSpin1r{ from{transform:rotate(0deg);}   to{transform:rotate(-360deg);} }

@keyframes wdRiseIn{
  from{opacity:0;transform:translate3d(-6%,14%,0);}
  to  {opacity:1;transform:translate3d(0,0,0);}
}
/* the divisions draw themselves on rather than simply appearing */
@keyframes wdPlusIn{
  from{opacity:0;transform:translate(-50%,-50%) scale(.4);}
  to  {opacity:1;transform:translate(-50%,-50%) scale(1);}
}

/* ============================================================
   CARD VIDEO PLAYER
   Outside every section's stacking context on purpose — see the note on the
   markup. Fixed, full-screen, and it owns the scroll while it is open.
   ============================================================ */
.vb{
  position:fixed;inset:0;z-index:120;
  display:flex;align-items:center;justify-content:center;
  /* Half-transparent, with the blur doing the separating instead of the ink.
     The grid stays legible behind the clip, which keeps the player feeling
     like a layer over the page rather than a different screen. The blur is
     what makes it readable at this opacity — at 50% with no blur the card
     artwork competes with the video for attention. */
  /* Light enough to read the grid through, and the blur comes down with it —
     18px of blur dissolved the cards into a wash, so lowering only the ink
     would not have brought them back. At 7px the artwork stays recognisable
     while still sitting clearly behind the clip. */
  background:rgba(18,20,18,.18);
  opacity:0;transition:opacity .36s ease;
  -webkit-backdrop-filter:blur(7px);backdrop-filter:blur(7px);
}
/* Safari on older iOS ignores backdrop-filter; with no blur at all, 18% ink
   leaves the page competing with the clip, so those get a heavier wash. */
@supports not ((backdrop-filter:blur(2px)) or (-webkit-backdrop-filter:blur(2px))){
  .vb{background:rgba(18,20,18,.6);}
}
.vb[hidden]{display:none;}
.vb.open{opacity:1;}

.vb-sheet{
  position:relative;width:min(94vw,1100px);
  display:flex;flex-direction:column;align-items:center;gap:12px;
  transform:scale(.97);transition:transform .28s cubic-bezier(.16,1,.3,1);
}
.vb.open .vb-sheet{transform:scale(1);}

/* THE BOX MUST BE THE VIDEO, not a stage the video sits inside.
   width:100% with a max-height forced a landscape-shaped element around a
   portrait clip, and the difference showed as the black bars down the sides
   and under it — that black was the element's own background behind a
   letterboxed picture, not part of the film.
   Leaving both dimensions auto under a pair of maximums lets the browser
   scale the clip to fit and size the element to the RESULT, so the box hugs
   the picture at any aspect ratio and there is nothing left over to show. */
.vb-video{
  display:block;width:auto;height:auto;
  max-width:100%;max-height:78vh;
  background:transparent;
  /* The radius has to clip the painted frame, not just the element box: a
     video paints its picture over its own background, so a plain
     border-radius leaves square corners showing through. overflow:hidden on
     a replaced element does nothing either — the mask is what actually
     rounds the picture, and the radius is repeated on it so the shadow
     follows the same shape. */
  border-radius:10px;overflow:hidden;
  -webkit-mask-image:-webkit-radial-gradient(#fff,#000);   /* Safari clip fix */
  box-shadow:0 24px 70px rgba(0,0,0,.45);
}
.vb-title{
  margin:0;text-align:center;
  font-family:var(--font-mono);
  font-size:clamp(10px,2.6vw,13px);letter-spacing:.16em;
  text-transform:uppercase;color:#f6f2e9;
  /* the backdrop is light enough now that pale type needs its own contrast */
  text-shadow:0 1px 10px rgba(10,12,10,.85),0 0 3px rgba(10,12,10,.7);font-weight:600;}

/* Close sits in the top-left corner of the SCREEN, not of the video, so it
   stays put whatever shape the clip turns out to be. */
.vb-x,.vb-nav{
  position:absolute;display:flex;align-items:center;justify-content:center;
  padding:0;border:1px solid rgba(246,242,233,.32);border-radius:50%;
  background:rgba(18,20,18,.55);color:#f6f2e9;cursor:pointer;
  -webkit-tap-highlight-color:transparent;
  transition:background .2s ease,border-color .2s ease,transform .2s ease;
}
.vb-x:hover,.vb-nav:hover,
.vb-x:focus-visible,.vb-nav:focus-visible{
  background:rgba(246,242,233,.16);border-color:rgba(246,242,233,.6);
}
.vb-x{
  top:max(14px,env(safe-area-inset-top));left:14px;
  width:44px;height:44px;font-size:26px;line-height:1;
}
/* Vertically centred on the viewport rather than the sheet: the arrows must
   not jump about as clips of different aspect ratios load. */
.vb-nav{
  top:50%;margin-top:-24px;width:48px;height:48px;
  font-size:30px;line-height:1;
}
.vb-nav.prev{left:10px;}
.vb-nav.next{right:10px;}
.vb-nav:active{transform:scale(.94);}

@media (max-width:520px){
  .vb-sheet{width:96vw;}
  .vb-nav{width:40px;height:40px;margin-top:-20px;font-size:25px;}
  .vb-nav.prev{left:6px;}
  .vb-nav.next{right:6px;}
  .vb-x{width:40px;height:40px;font-size:23px;left:10px;}
}
@media (prefers-reduced-motion:reduce){
  .vb,.vb-sheet{transition:none;}
}

/* Typed so they can be interpolated. Without a registered syntax a custom
   property is an untyped token: the transform that reads it recomputes, but
   there is nothing to animate BETWEEN, so the column jumps. Declared outside
   the media query because @property is a document-level rule; only the
   desktop panels reference them. */
@property --y{  syntax:'<percentage>'; inherits:false; initial-value:0%; }
@property --dx{ syntax:'<percentage>'; inherits:false; initial-value:0%; }
/* inherits, because every panel reads the row offset set on the track */
@property --o{  syntax:'<number>';     inherits:true;  initial-value:0;  }

/* ============================================================
   WORKS — hoisted out of the desktop media query.
   Every selector here is #works or .wk-, so it cannot reach another
   section; it sat inside min-width:981px only because that was the
   only width it was needed at. The phone needs the same construction
   with different proportions, and duplicating a hundred lines to get
   it would guarantee the two drift apart. Shared here, and each
   breakpoint below overrides only the numbers that differ.
   ============================================================ */
/* ==========================================================
   WORKS — A HORIZONTAL RAIL DRIVEN BY VERTICAL SCROLL

   The section is tall and empty; everything visible lives in .wk-stage,
   which sticks to the top for the whole of that height. The page scrolls
   normally the entire time — what the reader reads as sideways movement
   is the rail being translated left in proportion to how far through the
   section they are. Nothing here hijacks the scroll.

   Sticky rather than ScrollTrigger's pin, for the reason established in
   #chapter: pin injects a pin-spacer div into the flow and the surrounding
   grid stops adding up.

   The height in CSS is a FALLBACK only. The real height depends on how
   wide the rail turns out to be, which cannot be known until the cards
   have laid out, so works.js measures and sets it. If the script never
   runs the section is still a sane length and still scrolls past.
   ========================================================== */
#works{
  display:block;position:relative;z-index:2;
  height:600vh;                       /* replaced by works.js on load */
  background:var(--ind-bg,#ececeb);
}
.wk-stage{
  position:sticky;top:0;
  height:100vh;height:var(--vh);
  overflow:hidden;
  display:flex;align-items:center;

  /* THE BLIND. Seven horizontal bands, the same construction the EDO
     stage uses to close itself — reused rather than reinvented so the
     two hand-offs are visibly the same device. Each --rN runs 1 → 0 and
     its band fills upward from its own lower edge; staggered across the
     seven, that reads as a blind opening. At rest all seven are 0, which
     is a fully opaque mask, so a browser that never runs the script sees
     the section normally rather than seeing nothing. */
  --r0:0;--r1:0;--r2:0;--r3:0;--r4:0;--r5:0;--r6:0;
  -webkit-mask-image:
    linear-gradient(to bottom,transparent calc((0 + var(--r0))*100%/7),#000 calc((0 + var(--r0))*100%/7),#000 calc(1*100%/7),transparent calc(1*100%/7)),
    linear-gradient(to bottom,transparent calc((1 + var(--r1))*100%/7),#000 calc((1 + var(--r1))*100%/7),#000 calc(2*100%/7),transparent calc(2*100%/7)),
    linear-gradient(to bottom,transparent calc((2 + var(--r2))*100%/7),#000 calc((2 + var(--r2))*100%/7),#000 calc(3*100%/7),transparent calc(3*100%/7)),
    linear-gradient(to bottom,transparent calc((3 + var(--r3))*100%/7),#000 calc((3 + var(--r3))*100%/7),#000 calc(4*100%/7),transparent calc(4*100%/7)),
    linear-gradient(to bottom,transparent calc((4 + var(--r4))*100%/7),#000 calc((4 + var(--r4))*100%/7),#000 calc(5*100%/7),transparent calc(5*100%/7)),
    linear-gradient(to bottom,transparent calc((5 + var(--r5))*100%/7),#000 calc((5 + var(--r5))*100%/7),#000 calc(6*100%/7),transparent calc(6*100%/7)),
    linear-gradient(to bottom,transparent calc((6 + var(--r6))*100%/7),#000 calc((6 + var(--r6))*100%/7),#000 calc(7*100%/7),transparent calc(7*100%/7));
  mask-image:
    linear-gradient(to bottom,transparent calc((0 + var(--r0))*100%/7),#000 calc((0 + var(--r0))*100%/7),#000 calc(1*100%/7),transparent calc(1*100%/7)),
    linear-gradient(to bottom,transparent calc((1 + var(--r1))*100%/7),#000 calc((1 + var(--r1))*100%/7),#000 calc(2*100%/7),transparent calc(2*100%/7)),
    linear-gradient(to bottom,transparent calc((2 + var(--r2))*100%/7),#000 calc((2 + var(--r2))*100%/7),#000 calc(3*100%/7),transparent calc(3*100%/7)),
    linear-gradient(to bottom,transparent calc((3 + var(--r3))*100%/7),#000 calc((3 + var(--r3))*100%/7),#000 calc(4*100%/7),transparent calc(4*100%/7)),
    linear-gradient(to bottom,transparent calc((4 + var(--r4))*100%/7),#000 calc((4 + var(--r4))*100%/7),#000 calc(5*100%/7),transparent calc(5*100%/7)),
    linear-gradient(to bottom,transparent calc((5 + var(--r5))*100%/7),#000 calc((5 + var(--r5))*100%/7),#000 calc(6*100%/7),transparent calc(6*100%/7)),
    linear-gradient(to bottom,transparent calc((6 + var(--r6))*100%/7),#000 calc((6 + var(--r6))*100%/7),#000 calc(7*100%/7),transparent calc(7*100%/7));
}

/* ---- the rule and the node that cap the section ----
   The horizontal line runs the full width; the node sits on the centre,
   which is the same x the divider below descends from. Two 1px bars
   rather than a glyph, so it matches the lattice nodes elsewhere on the
   site and stays exactly 1px at any zoom. */
#works .wk-top{
  position:absolute;top:0;left:0;right:0;height:0;z-index:4;
  border-top:1px solid rgba(0,0,0,.13);
}
#works .wk-top i{
  position:absolute;top:0;left:50%;width:13px;height:13px;
  transform:translate(-50%,-50%);
}
#works .wk-top i::before,
#works .wk-top i::after{
  content:"";position:absolute;top:50%;left:50%;
  background:rgba(0,0,0,.42);
  transform:translate(-50%,-50%);
}
#works .wk-top i::before{width:13px;height:1px;}
#works .wk-top i::after {width:1px;height:13px;}

.wk-rail{
  --wk-gap:clamp(30px,3.2vw,72px);
  --wk-pad:clamp(28px,3.4vw,78px);
  --wk-h:clamp(240px,44vh,440px);     /* the card picture's height */
  position:relative;z-index:1;
  /* FULL STAGE HEIGHT, with each item stretched to match and its content
     centred inside. The alternative — centring the items themselves —
     leaves every item only as tall as its own content, and the dividing
     line between two of them then has no honest height to take. This way
     a line is simply top:0;bottom:0 of its own item. */
  height:100vh;height:var(--vh);
  display:flex;align-items:stretch;
  gap:var(--wk-gap);
  padding:0 var(--wk-pad);
  will-change:transform;
  /* one number, set by the script, in pixels */
  transform:translate3d(calc(var(--x,0px) * -1),0,0);
}
.wk-rail > *{
  position:relative;flex:0 0 auto;
  display:flex;flex-direction:column;justify-content:center;
  align-items:flex-start;
  /* the arrival: up from the lower right, set per item by the script */
  transform:translate3d(var(--cx,0px),var(--cy,0px),0);
}

/* The dividing lines belong to the ITEMS, not to the stage — in the
   reference they travel with the content rather than standing still while
   it passes, which is what makes the rail read as a strip being drawn past
   rather than a window being panned across.

   Two corrections ride on the transform. The counter-translate undoes the
   item's own arrival, so the line stays put while the card it belongs to
   rises into place — a line that flew up with the card would read as part
   of the card. The scaleY is the divider drawing itself down from the node
   as the section arrives; only the first line is on screen at that point,
   which is what makes it read as the screen being halved. */
.wk-rail > * + *::before{
  content:"";position:absolute;
  left:calc(var(--wk-gap) / -2);
  top:0;bottom:0;width:1px;
  background:rgba(0,0,0,.13);
  transform-origin:50% 0;
  transform:translate3d(calc(var(--cx,0px) * -1),calc(var(--cy,0px) * -1),0)
            scaleY(var(--sy,1));
}

/* ---- the opening and closing panels ----
   The opening panel is measured, not chosen: its width is whatever puts
   the first dividing line on the exact centre of the stage, so the screen
   reads as two halves — heading left, first card right. --half is the
   stage's real half-width, published by the script, because 50vw includes
   the scrollbar and would sit the line a few pixels off centre. */
.wk-panel{
  width:clamp(340px,36vw,600px);
  padding-right:clamp(20px,2.4vw,56px);
}
.wk-open{
  width:calc(var(--half,50vw) - var(--wk-pad) - var(--wk-gap) / 2);
}
.wk-eyebrow{
  margin:0 0 clamp(10px,1vw,20px);
  /* 400, not 600 — the local face is the Regular weight registered across
     100-900, so anything heavier is synthesised by the browser, and a
     faux-bold on a condensed face thickens the stems without narrowing
     the counters. It reads as a smeared version of itself. */
  font-family:'Bebas Neue',var(--font-display);font-weight:400;
  font-size:15vw;line-height:1;letter-spacing:.03em;
  color:#4e5848;
}
.wk-title{
  font-family:'Poppins',var(--font-display);font-weight:300;
  font-size:clamp(34px,4.1vw,74px);line-height:1.04;letter-spacing:-.015em;
  color:#2f2f2f;
}
/* THE CLOSING PANEL CARRIES A WASH.
   Every other panel on the rail sits on the section's flat white. This one
   is the last thing on screen before the hand-off, and the reference lifts
   it off the page with a soft diagonal gradient — lightest at the top
   right, settling to a warm grey at the lower left. It is painted on the
   panel rather than the stage so it travels in with the panel instead of
   being a fixed backdrop the rail slides across. It is also drawn wider
   than the panel and bled off the top and bottom, so no edge of the wash
   is ever visible: what the reader sees is a change in the light, not a
   rectangle with a gradient in it. */
/* THE CLOSING PANEL CARRIES A WASH.

   Painted straight onto the panel, not onto a pseudo-element behind it.
   Two attempts at the pseudo failed for different stacking reasons, and
   the element's own background sidesteps the question altogether: there is
   no layer to get the order wrong between. It is also the honest place for
   it — the wash belongs to this panel and travels with it.

   The panel is much wider here than the opening one. In the reference the
   wash is not a tinted card sitting in the middle of the screen, it is the
   whole right-hand side changing colour as the rail runs out, so the panel
   has to be wide enough that its edges are never both in frame at once. */
/* The closing panel holds no wash of its own. It carried one, and that was
   the mistake: a gradient the width of a panel inside a white stage reads
   as a tinted rectangle with an edge, and the edge is the first thing the
   eye finds. The wash belongs to the STAGE — see below — so it is one
   continuous field with no internal boundary anywhere. */
.wk-close{
  position:relative;
  width:clamp(620px,62vw,1180px);
  align-items:center;text-align:center;
}
.wk-out{
  font-family:'Poppins',var(--font-display);font-weight:300;
  font-size:clamp(17px,1.7vw,30px);line-height:1.34;color:#3a3a3a;
  max-width:22ch;
}
.wk-panel .wx-link{margin-top:clamp(22px,2.6vw,52px);}

/* ---- a card ----
   ONE HALF OF THE SCREEN, exactly. The card was sized from its picture
   height before, which made its width a consequence of how tall the
   window happened to be — on a wide, short screen that left a quarter of
   the right half empty and let the NEXT card show its edge, which is the
   one thing the opening frame must not do. It is sized from --half now,
   so slot plus its two half-gaps come to precisely 50%.

   The second term is the guard. A very wide, very short window would ask
   for a card taller than the stage; capping the width by the height that
   is actually available keeps the picture and its three lines of text
   inside the frame, and the picture simply gets smaller instead of being
   cut off. 76vh is the stage less the text block beneath it. */
.wx-card{
  width:min(calc(var(--half,50vw) - var(--wk-gap)),
            calc((var(--vh) * 0.76) * 1.5));
}
.wx-shot{
  width:100%;aspect-ratio:3 / 2;      /* the source is 900x600 */
  overflow:hidden;border-radius:4px;
  /* NO GROUND BEHIND THE PICTURE.
     This was #eee, from before the corners were cut out of the artwork.
     Now that the images carry real transparency, that grey is exactly
     what shows through the rounded corners — which is why the top two
     read as solid while the picture itself is transparent. Nothing
     behind it now, so the section's own ground comes through. */
  background:transparent;
}
.wx-shot img{width:100%;height:100%;object-fit:cover;display:block;}
.wx-card h3{
  margin:clamp(14px,1.4vw,26px) 0 0;
  font-family:'Poppins',var(--font-display);font-weight:400;
  font-size:clamp(16px,1.35vw,24px);line-height:1.25;color:#2f2f2f;
}
.wx-card p{
  margin:.55em 0 0;max-width:32ch;
  font-family:'Poppins',var(--font-display);font-weight:300;
  font-size:clamp(11px,.88vw,15px);line-height:1.55;color:#6a6a6a;
}
.wx-card .wx-link{margin-top:clamp(12px,1.2vw,22px);}

/* ---- the underlined link, shared ---- */
.wx-link{
  display:inline-flex;align-items:center;gap:clamp(18px,2vw,44px);
  padding-bottom:.65em;
  border-bottom:1px solid rgba(0,0,0,.3);
  font-family:'Poppins',var(--font-display);font-weight:400;
  font-size:clamp(10px,.76vw,13px);line-height:1.2;letter-spacing:.14em;
  text-transform:uppercase;color:#3a3a3a;text-decoration:none;
  transition:color .3s ease,border-color .3s ease;
}
.wx-link i{font-style:normal;transition:transform .35s cubic-bezier(.16,1,.3,1);}
.wx-link:hover{color:#000;border-color:rgba(0,0,0,.6);}
.wx-link:hover i{transform:translateX(5px);}
.wx-link.sm{gap:clamp(12px,1.4vw,30px);}
/* the CTAs are buttons, not links — they open a panel rather than going
   anywhere, and a control that does not navigate should not be an <a> */
button.wx-link{background:none;border-width:0 0 1px;cursor:pointer;text-align:left;}
/* one card carries the label as a caption with nothing behind it */
.wx-link.static{cursor:default;color:#6a6a6a;border-bottom-color:rgba(0,0,0,.16);}

/* ============================================================
   WORKS ON A PHONE
   ------------------------------------------------------------
   The same rail, re-proportioned. Two things differ from the wide
   build, and both follow from there being no second half of the
   screen to put anything in.

   THE OPENING PANEL DOES NOT TRAVEL. On desktop it is the first
   item on the strip and slides away with everything else. Here it
   is fixed to the top quarter and stays for the whole section, so
   the reader always has the heading in view while the cards pass
   underneath it.

   Holding it still takes a counter-translate rather than a fixed
   position. The rail carries a transform, and a transformed
   ancestor becomes the containing block for everything inside it —
   position:fixed included — so a fixed child would still ride along.
   Moving it right by exactly what the rail moves left cancels out,
   and costs nothing, because both read the same variable.
   ============================================================ */
@media (max-width:980px){
  #works{display:block;}

  .wk-rail{
    --wk-gap:26px;
    --wk-pad:22px;
  }

  /* out of the flex flow, so the strip begins at the first card
     rather than a screen and a half to its right.

     It carries its own padding rather than inheriting the rail's: an
     absolutely positioned child is placed against the rail's PADDING BOX,
     so left:0 put the heading hard against the screen edge while every
     card sat properly inset. */
  .wk-open{
    position:absolute;left:0;top:0;
    width:100vw;height:25%;
    padding:14px var(--wk-pad) 18px;
    justify-content:center;
    transform:translate3d(var(--x,0px),0,0);   /* cancels the rail */
    z-index:2;
  }
  /* the line under it reads as the same division the wide build
     makes vertically — the heading's quarter, then the cards */
  .wk-open::after{
    content:"";position:absolute;left:0;bottom:0;
    width:100vw;height:1px;background:rgba(0,0,0,.13);
  }
  .wk-eyebrow{font-size:15vw;margin-bottom:9px;}
  .wk-title{font-size:29px;letter-spacing:-.01em;}
  .wk-panel .wx-link{margin-top:20px;}

  /* the quarter above is spoken for, so the cards centre in what is
     left. A percentage padding would resolve against WIDTH, which on
     a phone is the one measurement that would make this wrong. */
  .wk-rail > *:not(.wk-open){
    padding-top:calc(var(--vh) * .25);
  }

  /* one per screen, with the next just off the edge */
  /* The picture now fills the card edge to edge. That is the end of this
     particular lever: any further increase has to come from the CARD, and
     the card is already 85% of the screen, so it would start eating the
     sliver of the next one that tells the reader there is more to come. */
  #works .wx-shot{width:100%;}
  .wx-card{width:calc(100vw - var(--wk-pad) * 2 - 26px);}
  /* On the same scale as the equivalent content elsewhere on the phone: a
     titled block with a picture is .wd-head at 14px over .wd-body at 10px,
     and these were 19 and 13 — the largest body copy on the page by some way. */
  .wx-card h3{font-size:14px;margin-top:12px;}
  .wx-card p{font-size:10px;max-width:none;}
  .wx-link{font-size:10px;letter-spacing:.12em;}
  .wx-card .wx-link{margin-top:12px;}

  .wk-close{width:calc(100vw - var(--wk-pad) * 2 - 26px);}
  .wk-out{font-size:19px;max-width:none;}

  /* the divider belongs to the heading here, not to the first card */
  .wk-rail > *:nth-child(2)::before{display:none;}

  /* ---- THE DIVIDER GROWS UPWARD, AND ITS NODE LEADS IT ----

     On a wide screen the line hangs from the rule across the top of the
     section and draws downward. There is no such rule here — the heading
     occupies the top quarter and the line would strike straight through
     it — so it starts at the foot of that quarter instead and climbs from
     the bottom of the frame to meet it.

     Two consequences, both handled below. The origin has to move to the
     line's FOOT, or scaling it would still grow it from the top. And the
     node has to travel: on the wide build it marks a fixed junction, but
     a line that is drawing itself has a moving tip, and a node left at
     the end point would sit in empty space until the line arrived. */
  .wk-rail > * + *::before{
    top:calc(var(--vh) * .25);bottom:0;
    transform-origin:50% 100%;
  }
  .wk-rail > * + *::after{
    content:"";position:absolute;
    left:calc(var(--wk-gap) / -2);
    top:calc(var(--vh) * .25);
    width:11px;height:11px;margin:-5.5px 0 0 -5.5px;
    /* two hairlines crossing, rather than a "+" glyph — a glyph sits off
       centre in its em box and changes shape with the font */
    background:
      linear-gradient(rgba(0,0,0,.42),rgba(0,0,0,.42)) center/11px 1px no-repeat,
      linear-gradient(rgba(0,0,0,.42),rgba(0,0,0,.42)) center/1px 11px no-repeat;
    /* first the same counter-translate the line takes, so the node does not
       ride up with an arriving card; then down by however much of the line
       is still to be drawn, which puts it exactly on the growing tip */
    transform:translate3d(calc(var(--cx,0px) * -1),calc(var(--cy,0px) * -1),0)
              translateY(calc(var(--vh) * .75 * (1 - var(--sy,1))));
    pointer-events:none;
  }

  /* ---- the ribbon keeps its scene, not its lattice ----
     opacity rather than display:none, and on the frame rather than on its
     parts: the grid lines are a background-image on this element and the
     nodes are its ::after, so there is no single property that reaches
     both. Taking the element's opacity to 0 also takes the 1px outer
     border with it, which is the same lattice seen edge-on. The element
     stays in place and still costs nothing — it is already
     pointer-events:none — so putting it back is one line. */
  #chapter .hero-frame{opacity:0;}

  /* the top rule caps the section as it does on the wide build */
  #works .wk-top i{width:11px;height:11px;}
  #works .wk-top i::before{width:11px;}
  #works .wk-top i::after{height:11px;}
}

/* ============================================================
   INDUSTRIES ON DESKTOP — pass one: the gates and the top block
   ------------------------------------------------------------
   #edo is a plain container at both widths now. Its own canvas is off and
   #speed with it, so opening it here opens exactly one thing: Industries.
   The driver in main.js already sizes and runs it; the two width tests
   that used to hold it to the phone are gone, and the phases it no longer
   needs return zero on their own because their elements are hidden.

   The LAYOUT needed almost nothing. Industries is built on a nine-row,
   four-column canvas keyed to a ninth of the viewport, so it re-proportions
   itself as the screen grows — the grid is already relative. What does not
   scale is the TYPE and the fixed pixel details inside the cards: 7px notes
   and a 52px badge are sized for a hand, and on a monitor they read as a
   phone screenshot pasted into the page. Those are what this block resets,
   and only those.
   ============================================================ */
@media (min-width:981px){
  #edo{display:block;position:relative;z-index:2;/* THE GROUND UNDER INDUSTRIES, NOT THE PAGE'S.
     #edo was transparent, so wherever Industries had not yet covered
     it — the strip above its sticky panel as it rises, and the space
     the Works slab uncovers — what showed through was the body's warm
     cream against Industries' neutral grey. That is the band in the
     top corner. It carries Industries' own colour now, so the two are
     the same surface and the join cannot be seen.
     Declared as the variable as well as used, so #industries inherits
     one base value; the driver still overrides it on #industries alone
     when the panels swap. */
  --ind-bg:#ececeb;background:var(--ind-bg);
}
  #industries{
    --m-div:4;--m-rh:calc(100vh/9);--m-rh:calc(var(--vh)/9);
    display:grid;position:sticky;top:0;z-index:0;
    height:100vh;height:var(--vh);
    grid-template-columns:repeat(4,minmax(0,1fr));grid-auto-rows:var(--m-rh);
    padding:0 clamp(28px,3.4vw,78px);gap:0;
    background:var(--ind-bg,#ececeb);overflow:hidden;
    opacity:var(--ind-op,1);
  }
  #industries .hero-frame{display:none;}

  /* the two halves of the heading, on the row they share */
  #industries .ind-a,#industries .ind-b{
    z-index:4;margin:0;
    font-family:'Bebas Neue',var(--font-display);font-weight:400;
    font-size:calc(var(--m-rh)*.62);line-height:.86;letter-spacing:.012em;
    text-transform:uppercase;white-space:nowrap;
  }
  #industries .ind-a{
    grid-column:1/4;grid-row:1;color:var(--ink);
    align-self:start;margin-top:calc(var(--m-rh)*.14 - .07em);
  }
  #industries .ind-b{
    grid-column:2/5;grid-row:1;color:#fff;text-align:right;
    align-self:end;margin-bottom:calc(2px - .16em);
  }
  #industries .ind-intro{
    grid-column:1/-1;grid-row:2;z-index:4;align-self:center;margin:0;
    text-align:center;padding:0 12px;
    font-family:'Poppins',var(--font-display);font-weight:300;
    font-size:clamp(12px,.95vw,17px);line-height:1.62;color:rgba(30,55,39,.72);
  }

  /* ---- the three cards across the top of each panel ----
     The row keeps its shape; every measurement inside it that was fixed in
     pixels is re-stated proportionally so the trio grows with the screen
     instead of sitting small in the corner of it. */
  #industries .ind-cards{gap:clamp(12px,1.3vw,28px);padding:calc(var(--m-rh)*.55) 0 calc(var(--m-rh)*.66);}
  #industries .ind-card{border-radius:4px;}
  #industries .ic2{padding:clamp(16px,1.7vw,34px) clamp(12px,1.2vw,24px);}
  #industries .ic3{padding:clamp(14px,1.5vw,30px) clamp(12px,1.2vw,24px);gap:clamp(10px,1vw,20px);}
  #industries .ic-kicker{font-size:clamp(9px,.72vw,13px);letter-spacing:.11em;}
  #industries .ic-icon{
    width:clamp(60px,5.2vw,104px);height:clamp(60px,5.2vw,104px);
  }
  #industries .ic-icon svg{width:50%;height:50%;}
  #industries .ic-note{font-size:clamp(10px,.8vw,15px);line-height:1.5;}
  #industries .ic-foot{gap:clamp(8px,.9vw,18px);}
  #industries .ic-foot b{font-size:clamp(9px,.75vw,14px);line-height:1.35;}
  #industries .ic-foot em{font-size:clamp(22px,2.1vw,42px);}
}

/* ============================================================
   THE WORKS PANEL LEAVES, AND INDUSTRIES IS ALREADY THERE
   ------------------------------------------------------------
   A reveal from underneath needs two things that were not true before.

   The panel has to be OPAQUE. The white ground was on the section, so the
   stage could slide as far as it liked and the same white stayed behind
   it — nothing to uncover. The ground moves to the stage, which is what
   makes it a slab rather than a window.

   And the section beneath has to be IN PLACE BEFORE the slide starts, not
   scrolling up behind it. #edo is pulled back by a screen so its sticky
   Industries layer has already pinned by the time the exit begins. The
   negative margin adds no height anywhere: it borrows the screen the exit
   phase added to #works.
   ============================================================ */
@media (min-width:981px){
  /* THE CLAIM ABOVE IS NOT QUITE TRUE, AND THIS IS THE BAND IN THE CORNER.
     #works is (VH + lead + travel + VH) tall and the exit is the last screen
     of it, so the exit runs from S+lead+travel to S+VH+lead+travel. The
     negative margin puts #edo's box start at exactly S+VH+lead+travel — the
     moment the exit ENDS. So Industries is not pinned before the slide, it
     is arriving from the bottom THROUGHOUT it, and the strip above its top
     edge is whatever lies behind: the body's warm cream, seen through a
     transparent #works.

     Enlarging the overlap would fix the geometry but drag every phase inside
     #edo a screen earlier, playing the start of Industries behind the slab.
     The colour is the actual complaint, so the colour is what this fixes.

     #works cannot simply be opaque — its last screen overlaps #edo, and an
     opaque section there would paint straight over the Industries heading.
     So it carries the ground everywhere EXCEPT that final screen: a hard
     stop at 100% - one viewport. Above the overlap the exposed strip is now
     Industries' own colour; inside it the section stays transparent and
     Industries shows through as before. One surface either way. */
  #works{
    background:linear-gradient(180deg,
      var(--ind-bg,#ececeb) 0,
      var(--ind-bg,#ececeb) calc(100% - var(--vh)),
      transparent           calc(100% - var(--vh)),
      transparent           100%);
    margin-bottom:calc(var(--vh) * -1);z-index:3;
  }
  .wk-stage{
    /* TOP TO BOTTOM, ACROSS THE WHOLE SLAB.
       Not a diagonal, and not on a panel. A diagonal gradient inside a
       rectangle announces the rectangle — the corner it darkens towards
       tells you exactly where the box is. Straight down, spanning the
       full stage, there is no internal edge to find: the only boundary
       the reader ever sees is the slab's own right-hand edge as it slides
       away, which is the one that is meant to be seen. */
    background:linear-gradient(180deg,
      #fdfdfc 0%, #f6f4f2 38%, #ece9e5 72%, #dedad5 100%);
    transform:translate3d(var(--sx,0%),0,0);
    will-change:transform;
  }
  /* corners and shadow only while it is genuinely a slab over something */
  .wk-stage.leaving{
    border-radius:0 14px 14px 0;
    box-shadow:26px 0 60px rgba(0,0,0,.14);
  }
  #edo{z-index:1;}
}

/* ============================================================
   INDUSTRIES ON DESKTOP — TWO SCREENS PER PANEL
   ------------------------------------------------------------
   On a phone a panel is one screen: cards, sub-header and marquee at the
   top, the rail already sitting in the rows beneath them. That is why the
   rail was visible on the first screen here — it was never meant to be a
   second view, only the bottom of a short one.

   Each panel is fourteen rows now instead of seven: the first seven hold
   the cards, the sub-header and the marquee, the second seven hold the
   rail on its own. The panel is lifted by half its own height between the
   two, which is exactly one screen.

   The track's arithmetic survives this untouched, and that is the reason
   for doing it in halves rather than in pixels. It is two panels at 1fr
   each; doubling its height doubles both, so the swap still travels 50%
   and still lands on the next panel. Had either number been absolute, the
   swap would have had to be re-derived to match.
   ============================================================ */
@media (min-width:981px){
  .ind-track{height:400%;}
  .ind-panel{
    grid-template-rows:repeat(14,var(--m-rh));
    transform:translate3d(0,calc(var(--ind-lift,0) * -50%),0);
    will-change:transform;
  }
  /* the second screen: heading strip, then the rail below it */
  .works-head{grid-row:8;}
  .works-rail{grid-row:9/15;padding:calc(var(--m-rh)*.5) 0 calc(var(--m-rh)*.6);}

  /* two across, measured rather than fixed: half the track less the gutter,
     so it holds whatever the window turns out to be */
  #industries .wk-card{
    flex:0 0 calc((100% - clamp(18px,1.8vw,38px)) / 2);
    padding:0 clamp(10px,1vw,20px);gap:clamp(8px,.8vw,16px);
  }
  #industries .wk-pad{flex:0 0 clamp(10px,1vw,22px);}
  #industries .wk-shot{border-radius:6px;}
  #industries .wk-tag{
    left:clamp(18px,1.7vw,34px);top:clamp(18px,1.7vw,34px);
    font-size:clamp(9px,.72vw,13px);
  }
  #industries .wk-big{
    left:clamp(16px,1.5vw,30px);right:clamp(16px,1.5vw,30px);
    bottom:clamp(14px,1.3vw,26px);font-size:clamp(18px,1.7vw,32px);
  }
  #industries .wk-kicker{font-size:clamp(9px,.72vw,13px);}
  #industries .wk-statement{font-size:clamp(14px,1.15vw,21px);line-height:1.34;}
  #industries .wk-link{font-size:clamp(9px,.7vw,12px);letter-spacing:.13em;}
  #industries .works-head h2{font-size:clamp(19px,1.7vw,32px);}
  #industries .ind-head{font-size:clamp(17px,1.6vw,30px);}
  #industries .ind-tag-track span{font-size:clamp(10px,.82vw,15px);}
}

/* ============================================================
   INDUSTRIES ON DESKTOP — the first screen, corrected
   ============================================================ */
@media (min-width:981px){
  /* ---- the heading stacks on the left ----
     The two halves sat at opposite ends of the row, which is the phone's
     arrangement. Both hang off the same line now, one above the other, the
     way the hero and What We Do set their headings. */
  /* ONE LINE, both halves. They are two separate <h2> grid items, and two
     items in the same cell stack on top of each other — which is exactly
     what was happening. Sharing the cell is still right; the second one is
     simply pushed clear of the first by the width of the word in front of
     it. That indent is in em, so it is measured in the font's own units and
     holds at every size, because both halves are set at the same size. */
  #industries .ind-a,#industries .ind-b{
    grid-column:1/-1;grid-row:1;
    justify-self:start;align-self:center;
    /* margin, not margin-top. The phone gives these two opposite nudges —
       a top one on the first and a NEGATIVE bottom one on the second — to
       sit them on their row's lines. Under align-self:center a bottom
       margin shifts the box, so that leftover nudge was pushing the second
       half down by half of itself. Both are cleared, then set together. */
    margin:calc(var(--m-rh)*.20) 0 0;
    text-align:left;
  }
  /* CENTRED AS A PAIR, WHICH TAKES A LEFT MARGIN RATHER THAN justify-self.
     Centring each half on its own would stack them on the middle, since
     they share one cell — so the line is placed instead: half the row,
     less half the line's own width, and the second half then sits its
     indent further along. A percentage margin on a grid item resolves
     against the grid area, so the 50% is the row and the arithmetic holds
     at any width; only the em constants are tied to these two words.
     INDUSTRIES runs 3.68em and WE SERVE about 2.83em, so the pair with its
     word space is 6.78em and half of that is 3.39em. */
  #industries .ind-a{margin-left:calc(50% - 3.39em);}
  #industries .ind-b{margin-left:calc(50% + 0.56em);}

  /* ---- the three cards ----
     Portrait and centred, at roughly the proportions of the reference
     rather than full-bleed letterboxes. Four rows tall against a group
     capped near two thirds of the width puts each card close to 4:5, which
     is what the recording measures. */
  #industries .ind-cards{
    /* five rows, not four. At four the cards measured 1.02 against the
       reference's 0.79 — square rather than portrait, because the group's
       width cap and the row height have to agree and only one of them was
       set from the picture. Five rows less the paddings puts them at 0.78. */
    /* Measured off the recording rather than guessed. There the group spans
       67% of the frame, each card is 21.3% of it, and the gaps between them
       are 1.35% — which puts a card at 0.79 wide to tall. Six rows less the
       paddings gives that height, so all three numbers agree instead of two
       of them fighting over the third. */
    grid-row:1/7;
    width:67%;margin-inline:auto;
    gap:1.35vw;
    padding:calc(var(--m-rh)*.55) 0 calc(var(--m-rh)*.90);
  }

  /* ---- the sub-header and the marquee sit on the floor ----
     They were on row 4, directly under the cards, which left most of the
     screen empty beneath them. Row 7 is the last row of the panel's first
     screen, so they rest on its bottom edge and the space opens up above
     them instead of below. */
  /* THE RULE ACROSS THE CARDS GOES.
     It is the panel's ::after, drawn 4.53 rows down — a measurement taken
     when a panel was seven rows and that put it just under the phone's
     cards. A panel is fourteen rows here, so the same number lands halfway
     up them. It marked the top of the rail on a screen where the rail was
     directly below; the rail has its own screen now and needs no line to
     announce it. */
  #industries .ind-panel::after{display:none;}

  /* The sub-header and the marquee share row 7, one against its ceiling and
     one against its floor. They were overlapping because each also carried
     padding INTO the row, and the two paddings met in the middle. The row
     is a hundred-odd pixels and the pair need about seventy, so the
     separation is already there to be had — it just has to not be spent. */
  #industries .ind-head{grid-row:7;align-self:start;margin:0;padding:0;}
  /* full width: a marquee that stops short of the frame reads as a block
     with text sliding out of it rather than as a band running past */
  #industries .ind-tags{
    grid-row:7;align-self:end;
    margin:0 calc(clamp(28px,3.4vw,78px) * -1);
    padding:0;
  }
  /* The negative margin alone could not fill the frame: the track is
     width:max-content, so it is exactly as wide as its labels and no wider,
     and on a phone ten of them come to about a screen. On a monitor they
     came to two thirds of one. The labels are spaced apart instead of
     stretched — spreading the gaps keeps the type at its own size, and the
     loop stays seamless because the padding is added to every item, so the
     duplicated half is still exactly half. */
  #industries .ind-tag-track span{padding:0 clamp(33px,3.63vw,78px);}

  /* ---- the rail's pictures, 20% smaller ---- */
  /* THE PICTURE KEEPS ITS OWN SHAPE.
     .wk-shot took whatever height the card had left over and cropped the
     image to fill it — fine for a photograph, wrong for these, which have
     their headline composed INTO the frame. A portrait box against a 3:2
     source crops the sides, and the sides are where the words are.
     Sized from the ratio instead, so nothing is cut at all: the width is
     given, the height follows, and object-fit has nothing left to trim. */
  #industries .wk-shot{
    flex:0 0 auto;align-self:center;
    /* Two limits, whichever bites first. 80% of the card is the width we
       want; the second term is the widest a 3:2 picture can be and still
       leave its heading and line room in the rows the rail has. Without it
       the card overflowed by ten pixels on a 1050-tall screen — and since
       the ratio is fixed, an overflow here would have gone back to cropping
       the very words this change is meant to protect. */
    width:min(80%, calc(var(--m-rh) * 5.85));
    height:auto;aspect-ratio:3 / 2;
  }

  /* the card's type matches the EDO rail's, so the two sections read as
     one family rather than as a large one followed by a small one */
  #industries .wk-meta h3{font-size:clamp(16px,1.35vw,24px);line-height:1.25;}
  #industries .wk-meta p{font-size:clamp(11px,.88vw,15px);line-height:1.55;}
  /* The copy shares the picture's box, not the card's.
     The picture is inset and centred, so its left edge sits in from the
     card's; the heading and line were still starting at the card edge and
     hanging out to the left of it. Repeating the offset as a fixed
     percentage would break the moment the second width limit bites and the
     picture narrows — so the block is simply given the SAME width and the
     same centring instead. Two boxes of one width, centred in one parent,
     share both edges at every size by construction. */
  #industries .wk-meta{
    gap:clamp(6px,.5vw,10px);
    align-self:center;
    width:min(80%, calc(var(--m-rh) * 5.85));
  }

  /* ---- the divider draws up from the floor, node first ----
     Same device as the Works rail on a phone, and for the same reason: a
     line that is drawing itself has a moving tip, and a node left at the
     end point would hang in empty space until the line reached it. The
     origin is the line's FOOT so it grows upward, and the node is pushed
     down by however much of the line is still undrawn. --sy and --lh are
     written per card by paintWorkCards. */
  #industries .wk-card + .wk-card::before{
    top:0;bottom:0;
    transform-origin:50% 100%;
    transform:scaleY(var(--sy,1));
  }
  #industries .wk-card + .wk-card::after{
    top:0;
    transform:translate(-50%,calc(-50% + 1px))
              translateY(calc(var(--lh,0px) * (1 - var(--sy,1))));
  }
}

/* ============================================================
   WHO WE ARE ON DESKTOP
   ------------------------------------------------------------
   The layout needed nothing. #crew carries its own canvas — ten rows to a
   viewport, four columns — and every box in it is placed in grid units,
   percentages or aspect ratios, so it re-proportions itself as the screen
   grows. The member block is already the two halves that were asked for:
   photo, name and role in the left column, biography in the right.

   What does not scale is the TYPE. A 9px caption and a 10px biography are
   sized for a hand; on a monitor they read as a phone screenshot dropped
   into the page. Those, the section's gutter, and the marquee's width are
   all this block changes.
   ============================================================ */
@media (min-width:981px){
  #crew{--m-pad:clamp(28px,3.4vw,78px);}

  #crew .hero-frame{display:none;}

  /* ---- the heading on one line, both halves left ----
     Same construction as Industries: the two <h2> share a cell, and the
     second is pushed clear of the first by the first word's own width in
     em, so the indent holds at every size. They were on separate rows,
     one flush right, which is the phone's stacked arrangement. */
  .cw-a,.cw-b{
    grid-column:1/-1;grid-row:2;
    justify-self:start;align-self:center;
    margin:0;text-align:left;
    font-size:calc(var(--m-rh)*.72);
  }
  /* same construction: WHO WE ARE indents 4.25em, THE TEAM runs about
     2.83em, so the pair is 7.08em and half is 3.54em */
  .cw-a{margin-left:calc(50% - 3.54em);}
  .cw-b{margin-left:calc(50% + 0.71em);color:#2f2f2f;}

  /* Up a row, and closer to it. The quote sat on row 4 with a third of a
     row of padding above it, leaving row 3 empty between it and the
     heading — nearly two rows of nothing on a screen this size. */
  .cw-quote{
    grid-row:3;
    padding:calc(var(--m-rh)*.10) 12px 0;
    font-size:clamp(14px,1.15vw,21px);line-height:1.55;
  }

  /* ---- the stats band ----
     Each item is a quarter of the viewport, so ten of them already run two
     and a half screens: the band is full width by construction and only the
     type inside it needed raising. */
  /* ---- the stats band ----
     Each tile was a QUARTER of the viewport, which on a monitor puts four
     enormous cards on screen with acres between them. Sized to itself
     instead, so six or seven sit in the frame and the band reads as a
     continuous run rather than as four posters going past. The type inside
     goes up by half, which is what lets the tile shrink without the
     numbers becoming incidental. */
  /* the tile width is published as a variable because the keyframe
     above measures the loop in tiles */
  #crew{--cw-item:clamp(150px,12vw,250px);}
  .cw-box{width:58%;gap:clamp(4px,.4vw,9px);padding:0 clamp(6px,.6vw,12px);}
  .cw-box b{font-size:clamp(19px,1.55vw,29px);}
  .cw-box em{font-size:clamp(9px,.72vw,13px);}
  .cw-cap{
    padding-top:calc(var(--m-rh)*.14);
    font-size:clamp(13px,1.03vw,19px);line-height:1.35;
  }
  /* THREE ROWS, NOT TWO.
     A tile is a square box plus its caption, and at the size the type is
     now that comes to more than two rows hold — so the band was overflowing
     and .cw-body, which starts on row 7, was cropping the tiles' feet. The
     row the quote gave up goes here, which both fixes the crop and leaves
     real air between the last tile and the rule beneath it. */
  .cw-stats{
    grid-row:4/7;
    padding:calc(var(--m-rh)*.30) 0 calc(var(--m-rh)*.42);
  }

  /* ---- the member block, two halves ---- */
  /* SIZED BY THE HEIGHT IT HAS, NOT THE WIDTH IT WANTS.
     74% of a half-screen is around 690px wide, which at this ratio is a
     596px-tall picture — and .cw-body is four rows, about 420px. It was
     overflowing by a third and .cw-body's overflow:hidden was cutting it
     off at the section's edge, which is why no amount of padding under it
     made any difference: the padding was there, the picture was simply
     taller than the box it sat in. Driven by the room actually available,
     with 48px held back at the foot, the width follows the ratio and the
     gap under it is real. */
  .cw-photo{
    width:auto;max-width:74%;
    /* 48px was not the reserve needed. Under the picture sit the role's
       own top margin (.22 of a row) and up to two lines of it at 23px —
       about 78px before any clear space at all — so the title was being
       cut by exactly what the reserve was short. Stated in full here: the
       two margins, the role, and 40px of floor. */
    height:calc(var(--m-rh)*4 - var(--m-rh)*.84 - 95px);
    /* LANDSCAPE, WHICH MEANS CHOOSING A CROP.
       The box is still driven by the height the panel can spare, so going
       landscape widens it rather than making it taller: at 16:9 it comes
       out around 470px against the 274 the near-square ratio gave, which
       is most of the empty column beside it.

       The sources are PORTRAIT — 1021x1361 — so a landscape frame has to
       discard a third of the image, and where it discards from matters.
       object-position holds the crop high, because in a standing portrait
       the face sits in the upper half and a centred crop would take the
       top of the head off. */
    aspect-ratio:16 / 9;
    margin:calc(var(--m-rh)*.62) 0 0 13%;
    border-radius:clamp(12px,1.1vw,20px);
  }
  .cw-name{
    left:clamp(12px,1.1vw,22px);bottom:clamp(10px,.9vw,18px);
    font-size:clamp(13px,1.05vw,19px);
  }
  .cw-role{
    margin:calc(var(--m-rh)*.22) 0 0 13%;
    font-size:clamp(15px,1.25vw,23px);
  }
  .cw-side{padding:calc(var(--m-rh)*.62) 13% calc(var(--m-rh)*.55) 6%;}
  /* the block keeps a floor of its own, so the bio and the label are not
     sitting on the section's bottom edge */
  /* at least 40px, whatever the row height works out to — a proportion
     alone drops under that on a short window, which is where the bio was
     ending up against the section's own edge */
  .cw-member{padding-bottom:max(40px, calc(var(--m-rh)*.45));}
  .cw-bio{font-size:clamp(12px,.95vw,17px);line-height:1.62;}
  .cw-lbl{margin:calc(var(--m-rh)*.22) 0 0;font-size:clamp(11px,.85vw,15px);}
}

/* ============================================================
   FOOTER ON DESKTOP
   The grid is already relative — seven rows to a viewport, four columns —
   so the placement carried over. The type did not: 8px legal text and a
   10.5px link are a phone's measurements. The wordmark is given the room
   a wide screen has, since it is the thing the reveal lands on.
   ============================================================ */
@media (min-width:981px){
  #foot{--m-pad:clamp(40px,4.4vw,104px);}
  #foot .hero-frame{display:none;}

  .ft-links a{font-size:clamp(13px,1.05vw,19px);}
  .ft-links{gap:.72em;}
  .ft-tag{font-size:clamp(10px,.78vw,14px);padding:7px 14px;margin-bottom:20px;}
  .ft-contact p{font-size:clamp(11px,.88vw,16px);line-height:1.6;margin:0 0 14px;}
  .ft-social a{width:clamp(38px,3vw,54px);height:clamp(38px,3vw,54px);}
  .ft-social svg{width:44%;height:44%;}
  .ft-legal-track span{font-size:clamp(10px,.8vw,14px);}

  /* the wordmark takes the two columns it sits in, and its size comes from
     the row rather than a fixed figure, so it grows with the frame */
  .ft-word{font-size:calc(var(--m-rh) * 1.28);}
}

/* the calls to action take the room a wide screen has */
@media (min-width:981px){
  /* the EDO mark is a section header, sized like What We Do's */
  /* +40%: 1.18 -> 1.652 and 9.5vw -> 13.3vw. Both terms scale, or the
     min() would simply switch to the other one and the increase would
     land at some widths and not others. */
  /* NO --m-rh HERE, WHICH IS WHY THIS HAD NEVER WORKED.
     --m-rh is declared on :root INSIDE the max-width:980px query, and every
     section that wants it above that width declares its own. #works does
     not — so on desktop var(--m-rh) resolved to nothing, the min() was
     invalid at computed-value time, and the whole declaration was thrown
     away. The mark had been falling back to the base 15vw all along, which
     is why raising this line changed nothing on screen.

     The guard the --m-rh term was there to provide — never taller than a
     seventh of the screen times the ratio — is stated directly against the
     viewport instead: vh/7 * 1.652 is vh * 0.236. Same intent, no
     dependency on a variable this section was never given. */
  .wk-eyebrow{font-size:min(calc(var(--vh) * .236),13.3vw);line-height:.84;}
  .ft-cta{font-size:clamp(34px,3.6vw,72px);}
  .ft-ctas{gap:.18em;padding-right:3%;}
}

/* ============================================================
   FOOTER
   ------------------------------------------------------------
   Rebuilt to the reference's arrangement, which is not the one that was
   here before: a strapline under the mark, a large question filling the
   left, two calls to action set small and wide on the right, and beneath
   them a row of three — copyright, enquiry, social. The old build had
   stacked nav panels and a giant wordmark, which is a different footer
   altogether, and no amount of adjusting was going to turn one into the
   other. It went and this replaced it.

   The green stays. The reference is near-black; the brief was to keep the
   colour this site already had, so the film shows through and everything
   above it is set light with a scrim to hold the contrast.
   ============================================================ */
#foot{
  --m-div:4;--m-rh:calc(var(--vh)/7);
  display:grid;position:relative;z-index:2;
  grid-template-columns:repeat(4,minmax(0,1fr));
  grid-auto-rows:var(--m-rh);
  height:100vh;height:var(--vh);
  padding:0 var(--m-pad);gap:0;overflow:hidden;
  background:#42553f;
  color:#eef0ea;
}
.ft-bg{position:absolute;inset:0;z-index:0;pointer-events:none;}
.ft-bg video{width:100%;height:100%;object-fit:cover;display:block;}
/* the type sits on a moving picture, so it needs a floor under it — a wash
   dark enough to hold small light text wherever the frame happens to be */
.ft-bg::after{
  content:"";position:absolute;inset:0;
  background:linear-gradient(180deg,rgba(28,38,28,.62),rgba(28,38,28,.42) 55%,rgba(28,38,28,.66));
}
#foot > *:not(.ft-bg){position:relative;z-index:2;}

/* ---- the mark, its line, and the clock ---- */
.ft-brand{
  grid-column:1/3;grid-row:1;align-self:end;
  font-family:'Bebas Neue',var(--font-display);font-weight:400;
  font-size:24px;line-height:1;letter-spacing:.03em;text-transform:uppercase;
}
.ft-brand sup{font-size:.42em;vertical-align:super;}
.ft-strap{
  grid-column:1/4;grid-row:2;align-self:start;margin:.55em 0 0;
  font-family:'Poppins',var(--font-display);font-weight:400;
  font-size:10px;letter-spacing:.13em;text-transform:uppercase;
  color:rgba(238,240,234,.68);
}
.ft-clock{
  grid-column:4/5;grid-row:2;align-self:start;justify-self:end;margin:.55em 0 0;
  font-family:var(--font-mono);font-size:10px;letter-spacing:.09em;
  color:rgba(238,240,234,.6);font-weight:600;}

/* ---- the question ---- */
.ft-head{
  grid-column:1/4;grid-row:3/5;align-self:center;margin:0;
  font-family:'Poppins',var(--font-display);font-weight:300;
  font-size:36px;line-height:1.06;letter-spacing:-.02em;
  color:#f4f6f0;
}

/* ---- the two calls to action ---- */
.ft-ctas{
  grid-column:1/-1;grid-row:5;align-self:center;
  display:flex;flex-wrap:wrap;gap:0 clamp(22px,2.4vw,54px);
}
.ft-cta{
  display:flex;align-items:flex-end;justify-content:space-between;
  gap:clamp(20px,2vw,44px);min-width:min(100%,270px);
  padding-bottom:.75em;
  text-decoration:none;color:#eef0ea;
  font-family:var(--font-mono);font-size:11px;
  letter-spacing:.11em;text-transform:uppercase;
  transition:border-color .35s ease;font-weight:600;}

.ft-cta-t{display:flex;align-items:flex-start;}
/* THE LETTER WAVE.

   Not a roll — I had this wrong. Stepping the reference frame by frame,
   nothing flips over: each letter's TRACKING opens and closes again, and
   the letters do it one after another so a band of loose spacing travels
   across the phrase. It runs RIGHT TO LEFT, which is why every letter
   carries a second index counting from the far end; --r is what the delay
   reads. Reversing that one number is the whole difference between the
   reference's motion and a wave running the wrong way.

   It has to be an animation rather than a transition. A transition can
   only move from one state to another and hold there; this expands and
   then relaxes back while the pointer is still down, which is two moves
   and needs keyframes.

   The gap is margin, not letter-spacing: these are flex items, and
   letter-spacing on a single character is applied inconsistently at the
   end of a run, whereas a margin is simply space between boxes. */
/* The arrow's draw, as a keyframe rather than a transition — see the note
   on .cw-arw. Document level on purpose: @keyframes declared inside a media
   query is invisible to rules outside it. */
@keyframes cwArw{
  from{clip-path:inset(0 100% 0 0);}
  to  {clip-path:inset(0 0 0 0);}
}

/* The role title is built from one <i> per letter so the wave can be
   staggered, and <i> is italic by default. That was corrected inside the
   desktop block only, which left every letter leaning below 981px. Stated
   at base instead: it is what the desktop rule already said, so nothing
   changes there, and the phone stops slanting. */
.cw-role i{font-style:normal;}
.cw-role i.sp{white-space:pre;}

@keyframes ftWave{
  0%   {margin-right:0;}
  42%  {margin-right:.40em;}
  100% {margin-right:0;}
}
.ft-cta-t{display:flex;align-items:baseline;}
/* <i> is italic by default and every letter is an <i>, which is where
   the slant was coming from — the element was chosen for brevity, not
   for emphasis. */
.ft-cta-t i{display:block;font-style:normal;}
.ft-cta-t i.sp{width:.34em;}
.ft-cta:hover .ft-cta-t i,
.ft-cta:focus-visible .ft-cta-t i{
  animation:ftWave 1.50s cubic-bezier(.33,0,.2,1) both;
  animation-delay:calc(var(--r) * .059s);
}

/* THE ARROW IS DRAWN, NOT MOVED.

   I had this as a slide across the link. Measuring the frames properly
   settles it: through the whole transition there is no arrow anywhere in
   the middle of the link. Then at one moment a mark appears at x=10 and
   its RIGHT edge extends — 15, then 19, then 22, then 23 — while its left
   edge never moves. It is not travelling. It is being drawn, left to
   right, from a fixed origin, over about a tenth of a second.

   So the reveal is a wipe, and clip-path is the honest way to write it:
   the element is in place the whole time and only the amount of it that
   is painted changes. A width transition would reflow the row on every
   frame; a transform would slide the glyph in from somewhere, which is
   the thing the frames say does not happen.

   It also waits. The label starts moving at once, and the arrow is not
   drawn until roughly three tenths of a second later — the gap is what
   makes it read as arriving into the space the label has vacated rather
   than as the two competing for it. */
.ft-cta{position:relative;}
.ft-arw{font-style:normal;line-height:inherit;}

.ft-arw-l{
  position:absolute;left:0;top:0;
  clip-path:inset(0 100% 0 0);
  transition:clip-path .31s linear;
}
.ft-cta:hover .ft-arw-l,
.ft-cta:focus-visible .ft-arw-l{
  clip-path:inset(0 0 0 0);
  transition:clip-path .31s linear .68s;
}

/* the one at rest simply goes, and quickly — the frames show it gone well
   before the new mark is drawn, so a slow fade would have the two on
   screen together, which never happens in the reference */
.ft-arw-r{margin-left:auto;transition:opacity .36s ease;}
.ft-cta:hover .ft-arw-r,
.ft-cta:focus-visible .ft-arw-r{opacity:0;}

/* THE LABEL CROSSES TO THE FAR END.

   Not a fixed nudge — it has to finish with its right edge on the right
   end of the line, whatever the label and the line happen to measure. So
   it is placed rather than pushed: `left` moves from one edge of the link
   to the other as a percentage OF THE LINK, and the translate takes back
   the label's own width so it stops flush instead of overhanging. The two
   are a pair, and a transform alone cannot stand in for them — a
   percentage in translateX is a percentage of the label, which is the
   wrong quantity entirely.

   Taking it out of the flow leaves the row with only the resting arrow to
   size it, so the link is given a height of its own. */
.ft-cta{min-height:1.35em;}
.ft-cta-t{
  position:absolute;left:0;top:0;transform:translateX(0);
  transition:left 1.37s cubic-bezier(.16,1,.3,1),
             transform 1.37s cubic-bezier(.16,1,.3,1);
}
.ft-cta:hover .ft-cta-t,
.ft-cta:focus-visible .ft-cta-t{left:100%;transform:translateX(-100%);}

.ft-cta:focus-visible{outline:1px solid rgba(238,240,234,.5);outline-offset:6px;}

/* THE UNDERLINE SWIPES; IT DOES NOT CHANGE COLOUR.

   Tracking the line's two ends through the recording: on hover its LEFT
   edge runs right while the right edge stays put — the line is collapsing
   towards its right end. Then it is whole again for an instant, and after
   that the RIGHT edge is short and extends rightwards. So it retracts to
   one end and redraws from the other.

   One element cannot do that. A transform-origin is fixed for the whole
   transition, and this needs the origin on the right while it leaves and
   on the left while it returns. Two lines stacked in the same place, each
   with its own origin, and the second delayed by exactly as long as the
   first takes: the handover happens at the moment the outgoing line has
   no width left, which is why no seam shows. */
.ft-cta::before,
.ft-cta::after{
  content:"";position:absolute;left:0;right:0;bottom:0;height:1px;
  /* one weight throughout. The pair used to differ, so the swipe doubled
     as a change of shade — the line looked like it was brightening rather
     than being redrawn, which is the thing that wanted fixing. */
  background:rgba(255,255,255,.92);
}
.ft-cta::before{transform-origin:100% 50%;transition:transform .65s cubic-bezier(.5,0,.75,0);}
.ft-cta::after{
  transform:scaleX(0);transform-origin:0 50%;
  transition:transform .75s cubic-bezier(.22,1,.36,1);
}
.ft-cta:hover::before,
.ft-cta:focus-visible::before{transform:scaleX(0);}
.ft-cta:hover::after,
.ft-cta:focus-visible::after{transform:scaleX(1);transition-delay:.62s;}

/* THE WATERMARK — SCAN LINES, FULL WIDTH, ON THE FLOOR.

   #foot > *:not(.ft-bg) sets position:relative on every child, and that
   selector carries an id plus a class while .ft-water carried only a
   class — so it won out and the absolute positioning never applied. The
   watermark was landing wherever the grid auto-placed it, which is why it
   sat at the top. Scoped to #foot here so the two match on specificity
   and the later rule wins on order, as intended.

   The letters are cut into horizontal bars by a repeating mask rather
   than being drawn as stripes. The mask is the only part that has to know
   about the stripes, so the type stays real type — it still lays out,
   still centres, still scales with the viewport, and the pattern rides
   over whatever it happens to be doing.

   The size is derived, not chosen: six characters of Poppins at this
   weight run about 4.2em, so a shade over 22vw fills the frame edge to
   edge. Change the word and that number changes with its length. */
#foot .ft-water{
  position:absolute;bottom:0;z-index:1;
  /* An absolute box is placed against the PADDING box, so left:0 would sit
     inside the section's gutter. Pulled out by exactly that gutter on both
     sides, the word reaches the real edges of the screen. */
  left:calc(var(--m-pad) * -1);right:calc(var(--m-pad) * -1);
  overflow:hidden;pointer-events:none;

  /* SPREAD, NOT STRETCHED.
     One letter per span in a row that justifies to both ends: the first
     letter's left edge lands on the left edge of the screen and the last
     one's right edge on the right, whatever the window measures and
     whatever the word turns out to be. Sizing the type to fill instead
     would only be right at one width, and scaling it to fit would squash
     a face that is already condensed — the letters would stop looking
     like Bebas and start looking like Bebas being sat on. */
  display:flex;justify-content:space-between;align-items:flex-end;
}
#foot .ft-water span{
  display:block;line-height:.80;margin-bottom:-.14em;
  font-family:'Bebas Neue',var(--font-display);font-weight:400;
  font-size:41vw;letter-spacing:-.02em;text-transform:uppercase;
  /* SOLID, AND FAINT.
     The stripes came from a repeating-gradient mask — 2px on, 6px off — which
     is why the letters read as ruled rather than filled. The mask is gone, so
     the shapes are whole.

     Losing it makes the mark roughly four times heavier though: the stripes
     were removing three quarters of every letter before the alpha was even
     applied. Held at 20% that would have arrived as a solid slab rather than
     a watermark, so the alpha comes down to match what the eye was actually
     receiving — a little under, since an unbroken shape reads stronger than
     the same ink shredded into lines. */
  color:rgba(238,240,234,.055);
}

/* ---- the closing row: copyright, enquiry, social ---- */
.ft-copy{
  grid-column:1/2;grid-row:6;align-self:start;margin:0;
  font-family:'Poppins',var(--font-display);font-weight:300;font-size:11px;
  color:rgba(238,240,234,.72);
}
.ft-copy sup{font-size:.55em;vertical-align:super;}
/* the credit line — a step dimmer than the copyright it sits under, so
   the hierarchy between "ours" and "built by" reads without needing a
   divider */
.ft-powered{display:inline-block;margin-top:.4em;font-size:.82em;color:rgba(238,240,234,.45);}
.ft-col{grid-row:6;align-self:start;display:flex;flex-direction:column;}
.ft-enq{grid-column:3/4;}
.ft-soc{grid-column:4/5;}
.ft-lbl{
  margin-bottom:1.5em;
  font-family:'Poppins',var(--font-display);font-weight:400;font-size:9.5px;
  letter-spacing:.13em;text-transform:uppercase;color:rgba(238,240,234,.55);
}
.ft-col p{
  margin:0 0 .7em;
  font-family:'Poppins',var(--font-display);font-weight:300;font-size:11px;
  color:rgba(238,240,234,.9);
}
.ft-col a{color:inherit;text-decoration:none;transition:color .3s ease;}
.ft-col a:hover{color:#fff;}
.ft-soc-grid{
  display:grid;grid-template-columns:repeat(2,minmax(0,1fr));
  gap:.7em clamp(14px,1.4vw,32px);
  font-family:'Poppins',var(--font-display);font-weight:300;font-size:11px;
  color:rgba(238,240,234,.9);
}

/* ---- desktop proportions ---- */
@media (min-width:981px){
  #foot{--m-pad:clamp(40px,4.4vw,104px);}
  .ft-brand{font-size:clamp(26px,2vw,38px);}
  .ft-strap,.ft-clock{font-size:clamp(11px,.82vw,15px);}
  .ft-head{font-size:clamp(48px,4.6vw,92px);}
  .ft-cta{font-size:clamp(12px,.92vw,16px);min-width:min(100%,clamp(300px,25vw,460px));}
  .ft-copy,.ft-col p,.ft-soc-grid{font-size:clamp(12px,.95vw,17px);}
  .ft-lbl{font-size:clamp(10px,.78vw,14px);}
}

/* ============================================================
   FOOTER ON A PHONE
   ------------------------------------------------------------
   Measured off the reference rather than eyeballed. Every gap below is
   the one in that screenshot expressed as a share of the frame — 30px of
   gutter in a 646px shot is 4.64vw, the drop from the second link to
   BUSINESS ENQUIRY is 177px which is 27.4vw, and so on. Proportions
   rather than pixels because the shot is one width and phones are many;
   the rhythm then holds at 360 as well as at 430.

   Type is clamped as well as proportional. A pure vw size is right in the
   shot and wrong at both ends of the range — 8px strapline on a small
   phone, 20px on a large one — so each carries a floor and a ceiling.

   The grid is dropped for a single column, and the order is set here
   rather than left to source order: the reference ends on the copyright,
   where the desktop build opens its closing row with it.
   ============================================================ */
@media (max-width:980px){
  #foot{
    display:flex;flex-direction:column;align-items:stretch;
    height:auto;min-height:100vh;min-height:var(--vh);
    /* the foot reserves the 33vw the watermark occupies below the copy */
    padding:0 4.64vw 33vw;
    gap:0;
  }

  /* The mark and the clock go. In the reference the logo lives in the
     fixed nav above and there is no clock on a phone at all; keeping
     either would put an item at the top that the layout has no room and
     no place for. The copyright still names the company, at the foot. */
  .ft-brand,.ft-clock{display:none;}

  /* EVERY BLOCK BACK ON THE LEFT EDGE.

     The headline and the calls to action were indented, and it was one
     property doing both: the desktop rules give .ft-head and .ft-ctas
     `align-self:center`, which in the wide grid centres them in their
     area. In a flex COLUMN the same declaration makes the box shrink to
     its contents and sit in the middle — the headline landed 28px in, the
     links 88px in, and the amount differed because each shrank to a
     different width. Stretching them puts every block on one line and the
     gutter becomes the only indent there is. */
  #foot > *{align-self:stretch;justify-self:stretch;text-align:left;}

  .ft-strap{order:1;margin:20.7vw 0 0;font-size:clamp(9.5px,2.17vw,15px);}
  .ft-head {order:2;margin:6.81vw 0 0;font-size:clamp(30px,8.67vw,58px);line-height:1.06;}
  .ft-ctas {order:3;margin:11.92vw 0 0;flex-direction:column;gap:8.51vw;}
  .ft-enq  {order:4;margin:22vw 0 0;}
  .ft-soc  {order:5;margin:11vw 0 0;}
  .ft-copy {order:6;margin:11vw 0 0;font-size:clamp(12px,2.63vw,18px);}

  /* the rule runs to about half the frame in the reference, not the full
     width — it is the label's line, not a divider */
  .ft-cta{
    width:49.07vw;min-width:12em;
    font-size:clamp(10px,2.63vw,18px);
  }

  /* A LABEL AND ITS LINES ARE ONE BLOCK.
     They read as a group only if the space inside is clearly smaller than
     the space around — before, the gap under the label was larger than
     the gap between the two lines beneath it, so BUSINESS ENQUIRY looked
     like a heading floating above two unrelated rows. Inside is tight
     now; the distance to the next group is more than double it. */
  .ft-lbl{margin-bottom:4.6vw;font-size:clamp(9.5px,2.32vw,16px);}
  .ft-col p{margin:0 0 3.4vw;font-size:clamp(12px,2.94vw,20px);}
  .ft-col p:last-child{margin-bottom:0;}
  .ft-soc-grid{
    gap:3.4vw 8vw;
    font-size:clamp(12px,2.94vw,20px);
  }

  #foot .ft-water span{font-size:26vw;}
}

/* ============================================================
   WHAT WE DO — LEAVING ONE PIECE AT A TIME
   ------------------------------------------------------------
   Placed at the end of the file deliberately. Fifty-three rules under
   #intro.play set an `animation` on these elements, and an animation with
   a backwards fill beats any transition on the same property — so this has
   to cancel the animation before it can transition anything. #intro.settled
   and #intro.play carry the same weight (one id, two classes), which means
   order alone decides it, and last wins. No !important needed, and none
   wanted: the next rule after this one should be able to override it too.

   .settled is the freeze — animations off, each piece held at the value it
   had reached. .out is the departure. They are applied a frame apart, so
   the second is a transition from a real starting value rather than a fill
   being withdrawn.
   ============================================================ */
#intro.settled .wd-a,
#intro.settled .wd-b,
#intro.settled .wd-intro,
#intro.settled .wd-rule,
#intro.settled .wd-steps,
#intro.settled .wd-rule::before,
#intro.settled .wd-rule::after,
#intro.settled .wd-p.l,
#intro.settled .wd-p.r{
  animation:none;
  opacity:1;
}

#intro.out .wd-a,
#intro.out .wd-b,
#intro.out .wd-intro,
#intro.out .wd-rule,
#intro.out .wd-steps{
  opacity:0;
  transition:opacity .62s cubic-bezier(.4,0,.6,1);
}
/* the order they go in is the order they arrived — heading first, the
   column track last, so the section empties from the top down */
#intro.out .wd-a    {transition-delay:0s;}
#intro.out .wd-b    {transition-delay:.09s;}
#intro.out .wd-intro{transition-delay:.18s;}
#intro.out .wd-rule {transition-delay:.27s;}
#intro.out .wd-steps{transition-delay:.36s;}


/* ============================================================
   THE FOOTER SKY
   Four soft shapes drifting behind the film. One SVG, one blur filter
   applied once to the group rather than to each shape, and CSS transforms
   to move them — so the expensive part is done at paint time and the
   animation itself is only compositing. No JavaScript: there is nothing
   here that needs to know about scroll, so nothing here should cost a
   frame's work.
   ============================================================ */
/* THE FOOTER SKY.

   Scoped to #foot, and that is not decoration: `#foot > *:not(.ft-bg)` sets
   position:relative on every child and carries an id, so a bare class loses
   to it and the layer would be laid out in the flow instead of positioned.

   Elements with a CSS blur, not an SVG with a filtered group. The SVG
   version rendered but would not move: a filter on the parent <g> gives the
   browser a cached raster of the whole group, and a transform on a child
   inside it does not reliably invalidate that cache. Here each shape carries
   its own blur, so the browser can blur once, keep the texture, and simply
   move it — which is both the reason it animates and the reason it is
   cheap. */
#foot .ft-sky{
  position:absolute;left:0;right:0;bottom:0;top:auto;
  height:66%;z-index:1;
  pointer-events:none;overflow:hidden;
  /* half strength, set once on the layer — dimming each gradient stop
     instead would mean five numbers to keep in agreement */
  opacity:.5;
}
#foot .ft-sky .pf{
  position:absolute;display:block;border-radius:50%;
  /* NO EDGE ANYWHERE.
     The gradient reaches zero well before the box does, so the shape has
     already faded out by the time its boundary arrives — nothing to catch
     the eye. The blur then softens what little structure is left. A hard
     stop at the edge is what makes a blurred circle still read as a
     circle; this one never gets there. */
  background:radial-gradient(closest-side,
    rgba(232,238,228,.42) 0%,
    rgba(232,238,228,.26) 34%,
    rgba(232,238,228,.09) 62%,
    rgba(232,238,228,0)   88%);
  filter:blur(46px);
  will-change:transform;
}
/* five shapes, overlapping, none of them the same size — an even spread
   would read as a row of blobs rather than as one bank of cloud */
/* 30% off each width, and the left offsets re-spread to suit: shrinking
   them in place would have opened gaps between the five and turned the
   bank back into a row of separate blobs. The blur comes down with them —
   66px around a shape a third smaller is not softening an edge any more,
   it is erasing the shape. */
#foot .ft-sky .pf1{left:-6%;  bottom:12%;  width:36%; aspect-ratio:2.1/1;}
#foot .ft-sky .pf2{left:18%;  bottom:-8%;  width:43%; aspect-ratio:2.4/1;}
#foot .ft-sky .pf3{left:52%;  bottom:18%;  width:35%; aspect-ratio:2/1;}
#foot .ft-sky .pf4{left:30%;  bottom:-13%; width:41%; aspect-ratio:2.6/1;}
#foot .ft-sky .pf5{left:70%;  bottom:28%;  width:31%; aspect-ratio:1.9/1;}

/* SLOW. The travel is a share of the frame so it reads the same on any
   screen, and the periods are long enough that no single shape is ever
   seen to arrive anywhere — about a minute and a half for the shortest.
   Four minutes for the longest, so the group's arrangement effectively
   never repeats. */
@keyframes ftDriftA{
  0%  {transform:translate3d(0,0,0)         scale(1);}
  50% {transform:translate3d(9vw,-2.4vh,0)  scale(1.12);}
  100%{transform:translate3d(0,0,0)         scale(1);}
}
@keyframes ftDriftB{
  0%  {transform:translate3d(0,0,0)         scale(1.09);}
  50% {transform:translate3d(-7.5vw,3vh,0)  scale(.93);}
  100%{transform:translate3d(0,0,0)         scale(1.09);}
}
#foot .ft-sky .pf1{animation:ftDriftA  97s ease-in-out infinite;}
#foot .ft-sky .pf2{animation:ftDriftB 131s ease-in-out infinite;}
#foot .ft-sky .pf3{animation:ftDriftB 163s ease-in-out infinite reverse;}
#foot .ft-sky .pf4{animation:ftDriftA 113s ease-in-out infinite reverse;}
#foot .ft-sky .pf5{animation:ftDriftA 149s ease-in-out infinite;}

/* the copy sits above the sky; the watermark sits between the two, so the
   shapes drift behind the letters rather than over them */
#foot .ft-water{z-index:2;}
#foot .ft-brand,#foot .ft-strap,#foot .ft-clock,#foot .ft-head,
#foot .ft-ctas,#foot .ft-copy,#foot .ft-col{z-index:3;}

@media (prefers-reduced-motion:reduce){
  #foot .ft-sky .pf{animation:none;}
}

/* the lead-in of each role line carries the weight; the rest of the line
   stays at the list's own weight so the pair reads as label and gloss */
.cw-list b{font-weight:600;color:#1f1f1f;}

/* ============================================================
   WHO WE ARE — PHONE SPACING
   ------------------------------------------------------------
   The section's rules sit at document level and the wide build overrides
   them, so the phone was running on the base values throughout. Two of
   those were too generous once the role lines became four real sentences
   rather than five short placeholders.

   The heading loses 15% of the space above it. .cw-a hangs from row 2 and
   .cw-b from row 3, so the pair is positioned by two margins pulling in
   opposite directions — both are eased rather than one, or the two halves
   would separate as they moved.

   The profile block starts higher and its own top padding comes down. That
   is where the overflow was: .cw-body is rows 7 to 11 with overflow
   hidden, so anything the panel could not fit was simply cut, and the
   panel began three quarters of a row inside its own box before a word of
   it was drawn. Recovering that, plus 25px off the foot, gives the fourth
   role line room to finish.
   ============================================================ */
@media (max-width:980px){
  /* 15% less air above the two halves of the heading, and the whole top
     group then lifted 15px. The heading, the quote and the stats band are
     three separate grid rows, so a lift has to be asked of each — moving
     one would only close the gap to the next rather than raise the set.
     The stats band carries a fourth adjustment: its own bottom padding
     grows by the same 15px it rose, which is what turns the lift into
     clearance beneath the marquee instead of just moving everything up. */
  .cw-a{margin-bottom:calc(var(--m-rh)*.03 - .16em - var(--m-rh)*.15);}
  .cw-b{margin-top:calc(var(--m-rh)*.31 - .07em - var(--m-rh)*.15);}
  .cw-a,.cw-b{transform:translateY(-15px);}
  .cw-quote{transform:translateY(-15px);}
  .cw-stats{transform:translateY(-15px);padding-bottom:calc(var(--m-rh)*.30 + 15px);}

  /* the panel opens a row earlier and wastes less of itself on its own
     top padding — the photo and the copy both start from the same line.
     The 20px offset moves the whole block down without moving its floor,
     so it costs 20px of the room recovered above rather than pushing the
     last line back under the clip. Of the ~140px that change gave back,
     this spends 20.

     Both of these are now variables on the section rather than values on the
     panel, because the node that marks the crossing has to move with the
     panel — it is nudged by the same 20px for the same reason. */
  #crew{--cw-row:6;--cw-off:20px;}
  .cw-side{padding-top:calc(var(--m-rh)*.34);}
  .cw-photo{margin-top:calc(var(--m-rh)*.34);}

  /* and 25px lifted off the bottom so the last line clears the edge */
  .cw-member{padding-bottom:25px;}
  .cw-list{margin-top:calc(var(--m-rh)*.08);}
}

/* ============================================================
   THE ROLE UNDER EACH PHOTO IS A LINK
   ------------------------------------------------------------
   The type and its position are untouched. The <p> became an <a> and each
   character got its own element, but the element flow is the same as
   before: inline-block letters lay out exactly as the text did, and the
   inner <span> is still the block that puts "Officer" on the second line
   at its own smaller size. Nothing here sets a font, a size or a margin —
   those all still come from the original rule.

   The hover is the footer's, sharing its keyframe rather than restating
   it: ftWave is declared at document level, so retuning the wave once
   changes both. The one part of the footer's behaviour left out is the
   label sliding to the far end of its rule — that reads on a single line
   on a wide rule, and would pull this two-line pair apart.
   ============================================================ */
@media (min-width:981px){
  .cw-role{
    position:relative;
    /* block so the left margin still places it; max-content so the rule
       and the arrow span the title rather than the whole column */
    display:block;width:max-content;
    text-decoration:none;color:inherit;
    /* no padding-right any more: it was reserving the corner the arrow used
       to be pinned into, and the arrow now sits inline after "Learn more" */
    padding-bottom:.42em;
  }
  /* the second line scales with the title instead of staying at its phone
     size, which on a 23px heading read as a stray caption */
  .cw-role .cw-m{font-size:clamp(11px,.85vw,15px);}
  /* inline-block, so a letter still flows where the character did */
  .cw-role i{display:inline-block;font-style:normal;}
  .cw-role i.sp{white-space:pre;}

  .cw-role:hover i:not(.cw-arw),
  .cw-role:focus-visible i:not(.cw-arw){
    animation:ftWave 1.5s cubic-bezier(.33,0,.2,1) both;
    animation-delay:calc(var(--r) * .045s);
  }

  /* the rule: one line retracts to its right end, a second draws in from
     the left, the handover timed so no seam shows */
  .cw-role::before,
  .cw-role::after{
    content:"";position:absolute;left:0;right:0;bottom:0;height:1px;
    background:rgba(47,47,47,.5);
  }
  .cw-role::before{transform-origin:100% 50%;transition:transform .65s cubic-bezier(.5,0,.75,0);}
  .cw-role::after{
    transform:scaleX(0);transform-origin:0 50%;
    transition:transform .75s cubic-bezier(.22,1,.36,1);
  }
  .cw-role:hover::before,
  .cw-role:focus-visible::before{transform:scaleX(0);}
  .cw-role:hover::after,
  .cw-role:focus-visible::after{transform:scaleX(1);transition-delay:.62s;}

  /* THE ARROW IS THE AFFORDANCE, SO IT IS ALWAYS THERE.
     It used to be clipped away until hover, which meant that until you
     happened to point at the title there was nothing to say it was a link
     at all. Resting state is now the drawn arrow.

     That inverts how the hover has to work. The wipe was a TRANSITION
     between two clip-paths, and a transition needs somewhere to travel
     from — with the arrow already drawn there is no gap left to cross, so
     hovering would simply do nothing. An animation re-runs from its own
     first frame no matter what the element already looks like, so the
     same left-to-right draw is a keyframe now, and it keeps the .68s
     delay that lets the underline finish its swipe first. Identical
     motion; it just no longer depends on the arrow having been absent. */
  .cw-role .cw-arw{
    /* static, not absolute. Pinned to the bottom-right corner it sat away
       from the words; the pair is a single "Learn more →" now, so it flows. */
    position:static;margin-left:.5em;
    display:inline-block;font-style:normal;line-height:1;
    clip-path:inset(0 0 0 0);
    animation:none;
  }
  .cw-role:hover .cw-arw,
  .cw-role:focus-visible .cw-arw{
    animation:cwArw .31s linear .68s both;
  }
  .cw-role:focus-visible{outline:1px solid rgba(47,47,47,.45);outline-offset:6px;}
}

/* ============================================================
   BACK TO THE TOP
   Fixed to the lower right, and it only exists once the reader has left
   the hero — a control that offers to take you where you already are is
   noise. hidden is toggled in JS as well as the class, so it leaves the
   tab order rather than sitting there invisible and focusable.
   ============================================================ */
.to-top{
  /* The bottom offset has to clear the phone's own furniture. A flat 16px is
     measured from the layout viewport, which on iOS Safari and Chrome for
     Android sits UNDER the address/tool bar — so the control was landing
     behind browser chrome on exactly the devices that need it most. The
     safe-area inset is zero on a desktop, so this costs nothing there. */
  position:fixed;right:clamp(16px,2.2vw,40px);
  bottom:calc(clamp(16px,2.2vw,40px) + env(safe-area-inset-bottom,0px));
  z-index:90;width:clamp(42px,3.4vw,56px);height:clamp(42px,3.4vw,56px);
  display:grid;place-items:center;
  border:1px solid rgba(30,55,39,.18);border-radius:50%;
  background:rgba(246,242,233,.82);
  -webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);
  box-shadow:0 4px 18px rgba(0,0,0,.10);
  color:#2f3a30;cursor:pointer;
  opacity:0;transform:translateY(10px);
  transition:opacity .38s ease,transform .38s cubic-bezier(.16,1,.3,1),
             background .3s ease,border-color .3s ease;
}
/* An author `display` beats the UA sheet's [hidden]{display:none}, so the
   attribute alone would not have taken it off the page — it would have sat
   there invisible, still occupying the corner and still swallowing clicks.
   Restating it at the same weight as the rule that broke it. */
.to-top[hidden]{display:none;}
.to-top.on{opacity:1;transform:translateY(0);}
.to-top:hover{background:#f6f2e9;border-color:rgba(30,55,39,.4);}
.to-top:focus-visible{outline:2px solid rgba(30,55,39,.55);outline-offset:3px;}
.to-top svg{
  width:42%;height:42%;fill:none;stroke:currentColor;
  stroke-width:1.9;stroke-linecap:round;stroke-linejoin:round;
}
@media (prefers-reduced-motion:reduce){
  .to-top{transition:opacity .2s ease;transform:none;}
}

/* ============================================================
   MOBILE TYPE — ONE SIZE PER ROLE
   ------------------------------------------------------------
   An audit of every font-size that survives below 981px found the scale
   sound in the places that matter — the section marks all land on 15vw,
   so What We Do and EDO are the same height, and the two prose blocks
   (.wd-body, .cw-bio) agree at 10px.

   The kicker did not. The same small uppercase label above a heading had
   drifted to four sizes in four sections — 10px, 10.5px, 11.5px and
   11.7px — none of them a decision, all of them arrived at separately.
   One value now.

   Scoped to max-width:980px on purpose. .kicker is a document-level rule
   with no desktop override, so editing it in place would have quietly
   resized the wide layout too; this block cannot reach that far.

   Deliberately NOT swept in: .ic-kicker (7.5px) and .ic-note (7px) inside
   the industry cards. Those sit with a 7px note in a fixed-height card and
   are a genuine micro-tier, not drift — pulling them up to 10px would
   overflow the card rather than tidy it.
   ============================================================ */
@media (max-width:980px){
  .kicker,
  #manifesto .kicker,
  .story-kicker,
  .edo-kicker,
  .wd-kicker{font-size:10px;}
}

/* ============================================================
   THE RAIL CARDS — ONE SHAPE, SEVEN TIMES
   ------------------------------------------------------------
   Each card is a flex COLUMN as tall as the viewport, and .wx-shot is a
   flex item in it. Flex items shrink by default, and an item sized by
   aspect-ratio shrinks by losing height — so on any window short enough
   for the column to overflow, the picture gives way first, and it gives
   way by however much the copy beneath it happens to need. Cards with
   longer text ended up with shorter pictures. That is the whole reason
   seven images cut from the same 900x600 masters could measure differently.

   flex:0 0 auto takes the picture out of that negotiation. Its height is
   now width / 1.5 and nothing else, at both widths, whatever the copy does.
   ============================================================ */
.wx-shot{flex:0 0 auto;}

@media (min-width:981px){
  /* The copy was held to a 32ch measure while the picture above it ran the
     full width of the card, so every card read as a wide image with a
     narrow column tucked under one corner — and because the measure fixed
     the character count rather than the width, cards with more words grew
     more lines and the block under each picture came out a different depth.
     Matching the picture's width makes the seven cards one shape.

     The trade is a long line: at this card width the measure runs past a
     hundred characters, which is well beyond what is comfortable to read.
     Say the word and I will cap it at something like 70ch, which would
     still sit well short of the picture's edge but read far better. */
  .wx-card p{max-width:none;}
}

/* ============================================================
   PROFILE SECTIONS — AVISHEK BISWAS / SATYAJIT DASGUPTA
   ------------------------------------------------------------
   A portrait held full-bleed while the frame travels over it. The
   arrangement follows the reference; the artwork, the words and the code
   are Virevo's own.

   Two viewports tall with a sticky stage inside, so there is one screen of
   scroll for the reveal to play through while the picture stays put. Every
   value is a function of --p, a single 0..1 progress written by main.js —
   nothing reads the clock, so scrolling back up un-plays it exactly.
   ============================================================ */
.pf-sec{
  /* IT ARRIVES OVER THE SECTION ABOVE IT, NOT AFTER IT.
     Pulled back by one screen so its box overlaps the last screen of #edo,
     and raised above it. #edo's own stage is sticky through that screen, so
     it holds still while this one rises across it — which is what makes the
     move read as a panel being dealt on top rather than as the page simply
     scrolling on. The same construction #speed already uses to come over
     #industries, so the page has one idea about this rather than two.

     The height carries the extra screen the negative margin borrowed: 3
     screens of box, one spent on the arrival, leaving the same two for the
     reveal that the section had before. */
  position:relative;z-index:6;
  /* Four screens: one spent arriving over the section above, two on the
     reveal, and one held at the end for the next profile to arrive across.
     The driver takes two off — the stage's own screen and that last one —
     so the reveal finishes before it is covered rather than underneath it. */
  height:400vh;height:calc(var(--vh) * 4);
  margin-top:-100vh;margin-top:calc(var(--vh) * -1);
  background:#0b0c0b;
  --p:0;
  /* Its OWN gutter. --m-pad is the canvas inset the grid sections share and
     it resolves to 2px here, which is what put the headline hard against the
     left edge with its first letter clipped. This section is not on that
     grid, so it states the margin it actually wants. */
  --cp-gut:clamp(22px,5.4vw,120px);
}
.pf-sec .cp-stage{
  position:sticky;top:0;
  height:100vh;height:var(--vh);
  overflow:hidden;
}

/* ---- the portrait ---- */
.pf-sec .cp-shot{position:absolute;inset:0;overflow:hidden;}
.pf-sec .cp-shot img{
  /* 28% was set for a 16:9 frame, where a wide window crops sideways and the
     vertical figure barely matters. The artwork is 4:3 now, so the crop has
     turned: a wide window takes 17% off the HEIGHT, and biasing that toward
     the top was cutting through his hands at the foot of the shot. Centred
     splits the loss evenly between the crown and the table. */
  width:100%;height:100%;object-fit:cover;object-position:center 50%;
  display:block;
  transform:scale(calc(1.08 - 0.08 * var(--p)));
  transform-origin:50% 42%;
  will-change:transform;
}
.pf-sec .cp-veil{
  position:absolute;inset:0;pointer-events:none;
  background:
    linear-gradient(180deg,rgba(8,9,8,.66) 0%,rgba(8,9,8,.16) 34%,rgba(8,9,8,.26) 62%,rgba(8,9,8,.82) 100%),
    linear-gradient(90deg,rgba(8,9,8,.74) 0%,rgba(8,9,8,.30) 38%,rgba(8,9,8,0) 62%);
  opacity:calc(1 - 0.40 * var(--p));
}

/* ---- left column: name, role, paragraph ---- */
.pf-sec .cp-left{
  position:absolute;z-index:3;
  left:var(--cp-gut);top:calc(var(--vh) * .13);
  width:min(44vw,620px);
}
.pf-sec .cp-name{
  margin:0;color:#f6f2e9;
  font-family:'Bebas Neue',var(--font-display);font-weight:700;
  /* Bebas has almost no descender space and its caps sit at the em-box top,
     so it takes a tighter line than the sans it replaced. */
  font-size:clamp(52px,7.6vw,132px);line-height:.88;letter-spacing:.006em;
}
.pf-sec .cp-name span{
  display:inline-block;
  transform:translateY(calc((1 - var(--w1)) * 106%));
  opacity:var(--w1);
}
.pf-sec .cp-name span:last-child{
  transform:translateY(calc((1 - var(--w2)) * 106%));
  opacity:var(--w2);
}
.pf-sec .cp-left{
  --w1:clamp(0,calc((var(--p) - .03) / .18),1);
  --w2:clamp(0,calc((var(--p) - .10) / .18),1);
  /* the paragraph's own progress, and the list's, both taken from --p so the
     two columns advance together rather than each on its own trigger */
  --q:clamp(0,calc((var(--p) - .26) / .46),1);
}
.pf-sec .cp-role{
  margin:clamp(16px,1.9vw,34px) 0 0;color:#f6f2e9;
  opacity:clamp(0,calc((var(--p) - .18) / .16),1);
  transform:translateY(calc((1 - clamp(0,calc((var(--p) - .18) / .16),1)) * 14px));
}
.pf-sec .cp-role::before,.pf-sec .cp-role::after{background:rgba(246,242,233,.55);}
.pf-sec .cp-role .cw-m{color:rgba(246,242,233,.72);}

/* ---- the paragraph, word by word ----
   The reference does not fade text in from nothing; the whole line is
   present and the words BRIGHTEN as the scroll passes along them, which is
   why the sentence stays readable throughout instead of assembling itself
   out of gaps. So the floor is .22, not 0.

   --n is the word's index and --words the count. Multiplying the progress
   by the count plus a small lead gives a head that travels past the last
   word before the phase ends; subtracting --n turns that into a per-word
   value, and the clamp does the rest. One expression, any length of text. */
.pf-sec .cp-bio{
  margin:clamp(20px,2.6vw,46px) 0 0;
  font-family:'Poppins',var(--font-display);font-weight:300;
  font-size:clamp(13px,1.12vw,20px);line-height:1.62;
  color:#f6f2e9;
}
.pf-sec .cp-bio span{
  display:inline-block;
  /* Divided by the spread, which is what gives the wave a soft leading edge
     instead of a hard on/off boundary: at 1 each word would flip the instant
     the head reached it, and the sentence would read as a row of switches. At
     3 the brightening runs about three words wide, so there is always a
     gradient travelling along the line.

     No transition on this. It is a scrubbed property — the scroll itself
     supplies the continuity — and a transition would lag the finger and fight
     the scrub. (It also made this impossible to measure: a frozen tab never
     advances a transition, so every reading came back stuck on the start
     value and the arithmetic looked broken when it was not.) */
  opacity:clamp(.22,calc((var(--q) * (var(--words) + 6) - var(--n)) / 3),1);
}

/* ---- right column: the role items, same treatment a line at a time ---- */
.pf-sec .cp-right{
  position:absolute;z-index:3;
  right:var(--cp-gut);top:50%;transform:translateY(-50%);
  width:min(27vw,360px);
  --q:clamp(0,calc((var(--p) - .30) / .44),1);
}
.pf-sec .cp-list{list-style:none;margin:0;padding:0;}
.pf-sec .cp-list li{
  padding:.62em 0;
  font-family:'Poppins',var(--font-display);font-weight:300;
  font-size:clamp(11px,.9vw,16px);line-height:1.35;
  letter-spacing:.07em;text-transform:uppercase;
  color:#f6f2e9;
  border-bottom:1px solid rgba(246,242,233,.18);
  /* same wave, spread over the rows — one line lighting as the one above it
     finishes, rather than all five arriving together */
  opacity:clamp(.22,calc((var(--q) * (var(--rows) + 3) - var(--n)) / 1.6),1);
}
.pf-sec .cp-list li:last-child{border-bottom:0;}

@media (max-width:980px){
  /* 16:9 artwork in a portrait window crops to the middle third, which is
     where he is. The two columns stop being columns and stack, because side
     by side at this width they would sit on the face. */
  .pf-sec{--cp-gut:20px;}
  /* The column is given a floor as well as a ceiling — bottom clears the
     role list beneath it — and becomes a flex column so the paragraph can be
     pushed down to sit just above that list. margin-top:auto is what does
     it: the free space collects above the paragraph instead of below the
     heading, which is the difference between "under the button" and "at the
     foot of the screen" without hard-coding a top offset that would drift
     with the headline's own height. */
  .pf-sec .cp-left{
    left:var(--cp-gut);right:var(--cp-gut);
    top:calc(var(--vh) * .08);bottom:calc(var(--vh) * .30);
    width:auto;display:flex;flex-direction:column;
  }
  .pf-sec .cp-name{font-size:15vw;}
  .pf-sec .cp-bio{font-size:12.5px;line-height:1.55;margin:auto 0 0;}
  .pf-sec .cp-shot img{object-position:center 30%;}
  .pf-sec .cp-right{
    left:var(--cp-gut);right:var(--cp-gut);top:auto;
    bottom:calc(var(--vh) * .06);width:auto;transform:none;
  }
  .pf-sec .cp-list li{font-size:10.5px;padding:.44em 0;letter-spacing:.05em;}
  .pf-sec .cp-veil{
    background:linear-gradient(180deg,rgba(8,9,8,.70) 0%,rgba(8,9,8,.20) 28%,rgba(8,9,8,.58) 56%,rgba(8,9,8,.92) 100%);
  }
}

@media (prefers-reduced-motion:reduce){
  .pf-sec .cp-shot img{transform:none;}
  .pf-sec .cp-name span,.pf-sec .cp-role{transform:none;}
  .pf-sec .cp-bio span,.pf-sec .cp-list li{opacity:1;}
}

/* ============================================================
   THE MIRRORED PROFILE — SATYAJIT DASGUPTA
   ------------------------------------------------------------
   Everything above is shared. This only swaps the two columns over, which
   is why it is a modifier class rather than a second copy of the block: one
   definition means a change to the reveal, the spacing or the type reaches
   both profiles instead of drifting apart the way two copies would.

   The artwork decides the direction. He is seated in the left third with
   clear ground to the right, so the reading column takes that empty side
   and the list takes the other — the mirror of Avishek's, whose subject
   sits right of centre.
   ============================================================ */
.pf-flip .cp-left{left:auto;right:var(--cp-gut);text-align:right;}
.pf-flip .cp-right{right:auto;left:var(--cp-gut);}

/* the underline and the wave read right-to-left with the text now */
.pf-flip .cp-role{margin-left:auto;}
.pf-flip .cp-list li{text-align:left;}

/* and the wash turns with it: the heavy side is the one carrying type, and
   on this frame that is the open green rather than the figure */
.pf-flip .cp-veil{
  background:
    linear-gradient(180deg,rgba(6,14,9,.60) 0%,rgba(6,14,9,.14) 34%,rgba(6,14,9,.24) 62%,rgba(6,14,9,.80) 100%),
    linear-gradient(270deg,rgba(6,14,9,.76) 0%,rgba(6,14,9,.32) 40%,rgba(6,14,9,0) 66%);
}
/* he sits low and left, so the crop holds that corner rather than the middle */
.pf-flip .cp-shot img{object-position:22% 62%;}

@media (max-width:980px){
  /* stacked at this width, so the mirror has nothing to mirror — both
     columns run the full width and the text goes back to the left, which is
     where a line of prose belongs on a phone */
  .pf-flip .cp-left{left:var(--cp-gut);right:var(--cp-gut);text-align:left;}
  .pf-flip .cp-right{left:var(--cp-gut);right:var(--cp-gut);}
  /* the role is a max-content block, so text-align cannot bring it back —
     it was the auto left margin holding it against the right edge while the
     name and the paragraph had already returned to the left */
  .pf-flip .cp-role{margin-left:0;}
  .pf-flip .cp-shot img{object-position:26% 58%;}
}

/* ============================================================
   NAV MENU — the letters, and the contact block
   ------------------------------------------------------------
   Each nav label is one <i> per letter carrying --i and --r, which is the
   same shape the role titles and the footer calls to action already use.
   That is deliberate: the page has one way of animating a letter run, and a
   second one invented here would drift away from it the first time either
   is retuned.

   TWO MOTIONS, AND THEY DO DIFFERENT JOBS.

   On OPEN the labels rise in on --i, left to right, staggered off the
   overlay's own transition so they arrive after the card rather than with
   it. Reading order, because the reader is meeting the list for the first
   time.

   On HOVER the wave runs on --r instead, right to left, breaking back
   toward the cursor's approach — the footer's behaviour, which was measured
   from the reference rather than guessed.
   ============================================================ */
#menu .menu-nav a{
  /* the letters need a line to sit on and a box to be centred in, and the
     block cannot be inline or the per-letter transforms would reflow it */
  display:block;white-space:nowrap;
}
#menu .menu-nav a i{
  display:inline-block;font-style:italic;
  /* the resting state IS the arrived state: if the overlay never animates —
     reduced motion, or a browser that drops the transition — the words are
     simply there rather than stuck at zero */
  opacity:1;transform:none;
}
#menu .menu-nav a i.sp{white-space:pre;}

/* ---- arrival, on open : MEASURED, NOT GUESSED ----
   From the recording, at 60fps:

     the overlay itself lands in ~0.20s, and the first label starts 0.08s
     after it — the list is not part of the panel's own move
     each label reaches its FULL WIDTH in a single frame while its ink keeps
     darkening for another ~0.45s
     the four labels start 6 frames apart. Exactly 6, three times over: 0.100s

   That middle point is the one that matters, and it is what I had wrong.
   A word arriving at full width in one frame cannot be a per-letter
   stagger or a wipe — those would walk the right-hand edge outward over
   several frames. It is the whole word fading up at once, with a small
   rise under it. So the stagger is per ITEM, not per letter, and --n on
   the <li> is what carries it.

   The per-letter markup still earns its keep on hover, below. */
#menu.open .menu-nav a{
  animation:mnRise .45s cubic-bezier(.17,.87,.28,1) both;
  animation-delay:calc(.08s + var(--n) * .10s);
}
@keyframes mnRise{
  /* the rise is slight — the measured centroid moves about a quarter of the
     cap height, so this is a word settling rather than one flying in */
  from{opacity:0;transform:translateY(.26em);}
  to  {opacity:1;transform:translateY(0);}
}

/* ---- the wave, on hover ----
   ftWave is the footer's, already document level, so this is the same
   motion the calls to action make — only the delay is re-keyed to --r so it
   travels the other way. */
#menu .menu-nav a:hover i,
#menu .menu-nav a:focus-visible i{
  animation:ftWave 1.5s cubic-bezier(.33,0,.2,1) both;
  animation-delay:calc(var(--r) * .042s);
}

/* ---- contact and socials ---- */
#menu .menu-meta{
  display:flex;justify-content:space-between;gap:18px;
  padding:15px 2px 4px;margin-top:4px;
  border-top:1px solid rgba(199,164,93,.14);
}
#menu .menu-blk{display:flex;flex-direction:column;gap:5px;min-width:0;}
#menu .menu-lbl{
  font-family:var(--font-mono);font-size:9.5px;letter-spacing:.18em;
  text-transform:uppercase;color:rgba(246,242,233,.42);margin-bottom:2px;font-weight:600;}
/* these are NOT the big italic nav links, so the blanket #menu a rule has to
   be undone for them — it is a bare element selector and loses to anything */
#menu .menu-meta a{
  display:inline-block;padding:0;text-align:left;
  font-family:'Poppins',var(--font-display);font-weight:300;font-style:normal;
  font-size:12px;line-height:1.5;color:rgba(246,242,233,.82);
  text-decoration:none;transition:color .25s ease;
}
#menu .menu-meta a:hover{color:var(--grid);}
#menu .menu-soc{display:grid;grid-template-columns:1fr 1fr;gap:2px 14px;}

@media (max-width:980px){
  #menu .menu-meta{flex-direction:column;gap:14px;padding-top:16px;}
  #menu .menu-meta a{font-size:11.5px;}
  #menu .menu-soc{gap:2px 10px;}
}
@media (prefers-reduced-motion:reduce){
  #menu.open .menu-nav a i,
  #menu .menu-nav a:hover i{animation:none;opacity:1;transform:none;}
}


/* ============================================================
   SIDE DRAWER — the letters, and the contact block
   Same treatment as the overlay menu, from the same measurement.
   ============================================================ */
.side-pane .sd-nav a{display:block;white-space:nowrap;}
.side-pane .sd-nav a i{display:inline-block;font-style:normal;}
.side-pane .sd-nav a i.sp{white-space:pre;}
/* the hover wave runs on --r, right to left, breaking back toward the
   cursor — the footer's behaviour, which was measured from the reference */
.side-pane .sd-nav a:hover i,
.side-pane .sd-nav a:focus-visible i{
  animation:ftWave 1.5s cubic-bezier(.33,0,.2,1) both;
  animation-delay:calc(var(--r) * .042s);
}

.side-pane .sd-meta{
  padding:16px 0 4px;margin-top:6px;
  border-top:1px solid rgba(246,242,233,.14);
  display:flex;flex-direction:column;gap:14px;
}
.side-pane .sd-lbl{
  display:block;margin-bottom:5px;
  font-family:var(--font-mono);font-size:9px;letter-spacing:.18em;
  text-transform:uppercase;color:rgba(246,242,233,.42);font-weight:600;}
/* NOT the big nav links — ".side-pane a" is a bare descendant rule and these
   have to climb over it */
.side-pane .sd-meta a{
  display:block;padding:0;
  font-family:'Poppins',var(--font-display);font-weight:300;
  font-size:11.5px;line-height:1.65;letter-spacing:0;
  color:rgba(246,242,233,.82);text-transform:none;
}
.side-pane .sd-meta a:hover{color:var(--grid);padding-left:0;}
.side-pane .sd-soc{display:grid;grid-template-columns:1fr 1fr;gap:0 12px;}

@media (prefers-reduced-motion:reduce){
  .side-pane.open .sd-nav li{animation:none;}
  .side-pane .sd-nav a:hover i{animation:none;}
}

/* ============================================================
   TYPE PARITY — DESKTOP
   ------------------------------------------------------------
   An audit of the effective font-size of every content role at both widths
   turned up two kinds of failure, and neither is visible from any single
   section — only from the table.

   1. ROLES THAT NEVER SCALE. A handful carry one fixed pixel size and no
      desktop rule at all, so they stay at their phone size while everything
      around them grows: the crew list and the solutions CTA at 11px, the
      industries sub-head at 13px. On a 1440 screen the body copy beside
      them is 12.7-13.7px and the labels 11-12px, so these read as small
      rather than as themselves.

   2. THE KICKER DRIFTED AGAIN, ON THE OTHER SIDE. The phone's four values
      were unified to 10px a while back, but that fix was deliberately
      scoped to max-width:980px so it could not disturb the wide layout —
      which left 11.5px, 10.5px and 7.5px still running up here.

   Values are chosen to sit where the role already sits on desktop rather
   than invented: labels track .ft-lbl, list items track body copy, CTAs sit
   between .wx-link and .ft-cta.

   .ic-kicker is deliberately NOT included, at either width. It shares a
   fixed-height industry card with a 7px note and is a genuine micro-tier,
   not drift — pulling it onto the label size would overflow the card.
   ============================================================ */
@media (min-width:981px){
  /* ---- roles that had no desktop size of their own ---- */
  .cw-list li{font-size:clamp(11px,.9vw,16px);}     /* was 11px flat — now tracks body */
  .sol-cta   {font-size:clamp(11px,.85vw,15px);}    /* was 11px flat — between the two CTAs */
  #industries .ind-head,
  .ind-head  {font-size:clamp(13px,1vw,18px);}      /* was 13px flat — a sub-head, so it grows */

  /* ---- one kicker size, as on the phone ---- */
  .kicker,
  #manifesto .kicker,
  .story-kicker,
  .edo-kicker,
  .wd-kicker{font-size:clamp(10px,.78vw,14px);}
}
