/* ══════════════════════════════════════════════════════════════════════
   VENTURE NEST — "WAYFINDING"
   The page is built as the hub's own signage system: a printed wall chart
   on cool paper, interrupted four times by a navy chapter with the yellow
   room in it. Hairline rules, not cards. A rate card, not a tile grid.

   Why the canvas alternates, in numbers: the hub's wall yellow (~#EDE817)
   is 1.30:1 against white — the photo has no edge and reads as stock.
   Against navy #0C152B it is 13.95:1. Brand green vs that yellow is
   1.12:1, so green may never touch the photography.
   ══════════════════════════════════════════════════════════════════════ */

/* Host Grotesk — the brand guide's own display face. One variable file,
   weights 300-800, and it covers every character on this page. */
/* url() resolves against THIS STYLESHEET, not the page. The files are at
   /assets/font/, and this file is /assets/css/site.css — so it is ../font/.
   Written as assets/font/ these resolved to /assets/css/assets/font/ and
   404ed on every page, which is invisible: the fallback stack renders and
   the site merely looks slightly wrong rather than broken. */
@font-face{font-family:"Host Grotesk";src:url(../font/host-grotesk.woff2)format("woff2");
  font-weight:300 800;font-style:normal;font-display:swap}
@font-face{font-family:Poppins;src:url(../font/poppins-400.woff2)format("woff2");font-weight:400;font-display:swap}
@font-face{font-family:Poppins;src:url(../font/poppins-500.woff2)format("woff2");font-weight:500;font-display:swap}
@font-face{font-family:Poppins;src:url(../font/poppins-600.woff2)format("woff2");font-weight:600;font-display:swap}
@font-face{font-family:Poppins;src:url(../font/poppins-700.woff2)format("woff2");font-weight:700;font-display:swap}

:root{
  /* THE PAGE IS LIGHT, AND IT HAS TO SAY SO. Without this the only
     color-scheme declarations in the sheet are the three `dark` ones on the ink
     chapters, so a browser looking for a hint finds nothing at the root and
     decides for itself. Samsung Internet's dark mode then re-colours the text
     and leaves the light backgrounds alone: navy goes pale and vanishes, the
     mint marker inverts to a dark block with white type, the primary button
     with it. Chrome and Edge do the same under Auto Dark Theme.

     Declaring light does not opt out of anything the reader chose — it tells
     the UA this design already has a light treatment, so it should not
     synthesise one. The dark chapters keep their own `dark` further down,
     which is what makes form controls and scrollbars correct inside them. */
  color-scheme:light;
  --paper:#F5F6F4; --paper-2:#E9EBE7;
  --ink:#0C152B;   --ink-2:#131C34;
  --rule-paper:rgba(12,21,43,.10); --rule-ink:rgba(255,255,255,.10);
  --text-paper:#0C152B; --muted-paper:#4D5B75; --micro-paper:#5A6472;
  --text-ink:#DDE2EC;  --muted-ink:#8D99AD;
  --green:#70EF97; --teal:#05C19D; --teal-ink:#037A63;
  /* Green AS TYPE. --green is a fill, not a glyph colour: #70EF97 is 1.34:1 on
     paper, which is not dim, it is unreadable. This is the same hue (138.4deg)
     and the same saturation (80%), taken down to 30% lightness, where it reads
     at 4.12:1 — clear of the 3:1 floor for large text with room to spare, and
     clear of 4.5:1 is one more step down if it is ever wanted at body size.
     Use --green for fills, --green-ink for letters. */
  --green-ink:#0F8A35;
  --teal-hi:#12CBAA; --teal-lo:#03AE90;   /* the #inside panel ramp */

  /* surfaces. The page was drawn as a printed chart, so every group was
     divided by a hairline — thirty-one of them, which reads as a spreadsheet.
     Groups now sit on rounded surfaces, and a rule survives only where it
     separates two things INSIDE one surface. */
  --surface:#FFFFFF; --hairline:rgba(12,21,43,.08);
  --r-sm:3px; --r-md:4px; --r-lg:6px; --r-xl:10px;
  --lift:0 18px 40px -28px rgba(12,21,43,.55);

  --frame:1392px; --read:1040px; --hdr:64px;
  --margin:16px; --gutter:20px; --cols:4;

  --ease-out-quint:cubic-bezier(.23,1,.32,1);
  --ease-out-cubic:cubic-bezier(.215,.61,.355,1);
  --t-press:110ms; --t-hover:160ms; --t-ui:200ms; --t-reveal:460ms;

  --sans:Poppins,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
  --display:"Host Grotesk",var(--sans);
}
@media (min-width:768px){:root{--margin:24px;--cols:8}}
@media (min-width:1024px){:root{--gutter:32px;--cols:12}}
@media (min-width:1200px){:root{--margin:40px}}

*,*::before,*::after{box-sizing:border-box}
/* Always reserve the scrollbar's width, on every page, whether or not that page
   happens to scroll.

   Everything on this site sits in .frame, which is centred with margin-inline:
   auto. On a browser with classic space-taking scrollbars — Windows, Linux, and
   any Mac set to "Show scroll bars: Always" — a page long enough to scroll is
   about 15px narrower than one that is not, and half of that lands on each
   side of every centred block. Clicking from a short page to a long one moved
   the whole header, the nav and every section under it by seven pixels.

   stable, not stable both-edges: the gutter goes on the side the scrollbar
   would use, which is where the layout already expects to lose the space.
   Browsers that do not know the property ignore it and behave as before. */
html{-webkit-text-size-adjust:100%;scrollbar-gutter:stable}
body{overflow-x:clip}
body{margin:0;background:var(--paper);color:var(--text-paper);font-family:var(--sans);
  font-size:1.0625rem;line-height:1.55;letter-spacing:-.011em;font-weight:400;
  -webkit-font-smoothing:antialiased;
  font-variant-numeric:lining-nums;font-optical-sizing:auto}
img{display:block;max-width:100%;height:auto;border-radius:0}
a{color:inherit;text-decoration:none}
ul,ol{margin:0;padding:0;list-style:none}
h1,h2,h3,p{margin:0}
:where(a,button):focus-visible{outline:2px solid var(--teal-ink);outline-offset:3px}

/* ── frame ───────────────────────────────────────────────────────── */
.frame{max-width:var(--frame);margin-inline:auto;padding-inline:max(var(--margin),env(safe-area-inset-left))}
.read{max-width:var(--read)}
.grid{display:grid;grid-template-columns:repeat(var(--cols),1fr);column-gap:var(--gutter);row-gap:64px}

/* ── type ────────────────────────────────────────────────────────── */
.label{font-size:.75rem;font-weight:600;letter-spacing:.06em;text-transform:uppercase;
  line-height:1.2;color:var(--micro-paper);font-variant-numeric:tabular-nums}
.ink .label{color:var(--muted-ink)}
.h2{font-size:1.625rem;font-weight:600;line-height:1.1;letter-spacing:-.022em;text-wrap:balance}
.lede{font-size:1.1875rem;line-height:1.45;letter-spacing:-.011em;max-width:34ch;text-wrap:balance}
.body{font-size:1.0625rem;line-height:1.55;max-width:64ch;text-wrap:pretty}
.muted{color:var(--muted-paper)}
@media (min-width:1200px){.h2{font-size:2.5rem}.lede{font-size:1.375rem}}

/* Display type. Host Grotesk 700 — the brand guide's own face — set in
   sentence case.
   It replaced Varien, which was unicase: cap-height equalled x-height, which is why it wanted
   line-height .86 and tracking that could never go negative. A face with real
   ascenders and descenders inverts both: the leading opens up, and large sizes
   want NEGATIVE tracking (-.02 to -.03em) or the words read loose. */
.hook{font-family:var(--display);font-weight:700;line-height:.94;
  container-type:inline-size;display:block;
  /* -.045em. The tightest kern Host Grotesk 700 sets in this string is "Tu" at
     -.095em, so tracking every pair by half of that stays inside what the type
     designer already sanctioned, and it sits at the geometric middle of the
     31->64px ramp on the .lc-head ladder (-.045 at 26px, -.065 at 40px). */
  --track:-.045em}
/* 5.31cqi, down from 9.1, because the column went 700 -> 1200 and the two have
   to move together: font-size IS a percentage of the column here, so leaving the
   ratio alone would have taken the headline to 109px. 1200 x 5.31% = 63.7px, the
   size it has always been. Only the shape changed.

   The number is set by the long line, not by taste. "Turning your idea into
   revenue and impact" measures 1087px at 63.7px, which is 91% of the column, and
   because both sides scale with cqi that 91% holds at every width the forced
   break applies to — measured 1087/1200, 1027/1132, 890/976, 780/852, 662/720.
   Nine percent is the margin the line has before it wraps to a third; do not
   raise the ratio without re-measuring that longest line. */
/* TRACKING GOES ON THE LINE, NOT ON THE h1, AND THAT IS THE WHOLE POINT.
   An em length resolves against the element it is declared on, computes to an
   absolute px, and inherits that px unchanged. .hook carries no font-size, so
   declaring letter-spacing up there resolved it against the UA's h1{font-size:2em}
   = 34px and handed a flat -1.02px down to a span rendering at 63.7px. Measured
   in Chrome at 1440px: the headline asked for -.03em and received -.016em. The
   type got LOOSER as it got BIGGER — the exact inversion of the .lc-head rule
   this sheet already wrote down. Declaring it here makes the em mean what it says
   at every size. Do not move these two back up.

   CHILD combinator, not descendant. The reveal splits each line into word and
   character spans; as `.hook span` this rule would make all 54 characters
   display:block and stack the headline into 54 lines. */
.hook > span{display:block;
  /* Two declarations, in this order, on purpose. cqi needs container queries;
     without them the clamp() below is an invalid value and the WHOLE
     declaration is dropped, which leaves the headline at whatever it inherits —
     about 30px, on the largest type on the site. The vw line lands first and
     survives, and it is tuned to track the real one rather than merely exist:
     50.2 against 51.8 at 1024, 37.6 against 38.2 at 768, and the same 31px
     floor on a phone. A browser with container queries overrides it. */
  font-size:clamp(31px,4.9vw,64px);
  font-size:clamp(31px,5.31cqi,68px);
  letter-spacing:var(--track);
  /* Word-spacing RELAXES, -.06em -> -.02em, while the letters close: this
     sheet's own instruction under .lc-head is to tighten the letters, not the
     gaps. Effective word gap goes 259 -> 242 thousandths of an em, still tighter
     than today and clear of the ~205 where words start to merge. */
  word-spacing:-.02em;
  /* Optical centring. Letter-spacing is applied after the last character too, so
     a negative track ends the line box short of the ink and centred type drifts
     right by half the track. text-indent shifts a centred line by half its own
     value, so feeding it --track cancels exactly that — and re-cancels itself if
     the track is ever changed. */
  text-indent:var(--track)}
/* Below 768 the two lines stop being lines. font-size has bottomed out on the
   31px clamp floor while the column keeps shrinking, so the long line — 527px of
   ink at 31px — no longer fits and would run out of the frame. Going inline hands
   the break back to the browser, which wraps the same sentence to three short
   lines on a phone and balances their lengths. The authored break is a wide-screen
   decision, and only a wide screen gets it.

   text-indent moves up to .hook here because an inline box ignores it and the
   optical-centring correction would be lost. The em is honest at this size and
   only at this size: .hook computes to 30px against a 31px line, so -.045em lands
   within a twentieth of a pixel of the value the span would have set. */
@media (max-width:767px){

  .hook > span{display:inline}
  .hook{text-wrap:balance;text-indent:var(--track)}
}
.chapter-title{font-family:var(--display);font-weight:700;line-height:1.0;
  letter-spacing:-.028em;word-spacing:-.06em;word-spacing:-.06em;font-size:clamp(32px,5.4vw,60px)}

/* ── header ──────────────────────────────────────────────────────── */
/* frosted bar: translucent ground + blur + saturation lift, with a bright
   top edge so it reads as a pane of glass rather than a flat tint. Not Apple's
   Liquid Glass — that has no web equivalent — but the same intent. */
.hdr{position:sticky;top:0;z-index:60;
  background:rgba(245,246,244,.52);
  -webkit-backdrop-filter:blur(22px) saturate(180%);
  backdrop-filter:blur(22px) saturate(180%);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.65);
  transition:background-color 220ms var(--ease-out-quint),
             box-shadow 220ms var(--ease-out-quint)}
.hdr[data-stuck]{background:rgba(245,246,244,.72);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.8),0 10px 26px -18px rgba(12,21,43,.5)}
@media (prefers-reduced-transparency:reduce){
  .hdr,.hdr[data-stuck]{background:var(--paper);
    -webkit-backdrop-filter:none;backdrop-filter:none}
}
.hdr::after{content:"";position:absolute;inset-inline:0;bottom:0;height:1px;background:var(--rule-paper);
  opacity:0;transition:opacity 150ms var(--ease-out-quint)}
.hdr[data-stuck]::after{opacity:1}
/* wrap: below 1024 the nav is a full-width row of its own under the bar, open
   or closed. Without this it takes a column beside the logo and squeezes the
   Visit Us button onto two lines. At 1024 the nav goes width:auto and nothing
   wraps. */
.hdr .frame{display:flex;flex-wrap:wrap;align-items:center;gap:14px;min-height:64px}
.brand{display:inline-flex;align-items:center;min-height:44px}
.brand img{height:24px;width:auto}
/* NO-JS IS THE BASE STATE, as everywhere else in this sheet. Without the
   script the five pages are simply a wrapped row under the bar — plain, but
   reachable. Below 1024 they used to be display:none with nothing to open them,
   so on a phone and on a tablet the site had no way to its own pages at all. */
.hdr nav{display:flex;flex-wrap:wrap;gap:2px 22px;width:100%;order:3;
  margin:0 0 8px;padding-top:2px}
.hdr nav a{display:inline-flex;align-items:center;min-height:44px;font-size:.9375rem;color:var(--muted-paper)}
.hdr .sp{margin-left:auto}

/* With the script, the row folds into the button. */
.js .hdr nav{display:none}
.js .hdr nav[data-open]{display:flex;flex-direction:column;gap:0;width:100%;
  margin:0;padding:4px 0 10px;order:3}
.js .hdr nav[data-open] a{width:100%;min-height:48px;font-size:1.0625rem;
  color:var(--text-paper);border-top:1px solid var(--rule-paper)}

/* The button. 44px square, the site's minimum tap target, and it only exists
   below the width at which the row itself fits. */
.hdr__menu{display:none;flex:none;align-items:center;justify-content:center;
  width:44px;height:44px;margin-right:-8px;padding:0;
  background:none;border:0;cursor:pointer;color:var(--text-paper)}
.js .hdr__menu{display:inline-flex}
.hdr__bars,.hdr__bars::before,.hdr__bars::after{content:"";display:block;
  width:18px;height:2px;border-radius:1px;background:currentColor}
.hdr__bars{position:relative}
.hdr__bars::before{position:absolute;top:-6px}
.hdr__bars::after{position:absolute;top:6px}
/* open: the bars cross */
.hdr__menu[aria-expanded="true"] .hdr__bars{background:transparent}
.hdr__menu[aria-expanded="true"] .hdr__bars::before{top:0;transform:rotate(45deg)}
.hdr__menu[aria-expanded="true"] .hdr__bars::after{top:0;transform:rotate(-45deg)}
@media (prefers-reduced-motion:no-preference){
  .hdr__bars,.hdr__bars::before,.hdr__bars::after{
    transition:transform var(--t-ui) var(--ease-out-quint),top var(--t-ui) var(--ease-out-quint),background var(--t-ui) linear}
}

/* At 1024 the row fits across the bar again, so the button has nothing to do. */
@media (min-width:1024px){
  .hdr nav,.js .hdr nav{display:flex;flex-direction:row;flex-wrap:nowrap;
    width:auto;order:0;gap:26px;margin:0 0 0 10px;padding:0}
  .js .hdr nav[data-open] a{width:auto;min-height:44px;font-size:.9375rem;
    color:var(--muted-paper);border-top:0}
  .js .hdr__menu{display:none}
}

/* ── buttons ─────────────────────────────────────────────────────── */
.btn{display:inline-flex;align-items:center;justify-content:center;min-height:52px;
  padding:15px 30px;border:0;border-radius:999px;font-family:var(--sans);gap:9px;
  font-size:1.0625rem;font-weight:600;letter-spacing:-.011em;cursor:pointer;
  transition:background-color var(--t-hover) var(--ease-out-quint),transform var(--t-press) var(--ease-out-quint)}
.btn--go{background:var(--green);color:var(--ink)}          /* 12.5:1 */
.btn--line{background:transparent;color:var(--ink);box-shadow:inset 0 0 0 1px rgba(12,21,43,.22)}
.btn__chev{width:7px;height:10px;flex:none;opacity:.45;
  transition:transform var(--t-hover) var(--ease-out-quint)}
.ink .btn--line{color:var(--text-ink);box-shadow:inset 0 0 0 1px rgba(255,255,255,.28)}
.btn:active{transform:scale(.98)}
@media (hover:hover) and (pointer:fine){
  .btn--go:hover{background:#5FE68B}
  .btn--line:hover{background:rgba(12,21,43,.05);box-shadow:inset 0 0 0 1px rgba(12,21,43,.4)}
  .btn--line:hover .btn__chev{transform:translateX(2px)}
  .ink .btn--line:hover{background:rgba(255,255,255,.07)}
}
.ulink{background-image:linear-gradient(currentColor,currentColor);background-size:0 1px;
  background-position:0 100%;background-repeat:no-repeat;transition:background-size var(--t-ui) var(--ease-out-quint)}
@media (hover:hover){.ulink:hover{background-size:100% 1px}}

/* ── hero ────────────────────────────────────────────────────────────
   Plain paper. This carried two brand-tinted layers, a mint/teal radial wash
   and a ruled 58px grid, both of which the new art makes redundant: it is a
   cut-out on a transparent ground with its own labels floating around it, so
   the wash sat behind the cut-out and the ruling ran straight through it.
   The section is now the same --paper as every other, and the picture is the
   only thing on it. */
/* .paper.hero, not .hero: `.paper{padding-block:78px}` is declared later at
   the same specificity and was silently winning both padding declarations. */
.paper.hero{position:relative;isolation:isolate;overflow:clip;display:grid;align-content:center;
  margin-top:calc(var(--hdr) * -1);padding-block:calc(88px + var(--hdr)) 68px;
  background:var(--paper)}
/* ── the hero gradient wave ────────────────────────────────────────
   The canvas is painted into this box by assets/js/gradient-wave.js. The
   section already carries isolation:isolate and overflow:clip, so the wave is
   contained and cannot escape into the header above or the router below.

   The copy has to be lifted deliberately. .hero__inner is a grid item in normal
   flow; an absolutely positioned sibling with any z-index at all paints in the
   positioned layer, which is ABOVE non-positioned in-flow content. Without the
   z-index:1 below, the wave covers the headline.

   The mask is the "soft" in the brief doing work no colour choice can do. The
   tints already sit at 1.07-1.13:1 against the paper, but a rectangle of them
   still has four edges, and an edge is what makes a background read as a panel.
   Fading the top eighth and the bottom third to nothing leaves colour in the
   middle and paper at the boundaries, so the section has no seam. */
.wave{position:absolute;inset:0;z-index:0;pointer-events:none;
  opacity:0;transition:opacity 700ms var(--ease-out-cubic);
  -webkit-mask-image:linear-gradient(to bottom,transparent 0,#000 12%,#000 66%,transparent 100%);
  mask-image:linear-gradient(to bottom,transparent 0,#000 12%,#000 66%,transparent 100%)}
/* Set by the script once the first frame is on the canvas, so the wave arrives
   rather than appearing mid-draw. */
.wave[data-wave-on]{opacity:1}
.wave canvas{display:block;width:100%;height:100%}
@media (prefers-reduced-motion:reduce){.wave{transition:none}}
.hero__inner{position:relative;z-index:1;display:grid;align-content:center;width:100%}
/* Graded, and graded on the RENDERED gaps rather than the margins, because the
   headline's line box carries 14px of its own below the last baseline. Declared
   30 / 56 / 76 measures 44 / 56 / 76 on screen, which is the shape the hero
   wants: the headline and the standfirst are one statement and sit close; the
   buttons are the thing to do and take a clear step out; the photographs are a
   different kind of object altogether and take the biggest step of the three.

   The first pass after removing the Open Thursday line was 30 / 46, which
   MEASURED 44 / 46 — two steps so nearly equal that the buttons read as a third
   line of the paragraph. That is the same failure the 24/22/32 note warned
   about, arrived at from the other direction. Check the rendered gaps, not the
   declared ones, if these are ever retuned. */
/* 700px, not 40ch. 40ch set it three lines deep under a headline that is now
   two, and the pair read as a wedge. At 700 it falls to two lines of 577 and
   613px — wide enough to sit under the headline rather than under its middle,
   and at 22px still about 53 characters, well inside the readable band. It stays
   far narrower than the 1200 column on purpose: matching the headline's width
   would push it past 90 characters a line. */
.hero .lede{margin-top:30px;max-width:700px;margin-inline:auto}
.hero .live{margin-top:18px;justify-content:center}
/* Every button row on the site, not just the hero's. .acts had only ever been
   styled under .hero, so everywhere else it was a plain block holding two
   inline-flex buttons with nothing between them but the collapsed space between
   the tags: 4.2px at 1440, and on a 390px screen — where they stop fitting side
   by side and wrap — 0.0px, the two pills touching. The hero rule below still
   wins on specificity; this is the floor under it. */
.acts{display:flex;flex-wrap:wrap;align-items:center;gap:12px}
.hero .acts{display:flex;flex-wrap:wrap;gap:12px;margin-top:56px;justify-content:center}
.hero .hook{margin-top:0}
/* Centred, and 1200px at EVERY width rather than --read small / 1200 large. The
   1200 is load-bearing beyond taste: .hook is an inline-size container, so its
   width IS the headline's font-size (5.31cqi -> 63.7px). Hold the column still
   and the headline holds still. It was 700 while the headline was three stacked
   lines; the headline is two wide ones now and the column had to open to take
   the long one. 1200 inside a 1392 frame leaves 96px of margin either side.

   Never shrink-wrap .hook to centre it — no width:fit-content, no
   display:inline-block, no margin-inline:auto on the h1 itself. An inline-size
   container that sizes to its own contents resolves its query units as if it
   were empty, and the headline silently collapses to the 31px clamp floor.
   Centring is text-align on this block and nothing else. */
/* width, NOT max-width. .hero__copy is a grid item, and margin-inline:auto on a
   grid item makes it shrink to fit its contents instead of taking the track. It
   came out 543px rather than the declared width, and because .hook is an
   inline-size container whose font-size is a cqi ratio, that silently shrank the
   headline with it. A definite width keeps the column — and therefore the
   headline — the size it is meant to be. */
.hero__copy{width:min(1200px,100%);margin-inline:auto;text-align:center;min-width:0}
/* The CTAs used to stack on a phone. They sit side by side now, sharing the
   row equally, so the fold keeps the picture rather than spending 125px of it
   on two full-width bars. See the mobile block at the end of the sheet. */
/* The accent under "the room" is the one green mark in the type. Green is
   rationed on this page, so it goes on the phrase the whole site is about —
   The Room is a thing you can actually book, and the sentence turns on it.
   It is drawn as a positioned bar rather than an inline background because
   .hook runs line-height .94: an inline background paints the full em box
   and would bleed into the line above. Navy on --green is 12.5:1, the same
   pairing as the primary button, so the two greens rhyme instead of
   competing.

   The hero deliberately has NO kicker label, unlike every other section on
   the page. The headline is the entrance and it opens alone; the clearance
   below the header is what holds it, not a line of type above it. */
/* nowrap is load-bearing, not tidiness: an absolutely positioned ::after on
   an inline that breaks across two lines draws ONE bar over the union of both
   fragments — a green rule straight through the gap. Two short words never
   need to break, so forbidding it is the cheap fix. */
/* isolation, so the marker can go BEHIND the glyphs. An ::after on an inline
   paints above that inline's own text, which was harmless while this was a
   4.7px rule sitting under the baseline and is fatal now it is a filled block.
   z-index:-1 alone would not do it: .hook__mark is position:relative with no
   z-index and so creates no stacking context, and the marker would sink past
   the section's own background and disappear. isolate makes the context that
   -1 is measured against. */
.hook__mark{position:relative;font-style:normal;white-space:nowrap;isolation:isolate}
/* Green words in the headline. <em> for the same reason .hook__mark uses it —
   these carry the sentence's emphasis — with the italic turned off, since the
   display face says it in colour instead.

   nowrap so a green word never breaks across the line ending; both are short. */
.hook__gr{color:var(--green);font-style:normal;white-space:nowrap}
/* Asked for more contrast, take the same hue two steps darker (7.1:1) rather
   than abandoning the colour, which is the point of the emphasis. */
@media (prefers-contrast:more){.hook__gr{color:#0A5E23}}
/* The marker, ported from the Remotion MarkerHighlight scene. Same gesture:
   a block of colour behind the phrase, scaled from the left edge, overshooting
   once and settling — a pen stroke, not a wipe.

   TWO THINGS ARE NOT PORTED, both because this palette already answers them.

   The source cross-fades the text from baseColor to highlightedTextColor as the
   marker passes under it. Here both ends are the same navy: it measures 16.73:1
   on the paper and 12.51:1 on the green, so the type is fully legible before and
   after and there is nothing to correct. Fading navy to navy is a frame budget
   spent on nothing.

   The source's `inset: 0 -0.1em` is a full em-box block. It cannot be used here:
   .hook runs line-height .94, so the em box is TALLER than the line it sits on
   and a full-height marker on line two would strike the descenders of line one.
   The block is inset from the top instead and sized to the glyphs.

   .3em is the measured number, not a round one. At .2em the block started 2.4px
   above the descender box of line one and the tails of "Turning", "your" and
   "impact" sat on the green. .3em puts 4.0px of paper between them at 1440 and,
   because the inset and the line-height are both proportional, 2.0px at the 31px
   phone size. The ascender tips of the h and t in "right" stand a couple of
   pixels proud of the block, which is what a real marker does. */
.hook__mark::after{content:"";position:absolute;z-index:-1;
  left:-.09em;right:-.09em;top:.3em;bottom:.02em;
  background:var(--green);pointer-events:none}
/* Green on paper is 1.34:1 — fine for decoration, which this is, since the
   sentence reads identically without it. But a reader who has asked for more
   contrast should get a defined edge, and the old rule answered that by filling
   with --teal-ink. That was right for a rule under the text and wrong for a
   block behind it: navy on --teal-ink is 3.43:1, so darkening the fill would
   have broken the text for exactly the people who asked for help. The fill
   stays green and the shape gets an edge instead. */
@media (prefers-contrast:more){
  .hook__mark::after{box-shadow:inset 0 0 0 2px var(--ink)}
}

@media (min-width:1024px){
  /* No viewport floor. Pinned to the window the hero grew while its content
     did not, and every extra pixel landed as dead space above the doors.
     Content plus padding is the right height. */
  /* 34px of clearance under a 64px headline read as pinned to the header —
     roughly half a line of air above three lines of display type. With no
     kicker to carry it, the whole clearance is padding: 86px, a little over
     one line of the headline, and the bottom moves 44 -> 56 so the block is
     not top-heavy either. */
  .paper.hero{padding-block:calc(86px + var(--hdr)) 56px;align-content:center}
  /* The hero is one centred column now: the art is gone, so there is no track
     to size and no pair to level. .hero__copy holds the 700px cap at every
     width, in the base rule. */
}

/* ── ink chapters ────────────────────────────────────────────────── */
.ink{background:var(--ink);color:var(--text-ink);color-scheme:dark;padding-block:78px}
.ink .muted{color:var(--muted-ink)}
.bleed{margin-inline:calc(50% - 50vw);width:100vw}
.bleed img{width:100%}
figure{margin:0}
figcaption{margin-top:10px;font-size:.8125rem;line-height:1.45;letter-spacing:.01em;
  max-width:44ch;color:var(--muted-ink)}
.paper figcaption{color:var(--micro-paper)}
.paper figure img{outline:1px solid var(--rule-paper);outline-offset:0}


/* photo chapters: unequal pairs. Never the same aspect twice in a row, and
   never two frames with high mean luminance adjacent without ground between. */
.fig-a,.fig-b,.fig-c,.fig-d{grid-column:1/-1}
@media (min-width:1024px){
  .fig-a{grid-column:1/6}         /* 4:5 tall  */
  .fig-b{grid-column:7/13}        /* 4:3 wide  */
  .fig-c{grid-column:3/9}         /* 1:1       */
  .fig-d{grid-column:10/13;margin-top:64px}  /* deliberately unlevel */
}


/* ── grounds ────────────────────────────────────────────────────────
   Thirteen thousand pixels of one flat colour is what made the middle of
   this page feel like filler. These are the interruptions, and they are
   built from devices the page ALREADY owns rather than new decoration:
   the masked 58px grid from the hero, the feTurbulence grain from the
   footer panel, and the green-to-teal ramp the brand guide itself uses.

   The grain is the load-bearing part. An even colour ramp with nothing in
   it is the thing that reads as machine-made; a gradient with film grain
   over it reads as printed. It costs one data-URI and no requests. */
.band,.chapter{position:relative;isolation:isolate}
.band::before,.chapter::before,.band::after,.chapter::after{
  content:"";position:absolute;inset:0;left:50%;width:100vw;
  transform:translateX(-50%);pointer-events:none}
.band::before,.chapter::before{z-index:-2}
.band::after,.chapter::after{z-index:-1}

/* paper band — the sheet cools by one step and picks the grid back up.
   NOT --paper-2 (#E9EBE7): that ground drops the teal call-to-action to
   4.41:1, under the 4.5 this page already passes at. #EFF1ED holds 4.66. */
.band::before{background:#EFF1ED}
.band::after{
  background-image:
    linear-gradient(to right,rgba(12,21,43,.055) 1px,transparent 1px),
    linear-gradient(to bottom,rgba(12,21,43,.055) 1px,transparent 1px);
  background-size:58px 58px;background-position:center top;
  -webkit-mask-image:radial-gradient(120% 86% at 50% 0%,#000 18%,transparent 76%);
  mask-image:radial-gradient(120% 86% at 50% 0%,#000 18%,transparent 76%)}

/* the dark chapter — navy, with the brand teal rising under one corner */
.chapter{color:var(--text-ink);color-scheme:dark;background:transparent}
/* every paper-ink colour that survives inside the dark chapter. Checked one by
   one against #0C152B rather than assumed — the old .pricing dt and .note were
   under 3:1 before this. */
.chapter .pull{border-inline-start-color:var(--teal)}
.chapter::before{
  background:
    radial-gradient(74% 86% at 10% 0%,rgba(5,193,157,.28) 0%,rgba(5,193,157,0) 62%),
    radial-gradient(58% 74% at 98% 98%,rgba(112,239,151,.15) 0%,rgba(112,239,151,0) 60%),
    linear-gradient(168deg,#0C152B 0%,#0B2536 54%,#0C152B 100%)}
.chapter::after{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");opacity:.42;mix-blend-mode:overlay}

/* what the dark ground has to repaint */
.chapter .muted,.chapter .gets dd,.chapter .arith b,.chapter .visit .m{color:var(--muted-ink)}
.chapter .steps li,.chapter .door{border-top-color:var(--rule-ink)}
.chapter .arith{background:rgba(255,255,255,.045);box-shadow:inset 0 0 0 1px var(--rule-ink)}
.chapter .arith div{border-bottom-color:var(--rule-ink)}
.chapter .fpo{background:rgba(255,255,255,.05);box-shadow:inset 0 0 0 1px var(--rule-ink);
  color:var(--muted-ink)}
/* The strip's plates are OPAQUE, not a translucent wash. A moving plate over a
   gradient has a different contrast at every point of its travel: measured off
   the render, the caption fell to 3.87:1 where the teal glow lifts the ramp to
   #283E4C. An opaque fill makes it 6.05:1 wherever the plate happens to be. */
.strip .fpo{background:#0E1A2E;box-shadow:inset 0 0 0 1px var(--rule-ink);
  color:var(--muted-ink)}
.chapter .fpo::before,.strip .fpo::before{background-image:repeating-linear-gradient(135deg,
  rgba(255,255,255,.055) 0 1px,transparent 1px 10px)}
.chapter .fpo__chip,.strip .fpo__chip{background:rgba(255,255,255,.12);color:var(--text-ink)}
.chapter .fpo__t,.strip .fpo__t{color:var(--text-ink)}
.chapter .fpo__n,.strip .fpo__n{color:var(--muted-ink)}

/* ── the image strip ───────────────────────────────────────────────
   A marquee, ported from the shadcn-style MarqueeLogoScroller now kept at
   components/ui/marquee-logo-scroller.tsx — same device, no React on this page.

   It sits on the DARK ramp for a measured reason, not a stylistic one: every
   interior here is a saturated yellow wall, which is 1.30:1 against white and
   13.95:1 against the navy. On paper these photographs would have no edge and
   read as stock; on the ramp they are framed. Green stays out of this strip
   entirely — brand green against that yellow is 1.12:1.

   The seam: `gap` puts space BETWEEN items but not after the last one, so with
   translateX(-50%) the loop lands half a gap early and visibly jumps. The
   trailing padding on the track is what makes the two halves equal. Do not
   remove it. */
.strip{position:relative;isolation:isolate;width:100vw;margin-inline:calc(50% - 50vw);
  margin-block:46px 8px;padding-block:44px 40px;color:var(--text-ink);color-scheme:dark;
  background:
    radial-gradient(74% 86% at 10% 0%,rgba(5,193,157,.26) 0%,rgba(5,193,157,0) 62%),
    radial-gradient(58% 74% at 98% 98%,rgba(112,239,151,.14) 0%,rgba(112,239,151,0) 60%),
    linear-gradient(168deg,#0C152B 0%,#0B2536 54%,#0C152B 100%)}
.strip::after{content:"";position:absolute;inset:0;z-index:-1;pointer-events:none;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");opacity:.42;mix-blend-mode:overlay}
.strip .label{color:#A6B2C2}

.mq{position:relative;overflow:hidden;margin-top:22px;
  -webkit-mask-image:linear-gradient(to right,transparent,#000 6%,#000 94%,transparent);
  mask-image:linear-gradient(to right,transparent,#000 6%,#000 94%,transparent)}
.mq__track{display:flex;width:max-content;align-items:stretch;
  gap:var(--mq-gap,16px);padding-right:var(--mq-gap,16px);
  animation:mq-drift var(--mq-dur,72s) linear infinite}
/* Three runs, not two. The travel is one run, so the strip stays full while
   the viewport is under two runs (5376px) — a two-run track only covered
   2688px and emptied out on an ultrawide before the loop reset. */
@keyframes mq-drift{from{transform:translateX(0)}to{transform:translateX(-33.3333%)}}
/* Guarded the way .reel guards its own: on a touch screen :hover sticks after
   a tap, which would freeze the strip with no way to restart it. Focus-within
   stays outside the guard — a keyboard user on a touch device still needs it. */
@media (hover:hover) and (pointer:fine){
  .mq:hover .mq__track{animation-play-state:paused}
}
.mq:focus-within .mq__track{animation-play-state:paused}
.mq:focus-visible{outline:2px solid var(--teal);outline-offset:4px}
/* Width as a property, not a literal, so a variant can retune it without a
   specificity fight. Defaults unchanged: 268 / 320. */
.mq{--mq-i:268px}
@media (min-width:768px){.mq{--mq-i:320px}}
.mq__i{flex:none;width:var(--mq-i)}
/* Moving content that runs longer than five seconds has to be stoppable. With
   reduced motion the strip stops being a marquee and becomes a scroller. */
@media (prefers-reduced-motion:reduce){
  .mq{overflow-x:auto;scroll-snap-type:x mandatory}
  .mq__track{animation:none}
  .mq__track>*{scroll-snap-align:start}
}

/* ── the hero's foot: a plinth of square photographs ───────────────
   The owner asked for a stack of squares under the CTAs, spanning the hero,
   drifting sideways forever. This is that, and it is the SAME marquee as the
   strip above — .mq, .mq__track, mq-drift, the seam padding, the edge mask and
   the reduced-motion branch are all reused. What is added here is a ground, a
   square, and a pause control.

   WHY IT HAS A GROUND OF ITS OWN. Photographs of the hub cannot sit on paper.
   The walls are a saturated yellow: 1.30:1 against --paper, 13.95:1 against
   --ink. On paper they have no edge and read as stock; on ink they are framed.
   The README states it as a rule — "every photograph sits on this" — so rather
   than bend it, the band brings the ink with it and becomes the hero's bottom
   edge, which the hero never had.

   The ground is FLAT ink, not .strip's ramp. That ramp's second radial is
   rgba(112,239,151,.14) — brand green — sitting at 98% 98%, which is exactly
   where the tiles are. Green is an adjacent hue to that wall yellow (1.12:1)
   and the sheet already forbids the pairing. */
.strip--foot{margin-block:76px 0;padding-block:0;
  background:var(--ink);color:var(--text-ink)}
/* The hero's own bottom padding is spent by the plinth. Without this the band
   lands 56px lower and drops under the fold on a 1440x813 laptop. */
.paper.hero:has(.strip--foot){padding-bottom:0}
.strip--foot .mq{margin-top:0}

/* Squares. aspect-ratio off a fixed width, so the height falls out and the loop
   maths never sees it. overflow:hidden because the track is align-items:stretch
   and aspect-ratio yields to an automatic minimum content size — one tile whose
   contents outgrow the square would make every tile in the line taller and
   break the pitch, the run width and the seam in one move. */
/* --mq-gap:0 — the squares touch. The ribbon IS the band; no ground shows
   between the pictures, above them or below them. The seam fix survives it
   because .mq__track's padding-right reads this same property, so gap and the
   trailing pad stay equal at zero. */
/* TWO runs, so the travel is half the track, not a third. Thirty-three tiles
   make a run 5016px wide at the desktop size and the loop only needs
   (runs - 1) x run >= viewport to never show a gap — one run covers 2560px
   twice over, so a third would be 33 more images for nothing.

   Speed. The drift is one run per --mq-dur, so 5016px / 120s = 42px/s at the
   desktop size. The sheet's own marquee runs 2688px / 72s = 37px/s, so this is
   a shade quicker than the house pace, which is what was asked for. The number
   that actually reads is the per-tile cadence, dur / tile-count = 3.6s, and
   because the pitch cancels it stays 3.6s at every breakpoint — the strip feels
   the same speed on a phone as on a monitor even though the pixels differ. */
.mq--sq{--mq-i:104px;--mq-gap:0px;--mq-dur:120s}
@media (min-width:768px){.mq--sq{--mq-i:132px}}
@media (min-width:1024px){.mq--sq{--mq-i:152px}}
@media (min-width:1600px){.mq--sq{--mq-i:172px}}
/* Inside no-preference, not outside it. .mq--sq .mq__track is 0,2,0 and would
   otherwise beat the reduced-motion branch's `.mq__track{animation:none}` at
   0,1,0 and put the name back, whatever the reader asked for. */
@media (prefers-reduced-motion:no-preference){
  .mq--sq .mq__track{animation-name:mq-drift-half}
}
@keyframes mq-drift-half{from{transform:translateX(0)}to{transform:translateX(-50%)}}
.mq--sq .mq__i{aspect-ratio:1;overflow:hidden;
  background:rgba(255,255,255,.04)}
/* The tiles are cut square at 448px, so object-fit has nothing to crop here —
   it is the guard for a future source that is not square. -webkit-user-drag
   stops a picture peeling off the strip when someone tries to swipe it. */
.mq--sq .mq__i img{width:100%;height:100%;object-fit:cover;display:block;
  -webkit-user-drag:none;user-select:none}

/* The edge fade has to be at least as wide as the pause control, or the control
   sits on top of a moving photograph. The inherited 6% is 23px on a 390px
   phone; the control is 44px. */
.strip--foot .mq{-webkit-mask-image:none;mask-image:none}

/* Placeholder until the photographs arrive. Same idiom as .reel__cell[data-fpo]
   — a hatch and a centred frame mark, so it reads as a reserved image and not a
   broken one — inverted for the ink ground. The number is the shot list; run 1
   carries a title attribute naming what belongs in each slot.

   To drop a photograph in, replace the tile's contents, nothing else:
     <div class="mq__i"><img src="assets/img/hero/01.webp" alt="" width="408"
          height="408" decoding="async" fetchpriority="low"></div>
   Do NOT add loading="lazy": it keys off intersection, and a tile parked
   offscreen in a max-content track pops in mid-drift. Cut the squares with a
   script into assets/img/hero/, the way tools/build-vdl-tiles.py does, so the
   crop rectangles survive a change of tile size. */
.mq--sq .mq__i[data-fpo]{position:relative;
  box-shadow:inset -1px 0 0 rgba(255,255,255,.10);
  background-image:repeating-linear-gradient(135deg,
    rgba(255,255,255,.055) 0 1px,transparent 1px 11px)}
.mq--sq .mq__i[data-fpo]::after{content:"";position:absolute;left:50%;top:50%;
  width:24px;height:21px;transform:translate(-50%,-50%);
  border:1.5px solid rgba(255,255,255,.20);border-radius:3px}

@media (prefers-reduced-motion:reduce){
  /* .mq already becomes a scroll-snap list here. */
  .strip--foot .mq{-webkit-mask-image:none;mask-image:none}
}

/* ── inside: a rail of rooms beside a deck of cards ─────────────────
   Ported from the FeatureCarousel now at components/ui/feature-carousel.tsx,
   matched to the reference: full-width pills stacked on a solid panel, the
   panel fading out top and bottom, and a deck of cards where the neighbours
   peek out desaturated behind the live one.

   The panel is the brand navy rather than the source's #62B2FE, and the
   caption sits over the picture on a scrim, as in the reference. */
.fc{display:grid;grid-template-columns:1fr;margin-top:38px;
  border-radius:28px;overflow:hidden;background:var(--surface);
  box-shadow:inset 0 0 0 1px var(--hairline)}
/* 768, not 900. The phone layout below now puts the rail beside the deck too,
   so leaving this at 900 left one stacked band between two side-by-side ones:
   side by side at 390, stacked at 800, side by side at 1000. At 768 the rail
   is 288px, which the full-size chips clear comfortably. */
/* Three tiers, because the constraint is one chip, not the breakpoint grid.
   "Innovation Program" is 252px at full size; the rail's content box is the
   track minus 36px of padding below 900 and 52px above it. At 40% the 768
   track gives exactly 252px of room, i.e. none. 46% through the tablet band
   puts it at 295px and hands back to the desktop 40% once the track is wide
   enough to carry it. */
@media (min-width:768px){.fc{grid-template-columns:minmax(0,46%) 1fr}}
@media (min-width:900px){.fc{grid-template-columns:minmax(0,40%) 1fr}}

/* the rail */
/* A vertical teal ramp, not flat green. Frosted glass needs something with
   depth behind it — over a flat fill the blur has nothing to pick up and the
   pills read as grey boxes. The two stops bracket brand --teal #05C19D, so
   this is the palette with a gradient in it, not a new hue. Vertical on
   purpose: the edge fades below are single colours, and only a vertical ramp
   has one colour along its whole top edge and another along its whole bottom. */
.fc__rail{position:relative;overflow:hidden;
  display:flex;align-items:center;justify-content:center;
  min-height:420px;padding:34px 18px;
  background:linear-gradient(to bottom,var(--teal-hi),var(--teal-lo))}
@media (min-width:900px){.fc__rail{min-height:560px;padding:40px 26px}}
/* the panel fades into itself at both ends, so the wheel has no hard cut */
.fc__rail::before,.fc__rail::after{content:"";position:absolute;inset-inline:0;
  height:88px;z-index:3;pointer-events:none}
.fc__rail::before{top:0;background:linear-gradient(to bottom,var(--teal-hi) 12%,transparent)}
.fc__rail::after{bottom:0;background:linear-gradient(to top,var(--teal-lo) 12%,transparent)}

.fc__slot{position:absolute;left:50%;width:max-content;max-width:calc(100% - 36px);
  transition:transform 620ms var(--ease-out-quint),opacity 620ms var(--ease-out-quint)}
/* Frosted glass, the same recipe as the header and the card caption: a thin
   white ground, a saturation lift so the teal reads through rather than going
   grey, and a bright inset top edge for the specular line. That top edge is
   what actually says "glass" at this size — a 52px pill covers 9% of the ramp,
   so the blur alone has very little to refract. */
.fc__chip{display:flex;align-items:center;gap:12px;min-height:52px;
  padding:13px 26px;border-radius:999px;cursor:pointer;text-align:left;
  font:inherit;font-size:.8125rem;font-weight:500;letter-spacing:.09em;
  text-transform:uppercase;white-space:nowrap;border:0;
  background:rgba(255,255,255,.24);
  -webkit-backdrop-filter:blur(16px) saturate(180%);
  backdrop-filter:blur(16px) saturate(180%);
  /* navy, never white. Measured off the render: navy on the frosted pill is
     5.4:1 one row off centre and 4.4:1 two rows off; white there is 1.8:1. */
  color:rgba(12,21,43,.84);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.55),
             inset 0 0 0 1px rgba(255,255,255,.28);
  transition:background-color var(--t-ui) var(--ease-out-quint),
             color var(--t-ui) var(--ease-out-quint),
             box-shadow var(--t-ui) var(--ease-out-quint)}
.fc__chip svg{width:18px;height:18px;flex:none;opacity:.72}
/* The live pill is the same material brought almost to opaque rather than a
   flat white slab — it stays in the glass family and still sits 2.1x clear of
   its neighbours in luminance, with navy on it measured at 17.7:1. */
.fc__slot[data-on] .fc__chip{background:rgba(255,255,255,.96);color:var(--ink);
  box-shadow:inset 0 1px 0 #fff,0 12px 28px -16px rgba(3,60,50,.65)}
.fc__slot[data-on] .fc__chip svg{opacity:1}
@media (hover:hover) and (pointer:fine){
  .fc__slot:not([data-on]) .fc__chip:hover{color:var(--ink);
    background:rgba(255,255,255,.4);
    box-shadow:inset 0 1px 0 rgba(255,255,255,.7),
               inset 0 0 0 1px rgba(255,255,255,.45)}
}
.fc__chip:focus-visible{outline:2px solid var(--ink);outline-offset:3px}
/* No backdrop to filter here, so the frost would flatten into a grey wash.
   Fall back to the outlined pill, which needs no translucency to read. */
@media (prefers-reduced-transparency:reduce){
  .fc__chip{background:transparent;-webkit-backdrop-filter:none;backdrop-filter:none;
    box-shadow:inset 0 0 0 1px rgba(12,21,43,.3)}
  .fc__slot[data-on] .fc__chip{background:#fff;box-shadow:none}
}

/* the deck */
/* 46px of top padding is load-bearing on the stacked layout: it is what keeps
   the teal panel clear of the photographs. The panel's foot against the hub's
   yellow walls is 1.64:1 — better than the green it replaced at 1.12:1, but
   still not an edge you can see, so the art direction's 40px of separation
   stands. Side by side there is 213px and it never applies. */
.fc__stage{position:relative;display:grid;place-items:center;
  min-height:460px;padding:46px 18px 34px;background:#FAFBF9}
@media (min-width:900px){.fc__stage{min-height:560px;padding:44px 24px}}
.fc__deck{position:relative;width:min(100%,362px);aspect-ratio:4/5}
.fc__card{position:absolute;inset:0;overflow:hidden;
  border-radius:22px;border:8px solid var(--surface);background:var(--paper-2);
  box-shadow:0 20px 50px -30px rgba(12,21,43,.55);
  transition:transform 620ms var(--ease-out-quint),opacity 620ms var(--ease-out-quint),
             filter 620ms var(--ease-out-quint)}
.fc__card[hidden]{display:none}
.fc__ph{position:absolute;inset:0;display:grid;place-items:center;text-align:center;
  padding:22px;color:var(--micro-paper);font-size:.8125rem;line-height:1.5;
  background:var(--paper-2);
  background-image:repeating-linear-gradient(135deg,
    rgba(12,21,43,.05) 0 1px,transparent 1px 10px)}
.fc__ph img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
/* A pane of glass rather than a dark scrim — the same recipe the header uses:
   translucent ground, blur with a saturation lift, and a bright top edge so it
   reads as glass and not a flat tint. It floats inset from the card rather than
   sitting flush, which is what makes it read as a layer above the picture.

   Light glass with NAVY type survives any photograph behind it: at 68% over the
   hub's bright yellow wall it composites to ~#F9F8B5, and over a dark frame to
   ~#B8B8B8 — both are high contrast against navy. A dark scrim only worked
   because our placeholders happen to be pale. */
.fc__cap{position:absolute;inset-inline:12px;bottom:12px;z-index:2;
  padding:15px 17px 16px;border-radius:16px;
  background:rgba(245,246,244,.68);
  -webkit-backdrop-filter:blur(22px) saturate(180%);
  backdrop-filter:blur(22px) saturate(180%);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.75),
             inset 0 0 0 1px rgba(255,255,255,.35),
             0 10px 30px -18px rgba(12,21,43,.55)}
/* Glass needs something behind it. Where the platform says no, it becomes an
   ordinary opaque panel — the header does exactly this. */
@media (prefers-reduced-transparency:reduce){
  .fc__cap{background:var(--paper);-webkit-backdrop-filter:none;backdrop-filter:none}
}
.fc__n{display:inline-flex;align-items:center;margin-bottom:9px;
  padding:5px 11px;border-radius:999px;
  background:rgba(12,21,43,.07);color:var(--muted-paper);
  font-size:.625rem;font-weight:600;letter-spacing:.14em;text-transform:uppercase}
.fc__d{font-size:1rem;font-weight:500;line-height:1.32;letter-spacing:-.014em;
  color:var(--text-paper)}

@media (prefers-reduced-motion:reduce){.fc__slot,.fc__card{transition:none}}

/* ── surfaces ──────────────────────────────────────────────────────
   One card recipe for the whole page: white on the cool paper, a hairline
   drawn INSIDE the box so it never adds a pixel to the layout, and a lift
   that only exists on hover. No gradient, no coloured shadow. */
.card{position:relative;display:flex;flex-direction:column;
  background:var(--surface);border-radius:var(--r-lg);padding:22px 20px;
  box-shadow:inset 0 0 0 1px var(--hairline);
  transition:box-shadow var(--t-hover) var(--ease-out-quint),
             transform var(--t-hover) var(--ease-out-quint)}
@media (hover:hover) and (pointer:fine){
  .card:hover{transform:translateY(-3px);
    box-shadow:inset 0 0 0 1px rgba(12,21,43,.14),var(--lift)}
}
.card:active{transform:translateY(0) scale(.995)}
@media (prefers-reduced-motion:reduce){.card,.card:hover{transform:none}}
@media (min-width:1024px){.card{padding:26px 24px}}

/* ── FPO — "for position only" ─────────────────────────────────────
   Every one of these is a slot waiting for a real photograph, video or
   gallery. It is drawn as a deliberate object rather than an empty div: a
   tinted panel, a hatch so it cannot be mistaken for a finished surface, a
   corner chip, and a line saying what belongs there.

   Each one already owns its aspect ratio, so dropping the real asset in is
   a one-line swap that shifts NOTHING. The hatch is a repeating pattern,
   not a colour gradient — the no-gradient rule is about decoration. */
.fpo{position:relative;display:grid;place-items:center;text-align:center;
  background:var(--paper-2);border-radius:var(--r-lg);overflow:hidden;
  box-shadow:inset 0 0 0 1px var(--hairline);color:var(--micro-paper)}
.fpo::before{content:"";position:absolute;inset:0;pointer-events:none;
  background-image:repeating-linear-gradient(135deg,
    rgba(12,21,43,.05) 0 1px,transparent 1px 10px)}
.fpo__b{position:relative;padding:26px 22px;max-width:34ch}
.fpo__t{display:block;font-size:.9375rem;font-weight:600;color:var(--muted-paper);letter-spacing:-.011em}
.fpo__n{display:block;margin-top:5px;font-size:.8125rem;line-height:1.45;max-width:28ch;margin-inline:auto}
.fpo__chip{position:absolute;top:12px;left:12px;z-index:1;
  font-size:.6875rem;font-weight:600;letter-spacing:.08em;text-transform:uppercase;
  color:var(--micro-paper);background:rgba(255,255,255,.72);
  border-radius:999px;padding:5px 10px;line-height:1}
.fpo__i{display:block;margin:0 auto 10px;opacity:.4}
/* the full-width band: a tall box on a phone, a letterbox on a desk */
/* The band is the only block in these chapters that carried NO margin of its
   own: figure{margin:0} is global, and both bands follow a .read block, so a
   383px-tall image landed 0px under the pull quote while everything else in
   the section steps 34px. It read as though the picture were part of the
   sentence. The scale here is the page's own — mt-s 14 / mt-m 34 / mt-l 56 —
   so this takes a step above the standard one, because breaking away from
   prose needs more room than continuing a list does. Slightly more above than
   below on purpose: the band belongs to the section, not to the paragraph it
   interrupts. */
.fpo--band{aspect-ratio:4/3;margin-block:38px 38px}
@media (min-width:700px){.fpo--band{aspect-ratio:16/9}}
@media (min-width:1024px){.fpo--band{margin-block:54px 44px}}
@media (min-width:1100px){.fpo--band{aspect-ratio:24/7}}
/* The offer-grid placeholder is the one FPO that shares a row with something
   else. At three columns it sits beside two cards, so its height has to come
   from the ROW — a ratio there makes it end short of the cards beside it and
   the row reads as unfinished. Below 1100px the grid is one or two columns
   and the placeholder is alone on its own row, where the ratio is the only
   thing giving it any height at all, so it keeps one. */
.fpo--offer{aspect-ratio:4/3}
@media (min-width:1100px){.fpo--offer{aspect-ratio:auto;align-self:stretch}}

/* ── rail — a carousel with no JavaScript ──────────────────────────
   scroll-snap does the whole job. Keyboard users get it free: the track is
   focusable and arrow keys scroll it. */
.rail{display:grid;grid-auto-flow:column;grid-auto-columns:82%;gap:12px;
  overflow-x:auto;overscroll-behavior-x:contain;scroll-snap-type:x mandatory;
  padding-bottom:6px;scrollbar-width:none}
.rail::-webkit-scrollbar{display:none}
.rail>*{scroll-snap-align:start}
.rail:focus-visible{outline:2px solid var(--teal-ink);outline-offset:6px;border-radius:var(--r-sm)}
.rail__hint{margin-top:10px;font-size:.8125rem;color:var(--micro-paper)}
@media (min-width:700px){.rail{grid-auto-columns:46%}}
@media (min-width:1100px){.rail{grid-auto-columns:31%;gap:18px}}

/* ── fan — overlapping frames, for a set of photos that is not a grid.
   NOT called .stack: this page already has a .stack utility on the "what we
   are" block, and a second definition of that name collapses its children
   into one grid cell. */
.fan{position:relative;display:grid;isolation:isolate}
.fan>*{grid-area:1/1;border-radius:var(--r-lg)}
.fan>*:nth-child(1){transform:rotate(-3deg) translate(-5%,2%) scale(.9);z-index:1}
.fan>*:nth-child(2){transform:rotate(2.4deg) translate(6%,-2%) scale(.94);z-index:2}
.fan>*:nth-child(3){z-index:3;box-shadow:inset 0 0 0 1px var(--hairline),var(--lift)}
.fan>*:not(:nth-child(3)){box-shadow:inset 0 0 0 1px var(--hairline)}
@media (prefers-reduced-motion:reduce){.fan>*{transform:none!important}}

/* ── the rate card ───────────────────────────────────────────────── */
.rate{counter-reset:svc}
/* Three offers, then two, then two. On the widest layout that is a three
   column grid with two cells left over — and those two cells are exactly
   where the photography belongs, so the grid completes itself. */
.rate__set{display:grid;gap:14px;margin-top:16px}
@media (min-width:700px){.rate__set{grid-template-columns:repeat(2,1fr);gap:16px}}
@media (min-width:1100px){.rate__set{grid-template-columns:repeat(3,1fr);gap:18px}}

.rate__row{position:relative;display:flex;flex-direction:column;
  background:var(--surface);border-radius:var(--r-lg);padding:22px 20px;
  box-shadow:inset 0 0 0 1px var(--hairline);
  transition:box-shadow var(--t-hover) var(--ease-out-quint),
             transform var(--t-hover) var(--ease-out-quint)}
@media (min-width:1024px){.rate__row{padding:26px 24px}}
@media (hover:hover) and (pointer:fine){
  .rate__row:hover{box-shadow:inset 0 0 0 1px rgba(12,21,43,.22),var(--lift)}
  .rate__row:hover .idx{background:var(--ink);color:#fff}
}
.rate__row:active{background:#FBFCFB}
@media (prefers-reduced-motion:reduce){.rate__row,.rate__row:hover{transform:none}}

/* the numeral survives the move off the table as a chip */
.rate__row .idx::before{counter-increment:svc;content:"0" counter(svc)}
.idx{display:inline-flex;align-items:center;justify-content:center;align-self:flex-start;
  min-width:34px;height:26px;padding-inline:10px;border-radius:999px;
  background:var(--paper-2);font-size:.75rem;font-weight:600;letter-spacing:.06em;
  color:var(--micro-paper);font-variant-numeric:tabular-nums;
  transition:background-color var(--t-hover) var(--ease-out-quint),
             color var(--t-hover) var(--ease-out-quint)}
.rate__name{font-size:1.25rem;font-weight:600;line-height:1.15;letter-spacing:-.022em;margin-top:14px}
.rate__def{font-size:.9375rem;color:var(--muted-paper);margin-top:7px;line-height:1.5}
@media (min-width:1100px){.rate__name{font-size:1.375rem}}

/* specs lose their rules and become marks — six fewer hairlines per card */
.rate__specs{margin-top:16px;display:grid;gap:9px}
.rate__specs li{position:relative;padding-left:17px;font-size:.875rem;line-height:1.45;
  color:var(--muted-paper)}
.rate__specs li::before{content:"";position:absolute;left:2px;top:.5em;width:5px;height:5px;
  border-radius:50%;background:rgba(12,21,43,.24)}

/* THE CARD BODY, and why it has to be a flex column of its own.
   .rate__row is the flex column, but the lettered cards wrap everything after
   the chip in one element — so that wrapper is the flex ITEM, and the
   margin-top:auto below was landing inside a plain block where it could do
   nothing at all. The result was three cards of equal height whose prices and
   buttons each sat wherever their own paragraph count left them. Making the
   wrapper a column that fills the card is what hands the auto margin something
   to push against. */
.rate__body{display:flex;flex-direction:column;flex:1;min-height:0}

/* Every card ends on the same tinted panel, whether it holds three prices or
   the sentence we use when an engagement has to be scoped. That is what stops
   a quote-only offer reading as a card with its price missing. */
.rate__price{margin-top:auto;padding-top:18px}

/* The footer sits on the floor of every card, so the prices line up across the
   row and the buttons line up under them — which only holds while each card's
   action area is one row high. Hence the nowrap below: two short labels that
   wrap to a second line in ONE card are exactly what pushes that card's price
   up and breaks the row. */
/* The price panel and the button are both solid blocks, so the gap between
   them has to be bigger than a gap between two lines of text or they read as
   one crowded object. 18px was the same distance the button sits from the
   card's own floor, which is what made it look stuck to the panel above. */
.rate__body .acts{margin-top:24px}
@media (max-width:699px){
  .rate__body .acts{margin-top:26px}
  /* and the panel gets its own room above, so the card reads as three steps:
     what you get, what it costs, what to do. */
  .rate__price{padding-top:22px}
}
@media (min-width:1100px){
  .rate__body .acts{flex-wrap:nowrap}
  .rate__body .acts .btn{white-space:nowrap}
}
.rate__price dl{display:grid;grid-template-columns:1fr auto;gap:9px 16px;align-items:baseline;
  background:var(--paper-2);border-radius:var(--r-sm);padding:14px 16px;
  font-variant-numeric:tabular-nums}
.rate__price dt{font-size:.875rem;color:var(--muted-paper)}
.rate__price dd{margin:0;font-size:1.125rem;font-weight:600;letter-spacing:-.012em;text-align:right}
.rate__price dd small{font-weight:400;font-size:.8125rem;color:var(--micro-paper)}

/* ── the film cell ───────────────────────────────────────────────
   The two cells the rate grid leaves over. On paper this page is a price
   list; these are the only two blocks on it that are ink, and that is the
   whole site's rule applied at cell size — everything read on paper, every
   photograph on ink. A film is a photograph that moves.

   The cell is a grid item and stretches to its row, so the stage takes
   whatever the caption does not, and the film ends level with the buttons in
   the cards beside it. No height is written down anywhere: the row decides. */
.vfilm{display:flex;flex-direction:column;overflow:hidden;
  background:var(--ink);color:var(--text-ink);border-radius:var(--r-lg)}
.vfilm__stage{position:relative;flex:1 1 auto;min-height:0;background:var(--ink)}
/* Cover, not contain. The frame is a cell in a grid, not a letterbox — and
   both films are portrait, so the crop takes the sides of a centred subject
   rather than the top of anyone's head. Full screen puts contain back. */
/* And framed from the top third, not the middle. Both films are a person
   talking to camera in a 9:16 frame, where the face sits well above centre —
   a centred crop of one takes the top off their head. --y is per cell, for
   the film that wants a different answer. */
.vp--fill .vp__v{object-fit:cover;object-position:50% var(--y,30%)}

/* The caption is a tombstone: beside the film, never over it. Refusal 2 holds
   for a moving picture exactly as it does for a still — and these two frames
   contain a white wall and a dark curtain between them, which is the pair no
   overlay survives. */
.vfilm__cap{display:flex;flex-direction:column;gap:5px;margin:0;padding:13px 15px 15px}
.vfilm__note{font-size:.875rem;line-height:1.45;color:var(--muted-ink);max-width:40ch;margin-top:3px}
.vfilm__k{font-size:.6875rem;font-weight:590;letter-spacing:.06em;
  text-transform:uppercase;color:var(--muted-ink)}
.vfilm__l{display:flex;flex-wrap:wrap;align-items:baseline;gap:2px 8px}
.vfilm__n{font-size:.9375rem;font-weight:600;letter-spacing:-.006em;color:var(--text-ink)}
.vfilm__tag{font-size:.8125rem;color:var(--muted-ink)}

/* THE WIDE FILM, AND WHY IT IS NOT CROPPED. The workshop footage is 16:9 room
   footage: the facilitator stands at one edge of the frame in half the shots,
   and the two watermarks sit within 12% of the left and right edges. Cropping
   a near-square cell out of it removes the speaker in some shots and both
   logos in all of them — so the stage takes the film's exact shape instead,
   and the caption panel below takes whatever the row has left. The exact
   ratio rather than 16/9: 640x358 is a tenth of a percent off, and a tenth of
   a percent is still a crop. */
.vfilm--wide .vfilm__stage{flex:none;aspect-ratio:640/358}
.vfilm--wide .vfilm__cap{flex:1 1 auto;justify-content:flex-end;padding:18px 18px 20px}
.vfilm--wide .vfilm__n{font-size:1.0625rem}

/* Below the three-column grid there is no spare cell to fill, so the cell
   stops pretending to be one.

   MOBILE: one column, and the stage needs a shape of its own because nothing
   is stretching it any more. 4:5 rather than the film's own 9:16 — a phone
   column at 9:16 is a 630px tower that pushes the next family off the screen
   entirely. */
@media (max-width:699px){
  .vfilm__stage{flex:none;aspect-ratio:4/5}
  .vfilm--wide .vfilm__stage{aspect-ratio:640/358}
}
/* TABLET: two columns, and both families fill their row exactly — so a third
   item would sit alone with a hole beside it, which is the thing this cell
   exists to prevent. It spans instead, and turns on its side: the film keeps
   its own portrait shape at a size that sets the panel's height, and the
   caption takes the width that a cover crop would otherwise have thrown
   away. */
@media (min-width:700px) and (max-width:1099px){
  .vfilm{grid-column:1/-1;flex-direction:row;align-items:stretch}
  .vfilm__stage{flex:0 0 min(38%,236px);aspect-ratio:640/1138}
  /* The wide film gets a wider column, but a capped one: stacked and spanning,
     a 16:9 film across a 1024px tablet is 546px of video in a page whose
     tallest card is 434. Beside its caption it stays the size of a card. */
  .vfilm--wide .vfilm__stage{flex:0 0 min(56%,500px);aspect-ratio:640/358}
  .vfilm__cap{flex:1 1 auto;justify-content:flex-end;padding:22px 24px 24px}
  .vfilm__k{font-size:.75rem}
  .vfilm__n{font-size:1.0625rem}
}



/* ── small pieces ────────────────────────────────────────────────── */
.pull{font-size:1.375rem;font-weight:400;line-height:1.35;letter-spacing:-.014em;
  max-width:26ch;padding-left:18px;border-left:2px solid var(--teal)}
.tiers{display:grid;gap:12px}
@media (min-width:1024px){.tiers{gap:18px}}
.tier{padding:22px 20px;background:var(--surface);border-radius:var(--r-lg);
  box-shadow:inset 0 0 0 1px var(--hairline)}
/* the three programme explainers are prose. Only the membership tiers — four
   comparable things you choose between — earn a box. */
.tiers--3{gap:32px var(--gutter)}
.tiers--3 .tier{padding:0;background:none;box-shadow:none}
.tier h3{font-size:1.0625rem;font-weight:600;letter-spacing:-.012em}
.tier .lvl{font-size:.75rem;font-weight:600;letter-spacing:.06em;text-transform:uppercase;
  color:var(--teal-ink);margin-top:3px}
.tier p{font-size:.9375rem;color:var(--muted-paper);margin-top:8px;max-width:52ch}
@media (min-width:1024px){.tiers{grid-template-columns:repeat(4,1fr);column-gap:var(--gutter)}
  .tier{border-bottom:0}}
.steps{counter-reset:st}
.steps{display:grid;grid-template-columns:1fr;gap:12px}
@media (min-width:700px){.steps{grid-template-columns:repeat(2,1fr);gap:14px}}
@media (min-width:1024px){.steps{grid-template-columns:repeat(4,1fr);gap:18px}}
.steps li{padding:22px 0 0;border-top:1px solid var(--rule-paper);display:grid;
  grid-template-columns:44px 1fr;column-gap:var(--gutter);align-items:baseline}
/* four across, the numeral beside the text leaves ~200px for a sentence.
   Above it instead. */
@media (min-width:1024px){.steps li{grid-template-columns:1fr;row-gap:8px}}
.steps li::before{counter-increment:st;content:"0" counter(st);font-size:.8125rem;font-weight:510;
  letter-spacing:.06em;color:var(--micro-paper);font-variant-numeric:tabular-nums}
.steps b{font-weight:600}
.steps span{display:block;font-size:.9375rem;color:var(--muted-paper);margin-top:2px}

/* ── the instant room quote ────────────────────────────────────────
   Feature 04 in the feature document, and the one the document is
   proudest of: "every other hub makes you email and wait overnight".
   It is arithmetic on three published numbers, so it belongs on the
   static page rather than waiting for the console. The total is the
   only thing that moves; the control is a ruled segmented row, not a
   widget, because nothing else on this page looks like a widget. */
/* ── the live line ─────────────────────────────────────────────────
   Feature 06: the Open Thursday banner that puts itself up and takes
   itself down. It states the same fact either way — the mark and the
   wording are what change — so the line is never a lie and never
   needs anybody to remember it. Rendered by JS only: with no JS there
   is no line, rather than a stale one. */
.live{display:none;align-items:center;gap:10px;margin-top:22px;
  font-size:.9375rem;color:var(--muted-paper)}
.live[data-on]{display:flex}
.live__dot{width:8px;height:8px;flex:none;border-radius:50%;background:var(--teal)}
.live[data-now] .live__dot{background:var(--green)}
.live[data-now] b{color:var(--text-paper);font-weight:600}

/* ── the four doors ────────────────────────────────────────────────
   The feature document says the home page is "four buttons". On a page that
   is one long chart rather than six routes, those buttons are also the only
   navigation a phone gets — the header nav is desktop-only.

   So they are not drawn as four objects. They are ONE RULED BAND holding four
   entries, the way a printed chart rules a table. The rules live on the
   CONTAINER, not the cells: a border on .doors is drawn across the whole box,
   so the 32px gutter cannot cut it, and it can never hang under an empty cell
   when the grid reflows to 2 or 1 column. That replaces four floating stubs
   with two continuous lines — a net reduction in rules on a page that already
   cut its hairline count deliberately, and the whole reason the row now reads
   as one object instead of four fragments.

   The arrow stays WITH THE NAME. Pinning it to each cell's right edge lines
   the four up in a column and looks tidy in isolation, but at four across the
   cell is 304px and the content stops around 200px in: the arrow for door 01
   ends up ~100px from "Book a desk" and one gutter — 32px — from door 02's
   numeral. By proximity it would belong to the wrong door. An even column of
   arrows is not worth a mark that points at its neighbour.

   Hover inks the cell's own segment of the shared rule from rgba(12,21,43,.10)
   to solid --ink. Nothing moves but the arrow, so the interaction cannot break
   the alignment the band exists to establish — which the old translateX(6px)
   did every time. */
.doors{display:grid;grid-template-columns:1fr;column-gap:var(--gutter);row-gap:0;
  margin-top:26px;
  border-top:1px solid var(--rule-paper);
  border-bottom:1px solid var(--rule-paper)}
.door{position:relative;display:grid;grid-template-columns:minmax(0,1fr);
  row-gap:5px;padding:17px 0 19px}

/* Interior rules, only where the grid actually wraps. A step lighter than the
   band's own two, so the band stays the outer object. They sit at -1px, in the
   shared pixel between two zero-gap rows, so the hover mark below can use one
   offset in every layout. */
.door::before{content:"";position:absolute;top:-1px;left:0;right:0;height:1px;
  background:var(--hairline)}
.door:first-child::before{content:none}
/* the hover mark rides on the band itself */
.door::after{content:"";position:absolute;top:-1px;left:0;right:0;height:1px;
  background:var(--ink);transform:scaleX(0);transform-origin:left center;
  transition:transform var(--t-ui) var(--ease-out-quint)}

.door__i{font-size:.75rem;font-weight:600;letter-spacing:.06em;line-height:1;
  color:var(--micro-paper);font-variant-numeric:tabular-nums;
  transition:color var(--t-hover) var(--ease-out-quint)}
.door__n{display:flex;align-items:center;gap:8px;
  font-size:1.0625rem;font-weight:600;letter-spacing:-.012em;line-height:1.25}
.door__p{font-size:.9375rem;line-height:1.35;color:var(--muted-paper);
  font-variant-numeric:tabular-nums}
/* .72 puts the arrow at 3.10:1 on --paper, over the 3:1 floor for a non-text
   mark, and a step quieter than the numeral at 5.53:1. 13px matches the two
   arrows already on the page. */
.door__arw{width:13px;height:13px;flex:none;color:var(--micro-paper);opacity:.72;
  transition:transform var(--t-hover) var(--ease-out-quint),
             opacity var(--t-hover) var(--ease-out-quint),
             color var(--t-hover) var(--ease-out-quint)}

@media (hover:hover) and (pointer:fine){
  .door:hover::after{transform:scaleX(1)}
  .door:hover .door__i{color:var(--teal-ink)}
  .door:hover .door__arw{color:var(--text-paper);opacity:1;transform:translate(2px,-2px)}
}
.door:focus-visible::after{transform:scaleX(1)}

/* 2 up. Cells 3 and 4 open a second row, whose interior rule would otherwise
   be two stubs with the gutter cut out of the middle — the exact fault the
   band removes. Cell 3's rule runs one gutter past its own right edge, into
   the gap and no further, so the two halves meet as one line. */
@media (min-width:768px){
  .doors{grid-template-columns:repeat(2,1fr)}
  .door{padding:19px 0 21px}
  .door:nth-child(-n+2)::before{content:none}
  .door:nth-child(3)::before{right:calc(var(--gutter) * -1)}
}
/* 4 up. One grid row, so every interior rule goes and only the band is left.
   The three text lines become three container rows and each cell subgrids onto
   them, so if a name ever wraps all four prices step down together rather than
   one cell falling out of line. */
@media (min-width:1024px){
  .doors{grid-template-columns:repeat(4,1fr);grid-template-rows:auto auto auto;row-gap:6px}
  .door{row-gap:6px;padding:22px 0 24px}
  .door::before{content:none}
}
@supports (grid-template-rows:subgrid){
  @media (min-width:1024px){.door{grid-row:span 3;grid-template-rows:subgrid}}
}
@media (prefers-reduced-motion:reduce){
  .door::after,.door__i,.door__arw{transition:none}
  .door:hover .door__arw{transform:none}
}

/* ── who it is for ─────────────────────────────────────────────────
   Five readers, what each needs, what each does. A visitor should find
   their own row and stop reading — that is the job of a chart. */
.aud{display:grid;grid-template-columns:1fr;gap:34px var(--gutter);margin-top:34px}
@media (min-width:700px){.aud{grid-template-columns:repeat(2,1fr);gap:14px}}
@media (min-width:1100px){.aud{grid-template-columns:repeat(3,1fr);gap:18px}}
.aud__r{display:flex;flex-direction:column;row-gap:6px;padding:0}
.aud__w{font-size:1.0625rem;font-weight:600;letter-spacing:-.012em}
.aud__n{font-size:.9375rem;color:var(--muted-paper);max-width:52ch}
.aud__d{font-size:.9375rem;color:var(--teal-ink);font-weight:600;margin-top:4px}
@media (min-width:1024px){
  .aud__r{padding:0}
}

/* ── service families ──────────────────────────────────────────────
   The rate card now runs 01–07 under three family headings, taken from
   the commercial service inventory. That inventory's fourth column is
   the monetisation model — how the business is financed, not what a
   customer buys — so it stays off the page. */
.rate__fam{display:flex;align-items:center;gap:12px;margin-top:46px}
.rate__fam:first-of-type{margin-top:28px}
.rate__fam .k{display:inline-flex;align-items:center;justify-content:center;flex:none;
  width:26px;height:26px;border-radius:9px;background:var(--ink);color:#fff;
  font-size:.75rem;font-weight:700;letter-spacing:0}
.rate__fam b{font-weight:600;color:var(--text-paper);font-size:.9375rem;
  letter-spacing:-.011em;text-transform:none}
.rate__fam .n{margin-left:auto;font-size:.8125rem;color:var(--micro-paper);
  font-variant-numeric:tabular-nums;flex:none}
.rate__quote{font-size:.875rem;color:var(--muted-paper);line-height:1.5;
  background:var(--paper-2);border-radius:var(--r-sm);padding:14px 16px}

/* ── what a member gets ────────────────────────────────────────── */
.gets{display:grid;grid-template-columns:1fr;gap:30px var(--gutter);margin-top:34px}
.gets div{margin:0;padding:0}
.gets dt{display:block;font-size:1.0625rem;font-weight:600;letter-spacing:-.012em}
.gets dd{display:block;margin:5px 0 0;font-size:.9375rem;color:var(--muted-paper);max-width:46ch}
@media (min-width:768px){.gets{grid-template-columns:repeat(2,1fr);column-gap:var(--gutter)}}
@media (min-width:1024px){.gets{grid-template-columns:repeat(3,1fr)}}

/* ── a price, set large ────────────────────────────────────────────
   The numeral is the loudest thing in the block, because on this page
   the price is the argument. Tabular, on its own rule. */
/* ── the arithmetic ────────────────────────────────────────────────
   No chart would help here. The comparison IS the sentence, so it is
   set as two figures a line and nothing else. */
.arith{margin-top:30px;background:var(--surface);border-radius:var(--r-lg);
  padding:6px 20px;box-shadow:inset 0 0 0 1px var(--hairline)}
.arith div{display:grid;grid-template-columns:1fr;row-gap:2px;padding-block:16px;
  border-bottom:1px solid var(--rule-paper);font-variant-numeric:tabular-nums}
.arith div:last-of-type{border-bottom:0}
.arith b{font-weight:400;font-size:1.0625rem;color:var(--muted-paper)}
.arith span{font-weight:510;font-size:1.0625rem;letter-spacing:-.012em}
@media (min-width:768px){
  .arith div{grid-template-columns:1fr 1fr;column-gap:var(--gutter);align-items:baseline;row-gap:0}
  .arith span{text-align:right}
}

/* three-up variant of the hairline tier grid */
@media (min-width:1024px){.tiers--3{grid-template-columns:repeat(3,1fr)}}

/* ── visit ─────────────────────────────────────────────────────── */
.visit{display:grid;grid-template-columns:1fr;gap:26px var(--gutter);margin-top:30px}
.visit div{margin:0;padding:0}
.visit dt{display:block;font-size:.75rem;font-weight:600;letter-spacing:.06em;
  text-transform:uppercase;color:var(--micro-paper)}
.visit dd{display:block;margin:5px 0 0;font-size:1.0625rem;max-width:34ch}
.visit .m{font-size:.9375rem;margin-top:2px;color:var(--muted-paper)}
@media (min-width:768px){.visit{grid-template-columns:repeat(3,1fr);column-gap:var(--gutter)}}


/* ── the world map ─────────────────────────────────────────────────
   What replaces the globe, and why the honesty argument survives the
   swap. The globe told the truth by being EMPTY: one dot, one place,
   look how small. A flat map with arcs can only keep that truth if the
   arcs MEASURE something rather than MARK something — so these are
   great-circle DISTANCES to Klanna Street, and the caption carries the
   kilometres. A distance is geometry. It cannot be misread as an
   address, and it cannot go stale the way a route can.

   Three mechanics carry it, and all three are load-bearing:
     1. Accra is still the only marker — teal, pulsing, named. The far
        ends are small ink ticks. Two endpoints of equal weight IS the
        second centre this page refuses, so the asymmetry is the point.
     2. Every arc is drawn far-end-to-Accra, so it reads as arrival,
        never as deployment.
     3. Lagos is still off the map. It is the exact claim the old
        comment refused — and at 407km it is 14px here, so it could not
        be drawn honestly even if the claim were sound.

   The dot matrix is a pre-generated asset, not a library: tools/
   build-world-dots.mjs runs dotted-map at BUILD time and writes
   assets/img/world-dots.svg. Nothing is fetched, and the page's script
   is now the only script on the site. The arcs are hand-authored SVG
   in the SAME coordinate space as that asset — viewBox 0 0 158 80,
   ellipsoidal Mercator over lat -56..71, lng -168..168. The Aceternity
   original projects equirectangular onto 800x400; against this asset
   that formula puts Accra in the Algerian Sahara at 22.1 N, 16.5 degrees
   north of the coast. The projection is recorded in world-dots.meta.json and
   must not be re-derived by hand.

   Colour, measured against --paper #F5F6F4: land dots --ink at .30 =
   1.95:1, decorative, no single dot of 5,412 carries meaning. Arcs and
   the Accra mark --teal-ink #037A63 = 4.88:1, endpoints --ink at .70 =
   6.49:1 — both pass WCAG 1.4.11's 3:1 for graphical objects, which
   DOES apply to them. Brand --teal #05C19D measures 2.12:1 here and
   --green #70EF97 measures 1.34:1, close enough to a hairline rule to
   vanish; neither may draw a line on paper. That is the whole reason
   --teal-ink exists as a token. */
.wwa{display:grid;gap:44px}
@media (min-width:1024px){.wwa{gap:56px}}
.wwa__fig{margin:0;width:100%}
/* The figure runs the full frame while the copy stays on --read, so the
   block steps out to the right by design — a chart is allowed to be
   wider than its caption. A 2:1 map inside the old 360px figure column
   rendered 360x180, which is 1.8px per dot cell: unreadable mush. */
.wmap{position:relative;width:100%;aspect-ratio:158/80;
  -webkit-mask-image:linear-gradient(to bottom,transparent 0,#000 10%,#000 90%,transparent 100%);
          mask-image:linear-gradient(to bottom,transparent 0,#000 10%,#000 90%,transparent 100%)}
/* outline:0 because .paper figure img
   draws a hairline round photographs, and round a transparent dot matrix that
   is a box round empty paper. The mask happens to clip it today; that is luck,
   not intent. */
.wmap__dots{display:block;width:100%;height:100%;outline:0}
/* The mask multiplies alpha, and alpha reduction is contrast reduction:
   an arc sitting where the mask is below .75 would drop under 3:1. Every
   endpoint here lands between 25% and 76% of the height and every apex
   between 33% and 65%, so all of them sit at mask alpha 1. Hold that
   bound when adding a city — do not soften the gradient instead. */
.wmap__arcs{position:absolute;inset:0;width:100%;height:100%;overflow:visible}
/* Why the stroke is sized in FIVE tiers instead of one non-scaling-stroke.

   One device pixel is 0.12 user units inside a 158-unit viewBox scaled to
   1312px, so a stroke authored in user units changes thickness with the
   viewport and has to be pinned to real pixels somehow. The obvious tool is
   vector-effect:non-scaling-stroke, and in Chrome it is mutually exclusive
   with a pathLength-normalised dash. Measured on a four-way test:
   pathLength=1 + dasharray:1 draws ONE segment on its own, and FOUR once
   non-scaling-stroke is added, because the dash is then measured in screen
   space while pathLength normalises user space. Contrast is not negotiable
   and the draw-in is, so the stroke is stepped at the breakpoints instead.

   THE NUMBER THAT MATTERS IS 1.44 DEVICE PX, NOT 1.0. An earlier version of
   this rule assumed coverage equals stroke width, which is only true when the
   stroke lands centred on a device-pixel row. On a curve it lands at every
   phase, and a stroke straddling two rows peaks at HALF its width. #037A63
   needs alpha .721 to clear 3:1, so anything under 1.44 device px has
   positions that fail. The old three tiers put 0.99 device px at vw=1024,
   and a pixel measurement of the rendered arcs found ~36% of their length
   under 3:1 through the whole 1024-1200 band on a 1x display. These five
   tiers hold 1.59 to 2.66 device px across 320-2560 at DPR 1, so the thinnest
   possible phase still clears the bar. Never add stroke-opacity: the pass
   depends on the stroke being able to reach full coverage. */
.wm__arc{fill:none;stroke:var(--teal-ink);stroke-linecap:round;stroke-width:.88}
@media (min-width:480px){.wm__arc{stroke-width:.56}}
@media (min-width:768px){.wm__arc{stroke-width:.35}}
@media (min-width:1024px){.wm__arc{stroke-width:.26}}
@media (min-width:1200px){.wm__arc{stroke-width:.225}}
.wm__end{fill:rgba(12,21,43,.7)}
.wm__accra{fill:var(--teal-ink)}
.wm__ring{fill:none;stroke:var(--teal-ink);stroke-width:1;vector-effect:non-scaling-stroke;
  transform-box:fill-box;transform-origin:center;opacity:0}

/* Default state is FINISHED, not blank: with no JS, with reduced motion,
   or in print, every arc is already drawn and nothing that carries
   meaning depends on movement. The globe made the same promise — it
   drew, parked on Accra, and stopped. */
.wm__arc{stroke-dasharray:1;stroke-dashoffset:0}
@media (prefers-reduced-motion:no-preference){
  .js .wm__arc{stroke-dashoffset:1;
    transition:stroke-dashoffset 1200ms var(--ease-out-quint);
    transition-delay:calc(var(--i,0) * 130ms)}
  .js .wwa__fig[data-in] .wm__arc{stroke-dashoffset:0}
  /* One heartbeat, on the one centre. Scale on a transform-box:fill-box
     circle is composited — unlike animating r, it costs no layout and no
     paint, which is why this may loop where the globe's RAF could not. */
  .js .wwa__fig[data-in] .wm__ring{animation:wm-pulse 2.6s var(--ease-out-cubic) infinite}
}
@keyframes wm-pulse{
  0%{transform:scale(1);opacity:.45}
  70%{opacity:0}
  100%{transform:scale(3.4);opacity:0}
}
/* !important because .js .wm__arc above is (0,2,0) and this is (0,1,0):
   media queries add no specificity, and prefers-reduced-motion still reports
   no-preference during a print pass, so without it the arcs print blank. */
@media print{.wm__arc{stroke-dashoffset:0!important}.wm__ring{display:none}}
.wwa__fig figcaption{margin-top:16px;font-variant-numeric:tabular-nums;max-width:76ch}

/* ── section rhythm ──────────────────────────────────────────────── */
.paper{padding-block:78px}
.stack>*+*{margin-top:26px}
.mt-s{margin-top:14px}.mt-m{margin-top:34px}.mt-l{margin-top:56px}




/* ── partner wall (from the LogoCarousel in components/ui) ──────────────
   Columns that cycle rather than a strip that scrolls: nothing is ever in
   continuous motion, which is what makes it read as quiet.

   MONOCHROME, and not for taste. Two of these marks cannot survive on paper in
   their own colours — DEEP is a mint outline and Zaps is a yellow wordmark,
   both near-invisible on #F5F6F4. `grayscale()` alone would not fix it: it
   preserves luminance, so the yellow still lands near-white. `brightness(0)`
   collapses every hue to one ink value, and the whole wall then reads as one
   set rather than seven competing brand palettes.

   Every column holds all seven marks and starts at a different offset, so no
   two columns ever show the same logo and no column sits still. */
.lw__head{display:grid;justify-items:center;gap:12px;text-align:center}
.lw__head h2{font-size:1.5rem;font-weight:600;line-height:1.15;
  letter-spacing:-.022em;text-wrap:balance;max-width:20ch}
@media (min-width:1024px){.lw__head h2{font-size:2.125rem;max-width:24ch}}

.lw{display:grid;grid-template-columns:repeat(2,1fr);gap:14px;
  justify-items:center;align-items:center;
  margin:38px auto 0;max-width:940px}
@media (min-width:768px){.lw{grid-template-columns:repeat(4,1fr);gap:26px}}
.lw__col{position:relative;width:100%;max-width:190px;height:78px}
@media (min-width:768px){.lw__col{height:96px}}
.lw__m{position:absolute;inset:0;margin:auto;width:auto;max-width:82%;
  opacity:0;transform:translateY(10px);
  transition:opacity 460ms var(--ease-out-quint),
             transform 460ms var(--ease-out-quint)}
.lw__m[data-on]{opacity:1;transform:none}
/* DEEP is a mint outline and Zaps a yellow-and-white wordmark: both are drawn
   for a dark ground and measure 1.46:1 and 1.63:1 on the paper. Their colours
   are untouched — they are simply given the background they were made for. */
.lw__m[data-ink]{background:var(--ink);border-radius:var(--r-lg);
  padding:13px 17px;box-sizing:content-box}

/* Reduced motion: no cycle at all. The columns collapse into the flow and one
   full set is laid out at once — a wall, not a carousel. */
@media (prefers-reduced-motion:reduce){
  .lw{display:flex;flex-wrap:wrap;justify-content:center;align-items:center;gap:22px 30px}
  .lw__col{display:contents}
  .lw__col:not(:first-child) .lw__m{display:none}
  .lw__m{position:static;opacity:1;transform:none;transition:none;max-width:none}
}

/* The link under the wall. These rules were lost when the marquee block they
   used to live in was deleted; without them the icons, which carry a viewBox
   and no width/height, fall back to the default replaced-element size — which
   is how a 24px plus became a 300px one. */
.lw__cta{display:flex;width:fit-content;margin:30px auto 0;
  align-items:center;gap:9px;min-height:44px;
  font-size:.75rem;font-weight:600;letter-spacing:.06em;text-transform:uppercase;
  color:var(--micro-paper);transition:color var(--t-hover) var(--ease-out-quint)}
.lw__cta svg{width:13px;height:13px;flex:none}
@media (hover:hover) and (pointer:fine){
  .lw__cta:hover{color:var(--text-paper)}
  .lw__cta:hover .lc__arw{transform:translate(1px,-1px)}
}

/* The ruled partner grid was replaced by the .lw wall above. Only the two
   rules the markup still uses survive; the cells, the crop marks and the
   full-bleed rules went with it. */
.lc-wrap{padding-block:56px 78px}
.lc__arw{width:13px;height:13px;transition:transform var(--t-hover) var(--ease-out-quint)}

/* ── testimonials — adapted from the Hypersphere client-feedback block.
   Its mixed-weight card wall is kept; the palette is ours. Green cards carry
   navy type (never white on green), ink and deep-teal cards carry light type.
   The ruled overlay on the green cards is the same grid as the hero. ── */
.tm{display:grid;gap:12px;margin-top:30px}
.tm__col{display:flex;flex-direction:column;gap:12px}
.tm__card{position:relative;overflow:hidden;border-radius:16px;padding:22px 22px 20px;
  display:flex;flex-direction:column;justify-content:flex-end;min-height:184px}
.tm__card blockquote{margin:0;font-size:1.0625rem;line-height:1.5;letter-spacing:-.006em;
  text-wrap:pretty}
.tm__by{display:flex;align-items:flex-end;justify-content:space-between;gap:14px;
  margin-top:20px}
.tm__by b{display:block;font-weight:600;font-size:1.0625rem;letter-spacing:-.012em}
.tm__by span{display:block;font-size:.875rem;opacity:.78;margin-top:1px}
/* white at .78 over the deep teal lands at 3.5:1 — this ground needs more */
.tm__card--teal .tm__by span{opacity:.92}
.tm__av{flex:none;width:52px;height:52px;border-radius:13px;display:grid;
  place-items:center;font-size:.8125rem;font-weight:600;letter-spacing:.04em;overflow:hidden}
/* A headshot drops straight into the same slot — the numeral is the fallback
   for anyone who has not sent one:
   <span class="tm__av"><img src="assets/img/voices/NAME.jpg" alt="" width="104" height="104" loading="lazy"></span>
   Square source, 104px or larger. Radius here is 13px, not a circle: nothing
   on this page is round except the buttons and the world map's dots. */
.tm__av img{width:100%;height:100%;object-fit:cover;display:block}
/* variants */
.tm__card--green{background:var(--green);color:var(--ink)}
.tm__card--green .tm__av{background:rgba(12,21,43,.12);color:var(--ink)}
.tm__card--ink{background:var(--ink);color:var(--text-ink)}
.tm__card--ink .tm__av{background:rgba(255,255,255,.13);color:var(--text-ink)}
.tm__card--teal{background:var(--teal-ink);color:#fff}
.tm__card--teal .tm__av{background:rgba(255,255,255,.18);color:#fff}
/* ruled overlay, straight from the reference */
.tm__card--green::before{content:"";position:absolute;inset:0;pointer-events:none;
  background-image:
    linear-gradient(to right,rgba(12,21,43,.10) 1px,transparent 1px),
    linear-gradient(to bottom,rgba(12,21,43,.10) 1px,transparent 1px);
  background-size:50px 56px;
  -webkit-mask-image:radial-gradient(ellipse 80% 50% at 50% 0%,#000 70%,transparent 110%);
  mask-image:radial-gradient(ellipse 80% 50% at 50% 0%,#000 70%,transparent 110%)}
.tm__card--green>*{position:relative}
@media (min-width:768px){
  .tm{grid-template-columns:repeat(2,1fr)}
  .tm__card{padding:26px 26px 24px}
}
@media (min-width:1024px){
  .tm{grid-template-columns:repeat(3,1fr);gap:14px}
  .tm__col{gap:14px}
  .tm__col--a .tm__card:first-child,.tm__col--c .tm__card:last-child{flex:7}
  .tm__col--a .tm__card:last-child,.tm__col--c .tm__card:first-child{flex:3}
  .tm__card blockquote{font-size:1.125rem}
}
.js .tm__card.rv{transition-delay:calc(var(--i,0) * 70ms)}

/* ── footer ──────────────────────────────────────────────────────
   The statement panel is the page's one flash of the printed-ink idea at
   full strength: navy + feTurbulence grain, sharp corners, display set
   across it word by word. Radii on this page are 4px or 0 — never soft. */
/* Skip link. The nav repeats above 13,000px of page, so a keyboard user who
   cannot see it needs one key to get past it. Hidden until focused — the
   :focus-within/:active exception is what makes a hidden focusable element
   legal. */
.skip{position:absolute;left:-9999px;top:0;z-index:100}
.skip:focus-visible{left:max(var(--margin),env(safe-area-inset-left));top:10px;
  display:inline-flex;align-items:center;min-height:44px;padding:0 18px;
  border-radius:4px;background:var(--ink);color:#fff;font-size:.9375rem;font-weight:600;
  outline:2px solid var(--teal-ink);outline-offset:2px}
.sr{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
.ftr{padding-block:34px 78px}
/* Flat --ink, no ramp. It used to carry the same three-layer gradient as the
   Virtual Nest chapter — two teal washes over a navy diagonal — so the two dark
   moments read as one device. That pairing is broken on purpose now; the chapter
   keeps the ramp and this is a plain band. If they should match again, the ramp
   is still at site.css line 433 and 474 to copy back.

   And flat, so it is SHORT. The panel was 300px tall holding a 68px line, which
   is 232px of empty navy — the height was carrying the gradient, not the words.
   With nothing to show off there is nothing to hold the height for, so the
   padding sets it and the band comes out at 132px. */
.ftr__panel{position:relative;overflow:hidden;border-radius:4px;
  background:var(--ink);
  display:grid;place-items:center;padding:32px 24px}
.ftr__noise{position:absolute;inset:0;width:100%;height:100%;opacity:.55;
  mix-blend-mode:overlay;pointer-events:none}
.ftr__statement{position:relative;font-family:var(--display);font-weight:700;word-spacing:-.06em;
  line-height:.95;letter-spacing:.005em;color:#fff;text-align:center;
  font-size:clamp(32px,7vw,72px)}
.ftr__statement span{display:inline-block}
.ftr__body{display:flex;flex-direction:column;gap:34px;margin-top:34px}
.ftr__left{display:flex;flex-direction:column;gap:28px}
.ftr__note{margin-top:10px;font-size:.9375rem;line-height:1.5;color:var(--muted-paper);max-width:42ch}
.ftr__form{display:flex;gap:10px;margin-top:16px;max-width:440px;flex-wrap:wrap}
.ftr__form input{flex:1 1 200px;min-height:52px;padding:0 16px;border-radius:4px;
  border:1px solid rgba(12,21,43,.18);background:transparent;font:inherit;font-size:.9375rem;color:var(--text-paper)}
.ftr__form input::placeholder{color:#8D99AD}
.ftr__form input:focus-visible{outline:2px solid var(--teal-ink);outline-offset:1px;border-color:transparent}
.ftr__form .btn{min-height:52px;padding-inline:24px;font-size:.9375rem}
.ftr__social{display:flex;flex-wrap:wrap;gap:24px}
.ftr__social a{display:inline-flex;align-items:center;gap:5px;min-height:44px;
  font-size:.9375rem;color:var(--muted-paper);transition:color var(--t-hover) var(--ease-out-quint)}
.ftr__social svg{width:13px;height:13px;transition:transform var(--t-hover) var(--ease-out-quint)}
.ftr__colophon{margin-top:14px;font-size:.8125rem;line-height:1.5;color:var(--micro-paper);max-width:48ch}
/* TWO columns on a phone, and a list rather than a stack of buttons.

   The old rule set min-height:44px on the li AND again on the a inside it, so
   every one of the thirteen links was a 44px row in a single column: 572px of
   links before a heading or a gap, and a footer 1289px tall — a fifth of the
   whole page on a 390px screen. The arrangement is taken from the shadcn-blocks
   Footer 7, which sets its link columns as a tight list (space-y-3, no minimum)
   and lets the GROUPS carry the spacing.

   Not all the way to Footer 7, though. Dropping the minimum entirely would put
   these links at about a 20px target, and 24px is the floor in WCAG 2.5.8. They
   sit at 34px: comfortably over that, under the 44px of 2.5.5 AAA, and a size
   this page can afford for a secondary nav that duplicates the header. */
.ftr__cols{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:28px 20px}
.ftr__cols ul{margin-top:6px}
.ftr__cols li{font-size:.9375rem;color:var(--muted-paper)}
.ftr__cols li a{display:flex;align-items:center;min-height:34px}
/* Three groups in two columns leaves the third alone in the left column with a
   dead half-row beside it. It spans instead, and its own items split in two —
   which suits it, because it is the odd group: Visit is opening hours and an
   address, and its first two items are not links at all. Targeted by position
   rather than a class so the markup is left alone. */
.ftr__cols > :last-child{grid-column:1 / -1}
.ftr__cols > :last-child ul{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));
  gap:0 20px}
/* The Visit block gets its own green ground on a phone. Square corners, not the
   4px the rest of the page uses — the vertices are the point of it.

   It runs the full measure and stops at the page gutter rather than bleeding to
   the screen edge, so it lines up with every other left edge in the footer
   instead of cutting across underneath them.

   Every colour inside it changes, and that is not decoration. The footer sets
   its links in --muted-paper and its labels in --micro-paper, which on this
   green measure 4.72:1 and 4.14:1 — the label failing 4.5 outright. Navy is
   12.51:1 on it, and navy-on-green is the palette's one hard rule. The focus
   ring moves to navy for the same reason: --teal-ink is 3.65:1 here. */
.ftr__cols > :last-child{
  background:var(--green);border-radius:0;
  padding:18px 16px 20px;margin-top:8px}
.ftr__cols > :last-child .label,
.ftr__cols > :last-child li,
.ftr__cols > :last-child li a{color:var(--ink)}
.ftr__cols > :last-child li a:focus-visible{outline-color:var(--ink)}
@media (hover:hover) and (pointer:fine){
  .ftr__social a:hover,.ftr__cols li a:hover{color:var(--text-paper)}
  .ftr__social a:hover svg{transform:translate(1px,-1px)}
}
@media (min-width:768px){
  .ftr__cols{grid-template-columns:repeat(3,1fr);gap:var(--gutter)}
  .ftr__cols li a{min-height:40px}
  /* three real columns from here, so the span, the split and the green ground
     all come off — this is a phone treatment only */
  .ftr__cols > :last-child{grid-column:auto;background:none;padding:0;margin-top:0}
  .ftr__cols > :last-child ul{display:block}
  .ftr__cols > :last-child .label{color:var(--micro-paper)}
  .ftr__cols > :last-child li,
  .ftr__cols > :last-child li a{color:var(--muted-paper)}
}
@media (min-width:1024px){
  .ftr__body{flex-direction:row;justify-content:space-between;gap:var(--gutter);margin-top:64px}
  .ftr__left{width:34%;justify-content:space-between}
  .ftr__cols{width:58%;gap:40px}
}

/* the statement arrives word by word — the one place this page staggers text */
@media (prefers-reduced-motion:no-preference){
  .js .ftr__statement span{opacity:0;transform:translateY(18px);
    transition:opacity 460ms var(--ease-out-cubic),transform 460ms var(--ease-out-cubic)}
  .js .ftr__panel[data-in] .ftr__statement span{opacity:1;transform:none}
  .js .ftr__panel[data-in] .ftr__statement span:nth-child(1){transition-delay:60ms}
  .js .ftr__panel[data-in] .ftr__statement span:nth-child(2){transition-delay:130ms}
  .js .ftr__panel[data-in] .ftr__statement span:nth-child(3){transition-delay:200ms}
  .js .ftr__panel[data-in] .ftr__statement span:nth-child(4){transition-delay:270ms}
}

/* ══ MOBILE ═══════════════════════════════════════════════════════════
   One scale change, then the four layouts that needed it.

   Type first. This sheet sizes type in rem and layout in px — 74 rem
   declarations and every one of them a font-size — so the entire type
   system can be scaled from the root without moving a single box. 14px is
   .875 of the 16px default: body 17->14.9, lede 19->16.6, h2 26->22.8,
   label 12->10.5, chip 13->11.4. The two display faces are deliberately
   untouched, because both are px-based: .hook is clamp(31px,9.1cqi,68px)
   and .chapter-title is clamp(32px,5.4vw,60px). The headlines keep their
   weight while the reading type comes down, which is the ratio a phone
   wants anyway. */
@media (max-width:767px){
  /* Only the gap. An earlier pass also stretched every button to the column
     width, but .acts is used sixteen times across the six pages — inside cards,
     beside prose, in the footer — and full-width buttons in all of them tore
     up layouts that were fine. The ask was the distance between two stacked
     actions; that is 4px, and nothing else moves. The hero's pair is exempt:
     it has its own side-by-side rule above. */
  .acts:not(.hero .acts){gap:16px}

  /* The statement panel was 97px tall here — 32px of padding either side of a
     35px line, which reads as a banner rather than the full stop the footer
     wants. The padding is the only thing to give it back. */
  .ftr__panel{padding:56px 24px}

  html{font-size:14px}

  /* The wordmark was 162px across a 358px column — 45% of the screen width
     spent on a logo. 19px tall puts it at 128px, a third. */
  .brand img{height:19px}

  /* Side by side, sharing the row. The long label wraps to two lines rather
     than being truncated or shrunk below a readable size, and both buttons
     stretch to the taller of the two so the pair stays level. Radius drops
     999px -> 10px: still a button, no longer a pill. */
  .hero .acts{flex-direction:row;flex-wrap:nowrap;align-items:stretch;gap:10px}
  .hero .acts .btn{flex:1 1 0;min-width:0;min-height:46px;padding:10px 12px;
    font-size:.8125rem;border-radius:10px;white-space:normal;text-align:center;
    line-height:1.25}
  .hero .acts .btn__chev{flex:none}

  /* The rooms, arranged the way the desktop does it: rail beside the deck,
     not stacked above it. Stacked it ran 903px tall for one card. The rail
     takes half rather than the desktop's 40% because the chips carry real
     words — "Innovation Program" is the constraint — and the slot's own
     max-width has to relax with it or the widest chip is clipped. */
  /* minmax(0,1fr) twice, not minmax(0,50%) 1fr: a bare 1fr is minmax(auto,1fr)
     and respects its content's min-content width, so the deck grew and squeezed
     the rail to 137px, which clipped the widest chip. Two floored tracks split
     the row exactly in half whatever the cards inside want. */
  .fc{grid-template-columns:minmax(0,1fr) minmax(0,1fr);margin-top:26px;border-radius:18px}
  .fc__rail{min-height:300px;padding:20px 8px}
  .fc__slot{max-width:calc(100% - 10px)}
  /* 10px type, 10px sides, 6px gap. "Innovation Program" is the widest chip
     and it comes out 162px inside a 163px rail content box, so the longest
     label in the set fits without the wheel clipping it. Anything wider here
     and that one chip runs under the rail's padding. */
  .fc__chip{min-height:40px;padding:9px 10px;gap:6px;font-size:.7142rem;
    letter-spacing:.04em}
  .fc__chip svg{width:14px;height:14px}
  /* min-height:0 so the stage takes its height from the card rather than the
     desktop's 460px floor. place-items stays centre: stretching it fought the
     deck's aspect-ratio and pushed the card out past the panel. */
  .fc__stage{min-height:0;padding:16px 10px}
  /* The deck's neighbours are pushed 74px aside by the wheel script, which is
     fine beside a 780px stage and not beside a 179px one: they slid out from
     under the live card and showed through the rail. !important is the only
     way to reach them, because that opacity is written as an inline style. */
  .fc__card[aria-hidden="true"]{opacity:0!important}
  .fc__deck{aspect-ratio:3/4}
  .fc__card{border-width:5px;border-radius:15px}
  .fc__ph{padding:14px;font-size:.6428rem}
  .fc__cap{inset-inline:6px;bottom:6px;padding:9px 10px 10px;border-radius:12px}
  .fc__n{margin-bottom:6px;padding:3px 8px;font-size:.5rem;letter-spacing:.1em}
  .fc__d{font-size:.7857rem;line-height:1.3}

  /* Voices in two columns, the desktop's three at phone scale. One column
     stacked all seven cards into 1,635px of scrolling. */
  /* Multi-column, not a 2-track grid. The markup groups the seven cards into
     three .tm__col wrappers for the desktop's staggered columns; dropped into
     a 2-track grid those groups tile as a|b then c, which left a dead column
     of whitespace beside the last group. display:contents dissolves the
     wrappers so all seven cards flow as one sequence, and columns:2 balances
     them into the same staggered shape the desktop has. */
  .tm{display:block;columns:2;column-gap:10px}
  .tm__col{display:contents}
  .tm__card{break-inside:avoid;margin-bottom:10px}
  .tm__card{padding:15px 15px 14px;min-height:148px;border-radius:13px}
  /* A 174px column is 18 characters at body size, so the quote takes its own
     step down rather than running as a ladder of two-word lines. */
  .tm__card blockquote{font-size:.9285rem;line-height:1.45}
  .tm__by{gap:9px}
  .tm__av{width:36px;height:36px;border-radius:9px;font-size:.6785rem}
}

/* ── motion: static end-state is the base rule ───────────────────── */
/* Visible is the base state. The hidden start only applies once JS has
   confirmed it can reveal them — otherwise a failed script hides the page. */
@media (prefers-reduced-motion:no-preference){
  .js .rv{opacity:0;transform:translateY(14px);
    transition:opacity var(--t-reveal) var(--ease-out-cubic),transform var(--t-reveal) var(--ease-out-cubic)}
  .js .rv[data-in]{opacity:1;transform:none}
  .js .rate__row.rv{transition-delay:calc(var(--i,0) * 60ms)}
}
@media print{.js .rv{opacity:1!important;transform:none!important}
  /* The rule above selects the h1; these select what is inside it. Printing
     during the reveal would otherwise put a blurred, half-transparent headline
     on the page. */
  .hook__ch{opacity:1!important;filter:none!important;transform:none!important}
  .hook__mark::after{transform:none!important}}

/* ── the hook reveal ──────────────────────────────────────────────
   Per-character blur-in, ported from the BlurReveal React component
   (motion/react) this hero was specced against. Same numbers: 20ms between
   characters (0.03s / speedReveal 1.5), 600ms each (0.3s / speedSegment 0.5),
   no initial delay, hidden = opacity 0 + blur + a short rise.

   Two departures, both forced by this page rather than taste:

     1. Blur and rise are in em, not px. A fixed 12px blur is 19% of the em at
        the 63.7px desktop size but 39% at the 31px phone size, where it stops
        reading as motion and starts reading as a smudge. .18em is the same
        optical amount at both ends of the clamp.

     2. The character spans are UNWRAPPED once each line lands. inline-block is
        required for transform, and inline-block kills the kerning pair between
        adjacent characters — the exact thing this change was asked to tighten.
        So the split exists only while the animation runs. The resting headline
        is the authored text run: kerned, tracked, one continuous line with the
        green bar across it. See splitHook() in site.js.

   .hook KEEPS its .rv class, which is what makes this cheap: it inherits the
   existing observer, the 2.5s safety net, the print rule and the reduced-motion
   gate. The two overrides below are 0,3,0 and beat .js .rv at 0,2,0; they drop
   the block-level 14px translate so the characters own all the movement, and
   holding the h1 at opacity 0 until [data-in] is what stops the unsplit
   headline flashing before the splitter has run. */
@media (prefers-reduced-motion:no-preference){
  .js .hook.rv{transform:none;transition:none}
  .js .hook.rv[data-in]{opacity:1}

  /* text-indent INHERITS, and an inline-block is a block container, so the
     optical-centring indent on the line would be re-applied inside every word
     box. letter-spacing is worse: the parent adds it between the atomic boxes
     AND each box adds it again after its own single glyph, so the split ran at
     roughly double the track and the letters collided. Both are cancelled here
     so the split measures the same as the text run it replaces. */
  .hook__w{display:inline-block;white-space:nowrap;text-indent:0}
  .hook__ch{display:inline-block;letter-spacing:0;text-indent:0;
    opacity:0;filter:blur(.18em);
    transform:translateY(.15em);
    transition:opacity 600ms var(--ease-out-cubic),
               filter 600ms var(--ease-out-cubic),
               transform 600ms var(--ease-out-cubic);
    transition-delay:calc(var(--i,0) * 20ms)}
  /* data-chars-in, NOT data-in. The .rv observer stamps data-in on the h1 within
     ~25ms, long before the splitter (which waits on the font) inserts anything.
     A span inserted into an already-[data-in] h1 resolves its FIRST computed
     style as the end state, and an element with no before-change style does not
     transition — so the reveal silently never played on a cold load. The
     splitter now raises its own flag, one frame after the spans exist. */
  .hook[data-chars-in] .hook__ch{opacity:1;filter:blur(0);transform:none}

  /* The second animation on this headline, and the reason the two do not fight:
     the characters blur in one at a time, and the marker is delayed by --u * 20ms
     — the index of the character before the phrase — so it starts as the last
     letter of "access" lands rather than under a word that is still a smudge.
     Reveal, then stroke. The splitter sets --u; see splitHook() in site.js.

     The overshoot is the port, and the number in it is solved rather than
     guessed. Remotion's spring({damping:14}) is underdamped — mass 1, stiffness
     100, damping ratio 0.70 — and its first peak is 1.046, reached 440ms in. A
     cubic-bezier can do the same if its second control point sits above 1, so
     y1 was solved for that exact peak: cubic-bezier(.34,1.37,.6,1) tops out at
     1.0460 at 62% of the tween. On the measured 332.6px marker that is 15.3px
     of overrun past "access" before it settles back — visible, which the first
     attempt at .32,1.16 was not: that one peaked at 1.0075, all of 2.5px.

     620ms, against the spring's ~930ms. That is a deliberate compression, not a
     match: the source is a 3-second loop and this is a page someone is trying to
     read past. The shape is the spring's; the duration is the page's.

     The transform goes on the ::after and NEVER a filter on the <em> itself:
     filter creates a containing block for absolutely positioned descendants,
     which would re-anchor the marker to the wrong box. */
  .js .hook__mark::after{transform:scaleX(0);transform-origin:left center}
  /* data-mark-on, not data-chars-in. The splitter raises it as the last line
     unwraps, which is the moment the <em> under this pseudo-element stops being
     replaced. Keyed to data-chars-in the animation would start on the throwaway
     clone, get destroyed with it mid-stroke, and restart its full delay from the
     new element — measured, that put the first stroke at 3.9s instead of 2.
     120ms so it does not begin in the same frame as the DOM swap. */
  .hook[data-mark-on] .hook__mark::after{
    animation:hook-mark 7s 120ms infinite backwards}
  /* Off screen, it stops. See the observer in splitHook(). */
  .hook[data-off] .hook__mark::after{animation-play-state:paused}

/* Seven seconds, of which the marker is only moving for 1.2. The rest is hold,
   because the thing being looped sits under the first line a reader meets and
   an animation that is always mid-move turns a headline into a banner. The
   proportion is taken from this sheet's own lc-wipe, which moves for 22% of its
   cycle and rests for 78%; this moves for 17.5%.

   The stroke is drawn from the left and wiped from the RIGHT — the origin swaps
   at 78% while the block is still full width, where swapping it costs nothing
   visually. That is the difference between a pen that travels across the phrase
   and clears in the same direction, and a box that grows and then ungrows.

   Timing functions per segment, not on the shorthand: the draw keeps the solved
   cubic-bezier(.34,1.37,.6,1) whose 1.0460 peak is the Remotion spring's own
   first peak, and the wipe uses an ease-in so it leaves faster than it arrived.
   The hold segments interpolate scaleX(1) to scaleX(1), so their easing is
   moot. */
@keyframes hook-mark{
  0%     {transform:scaleX(0); transform-origin:left center;
          animation-timing-function:cubic-bezier(.34,1.37,.6,1)}
  8.9%   {transform:scaleX(1); transform-origin:left center}
  78%    {transform:scaleX(1); transform-origin:left center}
  78.01% {transform:scaleX(1); transform-origin:right center;
          animation-timing-function:cubic-bezier(.5,0,.75,0)}
  86.6%  {transform:scaleX(0); transform-origin:right center}
  100%   {transform:scaleX(0); transform-origin:right center}
}
}

/* Reachable by a screen reader, invisible to everyone else. The splitter hides
   the character spans from the accessibility tree and puts one clean copy of
   the sentence here, exactly as the component it was ported from does; both are
   removed again when the spans unwrap. */
.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;
  overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}

/* ── the router ────────────────────────────────────────────────────
   v1.1, section 4.2. The team resolved D4 in favour of the QUESTION
   leading each row, and their questions run to twenty words, which the
   four-across doors could never hold. So this is the ledger form: one
   ruled band, question on the left, action on the right, every action
   landing in the same column. Five rows, full measure, no cards.

   The rules live on the container for the same reason they do on .doors —
   a border on the box cannot be cut by a gap, and it survives the reflow
   to one column without leaving a stub. */
/* --route-a is the action column, and it is a measured number: the longest
   label in this router, "Explore our ecosystem experiences", needs 250px at
   15px/600, plus a 12px gap and the 13px arrow. At the old 15rem it did not fit
   and wrapped to two lines, which dragged its arrow to the vertical middle of
   the pair while the other four sat on the first baseline. 17.5rem holds it.

   The second router, in What we offer, labels every row "Explore" and the rest
   of the phrase is screen-reader only, so it takes the compact width instead —
   at 17.5rem a 52px label would sit marooned two hundred pixels from its own
   arrow. */
.route{--route-a:17.5rem; --route-q:80ch;
  display:grid;grid-template-columns:1fr;margin-top:26px;
  border-top:1px solid var(--rule-paper);border-bottom:1px solid var(--rule-paper)}
/* Both tracks, because the two routers hold different things. This one's rows
   are a title and a sentence of prose, so its question keeps a reading measure;
   the router above holds five one-line questions and can take a longer one. */
.route--compact{--route-a:8.5rem; --route-q:52ch}
.route__row{position:relative;display:grid;grid-template-columns:1fr;gap:6px;
  padding:20px 0 21px;align-items:baseline;
  transition:background-color var(--t-ui) var(--ease-out-quint)}
.route__row::before{content:"";position:absolute;top:-1px;left:0;right:0;height:1px;
  background:var(--hairline)}
.route__row:first-child::before{content:none}
.route__row::after{content:"";position:absolute;top:-1px;left:0;right:0;height:1px;
  background:var(--ink);transform:scaleX(0);transform-origin:left center;
  transition:transform var(--t-ui) var(--ease-out-quint)}
/* 80ch, up from 52. The cap was doing real damage: it held every question to
   573px inside a 1000px column, so two of the five wrapped for want of space
   that was sitting empty beside them, and the row heights went 69/69/92/69/92.
   Measured, the longest question needs 842px on one line and 80ch is 881px, so
   all five now set on a single line and the five rows are the same height. It
   is a long measure for a paragraph and a fine one for a scannable question;
   below 1200px the column shrinks and they wrap on their own. */
.route__q{font-size:1.0625rem;font-weight:500;line-height:1.35;letter-spacing:-.012em;
  color:var(--text-paper);max-width:var(--route-q);text-wrap:pretty}
.route__a{display:inline-flex;align-items:center;gap:8px;
  font-size:.9375rem;font-weight:600;letter-spacing:-.008em;color:var(--teal-ink)}
.route__a svg{width:13px;height:13px;flex:none;
  transition:transform var(--t-hover) var(--ease-out-quint)}
/* The whole row is the target. It was a 106x23px link inside a 1312x69px row —
   2.7% of what the reader is obviously aiming at, and on a phone that is well
   under the 44px minimum in any direction. The pseudo-element keeps ONE link in
   the accessibility tree and the anchor keeps its own text, so nothing about
   how it is announced changes; only the hit area does. */
.route__a::before{content:"";position:absolute;inset:0}
@media (hover:hover) and (pointer:fine){
  .route__row:hover{background:var(--paper-2)}
  .route__row:hover::after{transform:scaleX(1)}
  .route__row:hover .route__a svg{transform:translate(2px,-2px)}
}
/* The focus ring goes on the STRETCHED PSEUDO-ELEMENT, which is exactly the row
   box. Three things fall out of that. The global :where(a,button):focus-visible
   ring is suppressed, because it draws around the label — leaving two rings, one
   round the words and one round the row, which is what the first pass rendered.
   The row itself does not carry the outline either: that needed :has(), and a
   browser without it would then draw no ring at all. And offset:-2px keeps the
   ring inside the row so it cannot collide with the rules above and below. */
.route__row:focus-within{background:var(--paper-2)}
.route__row:focus-within::after{transform:scaleX(1)}
.route__a:focus-visible{outline:none}
.route__a:focus-visible::before{outline:2px solid var(--ink);outline-offset:-2px;
  border-radius:3px}
@media (min-width:860px){
  .route__row{grid-template-columns:minmax(0,1fr) var(--route-a);
    gap:var(--gutter);padding:22px 0 24px}
  /* Two aligned edges instead of one. justify-self:end right-aligned the whole
     action, so every label began at a different x — measured, 166px of spread
     across the five, which is the ragged edge that made the column look
     unaligned. As a 1fr/auto grid filling a fixed column, the labels share a
     left edge and the arrows share a right edge. */
  .route__a{display:grid;grid-template-columns:1fr auto;align-items:center;
    width:100%;justify-self:end}
}
@media (prefers-reduced-motion:reduce){
  .route__row,.route__row::after,.route__a svg{transition:none}
}

/* ── founder impact ────────────────────────────────────────────────
   v1.1, section 4.5. The review split the old catch-all Proof section
   into two things that are not the same claim: WHO WE WORK WITH is an
   organisation's logo, and FOUNDER IMPACT is a named venture with an
   outcome attached. Keeping them one section blurred a partnership into
   a result.

   No cards. Each story is a ruled entry, the venture name in ink and the
   outcome underneath, because the outcome is the evidence and a box round
   it adds nothing. */
.impact{display:grid;gap:0;margin-top:22px;
  border-top:1px solid var(--rule-paper);border-bottom:1px solid var(--rule-paper)}
.impact__i{position:relative;padding:20px 0 22px}
.impact__i::before{content:"";position:absolute;top:-1px;left:0;right:0;height:1px;
  background:var(--hairline)}
.impact__i:first-child::before{content:none}
.impact__n{display:flex;align-items:baseline;gap:10px;
  font-size:1.0625rem;font-weight:600;letter-spacing:-.014em;color:var(--text-paper)}
.impact__tag{font-size:.6875rem;font-weight:600;letter-spacing:.08em;text-transform:uppercase;
  color:var(--teal-ink)}
.impact__d{margin-top:6px;font-size:.9375rem;line-height:1.5;color:var(--muted-paper);
  max-width:62ch}
.impact__todo{color:var(--micro-paper);font-style:normal}
@media (min-width:768px){
  .impact{grid-template-columns:repeat(2,1fr);column-gap:var(--gutter)}
  .impact__i:nth-child(-n+2)::before{content:none}
  .impact__i:nth-child(odd)::before{right:calc(var(--gutter) * -1)}
}

/* ── the cohort strip ──────────────────────────────────────────────
   v1.1, 4.4. Two facts the review wanted visible without opening a page:
   which cohort is running, and whether it is open. Tabular numerals so
   the date and the count sit on the same rhythm as every price. */
.cohort{display:flex;flex-wrap:wrap;align-items:center;gap:10px 18px;margin-top:20px;
  font-variant-numeric:tabular-nums}
.cohort__k{display:inline-flex;align-items:center;gap:9px;min-height:34px;
  padding:6px 14px;border-radius:999px;
  font-size:.8125rem;font-weight:600;letter-spacing:.01em;
  background:var(--green);color:var(--ink)}
.cohort__k .live__dot{background:var(--ink)}
.cohort__m{font-size:.9375rem;color:var(--muted-paper)}

/* ── enrol: the cohort line, now a control ─────────────────────────
   The AntiMetalButton arrangement — an ink rectangle with a coloured panel
   pinned inside its left edge that sweeps across on hover — with three
   deliberate departures.

   1. THE LABEL SITS ABOVE THE PANEL, NOT UNDER IT. The reference gives the
      accent z-10 so it covers the text as it expands, which is fine over "Book
      a demo" and wrong here: this label carries the cohort, the month and the
      only instruction on the control, and hiding it at the moment the pointer
      arrives is a flourish paid for with the enrolment. Worse on a phone, where
      :hover is not a hover but a post-tap state that sticks — the button would
      sit there afterwards as a blank teal bar. The panel arrives UNDER the
      words and recolours them instead.

   2. TEAL, NOT GREEN. The cleanup pass further down this sheet already took
      green off this exact chip and wrote down why: "the accent stops meaning
      'do this' when a status wears it too". Putting a green fill back here
      would re-open that, and leave two mint blocks in one section with the
      louder one on the browse action. Green stays on "Explore our programmes";
      this takes teal, which is the token for marks — and it is what the dot in
      this pill already used.

   3. SQUARE CORNERS THROUGHOUT, panel included. A rounded panel inside a
      pointed rectangle is two languages in one control, and the pointed
      vertices are the request. 0 is also the house radius; the 999px pills are
      the carve-out for .btn.

   Contrast at every state: rectangle on paper 16.7:1, resting label on ink
   14.0:1, label on teal once the panel lands 7.9:1. */
/* A <button>, not an <a>, since it plays rather than goes — hence the four
   resets a link never needed. text-align:left because a button centres its
   own contents and this one is a plate with a label ranged against a panel. */
.enrol{position:relative;display:inline-flex;align-items:center;
  min-height:44px;padding:7px 15px 7px calc(30px + 12px);
  border:0;border-radius:0;overflow:hidden;isolation:isolate;
  cursor:pointer;text-align:left;font-family:inherit;
  background:var(--ink);color:var(--text-ink);
  font-size:.8125rem;font-weight:600;letter-spacing:.01em;
  font-variant-numeric:tabular-nums}
.enrol__panel{position:absolute;left:4px;top:4px;bottom:4px;width:30px;z-index:0;
  background-color:var(--teal);
  /* The dot field is PAINT, not fifty circles. The reference builds five SVG
     groups of ten and animates every one of them forever; a tiling radial
     gradient draws the same texture in one layer, costs no DOM, and re-tiles
     itself as the panel widens instead of needing its groups re-spaced. */
  background-image:radial-gradient(circle,rgba(12,21,43,.55) 1px,transparent 1.3px);
  background-size:7px 7px}
/* One line, always. The label is short enough now that wrapping would only
   ever happen when something else has gone wrong — and a three-line pill with
   a panel down its side does not degrade, it breaks. */
.enrol__label{position:relative;z-index:1;white-space:nowrap}

/* The mark is the affordance that never moves and never needs a pointer, and
   it is the whole reason a touch reader can tell this rectangle apart from the
   status chip it used to be. It was a chevron while the pill was a link; a
   chevron promises a page, and this one opens a film over the page it is on,
   so it is the same play triangle the film card itself uses. Filled rather
   than stroked, which is why it carries no width from .btn__chev and sets its
   own — 10px of a 12 viewBox weighs what the 7x10 chevron did. */
.enrol__mark{position:relative;z-index:1;flex:none;width:10px;height:10px;
  margin-left:5px;opacity:.6}

@media (prefers-reduced-motion:no-preference){
  /* OUTSIDE the hover query. A tap uses these too — gating them behind
     (hover:hover) is what left this control completely inert on a phone. */
  .enrol__panel{transition:width var(--t-ui) var(--ease-out-quint)}
  /* The colour flip is DELAYED on purpose. The panel sweeps left to right and
     reaches the right-aligned label last; without the delay the label would
     turn navy while it is still over navy. Load-bearing — do not fold this
     into one shorthand. */
  .enrol__label,.enrol__mark{transition:color 90ms linear 120ms}
}

/* PRESS. On a touch screen this is the moment the control proves it is one, so
   the panel floods and the whole plate gives under the finger. :active fires on
   touch where :hover does not. */
.enrol:active{transform:scale(.985)}
.enrol:active .enrol__panel{width:calc(100% - 8px)}
.enrol:active .enrol__label,.enrol:active .enrol__mark{color:var(--ink)}

/* ARRIVAL, on touch only. With no hover there is nothing to discover the
   control by, so it demonstrates itself once when it scrolls into view: the
   panel runs out and back twice, which is the same gesture a mouse gets. Twice
   is 3s — deliberately under the five seconds at which moving content would
   need a pause control of its own. It ends where it started. */
@media (hover:none) and (prefers-reduced-motion:no-preference){
  .cohort[data-in] .enrol__panel{animation:enrol-demo 1500ms var(--ease-out-quint) 2 both}
  .cohort[data-in] .enrol__label,
  .cohort[data-in] .enrol__mark{animation:enrol-demo-ink 1500ms var(--ease-out-quint) 2 both}
}
@keyframes enrol-demo{
  0%{width:30px} 38%{width:calc(100% - 8px)} 76%,100%{width:30px}}
@keyframes enrol-demo-ink{
  0%{color:var(--text-ink)} 26%,58%{color:var(--ink)} 84%,100%{color:var(--text-ink)}}

@media (hover:hover) and (pointer:fine){
  .enrol:hover .enrol__panel,.enrol:focus-visible .enrol__panel{width:calc(100% - 8px)}
  .enrol:hover .enrol__label,.enrol:focus-visible .enrol__label,
  .enrol:hover .enrol__mark,.enrol:focus-visible .enrol__mark{color:var(--ink)}

  /* The wave, and only while someone is pointing at it. One sweeping band on
     one layer, rather than fifty dots looping forever on a page that is already
     running a photo reel and a marquee. Idle cost is zero. */
  @media (prefers-reduced-motion:no-preference){
    .enrol__panel::after{content:"";position:absolute;inset:0;
      background:linear-gradient(90deg,transparent,rgba(255,255,255,.42),transparent);
      width:40%;transform:translate3d(-120%,0,0)}
    .enrol:hover .enrol__panel::after,.enrol:focus-visible .enrol__panel::after{
      animation:enrol-sweep 1.4s linear infinite}
  }
}
@keyframes enrol-sweep{
  from{transform:translate3d(-120%,0,0)}
  to{transform:translate3d(260%,0,0)}}


/* ── copy beside a picture ─────────────────────────────────────────
   .wwa is not this. It was rebuilt for the world map, where the figure
   deliberately runs the full frame while the copy stays on --read, so
   borrowing it for a section with a photograph next to the text gives a
   full-bleed image under a narrow column. This is the ordinary split. */
/* ── the slats: a row, the open one carrying the picture, the open one carrying the picture ───
   The HoverExpand arrangement. One slat is open and the rest are edges, and
   pointing at an edge opens it. It also turns itself over on a timer, because
   the section has to say something to a reader who never moves the mouse.

   THE OPEN WIDTH IS COMPUTED, NOT DECLARED. The reference hard-codes 24rem open
   and 5rem closed, which only sums correctly inside its own max-width; dropped
   into this 525px figure it would overflow by about 340px. Here the open slat
   takes whatever is left after the closed ones and the gaps, so the row fills
   its container exactly at any width and the count can change without touching
   a number.

   --n is the slat count and lives on the markup, so adding a photograph is one
   line of HTML and one digit. */
.slats{position:relative;--slat-gap:4px;--slat:clamp(28px,4.2%,46px);--r:14px}
.slats__row{display:flex;gap:var(--slat-gap);
  height:clamp(260px,42vw,344px);align-items:stretch}
.slat{position:relative;padding:0;border:0;background:none;
  /* basis is the closed width; the open one grows into whatever is left. */
  flex:0 0 var(--slat);min-width:0;
  border-radius:var(--r);overflow:hidden;cursor:pointer;isolation:isolate}
.slat img{width:100%;height:100%;object-fit:cover;display:block;
  -webkit-user-drag:none;user-select:none}
/* Open: take all the free space. flex-grow rather than a computed width — the
   row then fills its container exactly whatever the count, and adding or
   removing a photograph needs no arithmetic anywhere.

   The width version this replaces was calc(100% - (n-1) * (slat + gap)), which
   Chrome rejected outright: --slat is a clamp() carrying a percentage, and a
   length-percentage inside a multiplication makes the whole expression invalid,
   so every slat silently fell back to the closed width. */
.slat[data-on]{flex-grow:1}
.slat:focus-visible{outline:2px solid var(--teal-ink);outline-offset:3px}

/* The veil is only under the open slat, and only so the index can be read off
   the picture. It is a bottom-up wash, not a box. */
.slat__veil{position:absolute;inset:0;opacity:0;pointer-events:none;
  background:linear-gradient(to top,rgba(12,21,43,.46),rgba(12,21,43,0) 46%)}
.slat__no{position:absolute;right:12px;bottom:10px;opacity:0;
  font-size:.6875rem;font-weight:600;letter-spacing:.06em;
  color:rgba(255,255,255,.72);font-variant-numeric:tabular-nums;
  pointer-events:none}
.slat[data-on] .slat__veil,.slat[data-on] .slat__no{opacity:1}

@media (prefers-reduced-motion:no-preference){
  .slat{transition:flex-grow 340ms var(--ease-out-cubic)}
  .slat__veil,.slat__no{transition:opacity 260ms var(--ease-out-cubic)}
}


.split{display:grid;gap:34px}
.split__fig{margin:0;width:100%;max-width:520px}
@media (min-width:900px){
  .split{grid-template-columns:minmax(0,1fr) minmax(0,40%);
    column-gap:clamp(28px,4vw,64px);row-gap:0;align-items:center}
  .split__fig{max-width:none}
}

/* ── lettered service indices ──────────────────────────────────────
   v1.1, 5.1: the three families stay A, B and C and the numbering
   RESTARTS under each, so a reader sees A1, A2, A3 then B1, B2 rather
   than one run from 01 to 07. The page's own counter cannot express
   that, so a lettered row carries its index as text and the counter's
   ::before is switched off. Without this both print, which is where
   "0101" came from. */
.rate__row--lettered .idx::before{content:none}
.rate__row--lettered .idx{font-variant-numeric:tabular-nums;letter-spacing:.02em}

/* ══ CLEANUP PASS ═══════════════════════════════════════════════════
   Three things measured on the built pages, not guessed at.

   1. The header action was a page-level button dropped into chrome:
      56px tall inside a 64px header, 88% of its height, at 17px type
      and a 999px pill. It was the loudest thing on every page and it
      lived in the furniture. A control in chrome is sized for chrome.

   2. Two saturated mint blocks sat above the fold on the programmes
      page, three counting the CTA below it. The accent stops meaning
      "do this" when a status wears it too, so the action keeps mint
      and the status gives it up.

   3. 235px of nothing between an inner page's h1 and the next
      section's heading, because two 78px .paper paddings stack on top
      of the intro's own margins. Air is not the same as a gap. */

/* 1. the header action, sized for the bar it sits in */
.hdr__cta{min-height:36px;padding:7px 12px;font-size:.8125rem;
  border-radius:8px;letter-spacing:-.014em;white-space:nowrap}
@media (min-width:768px){.hdr__cta{min-height:38px;padding:8px 14px}}

/* 2. the cohort marker is a STATUS, so it reads as one: a hairline chip
      with the teal dot doing the signalling, not a green lozenge
      competing with the button underneath it. */
.cohort__k{background:transparent;color:var(--text-paper);
  box-shadow:inset 0 0 0 1px var(--rule-paper);
  font-weight:600}
.cohort__k .live__dot{background:var(--teal-ink)}

/* 3. an inner page's opening block. The h1 sits at the top of the page
      with nothing above it, so it needs less room beneath it than a
      section landed on mid-scroll does. */
.page-head{padding-block:calc(30px + var(--hdr)) 8px}
@media (min-width:1024px){.page-head{padding-block:calc(38px + var(--hdr)) 10px}}
.page-head + .paper{padding-top:34px}

/* ── the page head, dressed as a hero ─────────────────────────────
   Two pages wear it: services and workspace — the site's two commercial pages,
   both doing the job the home page's hero does. Everything is on the modifier
   so the other four page heads keep the left-aligned block they had.

   The section has to become a containing block and an isolated stacking
   context before the wave can live in it, for the same two reasons the hero
   does: the wave is absolutely positioned and must not escape into the header
   above, and it must not be composited against anything outside this section.

   AND THE SECTION MUST NOT BE THE FRAME. inset:0 resolves against the
   containing block, so a framed section stops the colour at 1392px and leaves
   bare paper down both edges — two vertical seams on any screen wider than
   that, which is the one thing a background may not have. Full-bleed section,
   framed contents, exactly as the hero is built. Verified at 2000px: the wave
   measures 0 to 2000 with no gap, and the pixel row across the old frame
   boundary steps by nothing.

   And the copy has to be lifted deliberately. The children are grid/flow items;
   an absolutely positioned sibling with any z-index at all paints in the
   positioned layer, which sits ABOVE non-positioned in-flow content. Without
   the z-index below, the wave covers the headline. */
.page-head--hero{position:relative;isolation:isolate;overflow:clip;
  padding-block:calc(52px + var(--hdr)) 30px}
@media (min-width:1024px){
  .page-head--hero{padding-block:calc(66px + var(--hdr)) 38px}
}
.page-head--hero > *:not(.wave){position:relative;z-index:1}

/* Centred, and measured rather than left to the 1040px reading column: a
   centred line is read from both ends, so a long one costs more than the same
   line costs ranged left. The headline keeps a tighter measure than the lede
   because it is the larger of the two. */
.page-head--hero .read{margin-inline:auto;text-align:center}
.page-head--hero h1{max-width:24ch;margin-inline:auto;
  font-size:clamp(30px,4.4vw,46px);line-height:1.08}
/* balance, not pretty. pretty only lifts the orphan off the last line; a
   centred paragraph is read from both ends and wants every line the same
   length, which is the one thing balance does and pretty does not. */
.page-head--hero .body{max-width:56ch;margin-inline:auto;text-wrap:balance}
.page-head--hero .rmstrip{margin-inline:auto}

/* The header action is outlined, not filled. It appears on every page, so
   filling it with the accent guaranteed that no page could ever have only
   one mint element: the bar always claimed one before the content did.
   Outlined it still reads as the one button in a row of text links, and
   mint goes back to meaning "this is the thing to do on this page". */
.hdr__cta{box-shadow:inset 0 0 0 1px rgba(12,21,43,.28);font-weight:600}
@media (hover:hover) and (pointer:fine){
  .hdr__cta:hover{background:var(--ink);color:var(--paper);
    box-shadow:inset 0 0 0 1px var(--ink)}
}

/* A band that carries its own ruled component drops the grid overlay.
   The grid exists to give an empty tinted band some texture; laid behind
   a table that is already ruled it reads as a second, competing grid and
   the eye has to separate the two before it can read either. One set of
   lines per surface. */
.band:has(.route)::after,
.band:has(.impact)::after,
.band:has(.plans)::after,
.band:has(.doors)::after{content:none}

/* ── the logo cloud ────────────────────────────────────────────────
   Replaces the cycling column wall. The wall showed four of seven marks
   at a time and rotated them, which meant the answer to "who do you work
   with" changed every 2.6 seconds and was never complete. This shows all
   seven at once and animates the SURFACE instead: a wipe travels across
   the row, one mark at a time, and the set stays whole.

   The wipe is clip-path from the right, with a short blur and an opacity
   dip through the middle, staggered 110ms per mark. Pure CSS, so there is
   no timer; the only script is the observer that pauses it off screen,
   because there is still no reason to run an animation nobody is looking
   at. Under reduced motion the whole thing is static.

   Marks are smaller than the wall's were: a 40px box against 96px. Seven
   logos at that size read as a row of colophons rather than seven adverts.

   DEEP is a mint outline and Zaps a yellow-and-white wordmark. Both are
   drawn for a dark ground and measure 1.46:1 and 1.63:1 on this paper, so
   both keep the navy plate they were made for. The other five sit on the
   sheet untouched. */
/* Centred, under a centred heading. It was left-aligned while the heading
   above it was a left-aligned sentence; now the heading is one short centred
   phrase and the row centres with it, so the two read as one block rather
   than a title with a list hanging off its left edge.

   1000px because seven marks at 112 plus six gaps at 30 is 964. Narrower and
   the seventh wraps alone, which turns a roster of seven into six plus an
   afterthought. It still wraps honestly below that width. */
.lc{display:flex;flex-wrap:wrap;justify-content:center;align-items:flex-start;
  gap:20px 22px;margin:32px auto 0;max-width:1000px}
/* Phone sizes are the BASE here, so trimming them means restoring the desktop
   ones in the 768 block below — including .lc__m's height, which that block did
   not previously need to state.

   86 rather than 100: at 100 wide with 26px gaps, three marks measured 352px
   against the 343px a 375px phone actually offers, so the row broke to two and
   the wall ran five lines deep. At 86/22 three come to 302 and the wall reads as
   a block instead of a column. */
.lc__i{display:flex;flex-direction:column;align-items:center;gap:8px;width:86px}
.lc__m{display:flex;align-items:center;justify-content:center;
  width:100%;height:34px;will-change:clip-path,filter,opacity}
/* AFTER the base rules, not before them. .lc__m{height} is declared in both and
   both are one class deep, so at equal specificity source order decides — with
   this block above, the phone height won on desktop too. */
@media (min-width:768px){
  .lc{gap:26px 30px}
  .lc__i{width:112px;gap:9px}
  .lc__m{height:40px}
}
.lc__m img{max-width:100%;max-height:100%;width:auto;height:auto;
  object-fit:contain;display:block}
.lc__i[data-ink] .lc__m{background:var(--ink);border-radius:var(--r-md);
  padding:7px 10px;box-sizing:border-box}
.lc__n{font-size:.6875rem;font-weight:500;letter-spacing:.02em;line-height:1.3;
  color:var(--micro-paper);text-align:center;text-wrap:balance}

@keyframes lc-wipe{
  0%   {clip-path:inset(0 0 0 0);    filter:blur(0px); opacity:1}
  9%   {clip-path:inset(0 100% 0 0); filter:blur(5px); opacity:.22}
  22%  {clip-path:inset(0 0 0 0);    filter:blur(0px); opacity:1}
  100% {clip-path:inset(0 0 0 0);    filter:blur(0px); opacity:1}
}
@media (prefers-reduced-motion:no-preference){
  .lc[data-play] .lc__m{animation:lc-wipe 4.4s cubic-bezier(.16,1,.3,1) infinite;
    animation-delay:calc(var(--i,0) * 110ms)}
}
@media (hover:hover) and (pointer:fine){
  .lc__i{transition:transform var(--t-hover) var(--ease-out-quint)}
  .lc__i:hover{transform:scale(1.06)}
  .lc__i:hover .lc__m{animation-play-state:paused;filter:blur(0);opacity:1;
    clip-path:inset(0 0 0 0)}
}

/* The partner block is the one centred thing on the page, and the centring is
   scoped to it alone. .lc-wrap is the whole section and also carries Founder
   impact, whose heading and ruled list belong on the left margin with the rest
   of the page; centring the section would have taken them with it. */
/* Tracked tighter than any other heading on the site, and tracked by SIZE,
   because this h2 is 26px until 1200px and 40px above it and the two sizes do
   not tolerate the same value. Judged off rendered ladders at both.

   At 26px the word-spaces start closing by -.055 and "Who we" merges into
   "Whowe" by -.070, so the small size holds at -.045. At 40px there is far
   more room: -.065 still reads as four separate words and only collapses at
   -.085. Larger type takes tighter tracking, which is why one value for both
   would have to be the small size's value and would leave the display size
   looser than it wants to be.

   Word-spacing is left alone at both sizes. The hook pairs tight tracking
   with word-spacing:-.06em; doing that here is what actually merges the
   words. Tighten the letters, not the gaps between words. */
.lc-head{text-align:center;max-width:none;margin-inline:auto;
  letter-spacing:-.045em}
@media (min-width:1200px){.lc-head{letter-spacing:-.065em}}

/* ── the founder reel ──────────────────────────────────────────────
   Three columns of photographs drifting past a story, the middle one
   travelling against the outer two.

   THIS IS NOT THE STEPPED REEL IT REPLACED. That version moved the
   middle column by an exact pitch so a chosen tile landed dead centre.
   Continuous motion and a centred tile are mutually exclusive: if the
   column never stops there is no frame in which anything is centred.
   So the reel is now ambient and the story is driven only by its own
   controls. One consequence worth knowing: the tiles no longer
   correspond to the story on screen. They are the hub, not the
   speaker, which is why none of them is labelled.

   The loop is exact because there is no `gap`. Each tile carries its
   own margin-bottom, including the last, so the track is a clean
   repetition of one block and translateY(-50%) lands precisely on the
   seam. With `gap` the two halves are separated by one extra gap and
   every cycle jumps by half of it, which is the same seam bug the
   partner marquee had.

   Heights vary and widths do not. "Different breadths" in a vertical
   column has to mean height: varying width would pull the three
   columns out of alignment and the thing stops reading as a grid. */
.reel{--cell:118px;--gap:8px;
  display:grid;grid-template-columns:1fr;gap:0;margin-top:26px;
  border-radius:14px;overflow:hidden;background:var(--surface);
  box-shadow:inset 0 0 0 1px var(--hairline)}
@media (min-width:900px){
  .reel{grid-template-columns:minmax(0,380px) 1fr;align-items:stretch}
}

.reel__stage{position:relative;height:260px;overflow:hidden;background:var(--paper);
  -webkit-mask-image:linear-gradient(to bottom,transparent,#000 14%,#000 86%,transparent);
          mask-image:linear-gradient(to bottom,transparent,#000 14%,#000 86%,transparent)}
@media (min-width:900px){.reel__stage{height:auto;min-height:380px}}
.reel__cols{position:absolute;inset:0;display:flex;align-items:flex-start;
  justify-content:center;gap:var(--gap)}

/* The column is twice its content: one set, then the same set again. The
   animation carries it exactly one set upward and restarts, so the join is
   invisible. Outer columns run one way, the middle the other. */
.reel__col{display:flex;flex-direction:column;flex:none;width:var(--cell);
  will-change:transform}
@media (prefers-reduced-motion:no-preference){
  .reel[data-play] .reel__col{animation:reel-up 46s linear infinite}
  .reel[data-play] .reel__col[data-dir="down"]{animation-name:reel-down}
}
@keyframes reel-up  {from{transform:translateY(0)}      to{transform:translateY(-50%)}}
@keyframes reel-down{from{transform:translateY(-50%)}   to{transform:translateY(0)}}
/* the drift is decoration; pointing at it should not fight the reader */
@media (hover:hover) and (pointer:fine){
  .reel:hover .reel__col{animation-play-state:paused}
}

/* One tile. Height comes from --h so the column reads as a set of
   photographs rather than a grid of identical squares. */
.reel__cell{width:100%;height:var(--h,118px);margin-bottom:var(--gap);flex:none;
  border-radius:12px;overflow:hidden;position:relative;
  background:var(--paper-2);
  box-shadow:inset 0 0 0 1px var(--hairline)}
.reel__cell img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
/* the slot before a photograph exists: the page's own hatch and a small
   frame mark, so it reads as a reserved image and not as a broken one */
.reel__cell[data-fpo]{
  background-image:repeating-linear-gradient(135deg,
    rgba(12,21,43,.05) 0 1px,transparent 1px 11px)}
.reel__cell[data-fpo]::after{content:"";position:absolute;left:50%;top:50%;
  width:17px;height:15px;transform:translate(-50%,-50%);
  border:1.5px solid rgba(12,21,43,.16);border-radius:3px}

/* the story */
.reel__body{display:flex;flex-direction:column;justify-content:space-between;
  gap:22px;padding:26px 24px 24px}
@media (min-width:900px){.reel__body{padding:38px 38px 30px}}
.reel__mark{width:34px;height:34px;color:var(--rule-paper);flex:none}
.reel__text{position:relative;max-width:44ch}
.reel__q{font-size:1.125rem;font-weight:500;line-height:1.38;letter-spacing:-.016em;
  color:var(--text-paper);margin:0}
@media (min-width:900px){.reel__q{font-size:1.3125rem}}
.reel__a{margin:14px 0 0;font-size:.875rem;font-weight:600;letter-spacing:-.004em;
  color:var(--text-paper)}
.reel__tag{margin-left:9px;font-size:.6875rem;font-weight:600;letter-spacing:.08em;
  text-transform:uppercase;color:var(--teal-ink)}
.reel__a--todo,.reel__q--todo{color:var(--micro-paper)}

/* The venture's own mark, under its name. Small, on the sheet, no plate and
   no frame: it is an attribution, not a partner endorsement, and it should
   not carry more weight than the name above it. */
.reel__logo{display:flex;align-items:center;height:26px;margin-top:14px}
.reel__logo img{max-height:100%;max-width:120px;width:auto;height:auto;
  object-fit:contain;display:block}
.reel__logo[data-fpo]{width:104px;border-radius:5px;
  background:var(--paper-2);
  box-shadow:inset 0 0 0 1px var(--hairline);
  align-items:center;justify-content:center}
.reel__logo[data-fpo]::before{content:"LOGO";font-size:.5625rem;font-weight:600;
  letter-spacing:.14em;color:var(--micro-paper)}

/* character rise */
.reel__ch{display:inline-block;opacity:0;transform:translateY(10px);
  animation:reel-rise 460ms cubic-bezier(.16,1,.3,1) forwards}
@keyframes reel-rise{to{opacity:1;transform:translateY(0)}}
.reel__text[data-exiting] .reel__block{animation:reel-exit 240ms ease-in forwards}
@keyframes reel-exit{to{opacity:0;transform:translateY(-10px)}}
@media (prefers-reduced-motion:reduce){
  .reel__ch{opacity:1;transform:none;animation:none}
  .reel__text[data-exiting] .reel__block{animation:none}
}

/* The row wraps because the pill at its right end is wider than the two
   arrows put together; at 320px it drops to its own line rather than
   squeezing the arrows off the sheet. */
.reel__nav{display:flex;flex-wrap:wrap;align-items:center;gap:10px;row-gap:12px}
.reel__btn{display:grid;place-items:center;width:32px;height:32px;flex:none;
  border:0;border-radius:999px;cursor:pointer;background:transparent;color:var(--text-paper);
  box-shadow:inset 0 0 0 1px rgba(12,21,43,.2);
  transition:background-color var(--t-hover) var(--ease-out-quint),
             color var(--t-hover) var(--ease-out-quint),
             opacity var(--t-hover) var(--ease-out-quint)}
.reel__btn svg{width:13px;height:13px}
.reel__btn:disabled{opacity:.3;cursor:default}
@media (hover:hover) and (pointer:fine){
  .reel__btn:not(:disabled):hover{background:var(--ink);color:var(--paper);
    box-shadow:inset 0 0 0 1px var(--ink)}
}
.reel__btn:focus-visible{outline:2px solid var(--ink);outline-offset:2px}
.reel__count{font-size:.8125rem;font-variant-numeric:tabular-nums;color:var(--micro-paper);
  margin-left:2px}

/* ── the film, and the pill that opens it ──────────────────────────
   The pill lives at the right end of the story's own nav row. Not
   absolutely placed in the corner: the arrows and the counter are
   left-anchored, so a control that comes and goes as the stories turn
   moves nothing a reader's thumb is already resting on.

   Its motion is .enrol's gesture mirrored — a disc at the right end
   floods leftward across the plate. Width on one layer, as there,
   because a scaled circle would repaint every frame beside three
   columns of drifting photographs.

   And it runs itself. Most of this page is read on a phone, where
   there is no pointer to discover a button with, so the pill draws
   the sweep a mouse would draw: once, each time a story lands. The
   driver is in site.js, gated on the same three things the story
   rotation is gated on. */
.watch{position:relative;display:inline-flex;align-items:center;flex:none;
  margin-left:auto;min-height:40px;padding:0 46px 0 16px;
  border:0;border-radius:999px;overflow:hidden;isolation:isolate;cursor:pointer;
  background:transparent;color:var(--text-paper);
  box-shadow:inset 0 0 0 1px rgba(12,21,43,.2);
  font-family:inherit;font-size:.8125rem;font-weight:600;letter-spacing:-.004em;
  /* The panel animates WIDTH, which dirties layout every frame it moves —
     about a quarter of the time, beside three columns of drifting
     photographs on a mid-range Android. This bounds the dirty rect to
     40x32px as a guarantee rather than a hope. Not `size` (it would kill
     the label-driven intrinsic width) and not will-change (width does not
     composite, so it would promote a layer for nothing). */
  contain:layout paint}
/* display:inline-flex would otherwise beat the UA's [hidden] rule */
.watch[hidden]{display:none}
.watch__panel{position:absolute;right:4px;top:4px;bottom:4px;width:32px;z-index:0;
  border-radius:999px;background:var(--ink)}
.watch__label{position:relative;z-index:1}
/* right: 4px inset + half the 32px disc, less half the glyph */
.watch__play{position:absolute;right:14.5px;top:50%;z-index:1;
  width:11px;height:11px;margin-top:-5.5px;color:var(--paper)}

/* The transitions sit OUT here, not inside the hover query. Gating the
   transition itself is what once left this page's other pill inert to
   the touch: :active had nothing to animate. */
.watch__panel{transition:width var(--t-ui) var(--ease-out-quint)}
/* The flip is delayed because the panel sweeps right to left and reaches
   the left-aligned label last. Without it the word turns pale while it is
   still standing on white. Load-bearing — do not fold into one shorthand. */
.watch__label{transition:color 90ms linear 120ms}
.watch:active{transform:scale(.985)}
.watch:focus-visible{outline:2px solid var(--ink);outline-offset:2px}

/* ── the sweep it plays for itself, and keeps playing ──────────────
   A loop with a long rest, not a pulse: 5400ms is 1400 of sweep — the
   same 1400 this pill has always drawn, rescaled stop for stop so a
   single pass moves exactly as it did — then 4000 standing still.

   5400 is chosen against the other loops that can share a screen with
   it. In units of 200ms the story beat is 35, the partner-logo wipe is
   22 and this is 27: pairwise coprime, so no two of them repeat a phase
   relationship inside an hour. A period that divided 7000 would land the
   sweep at the same instant of every story and the card would read as a
   clock. That is the failure mode, and it is arithmetic, not taste.

   The 640ms delay fires ONCE, ahead of the first pass, so the story's
   characters finish rising before anything else in the card moves. After
   that the loop free-runs and is never restarted — restarting it on each
   story arrival is precisely what would tie it to the beat.

   Duty is 26% within a run and 15% across a full rotation, because the
   pill is absent for the two placeholder stories: three sweeps in the
   fourteen seconds it is on screen, then fourteen seconds of no pill. */
@media (prefers-reduced-motion:no-preference){
  .watch[data-demo] .watch__panel{
    animation:watch-fill 5400ms var(--ease-out-quint) 640ms infinite both}
  .watch[data-demo] .watch__label{
    animation:watch-fill-ink 5400ms var(--ease-out-quint) 640ms infinite both}
}
/* The old 1400ms stops rescaled and nothing else: 476/868/1400ms land on
   8.81/16.07/25.93 percent of 5400, and the label's 364/784/1232 on
   6.74/14.52/22.81. Every segment keeps its real duration and its own
   easing. Everything past the last stop is the rest. */
@keyframes watch-fill{
  0%{width:32px}
  8.81%,16.07%{width:calc(100% - 8px)}
  25.93%,100%{width:32px}}
@keyframes watch-fill-ink{
  0%{color:var(--text-paper)}
  6.74%,14.52%{color:var(--paper)}
  22.81%,100%{color:var(--text-paper)}}

/* AFTER the keyframes on purpose, and this is now load-bearing rather
   than tidy: an animation beats a plain declaration, and with the loop
   never ending these are the only things standing between a press and a
   control that cannot be seen to be pressed. Each lands in a DEFINED end
   state — the full panel — so the width transition carries it there from
   wherever the sweep had reached, and nothing freezes half open. */
.watch:active .watch__panel,
.watch:focus-visible .watch__panel{animation:none;width:calc(100% - 8px)}
.watch:active .watch__label,
.watch:focus-visible .watch__label{animation:none;color:var(--paper)}
@media (hover:hover) and (pointer:fine){
  .watch:hover .watch__panel{animation:none;width:calc(100% - 8px)}
  .watch:hover .watch__label{animation:none;color:var(--paper)}
}

/* The card itself. <dialog> rather than a hand-built overlay: the focus
   trap, Escape, the inert background and the top layer all arrive with
   it, and the top layer is the only thing that reliably clears this
   page's own containment.

   :not([open]) is written out because a browser without <dialog> gives
   the element no style at all — the video would otherwise sit in the
   middle of the page on every load. */
/* --ar is the film's own shape, width over height: 1.778 for a
   landscape 16:9, 0.5625 for the 9:16 a phone shoots. Every film so far
   is portrait, and a portrait film in a fixed landscape frame is a thin
   strip between two black fields — so the frame takes the film's shape
   rather than the other way round.

   Three limits, and for a portrait film the last one is the one that
   binds: sizing the card by the height the frame is allowed to take is
   what keeps the whole of it — film, name and tag — on the screen at
   every shape, with nothing to scroll. */
.film{width:min(920px,calc(100vw - 28px),calc((100dvh - 96px) * var(--ar,1.778)));max-width:none;
  padding:0;border:0;background:transparent;overflow:visible;color:var(--text-paper)}
.film:not([open]){display:none}
.film::backdrop{background:rgba(12,21,43,.82)}
.film__card{position:relative;border-radius:14px;overflow:hidden;
  background:var(--paper);box-shadow:var(--lift)}
.film__x{position:absolute;top:10px;right:10px;z-index:2;
  display:grid;place-items:center;width:40px;height:40px;flex:none;
  border:0;border-radius:999px;cursor:pointer;
  background:rgba(245,246,244,.92);color:var(--text-paper);
  box-shadow:inset 0 0 0 1px rgba(12,21,43,.2)}
.film__x svg{width:15px;height:15px}
.film__x:focus-visible{outline:2px solid var(--ink);outline-offset:2px}
/* the frame is reserved before the file arrives, so nothing resizes
   under the reader once it does */
.film__slot{position:relative;aspect-ratio:var(--ar,1.778);background:var(--ink)}
/* contain, not cover: a film that was shot in another shape is letterboxed
   on the ink rather than cropped. */
.film__slot video{display:block;width:100%;height:100%;object-fit:contain;background:var(--ink)}

/* ── the player ────────────────────────────────────────────────────
   Ported from a React component built on Tailwind and lucide: the same
   controls in the same places, redrawn in this site's materials.

   Three things came out in the port, all of them house rules:
     · the centre button's backdrop-filter is gone — refusal 6, and over
       a bright frame it turns to mud exactly as the header would have;
     · the bar's black-to-transparent scrim is a flat ink band instead.
       A fade only approximates contrast, and nobody has seen the
       footage it has to sit on; a band guarantees it, and hard edges
       between grounds are what the rest of the page does;
     · paper and ink, never #fff and #000 — refusal 10.

   White type is right here and nowhere else on the site: these controls
   stand on film, not on the sheet. */
.vp{position:absolute;inset:0;overflow:hidden;background:var(--ink);
  color:var(--paper)}
.vp[hidden]{display:none}
.vp__v{display:block;width:100%;height:100%;object-fit:contain;background:var(--ink)}
.vp__i{width:16px;height:16px;display:block}

/* the icon pairs: one of each is showing at any moment */
.vp__i--pause,.vp__i--shrink,.vp__i--quiet{display:none}
.vp[data-on] .vp__i--play{display:none}
.vp[data-on] .vp__i--pause{display:block}
.vp[data-full] .vp__i--grow{display:none}
.vp[data-full] .vp__i--shrink{display:block}
.vp[data-quiet] .vp__i--loud{display:none}
.vp[data-quiet] .vp__i--quiet{display:block}

/* The one control that must be findable before anything else. Ink at
   .58 rather than glass, so it holds its edge over a white wall and over
   a dark curtain alike — both of which these two films contain. */
.vp__big{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
  display:grid;place-items:center;width:64px;height:64px;padding:0;
  border:0;border-radius:999px;cursor:pointer;
  background:rgba(12,21,43,.58);color:var(--paper);
  box-shadow:inset 0 0 0 1px rgba(245,246,244,.45)}
.vp__big .vp__i{width:24px;height:24px}
/* The play mark is the pill's glyph — one play triangle on the page, not two.
   Its box is smaller than its siblings' because it is drawn on a 12 viewBox
   and fills 80% of it, against the pause bars' 55% of a 16: these sizes are
   what make the two weigh the same when they swap. The optical nudge that
   used to be a margin here is inside the path now, so a centred box is right. */
.vp__i--play{width:12px;height:12px}
.vp__big .vp__i--play{width:18px;height:18px}
.vp[data-idle][data-on] .vp__big{opacity:0;pointer-events:none}

.vp__bar{position:absolute;left:0;right:0;bottom:0;
  padding:12px 12px 10px;background:rgba(12,21,43,.82)}
.vp[data-idle] .vp__bar{opacity:0;pointer-events:none}
.vp__row{display:flex;align-items:center;gap:10px}
.vp__t{font-size:.6875rem;font-weight:600;font-variant-numeric:tabular-nums;
  color:var(--paper);flex:none;min-width:34px}
.vp__t:last-of-type{text-align:right}
.vp__btns{display:flex;align-items:center;justify-content:space-between;
  margin-top:6px}
.vp__grp{display:flex;align-items:center;gap:2px}
.vp__b{display:grid;place-items:center;width:34px;height:34px;flex:none;padding:0;
  border:0;border-radius:var(--r-md);cursor:pointer;
  background:transparent;color:var(--paper)}
.vp__b:focus-visible,.vp__big:focus-visible{outline:2px solid var(--paper);outline-offset:2px}

/* The volume slider opens on hover, and is simply always there on a
   touch screen — a control that needs a pointer to appear is a control
   a phone does not have. */
.vp__vol{display:flex;align-items:center}
.vp__lev{width:64px}
@media (hover:hover) and (pointer:fine){
  .vp__lev{width:0;opacity:0}
  .vp__vol:hover .vp__lev,.vp__vol:focus-within .vp__lev{width:64px;opacity:1}
}

/* Both sliders. --p is the filled fraction, set from script: a range
   input cannot draw its own progress, and the two-stop hard-edged fill
   is a bar, not a gradient — there is no fade in it. */
.vp__seek,.vp__lev{-webkit-appearance:none;appearance:none;
  height:4px;border-radius:999px;margin:0;cursor:pointer;
  background-image:linear-gradient(to right,
    var(--paper) 0 calc(var(--p,0) * 100%),
    rgba(245,246,244,.28) calc(var(--p,0) * 100%) 100%)}
.vp__seek{flex:1 1 auto;min-width:0}
.vp__seek::-webkit-slider-thumb,.vp__lev::-webkit-slider-thumb{
  -webkit-appearance:none;appearance:none;width:12px;height:12px;
  border:0;border-radius:999px;background:var(--paper);cursor:pointer}
.vp__lev::-webkit-slider-thumb{width:10px;height:10px}
.vp__seek::-moz-range-thumb,.vp__lev::-moz-range-thumb{
  width:12px;height:12px;border:0;border-radius:999px;background:var(--paper);cursor:pointer}
.vp__seek:focus-visible,.vp__lev:focus-visible{outline:2px solid var(--paper);outline-offset:3px}

/* Motion last, and never `all`: refusal 9. Only the properties that move. */
@media (prefers-reduced-motion:no-preference){
  .vp__big,.vp__bar{transition:opacity var(--t-ui) var(--ease-out-quint)}
  .vp__lev{transition:width var(--t-ui) var(--ease-out-quint),
                      opacity var(--t-ui) var(--ease-out-quint)}
  .vp__b,.vp__big{transition:background-color var(--t-hover) var(--ease-out-quint)}
  .vp__seek::-webkit-slider-thumb{transition:transform var(--t-hover) var(--ease-out-quint)}
  .vp__seek:hover::-webkit-slider-thumb{transform:scale(1.25)}
}
@media (hover:hover) and (pointer:fine){
  .vp__b:hover{background:rgba(245,246,244,.18)}
  .vp__big:hover{background:rgba(12,21,43,.74)}
}

/* Full screen. The frame's aspect-ratio has to go or the film sits in a
   letterbox inside a letterbox. */
.vp:fullscreen{position:fixed;background:var(--ink)}
.vp:fullscreen .vp__v{object-fit:contain}
.film__slot .fpo{position:absolute;inset:0;border-radius:0;box-shadow:none}
.film__cap{display:flex;flex-wrap:wrap;align-items:baseline;margin:0;
  padding:14px 18px 16px;
  font-size:.875rem;font-weight:600;letter-spacing:-.004em}

@media (prefers-reduced-motion:no-preference){
  .film[open]{animation:film-in 200ms var(--ease-out-quint) both}
  .film[open]::backdrop{animation:film-veil 160ms linear both}
}
@keyframes film-in{from{opacity:0;transform:translateY(8px)}}
@keyframes film-veil{from{opacity:0}}

/* ── the application pages ─────────────────────────────────────────
   Two forms, one shape: a step rail, one panel at a time, and a done state
   that replaces the form rather than sitting under it.

   Ported from 21st.dev's Wizard Steps (a clickable progress rail with
   direction-aware panels) and Inline Validation (a field that crossfades
   between hint, error and status). Both are React and both animate with
   motion/react; here the rail is a list, the panels are [hidden], and the
   crossfade is a two-line grid that does not reflow when the message swaps —
   which is the part that actually matters, because a field that grows by a
   line when you tab out of it pushes the next one under your cursor. */
/* The page head is centred over the card and holds the same measure, so the
   title, the lede and the card share one vertical axis all the way down. The
   back link keeps that measure too but stays left-aligned inside it, so it
   lands on the card's left edge rather than stranded against the frame. */
.apply-head{padding-block:calc(30px + var(--hdr)) 0}
@media (min-width:1024px){.apply-head{padding-block:calc(38px + var(--hdr)) 0}}
/* A grid, not width-plus-auto-margins: .backb is inline-flex, so giving it the
   column width would make the whole 34rem strip clickable. As a grid item with
   justify-self:start it keeps its own width and still lands on the card's left
   edge, which is where a back control is looked for. */
.apply-head{display:grid;grid-template-columns:minmax(0,var(--apply-w));
  justify-content:center;text-align:center}
.apply-head > .backb{justify-self:start;text-align:left}
/* The back control. A navy panel holds the arrow at the left end and grows to
   fill the pill on hover while the label fades under it, so the button ends as
   a single arrow pointing the way it sends you. One property animates — the
   panel's width — and the resting state is already complete, which is what a
   touch device gets and what anyone gets with motion turned down. */
.backb{position:relative;display:inline-flex;align-items:center;overflow:hidden;
  min-height:44px;border-radius:999px;text-decoration:none;
  border:1px solid var(--field-line);background:#fff;color:var(--text-paper);
  font-size:.875rem;font-weight:600;letter-spacing:-.006em}
.backb__t{padding:0 20px 0 calc(2.9rem + 14px);
  transition:opacity 500ms var(--ease-out-quint)}
.backb__i{position:absolute;inset:0;z-index:1;display:grid;place-items:center;
  width:2.9rem;background:var(--ink);color:#fff;
  transition:width 500ms var(--ease-out-quint)}
.backb__i svg{width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:1.75;
  stroke-linecap:round;stroke-linejoin:round}
@media (hover:hover){
  .backb:hover .backb__i{width:100%}
  .backb:hover .backb__t{opacity:0}
}
.backb:focus-visible{outline:2px solid var(--teal-ink);outline-offset:3px}
@media (prefers-reduced-motion:reduce){
  .backb__t,.backb__i{transition:none}
  .backb:hover .backb__i{width:2.9rem}
  .backb:hover .backb__t{opacity:1}
}
/* [hidden] is a UA rule at zero specificity, so ANY class that sets display
   beats it. .btn is display:inline-flex, which is why the Back and Send buttons
   were on screen at step one with the hidden attribute correctly set — the
   property said hidden and the cascade said otherwise. This is the guard, and
   it is deliberately global: every [hidden] on this page means it. */
[hidden]{display:none !important}


/* One centred column, not a card beside an aside. The measure is deliberate:
   34rem is roughly 60 characters of label, which is the width a single-column
   form wants — wide enough that nothing wraps, narrow enough that the eye does
   not travel from a label on the left to a field on the right. The price list
   follows the card in the same column instead of sitting alongside it. */
.apply-body{padding-block:8px 78px}
:root{--apply-w:34rem}
.apply__grid{display:grid;grid-template-columns:minmax(0,var(--apply-w));
  justify-content:center;gap:20px;align-items:start}
/* The ring was --hairline at 8%, which left the container reading fainter
   than the controls it holds now that those carry --field-line. */
.apply{--card-px:20px;--card-pt:26px;
  background:var(--surface,#fff);border-radius:var(--r-md,14px);
  box-shadow:inset 0 0 0 1px rgba(12,21,43,.12),0 1px 2px rgba(12,21,43,.04),
             0 10px 28px -14px rgba(12,21,43,.14);
  padding:var(--card-pt) var(--card-px) 22px;border:0;margin-top:26px}
@media (min-width:768px){.apply{--card-px:34px;--card-pt:34px;padding:34px 34px 28px}}

/* The banner, the way a Google Form carries one: inside the card, the card's
   own width, flush to its edges. It bleeds back out through the padding with
   negative margins taken from the same variables the padding uses, so the two
   cannot drift apart at a breakpoint. 4:1 — one crop serves every width,
   because the card is 544px at its widest and 468px on a phone, and a band
   that shallow does not need art directing twice. */
.apply__banner{display:block;background:var(--paper-2);
  /* It is the <img> itself, not a box around one, so width:100% would resolve
     against the card's content box and fall short of the bleed by exactly the
     padding. The width is stated, and object-fit absorbs any mismatch between
     4:1 and whatever ratio the dropped file happens to be. */
  /* the global img{max-width:100%} reset clamps this back to the content box
     and the bleed silently disappears, so it is opted out of here */
  width:calc(100% + (var(--card-px) * 2));max-width:none;height:auto;
  aspect-ratio:4/1;object-fit:cover;
  border-radius:var(--r-md,14px) var(--r-md,14px) 0 0;
  margin:calc(var(--card-pt) * -1) calc(var(--card-px) * -1) 24px;
  border-bottom:1px solid var(--hairline)}

/* the card's own heading, above the rail */
.apply__intro{margin:0 0 22px}
.apply__h{font-size:clamp(20px,3.4vw,25px);line-height:1.16;letter-spacing:-.022em;
  font-weight:560;margin:0;text-wrap:balance}
.apply__p{margin:9px 0 0;font-size:.9375rem;line-height:1.55;color:var(--muted-paper)}

/* the rail */
.rail{display:flex;flex-wrap:wrap;gap:6px 8px;margin:0 0 26px;padding:0 0 22px;
  border-bottom:1px solid var(--hairline);counter-reset:none}
.rail__s{display:flex;align-items:center;gap:9px;padding:6px 12px 6px 6px;border-radius:999px;
  font-size:.8125rem;font-weight:600;color:var(--micro-paper);
  transition:background-color var(--t-ui) var(--ease-out-quint),color var(--t-ui) var(--ease-out-quint)}
.rail__n{display:grid;place-items:center;width:24px;height:24px;flex:none;border-radius:50%;
  font-size:.75rem;font-variant-numeric:tabular-nums;
  background:var(--paper-2);color:var(--micro-paper);
  transition:background-color var(--t-ui) var(--ease-out-quint),color var(--t-ui) var(--ease-out-quint)}
.rail__s[data-on]{background:var(--paper-2);color:var(--text-paper)}
/* A step you have finished was reading paler than one you have not started. */
.rail__s[data-done]{color:var(--text-paper)}
.rail__s[data-on] .rail__n{background:var(--green);color:var(--ink)}
/* Done steps get the tick, and the tick is a glyph rather than an SVG so it
   inherits the weight of the numeral it replaces. */
.rail__s[data-done] .rail__n{background:var(--ink);color:#fff;font-size:.6875rem}
.rail__s[data-done] .rail__n::after{content:"\2713"}
.rail__s[data-done] .rail__n{color:transparent}
.rail__s[data-done] .rail__n::after{color:#fff;position:absolute}
.rail__n{position:relative}

/* fields */
.panel{border:0;padding:0;margin:0;min-inline-size:0}
.panel[hidden]{display:none}

/* A one-step form has no rail, and the rail is what drew the hairline under
   the card's own heading. Without it the intro and the first question run
   together into one block. */
.apply--solo .apply__intro{margin:0 0 24px;padding:0 0 22px;
  border-bottom:1px solid var(--hairline)}

/* ── rmstrip — the room, as a small collage that drifts ────────────
   Five views of the same room, butted edge to edge on a soft green board,
   sliding left for ever and a plain scroller again the moment anyone touches
   it. It starts where the text starts and stops well short of the page.

   THE BOARD IS A TINT, not the brand green. At full strength green against the
   hub's wall yellow is 1.12:1 — two colours the eye cannot rank — and with the
   pictures butted together there is no mat to hold them apart. The hairline
   ring gives the block its edge instead.

   THE BOARD MOVED OFF THE LIST, because the list is now the scroll port and
   the drift writes its scrollLeft: a padded scroll container reports trailing
   padding differently across engines and the loop maths would carry that
   difference.

   ONE PICTURE WIDTH AT EVERY BREAKPOINT. The strip already landed near 150px
   everywhere, so the drift changes the motion and nothing about the size.

   THE LOOP. The markup holds the five pictures twice and the script wraps the
   position back by exactly one run, so the join lands on identical content and
   cannot be seen. That holds only while ONE RUN IS WIDER THAN THE PORT — hence
   the max-width, derived from --rm-i so the two cannot drift apart. */
.rmstrip{--rm-i:150px;margin-block:30px 0;
  max-width:min(100%,calc(var(--rm-i) * 4.6 + 14px));
  background:rgba(112,239,151,.22);padding:7px;
  box-shadow:inset 0 0 0 1px rgba(12,21,43,.07)}
.rmstrip__t{display:grid;grid-auto-flow:column;grid-auto-columns:var(--rm-i);
  gap:0;margin:0;padding:0;list-style:none;
  overflow-x:auto;overscroll-behavior-x:contain;
  scroll-snap-type:x mandatory;scrollbar-width:none}
.rmstrip__t::-webkit-scrollbar{display:none}
.rmstrip__t > li{scroll-snap-align:center;min-width:0}
.rmstrip__t img{display:block;width:100%;height:auto;aspect-ratio:3/2;
  object-fit:cover;-webkit-user-drag:none;user-select:none}
.rmstrip__t:focus-visible{outline:2px solid var(--ink);outline-offset:3px}

/* While the drift owns the scroll position, mandatory snapping would haul it
   back to the nearest picture edge every frame. data-drift is set once, when
   the drift actually starts, and stays on while it is merely paused — handing
   snapping back on every hover would snap the strip with a visible jump each
   time a mouse arrived. With JS off, or under reduced motion, the attribute is
   never set and this is exactly the scroller it was.

   NO touch-action here, deliberately: the strip is the top of the first screen
   on a phone, and a vertical flick that starts on it must still scroll the
   page. Only the browser can tell which gesture it was. */
.rmstrip__t[data-drift]{scroll-snap-type:none}

/* ── trio — the three families, one open ───────────────────────────
   A port of SqueezeCarousel. Nine panels, three per family: one open at 16:9,
   the two behind it stepping down, and the rest a tail of slats — which is the
   shape the reference is built around and the reason it wanted seven panels.
   Three was too few to make a tail at all.

   ONE SHARE AND ONE RATIO GENERATE EVERY NUMBER. --trio-open is the open
   panel's share of the row, and the row's aspect-ratio is derived from it so
   that panel is exactly 16:9; the rest of the width is shared among the other
   eight by the weights in site.js. The row's width is constant at every
   instant of a transition — one panel gains precisely what the others give up
   — so the copy below never reflows. */
/* THE PANEL. The block sits on its own white rectangle, the same surface and
   hairline the rate cards above it use, so it reads as part of the same set
   rather than as a loose strip on the page. Soft, and no shadow: depth on this
   site is a ring, never a drop.

   IT FILLS THE FRAME. Capped at 980 it left the right third of a 1440 screen
   empty while the cards above ran the full width — a hole, not breathing room.
   The row now takes whatever the panel gives it. */
.trio{--trio-open:44%;margin-top:34px;
  background:var(--surface);border-radius:var(--r-lg);
  box-shadow:inset 0 0 0 1px var(--hairline);
  padding:22px 20px}
@media (min-width:1024px){.trio{padding:26px 28px}}

/* BASE = the phone, and the no-JS state. A snap scroller, not a squeeze: nine
   panels inside 328px would leave the closed ones a few pixels wide. */
.trio__head{display:flex;align-items:center;justify-content:space-between;
  gap:12px;margin-bottom:12px}
.trio__nav{display:none;align-items:center;gap:8px}

.trio__row{display:grid;grid-auto-flow:column;grid-auto-columns:84%;gap:10px;
  margin:0;overflow-x:auto;overscroll-behavior-x:contain;
  scroll-snap-type:x mandatory;scrollbar-width:none}
.trio__row::-webkit-scrollbar{display:none}

.trio__p{position:relative;margin:0;padding:0;border:0;background:none;
  min-width:0;aspect-ratio:16/9;scroll-snap-align:center;cursor:pointer;
  border-radius:var(--r-md);overflow:hidden;isolation:isolate;
  -webkit-tap-highlight-color:transparent}
/* The ring, as a pseudo: an inset box-shadow on .trio__p paints in the panel's
   own background layer, which the absolutely positioned picture then covers.
   This paints above it. 4px and a hairline, never a shadow. */
.trio__p::after{content:"";position:absolute;inset:0;z-index:2;pointer-events:none;
  border-radius:inherit;box-shadow:inset 0 0 0 1px var(--hairline)}
.trio__p:focus-visible{outline:2px solid var(--teal-ink);outline-offset:3px}

.trio__pic{position:absolute;inset:0;display:block}
.trio__pic img{width:100%;height:100%;object-fit:cover;display:block;
  -webkit-user-drag:none;user-select:none}

/* The family's name on the open panel, as the reference has it — but on a
   SOLID ink chip, not the reference's gradient scrim. The art direction rules
   out gradients outright, and a scrim over the hub's yellow wall is exactly
   the mud that rule was written about. A chip also guarantees its own contrast
   instead of hoping the photograph underneath is dark enough. It shows only on
   the open panel, where there is room for it to be read. */
.trio__tag{position:absolute;left:10px;bottom:10px;z-index:3;
  padding:5px 10px;border-radius:var(--r-md);
  background:var(--ink);color:var(--paper);
  font-size:.75rem;font-weight:600;letter-spacing:-.004em;line-height:1.2;
  white-space:nowrap;opacity:0;pointer-events:none}
.trio__p[data-on] .trio__tag{opacity:1}

/* The copy, in the reference's own shape: the dark sentence and the grey one
   run together as a single paragraph, with the action on the right of the same
   row rather than under it. */
.trio__copy{margin-top:18px;display:grid;gap:26px}
.trio__say{font-size:1rem;line-height:1.55;margin:0;max-width:58ch}
.trio__lead{color:var(--text-paper)}
.trio__sub{color:var(--muted-paper)}
.trio__c .acts{margin-top:14px}

/* ── go — the split action: a pill and a disc that swaps its arrow ──
   Ported from a React/Tailwind component. Two shapes rather than one: the
   words in a pill, the arrow in a circle beside it, both changing together
   and the arrow cycling out to the top right while its twin arrives from the
   bottom left.

   THE COLOURS ARE THE SITE'S, NOT THE REFERENCE'S. Green fill with ink type
   at rest, which is what .btn--go already is, and the two swap on hover: ink
   fill with green type, 12.2:1. Both directions are inside the rule that
   green is a fill on ink or the one CTA and never a glyph on paper — on ink
   it is a glyph with more contrast than the body text has.

   ONE HOVER, TWO SHAPES. The pill and the disc are separate boxes, so the
   state has to be driven from the anchor or a reader crossing the gap between
   them would see one change and not the other. */
/* ONE HEIGHT, WRITTEN ONCE, for both shapes. Left to themselves the pill took
   its height from its line box (56px) while the disc was set to 52, and two
   round shapes four pixels apart read as a mistake. Stretching the disc
   instead does not work either: everything inside it is absolutely
   positioned, so it has no intrinsic width for aspect-ratio to work back
   from and it collapses to zero. A shared variable is the honest fix. */
.go{--go-h:44px;display:inline-flex;align-items:center;gap:4px;
  border:0;background:none;padding:0;cursor:pointer;
  font-family:var(--sans);font-size:.9375rem;font-weight:600;
  letter-spacing:-.006em;color:var(--ink);text-decoration:none}
.go__t{display:inline-flex;align-items:center;height:var(--go-h);
  padding:0 20px;border-radius:999px;
  background:var(--green);color:var(--ink)}
.go__d{position:relative;flex:none;display:grid;place-items:center;
  width:var(--go-h);height:var(--go-h);border-radius:999px;overflow:hidden;
  background:var(--green);color:var(--ink)}
.go__a{position:absolute;left:50%;top:50%;width:16px;height:16px}
/* At rest the first arrow is centred and the second waits off the bottom
   left, on the diagonal the arrow itself points along — so the pair reads as
   one arrow travelling rather than two arrows crossing. */
.go__a{transform:translate(-50%,-50%)}
.go__a--next{transform:translate(-210%,110%)}
.go:hover .go__a,.go:focus-visible .go__a,.go:active .go__a{transform:translate(110%,-210%)}
.go:hover .go__a--next,.go:focus-visible .go__a--next,.go:active .go__a--next{transform:translate(-50%,-50%)}
.go:hover .go__t,.go:focus-visible .go__t,.go:active .go__t,
.go:hover .go__d,.go:focus-visible .go__d,.go:active .go__d{
  background:var(--ink);color:var(--green)}
.go:active{transform:scale(.985)}
.go:focus-visible{outline:2px solid var(--ink);outline-offset:3px;border-radius:999px}

/* The reference runs the swap at 500ms. This site's motion rules put a
   ceiling on interactive movement well below that, and at 500 the arrow is
   still travelling after the finger has gone. 320ms keeps the gesture
   legible — you can see it leave and see the other arrive — without the
   control feeling slow to answer. The colour moves faster than the arrow, so
   the button reads as responding immediately and then finishing its sentence. */
@media (prefers-reduced-motion:no-preference){
  .go__a{transition:transform 320ms var(--ease-out-quint)}
  .go__t,.go__d{transition:background-color var(--t-hover) var(--ease-out-quint),
                           color var(--t-hover) var(--ease-out-quint)}
  .go{transition:transform var(--t-press) var(--ease-out-quint)}
}

/* ONE COPY BLOCK AT A TIME, at every width — not only on a desk. All three in
   a single grid cell, so the shelf is as tall as the tallest and nothing below
   it steps up and down as the family changes.

   On the phone this is what gives the swipe observer something to do: without
   it all three sentences sat stacked under a row the reader was swiping, and
   the open family was a state nobody could see. */
.js .trio__copy{display:grid;gap:0}
.js .trio__c{grid-area:1/1;opacity:0;visibility:hidden}
.js .trio__c[data-on]{opacity:1;visibility:visible}
@media (prefers-reduced-motion:no-preference){
  .js .trio__c{transition:opacity var(--t-ui) var(--ease-out-cubic),
                          visibility 0s linear var(--t-ui)}
  .js .trio__c[data-on]{transition:opacity var(--t-ui) var(--ease-out-cubic),
                                   visibility 0s}
}

/* The arrows. Written fresh rather than borrowing .reel__btn: this sheet has
   been bitten once by two components sharing a name. */
.trio__btn{display:grid;place-items:center;width:34px;height:34px;flex:none;
  border:0;border-radius:var(--r-md);cursor:pointer;
  background:var(--ink);color:var(--paper);
  transition:opacity var(--t-hover) var(--ease-out-quint)}
.trio__btn svg{width:8px;height:10px}
/* aria-disabled, not disabled: .disabled drops focus to <body> the moment a
   keyboard reader reaches an end, and they reach one often. */
.trio__btn[aria-disabled="true"]{opacity:.28;cursor:default}
@media (hover:hover) and (pointer:fine){
  .trio__btn[aria-disabled="false"]:hover{opacity:.85}
}
.trio__btn:focus-visible{outline:2px solid var(--ink);outline-offset:2px}

@media (min-width:760px){
  /* No JS: a plain row of nine, still scrollable. */
  /* No cap: the panel's padding is the only inset. */

  /* JS: the squeeze. */
  .js .trio__nav{display:flex}
  /* The ratio is DERIVED from --trio-open, not chosen: the open panel is 44%
     of the row and must be 16:9, so the row is 1 / (0.44 * 9/16) = 4.04 times
     its own height. Change one without the other and the open panel silently
     stops being 16:9. */
  /* The gap is PIXELS, not a percentage, and that is load-bearing. site.js
     measures it with getComputedStyle to subtract it before sharing out the
     width — and a percentage gap comes back as the string "0.7%", which
     parseFloat reads as 0.7px. The row then overflowed by about 64px and the
     last slat was clipped off the end of the panel. */
  .js .trio__row{display:flex;align-items:stretch;gap:8px;aspect-ratio:4.04;
    overflow:visible;scroll-snap-type:none}
  /* flex-basis is written per panel by site.js, from the weights there. */
  /* shrink 0: the bases are computed in px against the row's real width,
     gaps already subtracted, so there is nothing left to shrink and any
     shrinking that did happen would come straight out of the 16:9. */
  .js .trio__p{flex:0 0 auto;aspect-ratio:auto;scroll-snap-align:none;min-width:0}
  .js .trio__p[data-on]{cursor:default}

  /* THE FIXED BLOCK — the one idea worth keeping from the React source. The
     picture is drawn once, at the OPEN panel's size, and centred; a panel only
     changes how much of it you can see.

     max-width:none IS NOT OPTIONAL. The global img rule is max-width:100%,
     which silently clamps the block back to the panel's own width and restores
     exactly the broken behaviour — the CSS would SAY 16:9 and the browser
     would not do it, with no error anywhere. Left to object-fit the picture
     re-crops and magnifies as the panel narrows, resampling an 880px source
     every frame, nine times over, on a mid-range Android. */
  .js .trio__pic img{position:absolute;top:0;left:50%;transform:translateX(-50%);
    height:100%;width:auto;max-width:none;
    aspect-ratio:16/9;object-fit:cover;object-position:50% 50%}

  /* Side by side only where there is room: the sentence on the left, the
     action on the right of the same row, as the reference has it. */
  .js .trio__c{display:flex;align-items:flex-start;justify-content:space-between;gap:40px}
  .js .trio__c .acts{margin-top:0;flex:none}
}

/* Motion last, and only the properties that move. Under reduce the layout is
   unchanged and the transition goes: the static end state IS the base rule,
   and with no autoplay nothing moves unless the reader clicks. */
@media (min-width:760px) and (prefers-reduced-motion:no-preference){
  .js .trio__p{transition:flex-basis var(--t-ui) var(--ease-out-quint)}
  .js .trio__tag{transition:opacity var(--t-ui) var(--ease-out-quint)}
}

/* The honeypot. Off-screen rather than display:none or hidden: a bot that
   reads the computed style skips anything invisible, and a field that is
   merely somewhere else is one it will still fill in. aria-hidden and
   tabindex=-1 keep it away from a screen reader and out of the tab order,
   so the only thing that ever types here is a machine. */
.trap{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}

/* The send failed and the reader has to be told. Ink on a pale ground rather
   than red-on-red: nothing is lost when this appears — their answers are still
   in the form behind it — so it is a notice, not an alarm. */
.apply__fail{margin:0 0 16px;padding:12px 14px;border-radius:var(--r-md);
  background:var(--paper-2);box-shadow:inset 0 0 0 1px rgba(12,21,43,.16);
  font-size:.875rem;line-height:1.45;color:var(--text-paper)}
.fld{margin-bottom:20px}
/* The last field already reserves a line for an error it is not showing, and
   the nav adds its own margin on top of that. Sixty-six pixels of nothing sat
   between the final control and the rule. */
.fld:last-child{margin-bottom:0}
.fld__l{display:block;font-size:.9375rem;font-weight:600;letter-spacing:-.008em;
  color:var(--text-paper);margin-bottom:7px}
/* Metadata about the question, not part of it, so it is set as a micro-label
   rather than trailing prose in the same sentence as the label. */
.fld__opt{display:inline-block;margin-left:6px;vertical-align:1px;
  font-weight:600;font-size:.625rem;letter-spacing:.07em;text-transform:uppercase;
  color:var(--micro-paper)}
.fld__h{font-size:.8125rem;line-height:1.45;color:var(--muted-paper);margin:-2px 0 8px}
/* --rule-paper is rgba(12,21,43,.10). Over the field fill that lands on about
   #DDE0E4, which measures 1.32:1 against the white card: the boxes had no edge
   worth the name, and that is what made every form read as unfinished rather
   than calm. --field-line is 3.45:1 on the card and 3.19:1 on the fill, so the
   control carries 1.4.11 on its boundary — the fill cannot carry it, being
   1.08:1 against the card it sits on. */
:root{--field-line:rgba(12,21,43,.5)}
.fld__i{display:block;width:100%;min-height:50px;padding:13px 16px;font:inherit;
  font-size:1rem;color:var(--text-paper);background:var(--paper);
  border:1px solid var(--field-line);border-radius:var(--r-sm,8px);
  transition:border-color var(--t-ui) var(--ease-out-quint),
             box-shadow var(--t-ui) var(--ease-out-quint),
             background-color var(--t-ui) var(--ease-out-quint)}
@media (hover:hover){
  .fld__i:hover:not(:focus){background:#fff;border-color:var(--muted-paper)}
}
textarea.fld__i{min-height:112px;resize:vertical;line-height:1.5}
/* The placeholders now carry real guidance ("The name you want on the sign"),
   so they have to be readable rather than merely present. #8D99AD on --paper
   measured 2.45:1; --micro-paper is 5.5:1, the same colour as the hint under
   the label, which is the other place guidance appears. Still far lighter than
   --text-paper, so a placeholder is not mistaken for an entered value. */
.fld__i::placeholder{color:var(--micro-paper);opacity:1}
.fld__i:focus-visible{outline:none;border-color:var(--teal-ink);background:#fff;
  box-shadow:0 0 0 3px rgba(3,122,99,.14)}
/* The arrow was two overlapping gradients pinned 22px from the top, so it only
   looked centred at exactly one field height. This is the same chevron the
   buttons use, rotated, centred by the shorthand, and it keeps its shape when
   the select grows. */
.fld__sel{appearance:none;-webkit-appearance:none;cursor:pointer;padding-right:44px;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6' fill='none' stroke='%234D5B75' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1 1l4 4 4-4'/%3E%3C/svg%3E");
  background-position:right 16px center;background-size:10px 6px;background-repeat:no-repeat}
/* Chrome renders a select's placeholder option at the text colour, so the
   "Choose one" line looked like an answer. It reads as a placeholder now. */
.fld__sel:has(option[value=""]:checked){color:var(--micro-paper)}
/* Invalid only once the reader has left the field. Marking a required input red
   while it is still empty and still focused is scolding someone for not having
   finished typing. */
.fld[data-bad] .fld__i{border-color:#B3261E;background:#FEF7F6}
/* A choice group has no box of its own, so without this the only sign of the
   problem was a line of text sixty pixels below the control. The border alone,
   not the pink fill: three answers tinted red reads as three wrong answers
   rather than one unanswered question. */
.fld[data-bad] .opt{border-color:#B3261E}
.fld[data-bad] .fld__i:focus-visible{box-shadow:0 0 0 3px rgba(179,38,30,.14)}
/* Two lines of grid, always. The message crossfades into a box that is already
   the right height, so nothing below it moves when an error appears. */
.fld__e{display:block;min-height:1.25rem;margin:6px 0 0;font-size:.8125rem;line-height:1.25rem;
  color:#B3261E;opacity:0;transition:opacity var(--t-ui) var(--ease-out-quint)}
.fld[data-bad] .fld__e{opacity:1}

/* choices */
.opts{display:grid;gap:8px}
/* Three short answers stacked into three full-width bars is a lot of furniture
   for "Yes" and "No". The generator sets this where every choice is short
   enough to sit on one line, and only there: one long choice in a row of three
   wraps to three lines and looks far worse than the stack it replaced. */
.opts--row{grid-template-columns:repeat(auto-fit,minmax(8.5rem,1fr))}
/* Two or three one-word answers stretched across the full measure leave most
   of each box empty. These size to a comfortable minimum and sit against the
   left rule with the labels, rather than spanning a width they do not need. */
.opts--tight{grid-template-columns:repeat(auto-fit,minmax(7.5rem,9.5rem));justify-content:start}
/* Two columns on a narrow card leaves a third answer alone in a half-width
   box. One per row instead: tidier, and a bigger target on the device where
   the target matters most. */
@media (max-width:560px){.opts--row{grid-template-columns:1fr}}
.opt{display:flex;align-items:center;gap:11px;padding:13px 14px;cursor:pointer;
  border:1px solid var(--field-line);border-radius:var(--r-sm,8px);background:var(--paper);
  font-size:.9375rem;min-height:50px;
  transition:border-color var(--t-ui) var(--ease-out-quint),background-color var(--t-ui) var(--ease-out-quint)}
.opt__c{position:absolute;opacity:0;width:1px;height:1px}
.opt__b{position:relative;flex:none;width:20px;height:20px;
  border:1.5px solid var(--field-line);background:#fff;
  transition:border-color var(--t-ui) var(--ease-out-quint),background-color var(--t-ui) var(--ease-out-quint)}
.opt:has(.opt__c[type=radio]) .opt__b{border-radius:50%}
.opt:has(.opt__c[type=checkbox]) .opt__b{border-radius:5px}
.opt__b::after{content:"";position:absolute;inset:3px;border-radius:inherit;
  background:var(--ink);transform:scale(0);
  transition:transform var(--t-ui) var(--ease-out-quint)}
.opt__c:checked+.opt__b{border-color:var(--ink);background:var(--green)}
.opt__c:checked+.opt__b::after{transform:scale(1)}
.opt:has(.opt__c:checked){border-color:var(--ink);background:var(--paper-2)}
.opt__c:focus-visible+.opt__b{outline:2px solid var(--teal-ink);outline-offset:3px}
@media (hover:hover){.opt:hover{border-color:var(--muted-paper)}}

/* nav */
/* The forward action takes the rest of the row, so at step one it is a single
   full-width button and later it is the wide half beside Back. The counter is
   forced onto its own line rather than squeezed to the right of a button that
   is now wide enough to reach the edge. */
.apply__nav{display:flex;flex-wrap:wrap;align-items:center;gap:12px;
  margin-top:18px;padding-top:22px;border-top:1px solid var(--hairline)}
.apply__nav .btn--go{flex:1 1 12rem;justify-content:center}
.apply__count{flex:1 0 100%;margin:2px 0 0;text-align:center;
  font-size:.8125rem;color:var(--micro-paper);font-variant-numeric:tabular-nums}
.apply[data-busy] .apply__nav .btn{pointer-events:none;opacity:.6}

/* done */
.done{background:var(--surface,#fff);border-radius:var(--r-md,14px);
  box-shadow:inset 0 0 0 1px rgba(12,21,43,.12),0 10px 28px -14px rgba(12,21,43,.14);
  padding:44px 24px;margin-top:26px;text-align:center}
@media (min-width:768px){.done{padding:64px 40px}}
.done__t{display:grid;place-items:center;width:52px;height:52px;margin:0 auto 20px;
  border-radius:50%;background:var(--green)}
.done__t::after{content:"\2713";font-size:1.5rem;font-weight:700;color:var(--ink)}
.done .acts{justify-content:center}

/* the price aside on Virtual Nest */
/* Under the card, in the same column and the same width. Three prices read
   better as a row of three than as a stacked list once they lose the side
   column, and the row keeps the block short so it does not compete with the
   form above it. */
.apply__side{background:var(--paper-2);border-radius:var(--r-md,14px);padding:20px 22px;margin-top:0}
.side__list{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px;margin:14px 0 0}
.side__row{padding:0}
.side__row+.side__row{border-left:1px solid var(--rule-paper);padding-left:12px}
@media (max-width:479px){
  .side__list{grid-template-columns:1fr}
  .side__row+.side__row{border-left:0;border-top:1px solid var(--rule-paper);
    padding-left:0;padding-top:12px}
}
.side__note{margin:16px 0 0;font-size:.8125rem;line-height:1.5;color:var(--muted-paper)}
.side__row dt{font-size:.75rem;font-weight:600;letter-spacing:.06em;text-transform:uppercase;
  color:var(--micro-paper)}
.side__row dd{margin:4px 0 0;font-size:1.375rem;font-weight:510;letter-spacing:-.022em;
  font-variant-numeric:tabular-nums;color:var(--text-paper)}
.side__row p{margin:5px 0 0;font-size:.8125rem;line-height:1.45;color:var(--muted-paper)}
@media (prefers-reduced-motion:reduce){
  .apply__back::before,.rail__s,.rail__n,.fld__i,.fld__e,.opt,.opt__b,.opt__b::after{transition:none}
}
/* The two label spans in the rail and the option rows. They inherit everything
   they need; these rules exist so the class-coverage audit has no blind spot
   and so the wrap behaviour is stated rather than accidental. */
.rail__t{white-space:nowrap}
.opt__t{flex:1 1 auto;min-width:0}

/* ── The Nest: stories that carry their own photograph ─────────────
   The team asked for each story to combine an authentic photograph with what
   happened and what came of it, so the ruled entry grows a picture column. It
   is a modifier rather than a change to .impact, because .impact is a plain
   ruled list elsewhere and those uses have no image to give it. */
.impact--told .impact__i{display:grid;grid-template-columns:1fr;gap:14px;align-items:start}
.impact__img{width:100%;height:auto;aspect-ratio:1;object-fit:cover;display:block;
  border-radius:var(--r-sm,8px);background:var(--paper-2)}
@media (min-width:720px){
  .impact--told .impact__i{grid-template-columns:168px minmax(0,1fr);gap:24px;
    padding-block:24px 26px}
  .impact--told .impact__n{margin-top:2px}
}
/* A photograph on the page rather than in a placeholder. Same rounded corner and
   hairline as every other framed image here. */
.shot{margin:26px 0 0}
.shot img{width:100%;height:auto;display:block;border-radius:var(--r-md,14px);
  aspect-ratio:16/9;object-fit:cover;box-shadow:inset 0 0 0 1px var(--hairline)}
.shot figcaption{margin-top:10px;font-size:.8125rem;line-height:1.45;color:var(--micro-paper)}

/* ── the visit page gallery ────────────────────────────────────────
   Three photographs of the actual rooms, so "come and see the place" shows the
   place. Square crops because every hub photograph in this library is 448x448;
   forcing a landscape ratio here would centre-crop faces out of them. */
.gal{display:grid;grid-template-columns:1fr;gap:20px;margin-top:26px}
@media (min-width:640px){.gal{grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--gutter)}}
.gal__i{margin:0}
.gal__i img{width:100%;height:auto;display:block;aspect-ratio:1;object-fit:cover;
  border-radius:var(--r-md,14px);box-shadow:inset 0 0 0 1px var(--hairline)}
.gal__i figcaption{margin-top:10px;font-size:.8125rem;line-height:1.45;color:var(--micro-paper)}

/* .impact goes two-up at 768 and that is right for a plain ruled list, but a
   story carrying a photograph needs the full measure: at half width the text
   column left after a 168px image is too narrow to read. The told variant stays
   one per row, and the rule offsets the two-column layout introduced are undone
   with it. */
@media (min-width:768px){
  .impact--told{grid-template-columns:1fr}
  .impact--told .impact__i:nth-child(-n+2)::before{content:""}
  .impact--told .impact__i:first-child::before{content:none}
  .impact--told .impact__i:nth-child(odd)::before{right:0}
  .impact--told .impact__i{grid-template-columns:200px minmax(0,1fr)}
}
/* The item has THREE children — image, heading, description — in a two-column
   grid, so auto-placement puts the image at 1/1, the heading at 2/1 and the
   description back at 1/2, underneath the photograph and half the width it
   needs. The image spans both rows and the two text rows stack beside it. */
.impact--told .impact__img{grid-row:1 / span 2}
.impact--told .impact__d{max-width:70ch}
/* The image spans two rows, so those rows split its height and the description
   drifted to the vertical middle with a gap under the heading. Row one takes
   its content, row two takes the remainder, and the description sits at the top
   of it — text hangs from the same line as the heading regardless of how tall
   the photograph is. */
@media (min-width:768px){
  .impact--told .impact__i{grid-template-rows:auto 1fr}
  .impact--told .impact__d{align-self:start;margin-top:8px}
}

/* ── Events ─────────────────────────────────────────────────────────────────
   Two pages: the list (events.html) and one event (event.html).

   Where this deliberately departs from Luma, which was the reference:

   1. THE DATE LEADS, NOT THE ARTWORK. Luma's event page opens with a square
      flyer that on a phone fills the screen and pushes the date, the place and
      the button below the fold. A visitor arriving from a WhatsApp link is
      asking "when, where, can I come" and has to scroll past a poster to find
      out. Here the banner is a wide strip — 3:1 on a desktop, 2:1 on a phone —
      so the facts are visible the moment the page opens. The artwork still
      sets the tone; it just stops being the content.

   2. THE MODE IS VISIBLE BEFORE YOU CLICK. Luma tells you an event is online
      only once you are on it. Every card here carries In person / Online /
      Both, because "can I come" is answered differently depending on which,
      and for an online event the answer is always yes.

   3. NOTHING IS BEHIND REGISTRATION. Luma hides the address until you sign up,
      which means deciding whether to register requires registering. The address
      is on the poster anyway.
*/

.ev-head{padding-block:calc(30px + var(--hdr)) 0}
/* The eyebrow over the list heading, in the same voice as the labels below it.
   Scoped to this page rather than changed on .label, which is the footer's
   label on all twelve pages and is not this page's to restyle. */
.ev-head .label{font-size:.8125rem;font-weight:700;letter-spacing:-.012em;
  text-transform:none}
@media (min-width:1024px){.ev-head{padding-block:calc(38px + var(--hdr)) 0}}

/* The Upcoming / Past switch. A segmented control, not tabs with a bottom rule:
   it reads as one control with two states rather than two things one of which
   happens to be underlined. */
.ev-switch{display:inline-flex;gap:2px;padding:3px;margin-top:22px;
  background:var(--paper-2);border-radius:999px}
.ev-switch__b{appearance:none;border:0;cursor:pointer;border-radius:999px;
  padding:9px 18px;min-height:40px;font:inherit;font-size:.875rem;font-weight:600;
  color:var(--muted-paper);background:transparent;
  transition:background-color var(--t-ui) var(--ease-out-quint),color var(--t-ui) var(--ease-out-quint)}
.ev-switch__b[aria-selected="true"]{background:var(--surface,#fff);color:var(--text-paper);
  box-shadow:0 1px 2px rgba(12,21,43,.08)}
.ev-switch__n{margin-left:7px;font-variant-numeric:tabular-nums;opacity:.55}
.ev-switch__b:focus-visible{outline:2px solid var(--teal-ink);outline-offset:2px}

/* A month heading, so a long list has somewhere for the eye to rest. */
/* Section labels on the events pages are set in sentence case, bold, with the
   tracking pulled in rather than opened out. Small caps with wide letterspacing
   is the default look for a label and it shouts; a tighter, heavier lowercase
   label reads as a heading at the same size without raising its voice. */
.ev-month{margin:34px 0 14px;font-size:.8125rem;font-weight:700;letter-spacing:-.012em;
  color:var(--micro-paper)}
.ev-month:first-of-type{margin-top:26px}

.ev-list{display:grid;gap:12px;margin:0;padding:0;list-style:none}

/* The card. One row: date chip, the words, the picture.
   Grid rather than flex so the picture can be absent without the text moving. */
.ev-card{position:relative;display:grid;grid-template-columns:auto minmax(0,1fr);
  gap:16px;align-items:start;padding:16px;background:var(--surface,#fff);
  border-radius:var(--r-md,14px);
  box-shadow:inset 0 0 0 1px rgba(12,21,43,.12),0 1px 2px rgba(12,21,43,.04);
  transition:box-shadow var(--t-ui) var(--ease-out-quint),transform var(--t-ui) var(--ease-out-quint)}
@media (min-width:760px){
  .ev-card{grid-template-columns:auto minmax(0,1fr) 104px;gap:20px;padding:18px}
}
@media (hover:hover){
  .ev-card:hover{box-shadow:inset 0 0 0 1px rgba(12,21,43,.2),0 8px 22px -14px rgba(12,21,43,.3);
    transform:translateY(-1px)}
}
/* The whole card is the target. A stretched pseudo-element rather than wrapping
   everything in an <a>, so the map link inside stays independently clickable. */
.ev-card__a::after{content:"";position:absolute;inset:0;border-radius:inherit}
.ev-card__a{text-decoration:none;color:inherit}
.ev-card__a:focus-visible::after{outline:2px solid var(--teal-ink);outline-offset:2px}

.ev-chip{display:grid;place-items:center;align-content:center;width:56px;height:56px;
  border-radius:10px;text-align:center;flex:none}
.ev-chip__m{font-size:.6875rem;font-weight:700;letter-spacing:-.012em;
  color:var(--muted-paper);line-height:1}
.ev-chip__d{font-size:1.375rem;font-weight:600;letter-spacing:-.02em;line-height:1.1;
  font-variant-numeric:tabular-nums;color:var(--text-paper)}

.ev-card__t{margin:0;font-size:1.0625rem;font-weight:600;letter-spacing:-.015em;
  line-height:1.3;color:var(--text-paper);text-wrap:balance}
.ev-card__s{margin:5px 0 0;font-size:.875rem;line-height:1.5;color:var(--muted-paper)}
.ev-card__meta{display:flex;flex-wrap:wrap;align-items:center;gap:5px 14px;margin-top:10px;
  font-size:.8125rem;color:var(--micro-paper)}
.ev-card__meta span{display:inline-flex;align-items:center;gap:6px}
.ev-card__meta svg{width:14px;height:14px;flex:none;fill:none;stroke:currentColor;
  stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round}

.ev-card__img{display:none}
@media (min-width:760px){
  /* The post itself, square, at the size a thumbnail wants to be. Cropping it
     was tried both ways and both were wrong: centred or left lands mid-headline,
     right lands on whatever is in the last quarter of the photo — a wall, in two
     of the six. The whole graphic says which event this is; a crop of it says
     nothing. */
  .ev-card__img{display:block;width:100%;aspect-ratio:1/1;object-fit:contain;
    align-self:center;border-radius:8px;background:var(--paper-2)}
}

/* Badges. Mode first, because it changes whether a reader can come at all. */
.ev-badge{display:inline-flex;align-items:center;gap:5px;padding:3px 9px;border-radius:999px;
  font-size:.6875rem;font-weight:600;letter-spacing:.01em;line-height:1.5}
.ev-badge--person{background:rgba(12,21,43,.07);color:var(--text-paper)}
.ev-badge--online{background:rgba(5,193,157,.14);color:var(--teal-ink)}
.ev-badge--both{background:rgba(112,239,151,.24);color:var(--green-ink)}
.ev-badge--live{background:var(--green);color:var(--ink)}
.ev-badge--cancelled{background:rgba(179,38,30,.1);color:#B3261E}
.ev-badge--pin{background:rgba(255,193,7,.18);color:#7a5800}

.ev-empty{padding:44px 20px;text-align:center;border:1px dashed var(--rule-paper);
  border-radius:var(--r-md,14px);margin-top:26px}
.ev-empty p{margin:0;color:var(--muted-paper);font-size:.9375rem}
.ev-empty p+p{margin-top:6px;font-size:.8125rem;color:var(--micro-paper)}

/* ── One event ──────────────────────────────────────────────────────────────
   Luma's anatomy, which is right: a narrow left rail that carries the poster
   and who is putting it on, and a wider right column that carries the title,
   the facts, the thing you press, and the prose. On a phone it stacks in that
   order, which is also the order somebody asks the questions in.

   Two departures, both because Luma is solving a marketplace problem we do not
   have. Their address is hidden until you register, so deciding whether to
   register requires registering; ours is on the poster anyway, so it is on the
   page. And their registration is a button to their own product, where ours is
   the form the rest of this site already uses. */

/* The artwork heads the rail at the shape it was made: 1:1, because the same
   file is the event's Instagram post. One graphic goes up on Instagram and the
   same graphic appears here, at the same proportions, uncut.

   contain rather than cover, still. A square slot and square art agree exactly,
   so nothing is cropped; and on the day somebody uploads a wide banner instead,
   it letterboxes on the paper rather than losing half its headline — which is
   what every crop this went through actually did. */
.ev-art{display:block;width:100%;aspect-ratio:1/1;object-fit:contain;
  border-radius:var(--r-md,14px);background:var(--paper-2)}
/* An event with no artwork gets a coloured block of the same shape. Declared
   BEFORE the phone rule, not after it: the two have equal specificity, so
   sitting below meant it won at every width and put a full square back on the
   phone screen for exactly the events with nothing to show in it. */
.ev-art--none{aspect-ratio:1/1}
/* The slot takes the shape of the file showing in it, and nothing is ever
   cropped. Phones used to cover-crop the square into 16:9, which cut words off
   every graphic; now an event carries a 16:9 file made for phones (picked by
   <picture> under 980px), and an event without one shows its square whole. */
.ev-artbox{display:block;min-width:0}
.ev-art--sq{aspect-ratio:1/1}
.ev-art--wide{aspect-ratio:16/9}
@media (max-width:979px){
  .ev-artbox--pair .ev-art{aspect-ratio:16/9}
  /* No artwork at all: a coloured band rather than a full square of nothing. */
  .ev-art--none{aspect-ratio:16/9}
}

/* Below 980px the rail is not a column, so it stops being a box: display:
   contents drops its children into the page grid, where order can put the
   artwork above the title and the hosts below the button. A phone reads it as
   one column in the order somebody asks — what is it, when, can I come, who is
   running it, and then the detail. */
.ev-wrap{display:grid;gap:26px;align-items:start;padding-block:22px 78px}
.ev-rail{display:contents}
.ev-art,.ev-artbox{order:1}
.ev-lede{order:2;min-width:0}
.ev-hosts{order:3}
.ev-main{order:4;min-width:0}
@media (min-width:980px){
  .ev-wrap{grid-template-columns:352px minmax(0,1fr);gap:22px 48px;
    grid-template-areas:"rail head" "rail main"}
  .ev-rail{display:grid;gap:22px;align-content:start;grid-area:rail}
  .ev-lede{grid-area:head}
  .ev-main{grid-area:main}
  .ev-art,.ev-artbox,.ev-hosts{order:0}
}

/* ── The left rail ── */

/* Left over from when the rail was a box at every width. Re-declaring display
   here beat the display:contents above by source order, which is why the hosts
   sat above the title on a phone. */
@media (min-width:980px){
  /* Sticky, because the right column is long and the artwork and the host are
     what a reader looks back at while reading it. */
  .ev-rail{position:sticky;top:calc(var(--hdr) + 18px);min-width:0}
}


.ev-present .ev-org__logo{width:68px;height:46px;border-radius:10px}
.ev-present{display:flex;align-items:center;gap:12px;padding:14px;
  background:var(--surface,#fff);border-radius:var(--r-md,14px);
  box-shadow:inset 0 0 0 1px rgba(12,21,43,.12)}
.ev-present__n{margin:0;font-size:.9375rem;font-weight:600;color:var(--text-paper);
  line-height:1.25}

.ev-orgs{margin:0;padding:0;list-style:none}
.ev-hosts{display:grid;gap:0;min-width:0}
.ev-orgs__h{margin:0 0 10px;font-size:.8125rem;font-weight:700;letter-spacing:-.012em;
  color:var(--micro-paper)}
.ev-orgs__h--with{margin-top:20px}
/* How many said yes. A count, not a row of faces: the people registering gave
   us a name and an email to come to an evening, not to be published on it. */
.ev-going{display:flex;align-items:center;gap:8px;margin:18px 0 0;padding-top:16px;
  border-top:1px solid var(--rule-paper);
  font-size:.8125rem;font-weight:600;color:var(--muted-paper)}
.ev-going svg{width:16px;height:16px;flex:none;fill:none;stroke:currentColor;
  stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}
/* Logo on the left, everything about the organisation stacked beside it:
   name, what they do, then how to reach them. The links used to sit out to the
   right on their own, which read as page furniture rather than as part of the
   partner, and left a two-line blurb wrapping early to make room for them. */
.ev-org{display:grid;grid-template-columns:auto minmax(0,1fr);gap:4px 12px;
  align-items:start;padding:13px 0;border-top:1px solid var(--rule-paper)}
.ev-org:first-child{border-top:0;padding-top:0}
.ev-org__logo{grid-row:span 2}
.ev-org__body{min-width:0}
/* A tile, not an avatar circle. These are company lockups — the marks here run
   from 1.6:1 to 7:1 — and a circle crops the wide ones to an illegible smudge.
   A landscape tile with contain gives every one of them the full width it was
   drawn at. */
.ev-org__logo{flex:none;width:56px;height:38px;border-radius:9px;object-fit:contain;
  background:var(--surface,#fff);padding:4px;
  box-shadow:inset 0 0 0 1px rgba(12,21,43,.1)}
/* A partner with no logo file gets their initial rather than an empty tile.
   An empty tile reads as a failed image; a monogram reads as a decision. */
span.ev-org__logo{display:grid;place-items:center;font-size:.9375rem;font-weight:600;
  color:var(--muted-paper);letter-spacing:-.01em;background:var(--paper-2);padding:0}
.ev-org__n{margin:0;font-size:.9375rem;font-weight:600;color:var(--text-paper);
  line-height:1.25}
.ev-org__b{margin:2px 0 0;font-size:.75rem;line-height:1.4;color:var(--micro-paper)}
.ev-org__tag{display:inline-block;margin-left:7px;padding:1px 7px;border-radius:999px;
  background:var(--paper-2);font-size:.6875rem;font-weight:700;letter-spacing:-.008em;
  color:var(--micro-paper);vertical-align:1px}
/* The social row. Icon-only, so a host with four links does not become four
   lines of text in a 352px rail. Pulled left by the button's own padding so
   the first glyph lines up with the name above it rather than sitting a few
   pixels in from it. */
.ev-org__s{display:flex;align-items:center;gap:2px;margin:2px 0 0 -7px}
.ev-present .ev-org__s{margin:0 0 0 auto}
.ev-org__s a{display:grid;place-items:center;width:30px;height:30px;border-radius:8px;
  color:var(--micro-paper);
  transition:background-color var(--t-ui) var(--ease-out-quint),color var(--t-ui) var(--ease-out-quint)}
/* Two kinds of mark in one row. A brand icon is a filled shape drawn by its
   owner; a globe or an envelope is a stroked UI icon. They cannot share a
   rule, and a stroked path under fill:currentColor is the blob this replaced. */
.ev-org__s .i-brand{width:15px;height:15px;fill:currentColor;stroke:none}
.ev-org__s .i-ui{width:17px;height:17px;fill:none;stroke:currentColor;stroke-width:1.9}
@media (hover:hover){.ev-org__s a:hover{background:var(--paper-2);color:var(--text-paper)}}
.ev-org__s a:focus-visible{outline:2px solid var(--teal-ink);outline-offset:2px}

/* ── The right column ── */

.ev-main{min-width:0}
.ev-title{margin:0;font-size:clamp(30px,4.6vw,46px);font-weight:600;
  letter-spacing:-.028em;line-height:1.06;text-wrap:balance}
.ev-sum{margin:14px 0 0;font-size:1.0625rem;line-height:1.55;color:var(--muted-paper);
  max-width:56ch}

/* The two facts that decide whether somebody comes. Not a bordered table: at
   this size a rule around them is furniture, and the icons already group them. */
.ev-when{display:grid;gap:14px;margin:24px 0 0}
.ev-when__row{display:flex;gap:14px;align-items:flex-start}
.ev-when__i{flex:none;display:grid;place-items:center;width:46px;height:46px;
  border-radius:10px;background:var(--paper-2)}
.ev-when__i svg{width:19px;height:19px;fill:none;stroke:var(--text-paper);
  stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
.ev-when__i--date{display:grid;place-items:center;align-content:center;gap:0}
.ev-when__m{font-size:.625rem;font-weight:700;letter-spacing:-.012em;
  color:var(--muted-paper);line-height:1}
.ev-when__d{font-size:1.125rem;font-weight:600;line-height:1.15;
  font-variant-numeric:tabular-nums;color:var(--text-paper)}
.ev-when__k{margin:0;font-size:.9375rem;font-weight:600;color:var(--text-paper);line-height:1.3}
.ev-when__out{display:inline-flex;align-items:center;gap:5px;color:inherit;
  text-decoration:none;
  transition:color var(--t-ui) var(--ease-out-quint)}
.ev-when__i2{width:13px;height:13px;flex:none;color:var(--micro-paper)}
@media (hover:hover){.ev-when__out:hover{color:var(--teal-ink)}}
.ev-when__out:focus-visible{outline:2px solid var(--teal-ink);outline-offset:3px;border-radius:4px}
.ev-when__v{margin:2px 0 0;font-size:.875rem;line-height:1.45;color:var(--muted-paper)}
.ev-when__v a{color:var(--teal-ink)}

.ev-body{margin-top:0;padding-top:0;border-top:0}
.ev-body h2{margin:0 0 14px;font-size:.8125rem;font-weight:700;letter-spacing:-.012em;
  color:var(--micro-paper)}
.ev-body p{margin:0 0 15px;font-size:1.0625rem;line-height:1.62;color:var(--text-paper);
  max-width:62ch;text-wrap:pretty}
.ev-body p:last-child{margin-bottom:0}

/* ── The registration card ── */

/* Collapsed, this card is one line and one button, so it is sized for that:
   a slim header strip, tight padding, a hairline instead of a lifted shadow.
   It was twice this tall for the same two things, which made the smallest
   decision on the page look like the biggest. */
.ev-reg{margin-top:24px;background:var(--surface,#fff);border-radius:var(--r-md,14px);
  overflow:hidden;box-shadow:inset 0 0 0 1px rgba(12,21,43,.11),
  0 1px 2px rgba(12,21,43,.04)}
.ev-reg__h{padding:8px 16px;background:var(--paper-2);
  border-bottom:1px solid var(--rule-paper);
  font-size:.75rem;font-weight:700;letter-spacing:-.012em;
  color:var(--micro-paper)}
.ev-reg__b{padding:16px}
.ev-reg__lead{margin:0 0 12px;font-size:.875rem;line-height:1.5;color:var(--muted-paper)}
.ev-reg__lead strong{color:var(--text-paper);font-weight:600}
/* The form reuses .fld/.fld__l/.fld__i wholesale — the same controls as the
   application forms, so a visitor meets one input style across the site and
   there is one place to fix a focus ring. */
.ev-reg .fld:last-of-type{margin-bottom:0}
/* Not the hero pill. Inside a card this size a 56px stadium button is the
   loudest thing on the page, so it takes the card's own radius and a height
   that still clears the 44px touch target. */
.ev-reg .btn--go{width:100%;justify-content:center;margin-top:14px;
  min-height:44px;padding:11px 18px;border-radius:10px;font-size:.9375rem}
/* The collapsed state is one line and this button; nothing above it to clear,
   so it does not want the margin the submit button below the fields wants. */
.ev-reg .ev-reg__open{margin-top:0}
/* The Optional marker, in this card's voice. Scoped rather than changed on
   .fld__opt, which is the same marker on the application forms — those are a
   separate decision, not a side effect of this one. */
.ev-reg .fld__opt{text-transform:none;font-weight:700;letter-spacing:-.008em;
  font-size:.75rem}
.ev-reg__note{margin:12px 0 0;font-size:.75rem;line-height:1.5;color:var(--micro-paper)}

/* A state that is not a form: full, closed, finished. */
.ev-reg__state{display:flex;gap:13px;align-items:flex-start;padding:4px 0}
.ev-reg__state svg{flex:none;width:20px;height:20px;margin-top:1px;fill:none;
  stroke:var(--muted-paper);stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
.ev-reg__state p{margin:0;font-size:.9375rem;line-height:1.5;color:var(--text-paper)}
.ev-reg__state span{display:block;margin-top:2px;font-size:.8125rem;color:var(--micro-paper)}

.ev-spots{display:inline-flex;align-items:center;gap:6px;margin-left:auto;
  font-size:.75rem;font-weight:600;color:var(--green-ink)}
.ev-reg__hrow{display:flex;align-items:center;gap:10px}

/* The done state, after a successful registration. */
.ev-done{text-align:center;padding:10px 0 4px}
.ev-done__t{display:grid;place-items:center;width:46px;height:46px;margin:0 auto 14px;
  border-radius:50%;background:var(--green)}
.ev-done__t::after{content:"\2713";font-size:1.375rem;font-weight:700;color:var(--ink)}
.ev-done h3{margin:0;font-size:1.0625rem;font-weight:600;color:var(--text-paper)}
.ev-done p{margin:8px 0 0;font-size:.875rem;line-height:1.55;color:var(--muted-paper)}

/* ── Location ──────────────────────────────────────────────────────────
   Last thing on the page, because it is the last thing a reader needs: they
   have decided by now and are working out how to get there. The address is
   text above the map rather than only a pin, so it can be copied, read aloud
   to a driver, or seen at all when the iframe is blocked. */
.ev-loc{margin-top:34px;padding-top:26px;border-top:1px solid var(--rule-paper)}
.ev-loc__h{margin:0 0 14px;font-size:.8125rem;font-weight:700;letter-spacing:-.012em;
  color:var(--micro-paper)}
.ev-loc__n{margin:0;font-size:1.0625rem;font-weight:600;color:var(--text-paper);
  line-height:1.35}
.ev-loc__a{margin:4px 0 0;font-size:.9375rem;line-height:1.5;color:var(--muted-paper)}
/* No pin, no map: the link carries the section on its own. */
.ev-loc__link{margin:14px 0 0}
.ev-loc__link a{display:inline-flex;align-items:center;gap:6px;
  font-size:.9375rem;font-weight:600;color:var(--teal-ink)}

.ev-map{position:relative;margin-top:16px;border-radius:var(--r-md,14px);
  overflow:hidden;background:var(--paper-2);
  box-shadow:inset 0 0 0 1px rgba(12,21,43,.12)}
.ev-map iframe{display:block;width:100%;height:320px;border:0;
  /* Untinted. A recoloured map is a map people stop trusting, and OSM's own
     palette already sits close to this page's paper. */
  background:var(--paper-2)}
@media (max-width:640px){.ev-map iframe{height:240px}}
/* The button sits over the map rather than beside it: the map is the thing
   being acted on, and an "Open in Maps" link two paragraphs away gets missed. */
.ev-map__out{position:absolute;top:12px;left:12px;display:inline-flex;align-items:center;
  gap:6px;padding:9px 13px;border-radius:9px;background:var(--surface,#fff);
  font-size:.8125rem;font-weight:600;color:var(--text-paper);text-decoration:none;
  box-shadow:0 1px 2px rgba(12,21,43,.14),0 6px 16px -8px rgba(12,21,43,.3);
  transition:background-color var(--t-ui) var(--ease-out-quint)}
.ev-map__i{width:14px;height:14px;flex:none}
@media (hover:hover){.ev-map__out:hover{background:var(--paper)}}
.ev-map__out:focus-visible{outline:2px solid var(--teal-ink);outline-offset:2px}

.ev-alert{display:flex;gap:11px;align-items:flex-start;margin-top:18px;padding:14px 16px;
  background:#FEF7F6;border:1px solid rgba(179,38,30,.25);border-radius:var(--r-sm,8px)}
.ev-alert p{margin:0;font-size:.875rem;line-height:1.5;color:#7a1f19}

.ev-back{margin-bottom:4px}

@media (prefers-reduced-motion:reduce){
  .ev-card,.ev-switch__b{transition:none}
  .ev-card:hover{transform:none}
}

/* ═══ the workspace page ═══════════════════════════════════════════
   This page sells two things with published prices, which is exactly what the
   services page does — so it is built from the same materials rather than the
   older hairline chart it used to be. White plate, 6px radius, hairline ring,
   lift on hover: one card language across both commercial pages instead of
   two, which is most of what made this page read as unfinished.

   ── the fact strip ───────────────────────────────────────────────
   The three questions everyone asks before they get as far as a price:
   how many fit, when is it open, where is it. Ruled rather than boxed — this
   sits under the headline and must not compete with the plates below it. */
/* Capped to the reading column and centred with it. At full frame width the
   strip ran 1392px under copy set to ~600px — the head read as two
   compositions stacked, a centred one and a full-bleed one. */
.facts{display:grid;grid-template-columns:1fr;gap:0;margin:34px auto 0;max-width:var(--read);
  border-top:1px solid var(--rule-paper);border-bottom:1px solid var(--rule-paper)}
.facts div{padding:16px 0;border-bottom:1px solid var(--rule-paper)}
.facts div:last-child{border-bottom:0}
.facts dt{font-size:.75rem;font-weight:600;letter-spacing:.06em;text-transform:uppercase;
  color:var(--micro-paper)}
.facts dd{margin:5px 0 0;font-size:1rem;line-height:1.4;letter-spacing:-.008em;
  font-variant-numeric:tabular-nums}
@media (min-width:760px){
  .facts{grid-template-columns:repeat(3,1fr);column-gap:var(--gutter)}
  .facts div{padding:18px 0;border-bottom:0}
}

/* ── the price plate ─────────────────────────────────────────────
   Label small and muted, figure large and dark: the value carries the weight
   and the label supports it, rather than the two competing at the same size
   the way the old hairline tiers did.

   THE UNIT RATE IS THE POINT OF THIS CARD. A column of three totals does not
   tell anyone which to buy — GHS 100, GHS 300 and GHS 750 are not comparable
   quantities until they are divided by their own duration. So each plate
   carries the arithmetic: 100 a day, 60 a day, 37.50 a day. That is division,
   not marketing, and it is the same argument the rest of this site makes by
   publishing the prices at all. */
.plans{display:grid;grid-template-columns:1fr;gap:12px;margin-top:30px}
/* 700, not 660. At 660 the three plates come out 200px wide and "GHS 1,500"
   at 30px has a space in it — one plate breaking its price across two lines
   while its neighbours keep theirs on one is worse than a stacked row. */
/* 768, the site's own tier, not 700: at 700 the three plates come out 200px
   wide and "GHS 1,500" at 30px has a space in it.
   AND THE GAP EQUALS THE PADDING. At 14px between plates against 18-22px
   inside them, three cards sat closer to each other than their own contents
   sat to their edges, which reads as one block cut into thirds. */
@media (min-width:768px){.plans{grid-template-columns:repeat(3,1fr);gap:18px}}
@media (min-width:1024px){.plans{gap:22px}}
/* NO HOVER, and no transition for a transform that was never applied. A plate
   is a price, not a control: a card that lifts under the pointer promises
   something to click, and there is nothing here to click. The cards on the
   services page lift because each one IS a link to its offer. */
.plan{position:relative;display:flex;flex-direction:column;
  background:var(--surface);border-radius:var(--r-lg);padding:20px 18px 18px;
  box-shadow:inset 0 0 0 1px var(--hairline)}
@media (min-width:1024px){.plan{padding:22px 22px 20px}}
/* .75rem, matching the global .label token. Four hand-rolled 11px copies of
   it were below both that token and the locked 12/13px label step — and two of
   them sat inside one panel beside a real .label at 12px, which is a size
   difference with no meaning behind it. */
.plan__k{margin:0;font-size:.75rem;font-weight:600;letter-spacing:.06em;
  text-transform:uppercase;color:var(--micro-paper)}
/* The price is one object and never breaks: "GHS 1,500" wrapping after the
   currency reads as two facts. */
.plan__fig{margin:9px 0 0;font-size:1.875rem;font-weight:510;line-height:1.05;
  letter-spacing:-.022em;font-variant-numeric:tabular-nums;white-space:nowrap}
@media (min-width:1024px){.plan__fig{font-size:2.125rem}}
/* The rate sits directly under the figure it comes from, tight, because it is
   the same fact stated a second way and not a new one. */
.plan__unit{margin:6px 0 0;font-size:.8125rem;font-weight:510;letter-spacing:.01em;
  color:var(--teal-ink);font-variant-numeric:tabular-nums}
.plan__note{margin:12px 0 0;font-size:.9375rem;line-height:1.45;
  color:var(--muted-paper)}

/* ── the spec band ───────────────────────────────────────────────
   THE BUG THIS FIXES. The room's three facts used to be a three-column row
   sitting directly under the three-column price row, on the same rhythm, with
   26px between them — closer than the gap inside either group. Two unrelated
   sets of three read as one set of six, which is the whole reason the section
   looked unstructured. They are a different kind of thing, so they get a
   different shape and real distance: a ruled band, its own heading, and 48px
   of air above it rather than 26. */
.spec{margin-top:48px;padding-top:26px;border-top:1px solid var(--rule-paper)}
@media (min-width:1024px){.spec{margin-top:56px}}
.spec__h{font-size:.75rem;font-weight:600;letter-spacing:.06em;text-transform:uppercase;
  color:var(--micro-paper)}
.spec__grid{display:grid;grid-template-columns:1fr;gap:22px var(--gutter);margin-top:18px}
@media (min-width:768px){.spec__grid{grid-template-columns:repeat(3,1fr)}}
/* The band's own name is the h3 (.spec__h, set as a micro-label — weight on
   screen and level in the outline are different things), so the three columns
   under it are h4s. h1 > h2 > h3 > h4 with nothing skipped. */
.spec__grid h4{font-size:1rem;font-weight:600;letter-spacing:-.011em}
.spec__grid ul{margin:10px 0 0;display:grid;gap:8px;list-style:none;padding:0}
.spec__grid li{position:relative;padding-left:16px;font-size:.9375rem;line-height:1.45;
  color:var(--muted-paper)}
.spec__grid li::before{content:"";position:absolute;left:2px;top:.55em;width:5px;height:5px;
  border-radius:50%;background:rgba(12,21,43,.24)}

/* ── the quote panel, rebuilt ────────────────────────────────────
   It was a single row: control, hours, and the total pushed to the far right
   at 32px, which put the one number the reader came for at the end of a line
   as the smallest thing on it. The panel is two parts now — what you choose,
   then what it costs — split by a rule, with the total set as the largest
   figure on the page and the action directly beneath it. Nothing moved
   logically; the arithmetic and every script hook are untouched. */
/* A container, because the two-up below has to answer to the PANEL's width
   and not the window's. Measured: with .split--form putting this in a 46%
   column, the total broke onto two lines from 900-980px and again at
   1024-1050px — the 1024 step raises the figure to 42px and --gutter to 32px
   at the same moment, so the wider window made the column tighter. */
.quote{margin-top:24px;background:var(--surface);border-radius:var(--r-lg);
  padding:0;overflow:hidden;box-shadow:inset 0 0 0 1px var(--hairline);
  container-type:inline-size}
.quote__row{display:grid;grid-template-columns:1fr;row-gap:18px;padding:22px 20px 24px}
@media (min-width:1024px){.quote__row{padding:26px 26px 28px}}
.quote__lab{font-size:.75rem;font-weight:600;letter-spacing:.06em;text-transform:uppercase;
  color:var(--micro-paper);margin-bottom:10px}
/* Equal thirds rather than shrink-to-fit: three options of very different word
   lengths set as three different widths look like an accident, and a segmented
   control is read as one object or it is not one. */
.quote__seg{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}
.quote__opt{appearance:none;font:inherit;font-size:.9375rem;font-weight:510;letter-spacing:-.011em;
  color:var(--muted-paper);background:transparent;border:0;cursor:pointer;
  min-height:44px;padding:0 10px;border-radius:var(--r-md);
  box-shadow:inset 0 0 0 1px var(--rule-paper);
  transition:color var(--t-hover) var(--ease-out-quint),
             box-shadow var(--t-hover) var(--ease-out-quint),
             background-color var(--t-hover) var(--ease-out-quint)}
/* WCAG 1.4.11 wants 3:1 for the boundary that identifies a state. The old
   selected state was a #E9EBE7 fill at 1.20:1 against the white panel with a
   ring at 2.08:1 — measured, and under the bar on both counts, so which of the
   three was chosen was not reliably visible. Ink fill: 18.14:1 for the
   boundary, 16.73:1 for the label on it. */
.quote__opt[aria-pressed="true"]{color:var(--paper);background:var(--ink);
  box-shadow:inset 0 0 0 1px var(--ink)}
@media (hover:hover) and (pointer:fine){
  .quote__opt:not([aria-pressed="true"]):hover{color:var(--text-paper);
    box-shadow:inset 0 0 0 1px rgba(12,21,43,.24)}
}
.quote__hrs{display:flex;align-items:center;gap:10px;margin:0;
  font-size:.9375rem;color:var(--muted-paper)}
.quote__hrs select{font:inherit;font-size:.9375rem;min-height:44px;padding:0 10px;
  border-radius:var(--r-md);border:1px solid var(--rule-paper);background:var(--paper);
  color:var(--text-paper);font-variant-numeric:tabular-nums}
.quote__hrs select:focus-visible{outline:2px solid var(--ink);outline-offset:2px}
/* Hidden but still occupying its row. display:none took the row out of the
   grid, so choosing Half day pulled the total and the button up 62px at the
   exact moment the total changed — the one number the reader is watching moved
   out from under them. */
.quote__hrs[hidden]{display:flex;visibility:hidden}

/* The answer half. Tinted, so the panel reads as a question and an answer
   rather than one undifferentiated box. */
.quote__sum{display:grid;grid-template-columns:1fr;row-gap:16px;align-items:end;
  padding:20px;background:var(--paper-2);border-top:1px solid var(--rule-paper)}
@container (min-width:440px){
  .quote__sum{grid-template-columns:1fr auto;column-gap:var(--gutter);row-gap:0;padding:22px 26px}
}
.quote__out{display:block}
.quote__out .label{display:block;margin-bottom:4px}
/* nowrap so a column that is too narrow fails loudly instead of silently
   folding the figure in half. */
.quote__out output{display:block;font-size:2.25rem;font-weight:510;line-height:1;
  letter-spacing:-.024em;font-variant-numeric:tabular-nums;white-space:nowrap}
@container (min-width:520px){.quote__out output{font-size:2.625rem}}
.quote__note{margin:14px 0 0;font-size:.8125rem;line-height:1.45;letter-spacing:.01em;
  color:var(--micro-paper);max-width:58ch}

/* ── the pair ────────────────────────────────────────────────────
   Two photographs of one room, and the pairing is the argument: empty is what
   you are hiring, full is whether thirty people fit in it. A single large shot
   can only answer one of those, and the one it answers is the less useful.

   THEY ARE CROPPED TO ONE RATIO. The files are 3:2 and 1:1, and two
   photographs at two heights side by side read as an accident rather than a
   pair — so both take 4:3 and cover. Deliberately not the ratio of either
   file: matching one of them would have looked like the other was the mistake. */
/* The photographs belong to the paragraph that introduces them, so they stay
   at 30px. The plates are a different group and take the same 48/56px .spec
   takes — without it, copy, photographs and prices were three unlike things on
   one 30px rhythm, which is the exact defect .spec exists to have fixed. */
.pair{display:grid;grid-template-columns:1fr;gap:12px;margin-top:30px}
.pair + .plans{margin-top:48px}
@media (min-width:1024px){.pair + .plans{margin-top:56px}}
@media (min-width:768px){.pair{grid-template-columns:1fr 1fr;gap:var(--gutter)}}
.pair figure{margin:0}
/* 4px, not the card's 6px: every other framed photograph on this site — .shot,
   the film cells' posters — is on --r-md, and a photograph that rounds harder
   than the ones beside it looks like a different component. */
.pair img{display:block;width:100%;height:auto;aspect-ratio:4/3;object-fit:cover;
  border-radius:var(--r-md);box-shadow:inset 0 0 0 1px var(--hairline)}
.pair figcaption{margin-top:10px;font-size:.8125rem;line-height:1.45;color:var(--micro-paper)}

/* The fact strip under a centred head is centred with it — a ruled row of
   three ranged left under centred copy reads as two compositions. */
.page-head--hero .facts{text-align:center}

/* The form variant of the split: a calculator wants a column, not a frame, and
   it wants to sit level with the top of the copy rather than centred against
   it — a panel centred on a three-line paragraph hangs below the heading it
   belongs to. */
.split--form{align-items:start}
@media (min-width:900px){
  .split--form{grid-template-columns:minmax(0,1fr) minmax(0,46%)}
}
.split--form .quote{margin-top:0}

/* The split figure was a bare <img> while everything beside it — the price
   plates, the pair, the film cells — carries a 6px corner and a hairline ring.
   One photograph on a page with square corners among five with rounded ones
   reads as the one that was forgotten, so it takes the same treatment. */
/* SCOPED, and that is the whole point of the modifier. .split__fig is also the
   home page's VNIP slat row — a .split__fig img rule reaches eleven images in
   a hover-expand strip that has its own corners and its own edges, and puts a
   6px radius and a hairline ring on every one of them. Only a figure that is
   actually one photograph takes this. */
.split__fig--photo img{display:block;width:100%;height:auto;border-radius:var(--r-md);
  box-shadow:inset 0 0 0 1px var(--hairline)}
.split__fig--photo figcaption{margin-top:10px;font-size:.8125rem;line-height:1.45;
  color:var(--micro-paper)}

/* The footnote under the calculator's copy is a footnote, not a third
   paragraph — .stack's 26px rhythm made it read as one. Ruled off and set
   smaller instead. */
.split--form .quote__note{margin-top:22px;padding-top:16px;
  border-top:1px solid var(--rule-paper);max-width:46ch}

/* The other half of the slot. Same height as the select it stands in for, so
   nothing below it moves when they swap. */
.quote__dur{display:flex;align-items:center;min-height:44px;margin:0;
  font-size:.9375rem;color:var(--muted-paper)}

/* Two columns where the band has two groups. The hot-desk band carries five
   facts; five across three columns is a 3/2/1 row that reads as a column
   missing its content. */
@media (min-width:768px){
  .spec__grid--two{grid-template-columns:repeat(2,1fr);max-width:var(--read)}
}

/* ════════════════════════════════════════════════════════════════════════
   The Nest page.

   Built from three references pulled off 21st.dev and read against coworking
   and about pages on Dribbble and Pinterest: an editorial collage hero (headline
   left, two overlapping photographs over a soft wash of the room), a row of
   full-height story cards, and a location card with a single Directions action.
   Translated into this site's own tokens rather than pasted: paper and ink,
   the one green, Host Grotesk for display and Poppins for text.

   "Soft" is done with radius and air, not with shadows. Cards round at 16px
   and sit on a hairline; the only lift is on the collage, where the overlap
   needs a shadow to read as two photographs rather than one. Labels are
   sentence case, bold and tight, like the rest of the recent pages: small
   caps opened out is the default look for a label and it shouts.
   ════════════════════════════════════════════════════════════════════════ */

.nest{--nest-r:16px;--nest-gap:clamp(56px,7vw,96px)}
.nest-k{margin:0 0 12px;font-size:.8125rem;font-weight:700;letter-spacing:-.012em;
  color:var(--green-ink)}
.nest-h2{margin:0;font-family:var(--display);font-size:clamp(28px,3.4vw,42px);
  font-weight:600;line-height:1.08;letter-spacing:-.03em;text-wrap:balance;max-width:20ch}
.nest-p{margin:16px 0 0;font-size:1.0625rem;line-height:1.6;color:var(--muted-paper);
  max-width:48ch;text-wrap:pretty}

/* ── hero ── */
.nest-hero{position:relative;isolation:isolate;overflow:clip;
  padding-block:calc(32px + var(--hdr)) clamp(48px,6vw,80px)}
/* The wash is the room itself, blurred to colour: warm, on-brand, and nothing a
   reader has to look at. Masked so it fades out before the copy ends. */
.nest-hero__wash{position:absolute;inset:0 0 auto;height:78%;z-index:-1;
  background:url(../img/nest/wash.webp) center/cover no-repeat;opacity:.34;
  filter:blur(28px) saturate(1.05);transform:scale(1.15);
  -webkit-mask-image:radial-gradient(90% 100% at 70% 0%,#000 30%,transparent 78%);
          mask-image:radial-gradient(90% 100% at 70% 0%,#000 30%,transparent 78%)}
.nest-hero__grid{display:grid;gap:48px;align-items:center}
@media (min-width:980px){.nest-hero__grid{grid-template-columns:1.05fr 1fr;gap:72px}}
.nest-hero__h{margin:0;font-family:var(--display);font-size:clamp(38px,5.4vw,66px);
  font-weight:600;line-height:1.02;letter-spacing:-.035em;text-wrap:balance;max-width:14ch}
.nest-hero__lead{margin:22px 0 30px;font-size:1.125rem;line-height:1.6;
  color:var(--muted-paper);max-width:46ch;text-wrap:pretty}
.nest-hero__meta{display:flex;align-items:center;gap:10px;margin:26px 0 0;
  font-size:.875rem;color:var(--micro-paper)}
.nest-dot{width:8px;height:8px;border-radius:50%;background:var(--green);
  box-shadow:0 0 0 4px rgba(112,239,151,.25)}

.nest-collage{position:relative;padding:0 0 56px 56px}
.nest-collage figure{margin:0;overflow:hidden;border-radius:var(--nest-r);background:var(--paper-2)}
.nest-collage img{display:block;width:100%;height:100%;object-fit:cover}
.nest-collage__main{aspect-ratio:4/5;max-height:620px;margin-left:auto;
  box-shadow:0 1px 2px rgba(12,21,43,.06)}
.nest-collage__inset{position:absolute;left:0;bottom:0;width:44%;aspect-ratio:1;
  border:6px solid var(--paper);border-radius:calc(var(--nest-r) + 6px);
  box-shadow:0 24px 48px -24px rgba(12,21,43,.45)}
.nest-collage__cap{position:absolute;right:16px;bottom:74px;margin:0;
  padding:8px 13px;border-radius:999px;background:rgba(255,255,255,.92);
  font-size:.8125rem;font-weight:500;color:var(--text-paper);
  box-shadow:0 1px 2px rgba(12,21,43,.08);-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px)}
@media (max-width:640px){
  .nest-collage{padding:0 0 40px 28px}
  .nest-collage__cap{display:none}
}

/* ── film ── */
.nest-film{display:grid;gap:28px;align-items:center;padding-bottom:var(--nest-gap)}
@media (min-width:980px){.nest-film{grid-template-columns:1fr 1.7fr;gap:56px}}
.nest-film__player{border-radius:var(--nest-r);overflow:hidden}
.nest-film__player .vfilm__stage{border-radius:var(--nest-r)}

/* ── stories ── */
.nest-stories{padding-bottom:var(--nest-gap)}
.nest-row{display:flex;align-items:flex-end;justify-content:space-between;gap:24px;margin-bottom:28px}
.nest-nav{display:none;gap:8px}
@media (min-width:760px){.nest-nav{display:flex}}
.nest-nav__b{display:grid;place-items:center;width:44px;height:44px;border:0;border-radius:50%;
  background:var(--surface);color:var(--text-paper);cursor:pointer;
  box-shadow:inset 0 0 0 1px rgba(12,21,43,.14);
  transition:background-color var(--t-ui) var(--ease-out-quint),opacity var(--t-ui)}
.nest-nav__b svg{width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:2;
  stroke-linecap:round;stroke-linejoin:round}
.nest-nav__b:disabled{opacity:.35;cursor:default}
@media (hover:hover){.nest-nav__b:not(:disabled):hover{background:var(--paper-2)}}
.nest-nav__b:focus-visible{outline:2px solid var(--teal-ink);outline-offset:2px}

/* The track bleeds to the right edge of the window but starts on the page's
   own left margin, so the first card lines up with the heading above it. */
.nest-track{display:flex;gap:16px;margin:0;padding:4px max(var(--margin),calc((100vw - var(--frame))/2 + var(--margin))) 8px;
  list-style:none;overflow-x:auto;scroll-snap-type:x mandatory;
  scroll-padding-inline:max(var(--margin),calc((100vw - var(--frame))/2 + var(--margin)));
  scrollbar-width:none;overscroll-behavior-x:contain}
.nest-track::-webkit-scrollbar{display:none}
.nest-card{position:relative;flex:0 0 min(78vw,340px);aspect-ratio:3/4;
  border-radius:var(--nest-r);overflow:hidden;scroll-snap-align:start;background:var(--ink)}
.nest-card img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  transition:transform .6s var(--ease-out-quint)}
.nest-card::after{content:"";position:absolute;inset:0;
  background:linear-gradient(180deg,rgba(12,21,43,0) 35%,rgba(12,21,43,.55) 62%,rgba(12,21,43,.92) 100%)}
@media (hover:hover){.nest-card:hover img{transform:scale(1.04)}}
.nest-card__b{position:absolute;inset:auto 0 0;z-index:1;padding:22px}
.nest-card__t{display:inline-block;margin-bottom:10px;padding:4px 10px;border-radius:999px;
  background:rgba(112,239,151,.18);color:var(--green);font-size:.75rem;font-weight:600}
.nest-card__h{margin:0;font-family:var(--display);font-size:1.375rem;font-weight:600;
  letter-spacing:-.02em;color:#fff}
.nest-card__p{margin:8px 0 0;font-size:.875rem;line-height:1.5;color:rgba(255,255,255,.78);
  display:-webkit-box;-webkit-line-clamp:4;-webkit-box-orient:vertical;overflow:hidden}

/* ── rooms ── */
.nest-rooms{padding-bottom:var(--nest-gap)}
.nest-mosaic{display:grid;grid-template-columns:repeat(2,1fr);gap:12px;margin-top:28px}
@media (min-width:860px){
  .nest-mosaic{grid-template-columns:repeat(4,1fr);grid-template-rows:repeat(2,230px);gap:16px}
  .nest-mosaic__big{grid-column:span 2;grid-row:span 2}
}
.nest-mosaic figure{position:relative;margin:0;overflow:hidden;border-radius:var(--nest-r);
  background:var(--paper-2);aspect-ratio:4/3}
@media (min-width:860px){.nest-mosaic figure{aspect-ratio:auto}}
.nest-mosaic__big{grid-column:span 2}
.nest-mosaic img{width:100%;height:100%;object-fit:cover;display:block}
.nest-mosaic figcaption{position:absolute;left:12px;bottom:12px;padding:6px 11px;
  border-radius:999px;background:rgba(255,255,255,.92);font-size:.75rem;font-weight:600;
  color:var(--text-paper)}

/* ── visit ── */
.nest-visit{display:grid;gap:40px;align-items:center;padding-bottom:var(--nest-gap)}
@media (min-width:980px){.nest-visit{grid-template-columns:1fr 1.1fr;gap:72px}}
.nest-loc{overflow:hidden;border-radius:calc(var(--nest-r) + 4px);background:var(--surface);
  box-shadow:inset 0 0 0 1px var(--hairline),0 30px 60px -40px rgba(12,21,43,.4)}
.nest-loc__img{aspect-ratio:4/3;overflow:hidden}
.nest-loc__img img{width:100%;height:100%;object-fit:cover;display:block}
.nest-loc__b{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:18px 20px}
.nest-loc__n{margin:0;font-family:var(--display);font-size:1.125rem;font-weight:600;letter-spacing:-.015em}
.nest-loc__c{margin:2px 0 0;font-size:.875rem;color:var(--micro-paper)}
.nest-pill{display:inline-flex;align-items:center;gap:8px;min-height:44px;padding:0 18px;
  border-radius:999px;background:var(--ink);color:#fff;font-size:.875rem;font-weight:600;
  text-decoration:none;transition:background-color var(--t-ui) var(--ease-out-quint)}
.nest-pill svg{width:15px;height:15px;fill:none;stroke:currentColor;stroke-width:2;
  stroke-linecap:round;stroke-linejoin:round;transition:transform var(--t-ui) var(--ease-out-quint)}
@media (hover:hover){.nest-pill:hover{background:var(--ink-2)}.nest-pill:hover svg{transform:translate(2px,-2px)}}
.nest-pill:focus-visible{outline:2px solid var(--teal-ink);outline-offset:3px}
.nest-dl{margin:28px 0 30px;padding:0}
.nest-dl>div{display:grid;grid-template-columns:120px 1fr;gap:16px;padding:16px 0;
  border-top:1px solid var(--rule-paper)}
.nest-dl>div:last-child{border-bottom:1px solid var(--rule-paper)}
.nest-dl dt{font-size:.875rem;font-weight:700;letter-spacing:-.01em;color:var(--text-paper)}
.nest-dl dd{margin:0;font-size:.9375rem;line-height:1.55;color:var(--muted-paper)}
@media (max-width:480px){.nest-dl>div{grid-template-columns:1fr;gap:4px}}

/* ═══ Workspace, redesigned ═══════════════════════════════════════════════
   Scoped to .ws so nothing else on the site moves. The shape comes from SLAB
   Design Studio's "The Seed Hub" on Dribbble: one soft light ground, one deep
   brand colour, generous rounded photographs, small pill labels. The pieces
   come from 21st.dev — the plan card and sliding arrow button from
   shadcnspace/pricing-01, the photo band from ravikatiyar/bento-gallery —
   rebuilt here in plain CSS because this site has no build step.

   Radii are larger than the rest of the site (16–24px against 4–10px) on
   purpose: this is the one page that sells a physical room, and the soft
   corner is doing the same job the rounded furniture does in the photos. */

.ws{--ws-r:22px;--ws-r-sm:14px;--ws-soft:#EEF0EC;--ws-line:rgba(12,21,43,.09)}
.ws [hidden]{display:none!important}

/* pills — sentence case, never tracked capitals */
.ws-pill{display:inline-flex;align-items:center;height:28px;padding:0 12px;border-radius:999px;
  font-size:.8125rem;font-weight:600;letter-spacing:-.01em;color:var(--muted-paper);
  background:var(--surface);box-shadow:inset 0 0 0 1px var(--ws-line)}
.ws-pill--solid{background:var(--ink);color:#fff;box-shadow:none}
.ws-pill--ink{background:rgba(255,255,255,.06);color:var(--text-ink);box-shadow:inset 0 0 0 1px var(--rule-ink)}

/* type */
.ws-h1{margin:18px 0 0;font-family:var(--display);font-size:clamp(2.35rem,5.2vw,4rem);font-weight:600;
  line-height:1.02;letter-spacing:-.035em;color:var(--text-paper);text-wrap:balance;max-width:14ch}
.ws-h2{margin:14px 0 0;font-family:var(--display);font-size:clamp(1.75rem,3.4vw,2.75rem);font-weight:600;
  line-height:1.06;letter-spacing:-.03em;color:var(--text-paper);text-wrap:balance;max-width:18ch}
.ws-h2--ink{color:#fff}
.ws-h3{margin:0;font-size:1.1875rem;font-weight:600;letter-spacing:-.018em;color:var(--text-paper)}
.ws-lede{margin:18px 0 0;font-size:1.1875rem;line-height:1.55;color:var(--muted-paper);max-width:46ch;text-wrap:pretty}
.ws-body{margin:0;font-size:1.0625rem;line-height:1.6;color:var(--muted-paper);max-width:52ch;text-wrap:pretty}
.ws-body--sm{font-size:.9375rem;margin-top:8px}
.ws-body--ink{color:var(--muted-ink);margin-top:14px}
.ws-fine{margin:16px 0 0;font-size:.8125rem;line-height:1.55;color:var(--micro-paper);max-width:52ch}

/* the sliding arrow button (shadcnspace/pricing-01) */
.ws-go{position:relative;display:inline-flex;align-items:center;height:52px;padding:0 60px 0 24px;
  border-radius:999px;background:var(--ink);color:#fff;font-size:.9375rem;font-weight:600;
  letter-spacing:-.01em;text-decoration:none;overflow:hidden;isolation:isolate;
  transition:padding .5s var(--ease-out-quint)}
.ws-go__l{position:relative;z-index:1;white-space:nowrap}
.ws-go__c{position:absolute;right:4px;top:4px;width:44px;height:44px;border-radius:50%;
  display:grid;place-items:center;background:var(--green);color:var(--ink);
  transition:right .5s var(--ease-out-quint),transform .5s var(--ease-out-quint)}
.ws-go__i{width:18px;height:18px}
@media (hover:hover) and (prefers-reduced-motion:no-preference){
  .ws-go:hover{padding:0 24px 0 60px}
  .ws-go:hover .ws-go__c{right:calc(100% - 48px);transform:rotate(45deg)}
}
.ws-go:focus-visible{outline:2px solid var(--teal-ink);outline-offset:3px}
.ws-go:active{transform:scale(.98)}

.ws-line{display:inline-flex;align-items:center;height:52px;padding:0 22px;border-radius:999px;
  color:var(--ink);font-size:.9375rem;font-weight:600;letter-spacing:-.01em;text-decoration:none;
  box-shadow:inset 0 0 0 1px rgba(12,21,43,.18);transition:background-color var(--t-ui) var(--ease-out-quint)}
@media (hover:hover){.ws-line:hover{background:var(--surface)}}
.ws-line:focus-visible{outline:2px solid var(--teal-ink);outline-offset:3px}
.ws-acts{display:flex;flex-wrap:wrap;align-items:center;gap:10px;margin-top:30px}

/* section rhythm */
.ws-sec{padding-block:72px 0}
.ws-head{margin-bottom:36px}
.ws-head--split{display:grid;gap:18px;align-items:end}
@media (min-width:900px){.ws-head--split{grid-template-columns:1.1fr .9fr;gap:48px}
  .ws-head--split .ws-body{justify-self:end}}

/* hero */
.ws-hero{display:grid;gap:44px;align-items:center;padding-block:calc(var(--hdr) + 36px) 24px}
@media (min-width:1000px){.ws-hero{grid-template-columns:1fr 1fr;gap:56px;padding-block:calc(var(--hdr) + 56px) 32px}}
/* The hero without its photographs: one column, centred, and the type
   carries the page on its own. */
.ws-hero--solo{grid-template-columns:1fr !important;justify-items:center;text-align:center}
.ws-hero--solo .ws-hero__copy{display:flex;flex-direction:column;align-items:center;max-width:900px}
.ws-hero--solo .ws-h1{max-width:18ch;margin-inline:auto}
.ws-hero--solo .ws-lede{margin-inline:auto}
.ws-hero--solo .ws-acts{justify-content:center}
.ws-hero--solo .ws-facts{margin-inline:auto;width:100%}
.ws-hero--solo .ws-facts li{align-items:center}
.ws-facts{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px;margin:40px 0 0;padding:0;list-style:none;max-width:560px}
.ws-facts li{display:flex;flex-direction:column;gap:10px;padding:14px;border-radius:var(--ws-r-sm);
  background:var(--surface);box-shadow:inset 0 0 0 1px var(--ws-line)}
.ws-facts span{display:flex;flex-direction:column;font-size:.8125rem;line-height:1.35;color:var(--muted-paper)}
.ws-facts b{font-size:.9375rem;font-weight:600;color:var(--text-paper);letter-spacing:-.01em}
.ws-i{width:20px;height:20px;color:var(--text-paper)}


/* two ways in */
.ws-ways{padding-block:56px 0}
.ws-ways__grid{display:grid;gap:20px}
@media (min-width:800px){.ws-ways__grid{grid-template-columns:1fr 1fr}}
.ws-way{display:flex;flex-direction:column;gap:14px;color:inherit;text-decoration:none}
.ws-way__ph{display:block;aspect-ratio:16/10;border-radius:var(--ws-r);overflow:hidden;background:var(--paper-2)}
.ws-way__ph img{display:block;width:100%;height:100%;object-fit:cover;transition:transform .7s var(--ease-out-quint)}
.ws-way__row{display:flex;align-items:center;justify-content:space-between;gap:16px;padding-inline:4px}
.ws-way__t{display:block;font-size:1.3125rem;font-weight:600;letter-spacing:-.02em;color:var(--text-paper)}
.ws-way__p{display:block;margin-top:2px;font-size:.9375rem;color:var(--muted-paper)}
.ws-way__d{padding-inline:4px;font-size:.9375rem;line-height:1.55;color:var(--muted-paper);max-width:46ch}
.ws-way__go{flex:none;display:grid;place-items:center;width:44px;height:44px;border-radius:50%;
  background:var(--ink);color:#fff;transition:transform .5s var(--ease-out-quint)}
.ws-way__go .ws-go__i{width:18px;height:18px}
@media (hover:hover) and (prefers-reduced-motion:no-preference){
  .ws-way:hover .ws-way__ph img{transform:scale(1.03)}
  .ws-way:hover .ws-way__go{transform:rotate(45deg)}
}
.ws-way:focus-visible{outline:2px solid var(--teal-ink);outline-offset:6px;border-radius:var(--ws-r)}

/* plan cards (shadcnspace/pricing-01, three across) */
.ws-plans{display:grid;gap:14px}
@media (min-width:860px){.ws-plans{grid-template-columns:repeat(3,1fr)}}
.ws-plan{display:flex;flex-direction:column;padding:26px;border-radius:var(--ws-r);
  background:var(--surface);box-shadow:inset 0 0 0 1px var(--ws-line)}
.ws-plan__top{display:flex;align-items:center;justify-content:space-between;gap:10px}
.ws-plan__badge{font-size:.8125rem;font-weight:600;letter-spacing:-.01em;color:var(--ink);
  background:var(--green);border-radius:999px;padding:4px 10px}
.ws-plan__fig{margin:34px 0 0;font-family:var(--display);font-size:2.75rem;font-weight:600;
  line-height:1;letter-spacing:-.035em;color:var(--text-paper);font-variant-numeric:tabular-nums}
.ws-plan__unit{margin:8px 0 0;font-size:.9375rem;color:var(--muted-paper)}
.ws-plan__rule{border:0;height:1px;background:var(--ws-line);margin:22px 0 18px}
.ws-plan__per{margin:0;font-size:.9375rem;font-weight:600;color:var(--text-paper)}
.ws-plan__note{margin:4px 0 0;font-size:.9375rem;line-height:1.5;color:var(--muted-paper)}
.ws-plan--feat{background:var(--ink);box-shadow:none}
.ws-plan--feat .ws-pill--solid{background:rgba(255,255,255,.1)}
.ws-plan--feat .ws-plan__fig,.ws-plan--feat .ws-plan__per{color:#fff}
.ws-plan--feat .ws-plan__unit,.ws-plan--feat .ws-plan__note{color:var(--muted-ink)}
.ws-plan--feat .ws-plan__rule{background:var(--rule-ink)}

/* included: one soft card, the pricing-01 "features" column laid flat */
.ws-incl{display:grid;gap:28px;margin-top:14px;padding:28px;border-radius:var(--ws-r);background:var(--ws-soft)}
@media (min-width:900px){.ws-incl{grid-template-columns:.9fr 1.1fr;gap:48px;padding:36px}}
.ws-incl .ws-go{margin-top:24px}
.ws-checks{display:grid;gap:14px;margin:0;padding:0;list-style:none;align-content:center}
@media (min-width:640px){.ws-checks{grid-template-columns:1fr 1fr;column-gap:28px}}
.ws-checks--one{grid-template-columns:1fr!important;gap:10px}
.ws-checks li{display:flex;gap:12px;align-items:flex-start;font-size:.9375rem;line-height:1.45;color:var(--text-paper)}
.ws-ck{flex:none;width:22px;height:22px;padding:4px;border-radius:50%;background:var(--surface);
  color:var(--teal-ink);stroke-width:2.4;box-shadow:inset 0 0 0 1px var(--ws-line)}

/* the navy chapter */
.ws-life{margin-top:96px;background:var(--ink);color:var(--text-ink)}
.ws-life__in{padding-block:88px}
.ws-film{margin:56px auto 0;border-radius:var(--ws-r-sm);background:var(--ink-2);max-width:640px;
  box-shadow:inset 0 0 0 1px var(--rule-ink)}
.ws-film .vfilm__stage{border-radius:var(--ws-r-sm) var(--ws-r-sm) 0 0;overflow:hidden}


/* hire */
.ws-pair{display:grid;gap:14px;margin-bottom:14px}
@media (min-width:700px){.ws-pair{grid-template-columns:1fr 1fr}}
.ws-pair figure{margin:0}
.ws-pair img{display:block;width:100%;aspect-ratio:4/3;object-fit:cover;border-radius:var(--ws-r);background:var(--paper-2)}
.ws-pair figcaption{margin-top:10px;padding-inline:4px;font-size:.875rem;color:var(--micro-paper)}

.ws-hire{display:grid;border-radius:var(--ws-r);background:var(--surface);
  box-shadow:inset 0 0 0 1px var(--ws-line);overflow:hidden;margin-top:14px}
@media (min-width:1000px){.ws-hire{grid-template-columns:1fr 1.15fr}}
.ws-hire__rates{display:grid;background:var(--ws-soft)}
.ws-rate{padding:24px 28px;border-bottom:1px solid var(--ws-line)}
.ws-rate:last-child{border-bottom:0}
.ws-rate__fig{margin:16px 0 0;font-family:var(--display);font-size:2.25rem;font-weight:600;line-height:1;
  letter-spacing:-.03em;color:var(--text-paper);font-variant-numeric:tabular-nums}
.ws-rate__unit{margin:6px 0 0;font-size:.9375rem;font-weight:500;color:var(--text-paper)}
.ws-rate__note{margin:2px 0 0;font-size:.9375rem;color:var(--muted-paper)}
.ws-hire__with{padding:28px;display:flex;flex-direction:column;justify-content:center}
@media (min-width:1000px){.ws-hire__with{padding:36px 40px}}
.ws-with{display:grid;gap:24px;margin-top:22px}
@media (min-width:640px){.ws-with{grid-template-columns:repeat(3,1fr)}}
.ws-with h4{margin:0 0 12px;font-size:.875rem;font-weight:600;letter-spacing:-.01em;color:var(--micro-paper)}

/* quote */
.ws-qcard{display:grid;gap:32px;padding:28px;border-radius:var(--ws-r);background:var(--ws-soft)}
@media (min-width:1000px){.ws-qcard{grid-template-columns:1fr 1fr;gap:56px;padding:44px;align-items:center}}
.ws-quote{margin:0;padding:26px;border-radius:var(--ws-r-sm);background:var(--surface);
  box-shadow:inset 0 0 0 1px var(--ws-line),0 18px 40px -30px rgba(12,21,43,.35)}
.ws-quote__lab,.ws-quote__hrs label,.ws-quote__out label{display:block;font-size:.875rem;font-weight:600;
  letter-spacing:-.01em;color:var(--micro-paper)}
.ws-seg{display:grid;grid-template-columns:repeat(3,1fr);gap:4px;margin-top:10px;padding:4px;
  border-radius:999px;background:var(--ws-soft)}
.ws-seg .quote__opt{height:44px;border:0;border-radius:999px;background:transparent;cursor:pointer;
  font:inherit;font-size:.9375rem;font-weight:600;color:var(--muted-paper);
  transition:background-color var(--t-ui) var(--ease-out-quint),color var(--t-ui) var(--ease-out-quint)}
.ws-seg .quote__opt{box-shadow:none}
.ws-seg .quote__opt[aria-pressed="true"]{background:var(--ink);color:#fff}
.ws-seg .quote__opt:focus-visible{outline:2px solid var(--teal-ink);outline-offset:2px}
.ws-quote__mid{min-height:78px;margin-top:20px}
.ws-quote__hrs{margin:0;display:flex;align-items:center;justify-content:space-between;gap:12px}
.ws-quote__hrs select{height:44px;min-width:92px;padding:0 14px;border-radius:12px;font:inherit;font-weight:600;
  color:var(--text-paper);background:var(--surface);border:1px solid rgba(12,21,43,.22)}
.ws-quote__dur{margin:0;height:44px;display:flex;align-items:center;font-size:.9375rem;font-weight:600;color:var(--text-paper)}
.ws-quote__sum{display:flex;flex-wrap:wrap;align-items:flex-end;justify-content:space-between;gap:16px;
  padding-top:20px;border-top:1px solid var(--ws-line)}
.ws-quote__out{margin:0}
.ws-quote__out output{display:block;margin-top:4px;font-family:var(--display);font-size:2.25rem;font-weight:600;
  letter-spacing:-.03em;line-height:1;color:var(--text-paper);font-variant-numeric:tabular-nums}

.ws #quote{padding-bottom:96px}
.ws-qcard .ws-body{margin-top:14px}

/* ── Shapes, after 21st.dev team-01 ──────────────────────────────────────
   The four silhouettes that component cuts its portraits into: a two-lobed
   "8", an arch, an oval and a three-lobed scallop. They are SVG masks rather
   than baked-in PNG cutouts, so they fit any photograph at any size and the
   photograph stays an ordinary <img> with its own alt text. Every shape is
   drawn on a 4:5 box, so each figure is held to 4:5 as well. */
.ws-shape{margin:0;aspect-ratio:4/5;overflow:hidden;background:var(--paper-2);
  -webkit-mask-size:100% 100%;mask-size:100% 100%;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat}
.ws-shape img{display:block;width:100%;height:100%;object-fit:cover;
  transition:filter .3s var(--ease-out-quint),transform .8s var(--ease-out-quint)}
.ws-shape--eight{-webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 125' preserveAspectRatio='none'%3E%3Crect x='0' y='0' width='100' height='66' rx='32'/%3E%3Crect x='0' y='59' width='100' height='66' rx='32'/%3E%3C/svg%3E");mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 125' preserveAspectRatio='none'%3E%3Crect x='0' y='0' width='100' height='66' rx='32'/%3E%3Crect x='0' y='59' width='100' height='66' rx='32'/%3E%3C/svg%3E")}
.ws-shape--arch{-webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 125' preserveAspectRatio='none'%3E%3Cpath d='M0 125V50a50 50 0 0 1 100 0v75z'/%3E%3C/svg%3E");mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 125' preserveAspectRatio='none'%3E%3Cpath d='M0 125V50a50 50 0 0 1 100 0v75z'/%3E%3C/svg%3E")}
.ws-shape--oval{-webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 125' preserveAspectRatio='none'%3E%3Cellipse cx='50' cy='62.5' rx='50' ry='62.5'/%3E%3C/svg%3E");mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 125' preserveAspectRatio='none'%3E%3Cellipse cx='50' cy='62.5' rx='50' ry='62.5'/%3E%3C/svg%3E")}
.ws-shape--scallop{-webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 125' preserveAspectRatio='none'%3E%3Crect x='0' y='0' width='100' height='46' rx='23'/%3E%3Crect x='0' y='39.5' width='100' height='46' rx='23'/%3E%3Crect x='0' y='79' width='100' height='46' rx='23'/%3E%3C/svg%3E");mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 125' preserveAspectRatio='none'%3E%3Crect x='0' y='0' width='100' height='46' rx='23'/%3E%3Crect x='0' y='39.5' width='100' height='46' rx='23'/%3E%3Crect x='0' y='79' width='100' height='46' rx='23'/%3E%3C/svg%3E")}

/* hero: two shapes, stepped */
.ws-duo{display:grid;grid-template-columns:1fr 1fr;gap:18px;align-items:start;max-width:600px;justify-self:end;width:100%}
.ws-duo__b{margin-top:64px}
.ws-duo__b img{object-position:50% 35%}

/* the band: team-01's row — centred heading, shapes, a title and a line */
.ws-life__head{display:flex;flex-direction:column;align-items:center;text-align:center;max-width:620px;margin:0 auto}
.ws-life__head .ws-h2{max-width:none}
.ws-trio{display:grid;gap:40px;margin:56px auto 0;padding:0;list-style:none;max-width:980px}
@media (min-width:720px){.ws-trio{grid-template-columns:repeat(3,1fr);gap:28px}}
.ws-trio__i{display:flex;flex-direction:column;align-items:center;text-align:center}
.ws-trio__i .ws-shape{width:100%;max-width:300px}
.ws-trio h3{margin:22px 0 0;font-size:1.25rem;font-weight:600;letter-spacing:-.02em;color:#fff}
.ws-trio p{margin:6px 0 0;font-size:.9375rem;line-height:1.5;color:var(--muted-ink)}
/* team-01's hover: the photograph drops to grey. Colour, not motion, so it
   stays under reduced motion; hover-capable pointers only. */
@media (hover:hover){.ws-trio__i:hover .ws-shape img,.ws-duo figure:hover img{filter:grayscale(1)}}

/* The hero's fact icons, in a soft tinted tile: the icon at a lighter stroke,
   teal on a pale teal wash, so the three read as a set and not as bare glyphs. */
.ws-facts li{gap:14px}
.ws-facts .ws-fi{display:grid;place-items:center;width:40px;height:40px;border-radius:12px;
  background:rgba(5,193,157,.10);color:var(--teal-ink)}
.ws-facts .ws-fi svg{width:22px;height:22px}

/* The three facts as ONE card: a single white strip split by hairline
   dividers, icons bare (no tile) in teal ink beside their text. Reads as one
   fact sheet instead of three buttons. */
.ws-facts{display:flex;gap:0;padding:6px;border-radius:20px;background:var(--surface);
  box-shadow:inset 0 0 0 1px var(--ws-line),0 14px 34px -26px rgba(12,21,43,.35);max-width:600px}
.ws-facts li{flex:1 1 0;flex-direction:row;align-items:center;gap:12px;padding:14px 16px;
  border-radius:0;background:none;box-shadow:none;position:relative}
.ws-facts li+li::before{content:"";position:absolute;left:0;top:22%;bottom:22%;width:1px;background:var(--ws-line)}
.ws-facts .ws-fi{width:auto;height:auto;background:none;border-radius:0;flex:none}
.ws-facts .ws-fi svg{width:24px;height:24px}
@media (max-width:560px){
  .ws-facts{flex-direction:column;padding:4px}
  .ws-facts li+li::before{left:16px;right:16px;top:0;bottom:auto;width:auto;height:1px}
}

/* ═══ Booking ═══════════════════════════════════════════════════════════════
   The shape is 21st.dev's c-calendar-14 (a month grid beside the day's start
   times, taken ones disabled rather than hidden) and v-calendar-8 (closed and
   full days greyed, the reason on hover), set in this site's own paper, navy
   and form fields. Soft by construction: white cards on the paper ground,
   22px corners, a hairline and a long faint shadow, navy for what is chosen
   and green kept to the one button that sends. */
.bk{--bk-r:22px;--bk-line:rgba(12,21,43,.09);padding-bottom:96px}
.bk [hidden]{display:none!important}
.bk-head{padding-block:calc(var(--hdr) + 28px) 28px}
.bk-h1{margin:22px 0 0;font-family:var(--display);font-size:clamp(2.2rem,4.6vw,3.4rem);font-weight:600;
  letter-spacing:-.035em;line-height:1.02;color:var(--text-paper)}
.bk-lede{margin:14px 0 0;font-size:1.125rem;line-height:1.55;color:var(--muted-paper);max-width:52ch}
.bk-loading,.bk-nojs{padding:40px 0;color:var(--muted-paper)}

.bk-grid{display:grid;gap:18px;align-items:start}
@media (min-width:1000px){.bk-grid{grid-template-columns:minmax(0,1fr) 360px;gap:24px}}
.bk-steps{display:grid;gap:14px;min-width:0}

.bk-card{padding:24px;border-radius:var(--bk-r);background:var(--surface);
  box-shadow:inset 0 0 0 1px var(--bk-line),0 24px 48px -40px rgba(12,21,43,.4);
  transition:opacity .3s var(--ease-out-quint)}
@media (min-width:640px){.bk-card{padding:28px 30px}}
.bk-card.is-locked{opacity:.55}
.bk-sh{display:flex;align-items:flex-start;gap:14px;margin-bottom:22px}
.bk-card.is-locked .bk-sh{margin-bottom:0}
.bk-num{flex:none;display:grid;place-items:center;width:30px;height:30px;border-radius:50%;
  font-size:.875rem;font-weight:700;color:var(--text-paper);background:var(--paper);box-shadow:inset 0 0 0 1px var(--bk-line)}
.bk-num.is-done{background:var(--ink);color:#fff;box-shadow:none}
.bk-num svg{width:15px;height:15px;fill:none;stroke:currentColor;stroke-width:2.4;stroke-linecap:round;stroke-linejoin:round}
.bk-h2{margin:3px 0 0;font-size:1.1875rem;font-weight:600;letter-spacing:-.02em;color:var(--text-paper)}
.bk-sub{margin:4px 0 0;font-size:.9375rem;color:var(--muted-paper)}
.bk-lab{display:block;font-size:.875rem;font-weight:600;letter-spacing:-.01em;color:var(--text-paper)}
.bk-hint{margin:8px 0 0;font-size:.8125rem;line-height:1.45;color:var(--micro-paper)}

/* what: desk or room */
.bk-seg{display:grid;grid-template-columns:1fr 1fr;gap:6px;padding:5px;border-radius:18px;background:var(--paper)}
.bk-seg button{display:flex;align-items:center;gap:12px;padding:12px 14px;border:0;border-radius:14px;
  background:transparent;font:inherit;text-align:left;color:var(--muted-paper);cursor:pointer;
  transition:background-color var(--t-ui) var(--ease-out-quint),box-shadow var(--t-ui) var(--ease-out-quint)}
.bk-seg button[aria-checked="true"]{background:var(--surface);color:var(--text-paper);
  box-shadow:0 1px 2px rgba(12,21,43,.08),0 8px 18px -12px rgba(12,21,43,.3)}
.bk-seg svg{flex:none;width:24px;height:24px;fill:none;stroke:currentColor;stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round}
.bk-seg span{display:flex;flex-direction:column;min-width:0}
.bk-seg b{font-size:.9844rem;font-weight:600;letter-spacing:-.01em}
.bk-seg small{font-size:.8125rem;color:var(--micro-paper)}
.bk-seg button:focus-visible,.bk-plan:focus-visible,.bk-day:focus-visible,.bk-slot:focus-visible,
.bk-cal__nav:focus-visible,.bk-stepper button:focus-visible{outline:2px solid var(--teal-ink);outline-offset:2px}

.bk-plans{display:grid;gap:10px;margin-top:14px}
@media (min-width:640px){.bk-plans{grid-template-columns:repeat(3,1fr)}}
.bk-plan{position:relative;display:flex;flex-direction:column;align-items:flex-start;gap:4px;padding:16px 16px 14px;
  border:0;border-radius:16px;background:var(--surface);font:inherit;text-align:left;cursor:pointer;
  box-shadow:inset 0 0 0 1px var(--bk-line);transition:box-shadow var(--t-ui) var(--ease-out-quint)}
@media (hover:hover){.bk-plan:hover{box-shadow:inset 0 0 0 1px rgba(12,21,43,.24)}}
.bk-plan[aria-checked="true"]{box-shadow:inset 0 0 0 2px var(--ink)}
.bk-plan__t{font-size:1rem;font-weight:600;letter-spacing:-.015em;color:var(--text-paper)}
.bk-plan__d{font-size:.8125rem;line-height:1.4;color:var(--muted-paper);min-height:2.3em}
.bk-plan__p{margin-top:8px;font-family:var(--display);font-size:1.375rem;font-weight:600;letter-spacing:-.03em;color:var(--text-paper)}
.bk-plan__p small{font-family:var(--sans);font-size:.8125rem;font-weight:500;letter-spacing:0;color:var(--micro-paper)}
.bk-plan__tick{position:absolute;top:12px;right:12px;display:grid;place-items:center;width:22px;height:22px;border-radius:50%;
  background:var(--ink);color:#fff;opacity:0;transform:scale(.6);transition:opacity .2s,transform .3s var(--ease-out-quint)}
.bk-plan__tick svg{width:12px;height:12px;fill:none;stroke:currentColor;stroke-width:2.8;stroke-linecap:round;stroke-linejoin:round}
.bk-plan[aria-checked="true"] .bk-plan__tick{opacity:1;transform:none}

.bk-extras{display:grid;gap:18px;margin-top:20px;padding-top:20px;border-top:1px solid var(--bk-line)}
@media (min-width:640px){.bk-extras{grid-template-columns:1fr 1fr}}
.bk-stepper__c{display:inline-flex;align-items:center;gap:4px;margin-top:8px;padding:4px;border-radius:999px;background:var(--paper)}
.bk-stepper__c button{width:40px;height:40px;border:0;border-radius:50%;background:var(--surface);font:inherit;font-size:1.25rem;
  color:var(--text-paper);cursor:pointer;box-shadow:0 1px 2px rgba(12,21,43,.08)}
.bk-stepper__c button:disabled{opacity:.35;cursor:default}
.bk-stepper__c output{min-width:86px;text-align:center;font-weight:600;color:var(--text-paper)}
.bk-people{margin-top:8px;max-width:140px}

/* when: the calendar */
.bk-cal{display:grid;gap:22px}
@media (min-width:760px){.bk-cal{grid-template-columns:minmax(0,1fr) 230px;gap:28px}}
.bk-cal__bar{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}
.bk-cal__title{margin:0;font-size:1rem;font-weight:600;letter-spacing:-.015em;color:var(--text-paper)}
.bk-cal__nav{display:grid;place-items:center;width:38px;height:38px;border:0;border-radius:50%;background:var(--paper);
  color:var(--text-paper);cursor:pointer;transition:background-color var(--t-ui)}
.bk-cal__nav:disabled{opacity:.35;cursor:default}
@media (hover:hover){.bk-cal__nav:not(:disabled):hover{background:var(--paper-2)}}
.bk-cal__nav svg{width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
.bk-cal__wk,.bk-cal__grid{display:grid;grid-template-columns:repeat(7,minmax(0,1fr));gap:4px}
.bk-cal__m{max-width:460px;width:100%}
.bk-cal__wk span{text-align:center;font-size:.75rem;font-weight:600;color:var(--micro-paper);padding-bottom:6px}
.bk-cal__grid.is-loading{opacity:.5;transition:opacity .2s}
.bk-day{position:relative;display:grid;place-items:center;height:46px;border:0;border-radius:12px;
  background:transparent;font:inherit;font-size:.9375rem;font-weight:600;font-variant-numeric:tabular-nums;
  color:var(--text-paper);cursor:pointer;transition:background-color var(--t-ui) var(--ease-out-quint)}
@media (hover:hover){.bk-day:not(:disabled):not(.is-on):hover{background:var(--paper)}}
.bk-day:not(:disabled){background:rgba(5,193,157,.07)}
.bk-day.is-off{color:rgba(12,21,43,.28);font-weight:500;cursor:default;background:transparent}
.bk-day.is-out{visibility:hidden}
.bk-day.is-today span{text-decoration:underline;text-underline-offset:4px;text-decoration-thickness:1.5px}
.bk-day.is-on{background:var(--ink);color:#fff}
.bk-day__dot{position:absolute;bottom:6px;left:50%;width:5px;height:5px;margin-left:-2.5px;border-radius:50%;background:#E0A100}
.bk-cal__key{display:flex;flex-wrap:wrap;gap:6px 16px;margin:14px 0 0;font-size:.75rem;color:var(--micro-paper)}
.bk-cal__key span{display:inline-flex;align-items:center;gap:6px}
.bk-cal__key i{display:inline-block;width:10px;height:10px;border-radius:3px}
.k-free{background:rgba(5,193,157,.18)}.k-off{background:var(--paper-2)}.k-low{background:#E0A100;border-radius:50%!important;width:6px!important;height:6px!important}

.bk-cal__side{min-width:0}
@media (min-width:760px){.bk-cal__side{padding-left:26px;border-left:1px solid var(--bk-line)}}
.bk-side__t{margin:0;font-size:1rem;font-weight:600;letter-spacing:-.015em;color:var(--text-paper)}
.bk-side__s{margin:4px 0 14px;font-size:.875rem;line-height:1.45;color:var(--muted-paper)}
.bk-empty{display:flex;gap:10px;align-items:flex-start;margin:0;padding:18px;border-radius:14px;background:var(--paper);
  font-size:.875rem;line-height:1.5;color:var(--muted-paper)}
.bk-empty svg{flex:none;width:18px;height:18px;margin-top:1px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round}
.bk-slots{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;max-height:344px;overflow-y:auto;padding:2px}
@media (min-width:760px){.bk-slots{grid-template-columns:1fr 1fr}}
.bk-slot{height:44px;border:0;border-radius:12px;background:var(--surface);font:inherit;font-size:.9375rem;font-weight:600;
  font-variant-numeric:tabular-nums;color:var(--text-paper);cursor:pointer;box-shadow:inset 0 0 0 1px var(--bk-line);
  transition:box-shadow var(--t-ui),background-color var(--t-ui)}
@media (hover:hover){.bk-slot:not(:disabled):not(.is-on):hover{box-shadow:inset 0 0 0 1px rgba(12,21,43,.3)}}
.bk-slot:disabled{color:rgba(12,21,43,.28);text-decoration:line-through;cursor:default;box-shadow:none;background:var(--paper)}
.bk-slot.is-on{background:var(--ink);color:#fff;box-shadow:none}
.bk-seats{margin:0;padding:14px 16px;border-radius:14px;background:rgba(5,193,157,.08);font-size:.875rem;color:var(--text-paper)}
.bk-seats b{font-family:var(--display);font-size:1.25rem;letter-spacing:-.02em}

/* who */
.bk-form__row{display:grid;gap:0 16px}
@media (min-width:640px){.bk-form__row{grid-template-columns:1fr 1fr}}
.bk-form textarea{resize:vertical;min-height:96px}

/* summary */
.bk-side{position:sticky;top:calc(var(--hdr) + 20px)}
.bk-sum{padding:24px;border-radius:var(--bk-r);background:var(--surface);
  box-shadow:inset 0 0 0 1px var(--bk-line),0 30px 60px -44px rgba(12,21,43,.55)}
.bk-sum__k{margin:0;font-size:.8125rem;font-weight:600;letter-spacing:-.01em;color:var(--micro-paper)}
.bk-sum__t{margin:6px 0 0;font-size:1.125rem;font-weight:600;letter-spacing:-.02em;color:var(--text-paper)}
.bk-sum__empty{margin:8px 0 0;font-size:.9375rem;line-height:1.5;color:var(--muted-paper)}
.bk-sum__dl{display:grid;gap:10px;margin:18px 0 0}
.bk-sum__dl div{display:flex;justify-content:space-between;gap:16px;font-size:.9375rem}
.bk-sum__dl dt{color:var(--muted-paper)}
.bk-sum__dl dd{margin:0;text-align:right;font-weight:600;color:var(--text-paper)}
.bk-sum__dl .muted{font-weight:500;color:var(--micro-paper)}
.bk-sum__total{display:flex;align-items:baseline;justify-content:space-between;margin-top:18px;padding-top:18px;
  border-top:1px dashed rgba(12,21,43,.16);font-size:.9375rem;color:var(--muted-paper)}
.bk-sum__total b{font-family:var(--display);font-size:1.9rem;font-weight:600;letter-spacing:-.035em;color:var(--text-paper);font-variant-numeric:tabular-nums}
.bk-sum__err{margin:12px 0 0;padding:10px 12px;border-radius:12px;background:#FEF7F6;font-size:.875rem;line-height:1.45;color:#B3261E}
.bk-sum__fine{margin:14px 0 0;font-size:.8125rem;line-height:1.5;color:var(--micro-paper)}

/* the one green thing: the button that sends */
.bk-go{position:relative;display:flex;align-items:center;justify-content:center;width:100%;height:54px;margin-top:20px;
  padding:0 60px 0 24px;border:0;border-radius:999px;background:var(--ink);color:#fff;font:inherit;font-size:1rem;
  font-weight:600;letter-spacing:-.01em;text-decoration:none;cursor:pointer;overflow:hidden;
  transition:opacity var(--t-ui),transform var(--t-press)}
.bk-go i{position:absolute;right:5px;top:5px;display:grid;place-items:center;width:44px;height:44px;border-radius:50%;
  background:var(--green);color:var(--ink);transition:transform .5s var(--ease-out-quint)}
.bk-go svg{width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
.bk-go:disabled{opacity:.35;cursor:default}
@media (hover:hover) and (prefers-reduced-motion:no-preference){.bk-go:not(:disabled):hover i{transform:rotate(45deg)}}
.bk-go:not(:disabled):active{transform:scale(.985)}
.bk-go:focus-visible{outline:2px solid var(--teal-ink);outline-offset:3px}
.bk-go--auto{width:auto;margin-top:0}
.bk-line{display:inline-flex;align-items:center;height:54px;padding:0 24px;border-radius:999px;color:var(--ink);
  font-weight:600;text-decoration:none;box-shadow:inset 0 0 0 1px rgba(12,21,43,.18)}

/* done */
.bk-done{max-width:620px;margin:0 auto;padding:40px 24px;border-radius:var(--bk-r);background:var(--surface);text-align:center;
  box-shadow:inset 0 0 0 1px var(--bk-line),0 30px 60px -44px rgba(12,21,43,.55)}
@media (min-width:640px){.bk-done{padding:52px 48px}}
.bk-done__ok{display:grid;place-items:center;width:64px;height:64px;margin:0 auto;border-radius:50%;background:var(--green);color:var(--ink)}
.bk-done__ok svg{width:28px;height:28px;fill:none;stroke:currentColor;stroke-width:2.6;stroke-linecap:round;stroke-linejoin:round}
.bk-done__t{margin:20px 0 0;font-family:var(--display);font-size:2.25rem;font-weight:600;letter-spacing:-.035em;color:var(--text-paper)}
.bk-done__s{margin:8px 0 0;color:var(--muted-paper)}
.bk-done__card{display:grid;gap:10px;margin:28px 0 0;padding:18px 20px;border-radius:16px;background:var(--paper);text-align:left}
.bk-done__card div{display:flex;justify-content:space-between;gap:16px;font-size:.9375rem}
.bk-done__card span{color:var(--muted-paper)}
.bk-done__card b{font-weight:600;color:var(--text-paper);text-align:right}
.bk-flow{display:grid;gap:14px;margin:28px 0 0;padding:0;list-style:none;text-align:left}
.bk-flow li{display:flex;gap:14px;align-items:flex-start;font-size:.9375rem;color:var(--muted-paper)}
.bk-flow li b{display:block;font-weight:600;color:var(--text-paper)}
.bk-flow i{flex:none;display:grid;place-items:center;width:28px;height:28px;border-radius:50%;font-style:normal;font-size:.8125rem;
  font-weight:700;background:var(--paper);color:var(--micro-paper)}
.bk-flow li.is-done i{background:var(--ink);color:#fff}
.bk-flow li.is-next i{background:rgba(5,193,157,.14);color:var(--teal-ink)}
.bk-flow svg{width:14px;height:14px;fill:none;stroke:currentColor;stroke-width:2.6;stroke-linecap:round;stroke-linejoin:round}
.bk-done__acts{display:flex;flex-wrap:wrap;justify-content:center;gap:10px;margin-top:32px}

/* ── Booking, flattened ─────────────────────────────────────────────────────
   No shadows anywhere. Cards are white on the paper ground with a single
   hairline, and depth comes from spacing rather than blur. The calendar drops
   its tinted day tiles: available days are plain ink, closed days fade, and
   only the chosen day is filled. Fields inside the booking page are white with
   a light hairline and a 12px corner instead of the grey boxed inputs. */
.bk{--bk-line:rgba(12,21,43,.08)}
.bk-card,.bk-sum,.bk-done{box-shadow:inset 0 0 0 1px var(--bk-line)!important;border-radius:20px}
.bk-card{padding:28px}
@media (min-width:640px){.bk-card{padding:32px 34px}}
.bk-steps{gap:12px}
.bk-card.is-locked{opacity:1;background:transparent;box-shadow:inset 0 0 0 1px var(--bk-line)!important}
.bk-card.is-locked .bk-h2{color:var(--micro-paper)}
.bk-card.is-locked .bk-num{background:transparent}

/* step numbers: quiet until done */
.bk-num{width:28px;height:28px;font-size:.8125rem;font-weight:600;background:transparent;
  box-shadow:inset 0 0 0 1px rgba(12,21,43,.16);color:var(--muted-paper)}
.bk-h2{font-size:1.125rem}

/* choose: a flat segmented control */
.bk-seg{padding:4px;border-radius:14px;background:var(--paper)}
.bk-seg button{border-radius:11px;padding:11px 14px}
.bk-seg button[aria-checked="true"]{box-shadow:inset 0 0 0 1px var(--bk-line)!important}
.bk-plan{border-radius:14px;padding:16px}
.bk-plan[aria-checked="true"]{box-shadow:inset 0 0 0 1.5px var(--ink)}
.bk-plan__p{font-size:1.25rem}
.bk-extras{border-top-color:var(--bk-line)}
.bk-stepper__c{background:transparent;box-shadow:inset 0 0 0 1px var(--bk-line)}
.bk-stepper__c button{box-shadow:none;background:var(--paper)}

/* calendar: ink on white, one filled circle */
.bk-cal__nav{background:transparent;box-shadow:inset 0 0 0 1px var(--bk-line)}
.bk-day:not(:disabled){background:transparent}
.bk-day{height:42px;border-radius:50%;max-width:42px;justify-self:center;width:100%;font-weight:500}
@media (hover:hover){.bk-day:not(:disabled):not(.is-on):hover{background:var(--paper)}}
.bk-day.is-off{color:rgba(12,21,43,.22)}
.bk-day.is-on{background:var(--ink);color:#fff;font-weight:600}
.bk-day.is-today:not(.is-on)::after{content:"";position:absolute;bottom:5px;left:50%;width:4px;height:4px;margin-left:-2px;
  border-radius:50%;background:var(--teal-ink)}
.bk-day.is-today span{text-decoration:none}
.bk-day__dot{bottom:4px;background:#E0A100}
.bk-cal__key .k-free{background:transparent;box-shadow:inset 0 0 0 1px rgba(12,21,43,.3)}
@media (min-width:760px){.bk-cal__side{border-left-color:var(--bk-line)}}
.bk-empty{background:transparent;box-shadow:inset 0 0 0 1px var(--bk-line);border-radius:14px}
.bk-slot{height:42px;border-radius:10px;font-weight:500;box-shadow:inset 0 0 0 1px var(--bk-line)}
.bk-slot.is-on{font-weight:600}
.bk-slot:disabled{background:transparent;box-shadow:none;color:rgba(12,21,43,.22)}
.bk-seats{background:var(--paper)}

/* fields: white, hairline, soft corner */
.bk .fld__i{background:var(--surface);border:1px solid rgba(12,21,43,.14);border-radius:12px;min-height:48px}
.bk .fld__i:focus-visible{border-color:var(--ink);box-shadow:0 0 0 3px rgba(12,21,43,.06)}
.bk .fld{margin-bottom:16px}
.bk .fld__l{font-size:.875rem;font-weight:500;margin-bottom:6px}
.bk-people{max-width:120px}

/* summary: flat, with one rule before the total */
.bk-sum{padding:26px}
.bk-sum__total{border-top:1px solid var(--bk-line)}
.bk-go{height:52px}
.bk-done__card{background:transparent;box-shadow:inset 0 0 0 1px var(--bk-line)}
.bk .fld__opt{text-transform:none;letter-spacing:-.008em;font-size:.75rem;font-weight:500;color:var(--micro-paper)}
.bk .fld__e{min-height:0;margin-top:4px}
.bk .fld__e:empty{display:none}

/* ── Plan cards read as choices ─────────────────────────────────────────────
   A price list is something to read; these have to be pressed. So each card
   carries a radio ring from the start (filled with a check once chosen), a
   "Select" pill at its foot, and a hover that lifts the outline. Until one is
   chosen, a line above says "Choose a pass to continue" and the cards breathe
   once — a single soft outline pulse, skipped under reduced motion. */
.bk-pick{display:flex;align-items:center;gap:8px;margin:18px 0 0;font-size:.875rem;font-weight:600;color:var(--text-paper)}
.bk-pick::before{content:"";width:8px;height:8px;border-radius:50%;background:var(--teal)}
.bk-pick.is-done{color:var(--micro-paper);font-weight:500}
.bk-pick.is-done::before{background:var(--ink)}
.bk-plans{margin-top:10px}
.bk-plan{padding-bottom:16px;cursor:pointer}
.bk-plan__tick{opacity:1;transform:none;background:transparent;color:transparent;
  box-shadow:inset 0 0 0 1.5px rgba(12,21,43,.22);transition:background-color .2s,box-shadow .2s,color .2s}
.bk-plan[aria-checked="true"] .bk-plan__tick{background:var(--ink);color:#fff;box-shadow:none}
.bk-plan__cta{display:inline-flex;align-items:center;height:30px;margin-top:14px;padding:0 14px;border-radius:999px;
  font-size:.8125rem;font-weight:600;color:var(--text-paper);background:var(--paper);
  transition:background-color .2s,color .2s}
.bk-plan[aria-checked="true"] .bk-plan__cta{background:var(--ink);color:#fff}
@media (hover:hover){
  .bk-plan:not([aria-checked="true"]):hover{box-shadow:inset 0 0 0 1.5px rgba(12,21,43,.45)}
  .bk-plan:not([aria-checked="true"]):hover .bk-plan__cta{background:var(--ink);color:#fff}
  .bk-plan:not([aria-checked="true"]):hover .bk-plan__tick{box-shadow:inset 0 0 0 1.5px var(--ink)}
}
@media (prefers-reduced-motion:no-preference){
  .bk-plans.is-waiting .bk-plan{animation:bk-breathe 1.6s var(--ease-out-quint) .4s 1 both}
  .bk-plans.is-waiting .bk-plan:nth-child(2){animation-delay:.55s}
  .bk-plans.is-waiting .bk-plan:nth-child(3){animation-delay:.7s}
}
@keyframes bk-breathe{
  0%,100%{box-shadow:inset 0 0 0 1px var(--bk-line)}
  40%{box-shadow:inset 0 0 0 1.5px var(--teal),0 0 0 4px rgba(5,193,157,.12)}
}

/* The hours picker used the browser's own arrow, which Chrome pins a few pixels
   from the border. Same chevron and inset as .fld__sel, so every select on the
   site ends the same way: 16px from the edge, centred on the height. */
.quote__hrs select,.ws-quote__hrs select{appearance:none;-webkit-appearance:none;cursor:pointer;
  padding-left:16px;padding-right:40px;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6' fill='none' stroke='%234D5B75' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1 1l4 4 4-4'/%3E%3C/svg%3E");
  background-position:right 16px center;background-size:10px 6px;background-repeat:no-repeat}

/* ── Home: the workspace film ───────────────────────────────────────────────
   A muted loop with no controls, so no player chrome and no play button;
   iOS draws its own start button when autoplay is refused (Low Power Mode),
   and that is hidden too, leaving the poster. Spacing is deliberately tight:
   the film sits close to the questions above and the offer below. */
.home-film{padding-top:8px;padding-bottom:clamp(28px,4vw,48px)}
.home-film__player{margin:12px 0 0;border-radius:16px;overflow:hidden;background:var(--ink);aspect-ratio:16/9}
.home-film__v{display:block;width:100%;height:100%;object-fit:cover;pointer-events:none}
.home-film__v::-webkit-media-controls,
.home-film__v::-webkit-media-controls-start-playback-button,
.home-film__v::-webkit-media-controls-overlay-play-button{display:none!important;-webkit-appearance:none}
/* The question list hands straight to the film: its usual 78px foot would
   stack on the film's own spacing and leave a blank screen-height on a phone. */
section[aria-labelledby="start-h"]:has(+ .home-film){padding-bottom:28px}
.home-film{padding-top:0;padding-bottom:28px}

/* ── Events pages: pure white ───────────────────────────────────────────────
   The two event pages sit on white rather than the site's warm paper, so the
   artwork carries the colour. Redefining the tokens on <body> moves every
   surface that uses them at once (header glass, switch track, image grounds)
   instead of overriding each one. */
.page-white{--paper:#FFFFFF;--paper-2:#F3F4F6;background:#FFFFFF}
.page-white .hdr{background:rgba(255,255,255,.6)}
.page-white .hdr[data-stuck]{background:rgba(255,255,255,.82)}

/* ── Event cards ───────────────────────────────────────────────────────────
   One, two, then three across. Hairline edge and a whisper of shadow: on a
   white page a card needs an edge to exist, and anything heavier than this
   reads as a button. */
.evl{display:grid;gap:22px;margin:26px 0 0;padding:0;list-style:none;grid-template-columns:minmax(0,1fr)}
@media (min-width:640px){.evl{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (min-width:1024px){.evl{grid-template-columns:repeat(3,minmax(0,1fr));gap:26px}}
.evl>li{display:flex;min-width:0}

.evc{position:relative;display:flex;flex-direction:column;width:100%;overflow:hidden;
  background:#fff;border-radius:20px;
  box-shadow:0 0 0 1px rgba(12,21,43,.08),0 1px 2px rgba(12,21,43,.04);
  transition:box-shadow var(--t-ui) var(--ease-out-quint),transform var(--t-ui) var(--ease-out-quint)}
@media (hover:hover){
  .evc:hover{transform:translateY(-2px);
    box-shadow:0 0 0 1px rgba(12,21,43,.1),0 14px 34px -18px rgba(12,21,43,.28)}
  .evc:hover .evc__img{transform:scale(1.02)}
}
@media (prefers-reduced-motion:reduce){
  .evc,.evc:hover,.evc:hover .evc__img{transform:none}
}

/* The picture. 16:9, the shape of the phone artwork, which therefore fills it
   exactly. Contain, never cover: no event's words are ever cut. */
.evc__media{position:relative;aspect-ratio:16/9;overflow:hidden;background:var(--paper-2)}
.evc__img{position:absolute;inset:0;width:100%;height:100%;object-fit:contain;
  transition:transform 400ms var(--ease-out-quint)}
.evc__bg{position:absolute;inset:-12%;width:124%;height:124%;object-fit:cover;
  filter:blur(22px) saturate(1.15);opacity:.55}
.evc__blank{position:absolute;inset:0}

/* Pills on the picture: white, soft, small. */
.evc__date,.evc__mode,.evc__state{position:absolute;z-index:1}
.evc__date{top:12px;left:12px;display:grid;place-items:center;min-width:50px;padding:7px 8px 6px;
  background:#fff;border-radius:12px;box-shadow:0 2px 10px -4px rgba(12,21,43,.25);text-align:center}
.evc__m{font-size:.6875rem;font-weight:700;letter-spacing:-.005em;line-height:1;color:var(--teal-ink)}
.evc__d{margin-top:2px;font-size:1.3125rem;font-weight:700;letter-spacing:-.02em;line-height:1;
  font-variant-numeric:tabular-nums;color:var(--text-paper)}
.evc__mode{top:12px;right:12px;display:inline-flex;align-items:center;gap:6px;padding:6px 11px;
  font-size:.75rem;font-weight:600;color:var(--text-paper);background:rgba(255,255,255,.94);
  border-radius:999px;box-shadow:0 2px 10px -4px rgba(12,21,43,.25)}
.evc__mode svg,.evc__when svg,.evc__where svg,.evc__going svg{width:14px;height:14px;flex:none;
  fill:none;stroke:currentColor;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
.evc__state{left:12px;bottom:12px;padding:5px 11px;font-size:.75rem;font-weight:600;color:#fff;
  background:rgba(12,21,43,.72);border-radius:999px;
  -webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px)}
.evc__state--live{background:var(--green);color:var(--ink)}
.evc__state--off{background:#B3261E}
.evc__state--pin{background:#fff;color:#7a5800}

/* The words. */
.evc__body{display:flex;flex:1;flex-direction:column;padding:18px 20px 20px}
.evc__when,.evc__where{display:flex;align-items:center;gap:8px;margin:0;
  font-size:.8125rem;color:var(--muted-paper)}
.evc__where span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.evc__t{margin:10px 0 0;font-size:1.125rem;font-weight:600;letter-spacing:-.018em;line-height:1.3;
  color:var(--text-paper)}
.evc__a{color:inherit;text-decoration:none}
/* The whole card is the link. */
.evc__a::after{content:"";position:absolute;inset:0;z-index:2;border-radius:inherit}
.evc__a:focus-visible{outline:none}
.evc__a:focus-visible::after{box-shadow:0 0 0 2px var(--teal-ink)}
.evc__s{margin:8px 0 0;font-size:.875rem;line-height:1.55;color:var(--muted-paper);
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.evc__where{margin-top:14px}
.evc__foot{display:flex;align-items:center;gap:10px;margin-top:auto;padding-top:16px}
.evc__going{display:inline-flex;align-items:center;gap:6px;font-size:.8125rem;color:var(--muted-paper)}
.evc__flag{margin-left:auto;padding:4px 11px;font-size:.75rem;font-weight:600;border-radius:999px;
  color:var(--muted-paper);background:var(--paper-2)}
.evc__flag--open{color:var(--teal-ink);background:rgba(5,193,157,.1)}

/* ── The weekly note: what the Subscribe box says back ──────────────────────
   Same line under the form, coloured by outcome so a glance says whether it
   worked. A small tick on success; errors in the form red the site uses. */
.ftr__colophon[data-note-said][data-kind="ok"]{display:flex;align-items:flex-start;gap:8px;
  color:var(--teal-ink);font-weight:600}
.ftr__colophon[data-note-said][data-kind="ok"]::before{content:"";flex:none;width:16px;height:16px;margin-top:1px;
  background:currentColor;
  -webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E") center/contain no-repeat;
  mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E") center/contain no-repeat}
.ftr__colophon[data-note-said][data-kind="bad"]{color:#B3261E;font-weight:600}
.ftr__form .btn:disabled{opacity:.6;cursor:progress}
