/* ---- Grain (display) + Mafra (body) ----
   Grain is the logo's own typeface, so every display and label surface uses it.
   Mafra carries reading copy.

   LICENCE. Grain is Superior Type; Mafra is Monotype via MyFonts. All five
   faces below are licensed webfonts supplied by the foundries. Do not swap in
   a file converted from a desktop .otf — the Superior Type desktop EULA does
   not permit conversion, and its webfont tier is metered by pageviews. */
@font-face {
  font-family: "Grain";
  src: url("fonts/Grain-Light.woff2") format("woff2");
  font-weight: 300;              /* licensed */
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Grain";
  src: url("fonts/Grain-Regular.woff2") format("woff2");
  font-weight: 400;              /* licensed */
  font-style: normal;
  font-display: swap;
}


@font-face {
  font-family: "Grain";
  src: url("fonts/Grain-SemiBold.woff2") format("woff2");
  font-weight: 600 700;          /* licensed — also catches the 700 rules */
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Grain";
  src: url("fonts/Grain-ExtraBold.woff2") format("woff2");
  font-weight: 800;              /* licensed */
  font-style: normal;
  font-display: swap;
}


@font-face {
  font-family: "Mafra";
  src: url("fonts/Mafra-Book.woff2") format("woff2");
  font-weight: 400;              /* licensed */
  font-style: normal;
  font-display: swap;
}

:root {
  /* --font-mono is now the DISPLAY face (Grain, the logo's typeface) and
     --font-sans the READING face (Mafra). The variable names are historical;
     every rule that referenced them still points at the right role. */
  --font-mono: "Grain", "Helvetica Neue", Helvetica, Arial, sans-serif;
  --font-sans: "Mafra", Georgia, "Times New Roman", serif;

  /* Gradient stops, sampled top -> bottom. Blue peaks around 40% and the
     hand-off to orange is held late and wide, as in the comp. */
  --g-stops:
    #000000  0%,
    #12103f 18%,
    #33309b 31%,
    #4a44b8 40%,
    #5a4fb0 44%,
    #6d55a0 49%,
    #8a5f88 53%,
    #ab6a70 58%,
    #cc7458 62%,
    #e07a4d 67%,
    #e87b4a 71%,
    #ec7a4e 80%,
    #ef7a52 100%;

  --ink: #ffffff;          /* on-video text in the hero */
  --cream: #fbf3ec;        /* matches the logo fill */
  --ink-dark: #3f303c;     /* every non-cream text colour on the page */

  /* ---- One grid for the whole page ----
     The hero and the content below were on different systems: the hero sat at
     2.25vw edges, the ported page at 4vw, so nothing lined up across the seam.
     Both now use these. 12 columns, and every column line is shared top to
     bottom. */
  --edge: clamp(18px, 2.25vw, 50px);
  --gutter: 2vw;

  /* Where the copy starts, as a distance across the wordmark. It began life
     locked to the left edge of the "c" (79.86%, measured off logo.svg) and is
     now nudged just past it — which is where reference3 actually had it. One
     number: raise it to move the copy right, lower it to move left. */
  --copy-indent: 80.8%;

  /* Hero type, sized off the reference. The wordmark is small and pinned to
     the top; the statement is centred in the frame. */
  --hero-logo: min(18vw, 340px);
  /* Parity with the section headlines below (.text-headline, 5vw). At the old
     2.55vw the page's primary sentence rendered 36.7px against their 72px —
     literally half the size of a secondary headline, and smaller than every
     other piece of display type on the site. Centred symmetry hid that; left
     aligned there is nothing propping it up. */
  --hero-statement: clamp(40px, 5vw, 80px);
  /* How far above dead centre the statement sits. Set against the footage:
     the phone's screen enters frame at a steady ~47% of viewport height (it
     holds that across window shapes because the video is 16:9 and cover crops
     symmetrically), so the statement is lifted to bottom out around 41% and
     leave the phone clear. */
  /* 22vh was tuned when the statement was 36px and had to climb clear of the
     handset. At 72px that lift crowds the wordmark above it and leaves a void
     below, so the left column reads as two clusters with a hole between them.
     13vh sits the statement low enough to close both. Lower than about 6vh
     and the second clause lands on the phone screen — that is the floor. */
  /* CTA at the foot of the hero. */
  --hero-cta-size: clamp(15px, 1.05vw, 17px);

  /* Hero mask. Pulled in from lassie.ai's flat 32/64 so the closed mask always
     sits comfortably inside --edge; at ~1440 a 2.2vw inset and a 2.25vw page
     edge are within a pixel of each other, which clipped the copy. Keeping the
     inset a fraction of the edge means the hero content never has to shift
     sideways to dodge it, so the grid holds at every scroll position. */
  /* Height of the strip the hero hangs below the fold to cover the band
     iOS Safari paints behind its floating toolbar. Zero everywhere else. */
  --hero-skirt: 0px;

  /* The left line the hero hangs off. The body below is a 12-column grid
     inside a container padded by --edge, and its leftmost content — the
     labels, the portraits, the footer's first column — starts on column 2.
     Deriving it rather than eyeballing keeps the hero on that line if the
     edge or the gutter ever changes. On phones the body collapses to flush
     --edge, so the phone block re-points this at the edge. */
  --col: calc((100vw - 2 * var(--edge) - 11 * var(--gutter)) / 12);
  --hero-indent: calc(var(--edge) + var(--col) + var(--gutter));

  --peel: 0;
  --peel-inset: clamp(12px, 1.35vw, 30px);
  --peel-radius: clamp(24px, 2.7vw, 60px);
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  /* Black, not cream. This is the document background, and iOS Safari tints
     its status bar and the band behind its toolbar with it — which is where
     the cream borders around the hero were coming from. It also sets the
     rubber-band overscroll colour, and black is right for both while the
     footage is on screen. hero.js swaps it to cream once the page below has
     taken over, so the bottom of the site overscrolls cream.

     Note this is NOT the colour behind the peel mask: that comes from
     .hero's own background, which stays cream. */
  background: #0d0a0c;
}

body {
  /* Sans is the default; mono is applied deliberately, element by element. */
  font-family: var(--font-sans);
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
  /* Fraktion ships a real Bold in both the mono and the sans, so nothing needs
     faking. (Supply had none, which is why this was previously set to
     "weight" to let the browser stand one in.) */
  font-synthesis: none;
}

/* ---- The hero and its rounded-rectangle mask ----
   The hero scrolls away normally; only the clip closes in. --peel is a 0 -> 1
   scroll progress set by hero.js, so the mask animates from full bleed to a
   32px inset with 64px corners. */
.hero {
  position: relative;
  /* svh is the SMALLEST viewport height — the size with Safari's toolbar fully
     expanded — so a hero sized to it comes up short the moment the toolbar
     retracts. dvh tracks the live viewport and fixes that, with svh left as
     the fallback for browsers without dvh.

     dvh alone is still not enough on iOS: Safari paints page content behind
     its floating bottom bar but does not count that band in dvh, so the
     section below shows through it. The skirt covers that band. It hangs
     below the fold, and .content below is pinned to the viewport height
     rather than the hero's, so nothing inside the hero moves. */
  height: 100svh;
  height: calc(100dvh + var(--hero-skirt));
  background: var(--cream);
}

.hero__mask {
  position: absolute;
  inset: 0;
  clip-path: inset(
    calc(var(--peel, 0) * var(--peel-inset))
    round calc(var(--peel, 0) * var(--peel-radius))
  );
  will-change: clip-path;
}


/* ---- Layer 1: header media ----
   cover, not stretch: this is a photograph/footage, so it crops to fit rather
   than distorting. Centred, so the crop takes evenly off both sides.
   The rule serves both a <div> (still) and a <video> (motion) — background-*
   covers the former, object-fit the latter. */
.photo {
  position: absolute;
  inset: 0;
  z-index: 0;
  background-color: #000;
}

/* Scoped to the div. Unscoped, <video class="photo"> matched too and fetched
   the still in full behind footage that covers it completely — the poster
   attribute already handles the pre-decode frame. */
div.photo {
  background-image: url("header.jpg");
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}

img.photo,
video.photo {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  /* Safari paints control chrome on some builds without this. */
  pointer-events: none;
}

/* ---- Layer 1: gradient ----
   Blue and orange are near-complementary, so interpolating them in sRGB drags
   the midtones through a desaturated grey-brown. Oklab keeps the mid-band
   chromatic, which is the difference between a muddy blend and a sunset. The
   plain sRGB declaration first is the fallback for browsers that don't parse
   the colour-interpolation syntax.

   Drop a gradient.png in beside this file and it takes over automatically —
   a layer that fails to load simply doesn't paint, so the CSS gradient below
   it is the fallback until the artwork exists. 100% 100% stretches the image
   to the viewport rather than cropping: the artwork is a smooth vertical ramp
   with no detail to distort, and stretching guarantees the whole ramp stays
   visible at every aspect ratio. Swap to `cover` to crop instead. */
.gradient {
  position: absolute;
  inset: 0;
  z-index: 0;
  background-color: #000;
  background-repeat: no-repeat;
  background-position: center;
  background-size: 100% 100%;
  background-image: linear-gradient(to bottom, var(--g-stops));
  background-image: url("gradient-slim.png"), linear-gradient(to bottom in oklab, var(--g-stops));
}

/* A whisper of noise. A full-viewport gradient this smooth will band into
   visible stripes on 8-bit displays; dithering breaks up the steps. */
.grain {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  /* Overlay rather than a flat wash: it leaves the pure blacks at the top
     untouched and only dithers where there is tone to band. */
  mix-blend-mode: overlay;
  opacity: 0.10;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ---- Layer 3: pixel wave ----
   Masked to nothing well above the type. The wave is already parameterised to
   stay in the upper band, but the mask is the guarantee: no matter how the
   wave is retuned or how short the viewport gets, it cannot reach the
   wordmark. It also lets the band dissolve into the gradient rather than
   stopping on a hard line. */
#pixels {
  position: absolute;
  inset: 0;
  z-index: 2;
  width: 100%;
  height: 100%;
  display: block;
  pointer-events: none;
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 48%, transparent 72%);
  mask-image: linear-gradient(to bottom, #000 0%, #000 48%, transparent 72%);
}

/* ---- Rays (parked) ---- */
#rays {
  position: absolute;
  inset: 0;
  z-index: 2;
  width: 100%;
  height: 100%;
  display: block;
  pointer-events: none;
}

/* ---- Layer 3: content ----
   Locked to the bottom edge: wordmark left, copy right. The burst hangs from
   the top, so the two never collide. */
.content {
  position: relative;
  z-index: 3;
  /* The viewport, not the hero: the hero is taller by --hero-skirt, and
     inheriting that would push the centred statement down by half of it and
     the bottom-anchored button down by all of it. */
  height: 100svh;
  height: 100dvh;
  /* One stack, not three independently positioned blocks.

     Before this, the wordmark was pinned to the top with a vh offset while
     the headline group was vertically centred with a vh lift and the leading
     was vw-based. Three different mechanisms, so they drifted apart at
     different rates as the window changed shape: the gap between the mark and
     the headline ran from -6px at a 2.29 aspect (they OVERLAPPED) to 238px at
     1.00. A 40x swing.

     As one flex column with gaps expressed in the headline's own size, the
     relationships are invariant — the composition scales with the type rather
     than with the window, and the aspect ratio only changes how much empty
     space sits above and below it.

     1.3em against a 1.1 line-height also means the gaps stay larger than the
     leading by construction, which is the rule the rhythm needs, rather than
     something to re-check at every breakpoint. */
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  gap: calc(var(--hero-statement) * 1.3);
  /* The mask inset is added so the type keeps clear of the rounded frame as
     it closes. Left hangs off the grid line; the right only needs the page
     edge — padding both sides with --hero-indent cost the statement 149px of
     measure at 1440 and forced it to wrap a size earlier than it had to. */
  padding: calc(var(--peel, 0) * var(--peel-inset)) var(--edge)
           calc(var(--peel, 0) * var(--peel-inset)) var(--hero-indent);
}

/* The wordmark, on the same left line as everything else. --hero-logo is the
   one number to change.
   Pinned to the top and taken out of flow, so the statement below is the only
   in-flow child of .content and its align-items: center centres it on the
   frame rather than on the leftover space under the logo. */
.masthead {
  /* In the flow now — see .content. */
  flex: 0 0 auto;
  font-size: clamp(18px, 2.1vw, 42px);
  text-align: left;
}

.wordmark {
  margin: 0;
  font-size: inherit;
  line-height: 0;
  /* This <h1> holds nothing but the logo image, but it is still an <h1>, so
     the UA gives it font-weight: bold — and it inherits Mafra from body. That
     was enough to make the browser download Mafra Bold (48 KB) to set a text
     node that does not exist. 400 resolves to the Book already in use. */
  font-weight: 400;
}

.wordmark img {
  display: inline-block;
  width: var(--hero-logo);
  max-width: 100%;
  height: auto;
  /* Drifts up into place on load. `both` holds the from-state before the delay
     so there is no flash of the mark at full opacity first. */
  animation: hero-rise 1.8s cubic-bezier(0.22, 0.61, 0.36, 1) 0.2s both;
}

/* Fraktion Mono Bold, centred. Fraktion has a real 700, so this is the drawn
   Bold rather than a synthesised one. */
.hero-statement {
  margin: 0;
  /* Generous: each clause fits on one line at this width, so the cap is only
     a guard against wrapping on very narrow desktop windows. */
  max-width: min(100%, 44ch);
  text-align: left;
  text-wrap: balance;
  /* Grain — the logo's own typeface, sitting directly under the wordmark.
     Light (300) is a TRIAL weight. */
  font-family: var(--font-mono);
  font-weight: 300;
  font-size: var(--hero-statement);
  /* Display leading, not reading leading. 1.35 came from when this was two
     lines at a smaller size; at 72px over three lines it put 97px between
     them — larger than the 52px gap to the wordmark above and the 38px gap to
     the button below. The biggest space in the composition was INSIDE the
     headline, so the three lines never cohered as a block and the mark looked
     glued to the first one. */
  line-height: 1.1;
  /* Normal. Every negative value here was inherited from an earlier typeface:
     -0.045em to close up monospace's fixed pitch, then -0.02em for Fraktion
     Sans. Grain is tightly fitted already and wants neither. */
  letter-spacing: normal;
  color: var(--ink);
  /* Follows the wordmark up, a beat later. */
  animation: hero-rise 1.8s cubic-bezier(0.22, 0.61, 0.36, 1) 0.5s both;
}

/* Headline and button as one group. On the old hero the CTA was pinned to the
   frame's bottom edge, 400-odd pixels below the statement with nothing between
   them — it read as stranded rather than as the message's action. Grouping
   them makes the CTA belong to the sentence, and the group takes the lift the
   statement used to carry on its own.

   Doubled, because a centred grid item is centred by its MARGIN box: a bottom
   margin of M moves it up by only M/2. Margin rather than transform, since the
   drift-in animation owns transform. */
.hero-col {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  /* Same measure as the gap above the headline, for the same reason. */
  gap: calc(var(--hero-statement) * 1.3);
}


/* On at every width. The sentence is 40 characters — unbroken it runs to
   1498px at the desktop size. Three lines, broken at the grammar. */
.hero-statement .hs-wrap {
  display: inline;
}

/* In flow under the headline now, not pinned to the frame. */
.hero-cta {
  flex: 0 0 auto;

  font-family: var(--font-mono);
  font-weight: 400;
  font-size: var(--hero-cta-size);
  /* Caps to match the label voice used through the page below (LOCATION,
     CONTACT, STUDIO OVERVIEW) and to read as a control rather than a
     continuation of the sentence above. Caps need the tracking — without it
     they set cramped. */
  text-transform: uppercase;
  letter-spacing: 0.1em;
  line-height: 1;

  /* Padding in em, so the shape grows with the label rather than needing a
     second set of breakpoints. */
  padding: 1em 2em;
  /* Outline, not fill. A solid cream block beside the statement read as the
     heaviest thing in the frame, which is the wrong weight for the third
     element in the hierarchy. 1px so the rule stays a line rather than a band
     — box-sizing: border-box keeps the outline and filled states the same
     size, so the hover swap shifts nothing. */
  border: 1px solid var(--ink);
  border-radius: 10px;
  /* It became an <a> when the demo got a URL. A button has no underline and no
     inherited colour; a link has both, so they are reset here. */
  text-decoration: none;
  background: transparent;
  color: var(--ink);
  cursor: pointer;
  white-space: nowrap;
  /* An <a> is inline by default; inline-flex both centres the label and keeps
     the padding honest. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  -webkit-appearance: none;
  appearance: none;

  transition: background-color 0.22s ease, color 0.22s ease,
              border-color 0.22s ease;
  /* Third beat of the entrance, after the wordmark and the statement. */
  animation: hero-rise 1.8s cubic-bezier(0.22, 0.61, 0.36, 1) 0.8s both;
}

/* Fills on hover: the outline becomes the block, and the label inverts. */
.hero-cta:hover,
.hero-cta:focus-visible {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--ink-dark);
}

/* Keyboard users get a ring; mouse users do not. Offset so it reads against
   the footage rather than blending into the button edge. */
.hero-cta:focus-visible {
  outline: 2px solid var(--cream);
  outline-offset: 3px;
}

@keyframes hero-rise {
  from {
    opacity: 0;
    transform: translateY(2.2rem);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* The shared keyframes overwrite transform, which would undo the CTA's
   translateX(-50%) centring. Its own keyframes carry the centring through. */
@media (prefers-reduced-motion: reduce) {
  .wordmark img,
  .hero-statement,
  .hero-cta {
    animation: none;
  }
}

/* Hero subtitle — no element matches this any more; kept so the subtitle can
   be restored with the markup alone. Inner comments stripped, since CSS
   comments do not nest.

   .intro { grid-row: 1; grid-column: 2; text-align: left; line-height: 1.35; }
   .intro p { margin: 0 0 2rem; font-size: 0.794em; font-weight: 400;
              letter-spacing: 0; }
   .intro p:last-child { margin-bottom: 0; }
*/

@media (max-width: 700px) {
  /* Three tiers, in order of what should be read first.

     The mark was 52vw, which made it 204px wide against a 232px statement —
     near enough the same measure, and heavier, so the two competed and neither
     led. At 38vw it identifies without arguing with the sentence.

     The statement gains a point of size and drops ~47px, which opens the top
     margin above the mark and stops the second clause landing on the handset.

     The button keeps the bottom, as the anchor of the frame, but with real
     margin under it rather than the 34px that read as pinned to the edge. */
  :root {
    --hero-logo: min(38vw, 200px);
    /* Raised with the desktop scale. A narrow viewport needs a higher vw to
       reach the same optical size, and left-aligned the statement has to hold
       the column on its own. */
    --hero-statement: clamp(26px, 8.5vw, 38px);
    --hero-skirt: 130px;
  }
  /* Light holds at display size but goes fragile at phone sizes over moving
     footage, so the statement steps back to Regular there. */
  .hero-statement {
    font-weight: 400;
  }

  /* Two lines per clause. Left to wrap on its own the sentence broke mid-
     clause ("Health technology moves / fast Understanding"), which read as one
     run-on now that the full stops are gone. These breaks put the seam where
     the grammar is. */
  .hero-statement .hs-wrap {
    display: inline;
  }

  /* Balance off — the hard breaks above are the composition now, and balance
     exists to choose breaks on its own. The longhand, not `text-wrap: normal`,
     which Chrome leaves the style component of untouched. */
  .hero-statement {
    text-wrap-style: auto;
  }

}

/* The body's leftmost content — labels, portraits, first footer column —
   moves from column 2 to column 1 at this width, which is flush with --edge.
   The hero follows it, so the two halves of the page stay on one line. */
@media (max-width: 1024px) {
  :root {
    --hero-indent: var(--edge);
  }
}

/* ---- Short viewports ----
   Landscape phones, and any desktop window dragged short.

   The phone rule further down keys off WIDTH, but the hero is a vertical
   composition and what actually constrains it is HEIGHT. A 667x375 landscape
   phone is narrow enough to take the portrait sizing and only 375px tall, so
   the wordmark lands 79px on top of the statement. A 852x393 phone is the
   opposite: wide enough to take the desktop sizing, in a 393px-tall box.
   Height is the honest test for both. */
@media (max-height: 500px) {
  :root {
    --hero-logo: min(20vw, 124px);
    --hero-statement: clamp(16px, 5.2vh, 26px);
    --hero-skirt: 70px;
  }

  /* One line per clause. The per-clause breaks exist because a portrait phone
     is too narrow to hold a clause on one line; landscape has width to spare,
     and four lines of statement will not fit 375px of height. The space before
     each <br> in the markup keeps the words apart when it is hidden. */
  .hero-statement .hs-wrap {
    display: none;
  }

  .hero-statement {
    /* Light gets fragile below ~20px over moving footage. */
    font-weight: 400;
  }

}

/* ---- Tablet portrait ----
   Wide enough for the desktop rules, but roughly 3:4 rather than 16:9. The
   desktop lift is measured to clear the handset in a landscape frame; in a
   portrait one the handset sits much lower, so the same lift strands the
   statement in the top quarter and leaves 725px between it and the button.
   Less lift, and a size that is not pinned to its clamp floor. */
@media (min-width: 701px) and (max-width: 1024px) and (orientation: portrait) {
  :root {
  }
}

/* ---- The plate on a portrait phone ----
   A separate, portrait-shot plate from the same set, served by the media
   query in <picture>. It carries its own dark area above the handset, so the
   hero goes back to being a single full-bleed image — no stack, no fill, no
   fade, none of the machinery the landscape plate needed here.

   Measured on it: the top 40% peaks at 0.17 luminance, which is comfortably
   dark enough for white type, and the wood falls off into black on its own
   because it is one photograph rather than a join.

   The type sits high so it clears the handset, which starts at 54% of the
   plate's height. */
@media (max-width: 700px) {
  .content {
    justify-content: flex-start;
    padding-top: clamp(34px, 7.5vh, 80px);
  }

  /* Pushed in past what cover gives. Unzoomed, the app screen was 33% of the
     width and 26% of the height — small for a headline promising you can read
     it at a glance. The origin is the handset, so the type does not move and
     only the photograph grows beneath it.

     Note the direction: a TALLER box (see --hero-skirt) crops more and
     therefore enlarges the handset. Reducing the skirt would shrink it.

     The asset is 1400px wide for a 402px frame, so even at this scale there
     is roughly 2.9x density spare — nothing is being upscaled. */
  img.photo {
    transform: scale(1.18);
    transform-origin: 46% 78%;
  }
}


/* ====================================================================
   Hero media toggle — still plate vs. original footage
   ====================================================================
   Jeff prefers the motion version; the still plate is what solves the
   phone crop. Rather than pick one, the hero carries a control.

   Deliberately a two-segment control rather than a single switch: a lone
   toggle on a marketing hero reads as indecision, whereas a labelled pair
   showing which is active reads as a choice being offered. It borrows the
   .hero-cta's language — Grain, caps, 0.1em tracking, 1px rim — at roughly
   two-thirds the size, so it sits a clear step below the CTA in weight.

   Built by hero.js, not markup: the control is useless without JS, and the
   footage is only fetched when someone actually asks for it.

   To remove the whole feature: delete this block, the mode block in
   hero.js, and the two header-video files. */

.hero-switch {
  position: absolute;
  z-index: 3;
  bottom: calc(var(--edge) + var(--hero-skirt));
  right: var(--edge);

  display: flex;
  gap: 2px;
  padding: 2px;
  border: 1px solid color-mix(in srgb, var(--ink) 45%, transparent);
  border-radius: 999px;
  /* The footage behind this moves, so a flat tint alone would sometimes sit
     on a bright frame. The blur keeps the rim legible over either. */
  background: color-mix(in srgb, #0d0a0c 30%, transparent);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);

  /* Quieter than the CTA until you go near it — it is an option, not the
     thing the page is asking you to do. */
  --switch-rest: 0.55;
  opacity: var(--switch-rest);
  transition: opacity 0.25s ease;

  /* Last beat of the entrance, after the CTA's 0.8s.

     Its own keyframes, and BACKWARDS fill rather than both, for one reason:
     hero-rise ends at opacity 1 and `both` pins it there, which would
     override the resting 0.55 above and leave :hover with nothing to do.
     Backwards holds the from-state through the delay and then hands the
     element back to its own styles — and because the keyframe lands on the
     same custom property, there is no snap at the handover. */
  animation: hero-switch-rise 1.4s cubic-bezier(0.22, 0.61, 0.36, 1) 1.3s
             backwards;
}

@keyframes hero-switch-rise {
  from {
    opacity: 0;
    transform: translateY(2.2rem);
  }
  to {
    opacity: var(--switch-rest);
    transform: translateY(0);
  }
}

.hero-switch:hover,
.hero-switch:focus-within {
  opacity: 1;
}

.hero-switch button {
  font-family: var(--font-mono);
  font-weight: 400;
  font-size: clamp(10px, 0.72vw, 12px);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  line-height: 1;

  padding: 0.75em 1.5em;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: var(--ink);
  cursor: pointer;
  white-space: nowrap;
  -webkit-appearance: none;
  appearance: none;
  transition: background-color 0.22s ease, color 0.22s ease;
}

/* Active segment fills, exactly as the CTA does on hover — same gesture, so
   the two controls feel like one family. */
.hero-switch button[aria-pressed="true"] {
  background: var(--ink);
  color: var(--ink-dark);
}

.hero-switch button:focus-visible {
  outline: 2px solid var(--cream);
  outline-offset: 2px;
}

/* ---- the CGM plate's framing ----
   The clay-court frame, extended 798px to the left (3294x1664, ~2:1). That
   extension is what makes it usable: before it, the source was 3:2 going into
   a 1.6 box, which crops vertically only — no horizontal slack at all — and
   the headline ran straight across the handset.

   At ~2:1 there IS slack, 19% of the width, and spending it leftwards puts
   the whole type column on the dark hair. Measured at 1440x900, sampling the
   headline band in six slices:

      object-position   worst slice (mean)   worst pixel
         50% (centred)        5.3:1             1.34:1
         30%                  9.1:1             1.61:1
         20%                 14.9:1             4.06:1

   For reference the café plate, the current default, measures 12.0:1 and
   3.88:1 the same way — so at 20% this frame is now the more legible of the
   two. The bottle's specular highlight is what the lower numbers are; 20%
   pushes it clear of the type and still keeps the sensor in frame. */
/* img.photo--cgm, not .photo--cgm: the base rule up at "Layer 1" is
   `img.photo, video.photo { object-fit: cover; object-position: center }`,
   which is (0,1,1). A bare class is (0,1,0) and loses to it at every width —
   this silently sat at centre until it was measured. */
img.photo--cgm {
  object-position: 20% center;
}

/* ---- the crossfade ----
   Both layers stay in the DOM once the footage has been built, stacked on
   inset: 0, so switching is an opacity swap rather than a reflow. visibility
   rides along on the same transition so the hidden layer stops taking hit
   tests once it has faded. */
.photo {
  transition: opacity 0.45s ease, visibility 0s linear 0s;
}

.photo.is-off {
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.45s ease, visibility 0s linear 0.45s;
}

@media (prefers-reduced-motion: reduce) {
  .hero-switch {
    animation: none;
  }
  .photo {
    transition: none;
  }
}

/* Phones: same corner, smaller. It was briefly moved up beside the wordmark
   — the top right is the only large empty area in the portrait plate — but at
   that size and brightness it read as a second brand element sitting level
   with the logo, which is the one thing on the page that should be alone.

   Bottom right works here because of the skirt in the offset above: the hero
   box runs 130px past the bottom of the visible viewport on phones, so
   bottom: edge + skirt lands the control an edge's width above the real
   bottom rather than underneath Safari's toolbar. It sits over dark wood,
   clear of the hand. */
@media (max-width: 700px) {
  .hero-switch {
    /* Moved to the bottom LEFT on phones. On the clay frame the sensor sits
       low and right, and on a short phone (360x640) the pill landed straight
       on top of it — covering the one thing that frame exists to show. The
       left corner is hair or shadow in all three headers.

       Raised off 0.7 because there is no hover on touch to bring it up, and
       over near-black hair the translucent rim all but vanished at that
       value. Set on the custom property, not opacity, so the entrance
       keyframe lands on this value too. */
    left: var(--edge);
    right: auto;
    --switch-rest: 0.88;
    border-color: color-mix(in srgb, var(--ink) 65%, transparent);
  }

  /* The footage is 16:9 going into a box of roughly 0.4, so cover throws away
     about three quarters of the width. Centred, that cut ran straight down
     the middle of the handset — the one thing the frame is about — and left
     the headline sitting over the lit coat on the left.

     Measured over the whole 10s clip, sampling the headline's band every
     third of a second, worst case against white type:

        object-position  50% (centred) -> 3.96:1
        object-position  66%           -> 10.59:1

     So this is not only the composition fix; it is the legibility fix. The
     still plate is still the better phone hero — it was shot portrait — but
     the footage is now a real option here rather than a broken one. */
  video.photo {
    object-position: 66% center;
  }

  /* Phones get their own frame of the same scene: 2499x3396, the clay
     extended 2022px upward for the type and 789px downward for the sensor.
     Both directions were needed, and for different reasons.

     UPWARD gave the type a ground. A 0.398 box against a 0.736 source keeps
     54% of the width and the whole height, so the headline sits on open clay:
     measured in a 6x8 grid, the top three rows run 7.2-8.9:1 mean with a
     worst pixel of 5.5:1 — no specular highlights anywhere, which is more
     than can be said for any other header here.

     DOWNWARD rescued the sensor. --hero-skirt makes the hero 100dvh + 130px
     so iOS's toolbar has hero behind it rather than the cream section, which
     means the bottom ~13% is never seen. The sensor sat at 94.4% of the
     previous frame — inside that band. No crop or object-position could lift
     it: the source is proportionally far wider than the box, so cover gives
     no vertical slack at all, and a zoom anchored low enough to move the
     sensor up needs 3.6x, by which point the window is narrower than the
     handset. Only more image below it worked. It now sits at 78.7%.

     The zoom here is a different job — recovering apparent size. Adding 21%
     of height shrank everything in the frame by the same amount, so the app
     screen had to be won back. Origin at the sensor's own position, so the
     one thing this frame exists for does not move while the handset grows
     around it. 2.96x density at this crop, so nothing is upscaled. */
  img.photo--cgm {
    object-position: 56% center;
    /* 1.10, down from 1.22. At 1.22 the handset's top corner landed at x=209
       on a 402pt phone and the CTA's right edge is also x=209 — the two
       corners met exactly, which reads as the photograph touching the button.

       The origin is the sensor, so reducing the scale pulls the handset down
       and to the right along the line between them: 54px of clearance below
       the button becomes 92px, and the corner moves 15px clear horizontally.
       The sensor itself does not move, because it is the fixed point. */
    transform: scale(1.1);
    transform-origin: 88% 79%;
  }

  .hero-switch button {
    font-size: 10px;
    padding: 0.7em 1.15em;
  }
}
