/* ============================================================
   Shelf by ChampOS: homepage design system.

   Standalone on purpose. This file does NOT load or depend on
   styles.css, which stays byte-identical to main so that
   operator-home.html restores with a single copy.

   The brief was "premium, warm, real-world, not the generic
   dark-gradient AI-startup look". The palette is still the brand
   navy, so the thing being avoided is the treatment, not the
   darkness: no glow, no starfield, no neon ramp, no gradient text.
   Warmth comes from the photography, a warm hairline on every
   surface, and a lot of empty space.
   ============================================================ */

:root {
  /* Brand */
  --navy:        #0A0E1A;
  --white:       #F1F5F9;
  --blue:        #3B82F6;
  --muted:       #93A3B8;

  /* Surfaces, lifted off the page background rather than tinted */
  --raised:      #10151F;
  --raised-2:    #151B27;

  /* Hairlines. The warm one is what keeps the dark from going clinical. */
  --line:        rgba(241, 245, 249, 0.08);
  --line-warm:   rgba(222, 198, 166, 0.13);

  /* Light blue for the second headline line, legible over photography */
  --blue-soft:   #9DC0F5;

  /* Spacious scale */
  --s-1: 8px;
  --s-2: 16px;
  --s-3: 24px;
  --s-4: 40px;
  --s-5: 64px;
  --s-6: 96px;
  --s-7: 128px;
  --s-8: 168px;

  --radius-card: 28px;
  --radius-sm:   16px;

  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

  /* Measured header height. shelf.js overwrites this on the root from
     the real element; the value here is the fallback for first paint
     and for no-JS. */
  --nav-h: 73px;
  /* Half the gap between two sections: every section on /shelf carries
     this top and bottom, so neighbours always add up to 140px. */
  --sec-gap: 60px;

  --ease: cubic-bezier(0.16, 1, 0.3, 1);
}

/* FALLBACK FACES, SIZED TO MATCH THE REAL ONES.

   Both families arrive over the network, and until they do the browser
   draws in whatever is local. Georgia italic sets 31% wider than
   Instrument Serif italic, so every accent phrase on the page starts
   too wide and snaps narrower when the real face lands: on a phone
   that changes how many lines the headline takes, and everything under
   it moves. That was the whole of this page's layout shift.

   size-adjust scales the local face so it occupies the space the real
   one will. The ratios are measured, not guessed: a string set at
   100px in each, real width over fallback width.

   Line boxes already agree because line-height is a number everywhere
   on this site rather than `normal`, so no ascent or descent override
   is needed. */
/* THE TWO REAL FACES, SERVED FROM THIS ORIGIN.

   Downloaded once from Google Fonts. They used to be named by a 1KB
   stylesheet at fonts.googleapis.com, which Lighthouse measured at
   880ms of render blocking on mobile: the bytes were nothing, the cost
   was a handshake with a third party on the critical path. The pages
   already preloaded both files, so that request bought nothing but the
   two @font-face rules below, which now simply live here.

   This is the argument the Signature face further down this file
   already makes for Caveat, applied to the other two.

   Manrope's Latin subset is ONE variable file covering 200 to 700, so
   the range is declared and the browser interpolates; six separate
   weights would be six copies of the same file.

   unicode-range is Google's own Latin range, kept so a character
   outside it falls through to the next family in the stack rather than
   being asked of a file that does not contain it.

   display: optional, matching the fallbacks below. A face that loses
   the race sits out this page view, so nothing swaps mid-view and
   nothing rewraps under the reader. */
@font-face {
  font-family: "Manrope";
  font-style: normal;
  font-weight: 200 700;
  font-display: optional;
  src: url("public/fonts/manrope-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Instrument Serif";
  font-style: italic;
  font-weight: 400;
  font-display: optional;
  src: url("public/fonts/instrument-serif-italic-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Manrope fallback";
  src: local("Arial");
  size-adjust: 101.5%;
  ascent-override: 100%;
  descent-override: 25%;
  line-gap-override: 0%;
}
@font-face {
  font-family: "Instrument Serif fallback";
  src: local("Georgia");
  size-adjust: 76.4%;
  ascent-override: 100%;
  descent-override: 25%;
  line-gap-override: 0%;
}

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

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--navy);
  color: var(--white);
  font-family: 'Manrope', 'Manrope fallback', system-ui, -apple-system, sans-serif;
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

img { max-width: 100%; display: block; }

a { color: inherit; text-decoration: none; }

:focus-visible {
  outline: 2px solid var(--blue);
  outline-offset: 3px;
  border-radius: 4px;
}

.container {
  width: 100%;
  max-width: 1280px;
  margin: 0 auto;
  padding: 0 var(--s-3);
}

[hidden] { display: none !important; }

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

/* Holds filter definitions only. display:none would stop some browsers
   resolving the filter, so it is taken out of flow instead. */
.svg-defs {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
}

/* ============================================================
   Type
   ============================================================ */

/* Large headlines run light, not black. That single choice is most
   of what separates this from the stock AI landing page. */
h1, h2, h3 {
  margin: 0;
  font-weight: 300;
  letter-spacing: -0.028em;
  line-height: 1.04;
}

/* The size every section heading on the site is set at. It had no rule
   at all, so each one was landing on the browser's own 1.5em while the
   headings that happened to carry a second class were twice that. A
   step under .guarantee-title, which is the closing statement and is
   meant to be the largest thing on its page. */
.section-title {
  font-size: clamp(1.85rem, 3.4vw, 2.7rem);
  line-height: 1.07;
}

/* Keeps a figure and its unit on one line. */
.nb { white-space: nowrap; }

/* Accent words. One italic serif phrase per headline, in light blue.
   Instrument Serif sits optically smaller than Manrope at the same
   size, so it is nudged up and its negative tracking released. */
.accent {
  /* inline-block so the hero entrance can transform it, and so the
     accent phrase never breaks across lines. */
  display: inline-block;
  font-family: 'Instrument Serif', 'Instrument Serif fallback', Georgia, serif;
  font-style: italic;
  font-weight: 400;
  font-size: 1.1em;
  letter-spacing: 0;
  color: var(--blue-soft);
}

.eyebrow {
  margin: 0;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
}

.lede {
  color: var(--muted);
  font-size: 1.02rem;
  line-height: 1.65;
  margin: 0;
}

/* ============================================================
   Buttons
   ============================================================ */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  min-height: 46px;
  padding: 12px 24px;
  border: 1px solid transparent;
  border-radius: 999px;
  font-family: inherit;
  font-size: 0.94rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  cursor: pointer;
  white-space: nowrap;
  transition: background-color 220ms var(--ease),
              border-color 220ms var(--ease),
              transform 220ms var(--ease);
}

.btn-primary {
  /* A step darker than --blue. White on --blue is 3.67:1, and 15px
     text needs 4.5:1; this is 5.17:1. The token is left alone because
     it is also the focus ring and the accent italic, which pass. */
  background: #2563EB;
  color: #fff;
}
.btn-primary:hover { background: #1D4ED8; transform: translateY(-1px); }

/* Sits on photography, so it needs its own backdrop rather than
   relying on whatever is behind it. */
.btn-ghost {
  background: rgba(241, 245, 249, 0.07);
  border-color: rgba(241, 245, 249, 0.20);
  color: var(--white);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}
.btn-ghost:hover {
  background: rgba(241, 245, 249, 0.13);
  border-color: rgba(241, 245, 249, 0.32);
  transform: translateY(-1px);
}

.btn-sm { min-height: 40px; padding: 9px 18px; font-size: 0.88rem; }

/* Quiet underlined link, used for the press line under the hero CTAs */
.link-quiet {
  color: var(--muted);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-color: rgba(147, 163, 184, 0.38);
  transition: color 200ms var(--ease), text-decoration-color 200ms var(--ease);
}
.link-quiet:hover { color: var(--white); text-decoration-color: var(--white); }

/* ============================================================
   Nav
   ============================================================ */

.nav {
  padding: var(--s-2) 0;
}

/* Travels with the scroll, and arrives transparent so the hero art
   reaches the top of the window on load. shelf.js adds .is-scrolled
   past 40px, which fades the band in. The border is declared
   transparent rather than added later so only its colour animates and
   the bar never changes height mid-transition.

   Nothing inside needs overriding for the navy: the wordmark keeps its
   two-tone "OS" and the CTA stays the only saturated thing above the
   fold. */
.nav-solid {
  position: sticky;
  top: 0;
  z-index: 50;
  background: transparent;
  border-bottom: 1px solid transparent;
  transition: background-color 280ms var(--ease), border-color 280ms var(--ease);
}
.nav-solid.is-scrolled {
  background: var(--navy);
  border-bottom-color: var(--line-warm);
}

/* Transparent over art, the links were clearing AA by 0.06, which is
   not a margin worth shipping: a warm highlight landing behind them
   would tip it. This is the usual answer for a header over a
   photograph, a short fall-off from the top edge, extended past the
   bar so it reaches zero before the boundary and leaves no seam. It
   goes once the solid band takes over. */
.nav-solid::before {
  content: "";
  position: absolute;
  inset: 0 0 -28px 0;
  pointer-events: none;
  background: linear-gradient(180deg, rgba(10, 14, 26, 0.62) 0%, rgba(10, 14, 26, 0) 100%);
  transition: opacity 280ms var(--ease);
}
.nav-solid.is-scrolled::before { opacity: 0; }

/* Keep the row above its own scrim */
.nav-solid .container { position: relative; z-index: 1; }
.nav-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
}
.nav-logo img { height: 28px; width: auto; }

.nav-links {
  display: flex;
  align-items: center;
  gap: var(--s-3);
}
.nav-links a:not(.btn) {
  font-size: 0.92rem;
  font-weight: 500;
  color: var(--muted);
  transition: color 200ms var(--ease);
}
.nav-links a:not(.btn):hover { color: var(--white); }

/* The menu button and the panel it opens. Both ship on every page and
   the button is hidden above 640px, where the inline links are the
   navigation. The panel stays in the DOM with [hidden] rather than
   being built on demand, so the markup is the same for everyone and
   the links are reachable the moment the attribute comes off. */
.nav-toggle {
  display: none;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  padding: 0;
  background: rgba(241, 245, 249, 0.07);
  border: 1px solid var(--line);
  border-radius: 10px;
  cursor: pointer;
}
.nav-toggle:focus-visible {
  outline: 2px solid var(--blue);
  outline-offset: 2px;
}

/* Three bars from one element: the middle is the box, the outer two
   are its before and after. Open, the outer two cross and the middle
   goes, which is the usual close mark without a second icon to ship. */
.nav-toggle-bars,
.nav-toggle-bars::before,
.nav-toggle-bars::after {
  content: "";
  display: block;
  position: absolute;
  width: 18px;
  height: 2px;
  border-radius: 2px;
  background: var(--white);
  transition: transform 220ms var(--ease), opacity 160ms var(--ease);
}
.nav-toggle-bars::before { transform: translateY(-6px); }
.nav-toggle-bars::after  { transform: translateY(6px); }
.nav-toggle { position: relative; }
.nav-toggle[aria-expanded="true"] .nav-toggle-bars { background: transparent; }
.nav-toggle[aria-expanded="true"] .nav-toggle-bars::before { transform: rotate(45deg); }
.nav-toggle[aria-expanded="true"] .nav-toggle-bars::after  { transform: rotate(-45deg); }

.nav-menu {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  padding: var(--s-3) 0 var(--s-2);
  border-top: 1px solid var(--line-warm);
  margin-top: var(--s-3);
}
.nav-menu[hidden] { display: none; }
.nav-menu a:not(.btn) {
  font-size: 1rem;
  font-weight: 500;
  color: var(--white);
  padding: 10px 2px;
}
.nav-menu .btn { width: 100%; }

/* Above the breakpoint the inline links are the navigation, so the
   panel cannot be opened even if the attribute were removed. 768 to
   match the button: below that the bar is the logo and the button,
   and the panel is the only way to the links. */
@media (min-width: 768px) {
  .nav-menu { display: none; }
}

/* ============================================================
   404
   ============================================================ */

.notfound-lede {
  max-width: 44ch;
  margin: var(--s-3) auto 0;
}
.notfound-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--s-2);
  margin-top: var(--s-4);
}

/* ============================================================
   Hero, shared by both options
   ============================================================ */

/* Pulled up under the transparent header so the art starts at the very
   top of the window, then sized to the whole first screen so it still
   lands exactly on the fold. svh rather than vh so mobile browser
   chrome does not push the bottom of the art away. */
.hero {
  padding: 0;
  display: flex;
  margin-top: calc(-1 * var(--nav-h));
  min-height: 100svh;
}
.hero > .hero-card { flex: 1; }

.hero-title {
  font-size: clamp(2.6rem, 6.1vw, 5rem);
  line-height: 1.02;
}
.hero-title .line { display: block; }

.hero-lede {
  margin: var(--s-3) auto 0;
  max-width: 46ch;
  font-size: 1.05rem;
}

.hero-ctas {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: var(--s-4);
}

.hero-aside {
  display: inline-block;
  margin-top: var(--s-3);
  font-size: 0.85rem;
}

/* --- Option A: content centred on a full-width photo card --- */

/* Full bleed: no radius, no side borders, and the container lives
   inside it so the plate spans the viewport while the words stay on
   the page grid. Top padding clears the overlaid nav, which is what
   keeps the headline optically centred rather than sitting low. */
.hero-card {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  min-height: 520px;
  display: grid;
  place-items: center;
  padding: var(--s-6) 0;
  background-color: #0A0E1A;
  border-bottom: 1px solid var(--line-warm);
}

/* Plate and scrim are separate layers so the art can be lifted without
   lifting the text sitting on it.

   The plate is three layers behind fluted glass: the art, the warm
   light that moves, and the flutes themselves. Only the light layer
   animates, and it animates transform and opacity alone, so the whole
   loop runs on the compositor and the main thread stays idle. */
.hero-plate {
  position: absolute;
  inset: 0;
  z-index: -2;
  overflow: hidden;
  isolation: isolate;
}

/* Overhangs the plate, because displacement at the very edge samples
   from outside the layer and would leave a transparent lip. */
.hero-art {
  position: absolute;
  inset: -24px;
  background: url("public/hero-kitchen.jpg") center 60% / cover no-repeat;
  /* Lifted past the old 1.40 because the glass costs about a sixth of
     the light on its way through. */
  filter: brightness(1.62) saturate(1.08) url("#flute");
}

/* The tungsten pools warming up. Screen rather than normal so this
   reads as light rather than as a wash laid over the shelves. */
.hero-glow {
  position: absolute;
  inset: -26%;
  mix-blend-mode: screen;
  background:
    /* The core was already close to saturating against a screen blend,
       so the extra brightness goes into the falloff and the radius
       rather than the centre: more of the shelf wall gets lit rather
       than a hotter spot in the middle of it. */
    radial-gradient(31% 40% at 50% 32%,
      rgba(255, 181, 106, 0.94) 0%,
      rgba(255, 152, 74, 0.47) 44%,
      rgba(255, 140, 60, 0) 80%),
    radial-gradient(25% 34% at 23% 36%,
      rgba(255, 170, 100, 0.66) 0%,
      rgba(255, 150, 70, 0) 78%);
  animation: heroGlow 12s cubic-bezier(0.45, 0, 0.55, 1) infinite;
}

/* The glint: a soft diagonal band that crosses the glass every nine
   seconds. It sits under the flutes, so the rib shading cuts it into
   segments as it travels and each flute catches it in turn.

   Its height is written by shelf.js, not fixed at 40%, because the
   headline sits between 25% and 36% of the hero depending on the
   window and a fixed 40% would run the band straight through the
   words at every size. --glint-h is the smaller of 40% and the gap
   above the headline, so the band is as tall as it can be and still
   never reaches the text. */
.hero-glint {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: var(--glint-h, 24%);
  pointer-events: none;
  mix-blend-mode: screen;
  /* It screens over the brightest part of the plate, where the ambient
     pool already sits, so it has to be strong to register at all. */
  background: linear-gradient(100deg,
    rgba(255, 214, 170, 0) 40%,
    rgba(255, 220, 180, 0.26) 46%,
    rgba(255, 236, 206, 0.88) 50%,
    rgba(255, 220, 180, 0.26) 54%,
    rgba(255, 214, 170, 0) 60%);
  /* Soft top and bottom edges, so the band has no horizontal seam. */
  -webkit-mask-image: linear-gradient(180deg, transparent 0%, #000 16%, #000 70%, transparent 100%);
  mask-image: linear-gradient(180deg, transparent 0%, #000 16%, #000 70%, transparent 100%);
  animation: heroGlint 9s linear infinite;
}

/* 1.4s of crossing, then it waits out the rest of the nine seconds
   off the right-hand edge. The return to the left happens while it is
   at zero opacity, so there is no jump to hide. */
@keyframes heroGlint {
  0%    { transform: translate3d(-100%, 0, 0); opacity: 0;
          animation-timing-function: cubic-bezier(0.32, 0.12, 0.68, 0.88); }
  1%    { opacity: 1; }
  14.5% { opacity: 1; }
  15.6% { transform: translate3d(100%, 0, 0); opacity: 0; }
  100%  { transform: translate3d(-100%, 0, 0); opacity: 0; }
}

/* Lights coming on. One pass per page load, over the whole plate so
   the flutes and the warm light come up with the art. It sits above
   everything in the plate and below the scrim, so the headline and the
   buttons are at full strength from the first frame. */
.hero-lightson {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: #000;
  animation: heroLightsOn 1800ms cubic-bezier(0.16, 1, 0.3, 1) both;
}

@keyframes heroLightsOn {
  from { opacity: 0.60; }
  to   { opacity: 0; }
}

/* Reeded glass: the face and seam of each flute, plus the faint
   horizontal joins of a walk-in door. Static and normally blended, so
   it costs one paint and nothing per frame. The refraction itself is
   the SVG displacement on .hero-art, not this.

   The flutes are uneven, which a repeating-linear-gradient cannot be,
   so the shading comes from an image cut from the same widths as the
   displacement map. Both are inset -24px, because the filter's user
   space starts at .hero-art's border box: matching the two insets is
   what keeps the seam sitting on the refraction rather than beside
   it. */
.hero-ribs {
  position: absolute;
  inset: -24px;
  background-image:
    url("public/flute-shade.png"),
    repeating-linear-gradient(180deg,
      rgba(255, 255, 255, 0) 0px,
      rgba(255, 255, 255, 0) 268px,
      rgba(0, 0, 0, 0.14) 269px,
      rgba(255, 255, 255, 0.035) 271.5px,
      rgba(255, 255, 255, 0) 274px);
  background-size: 2560px 100%, auto;
  background-repeat: repeat, repeat;
  /* Glass is only visible where something is lit behind it. This runs
     the flutes down to almost nothing in the dark, where at full
     strength they stop reading as glass and start reading as stripes,
     and leaves them where the warm light comes through. */
  -webkit-mask-image: radial-gradient(86% 52% at 46% 13%, #000 0%, rgba(0, 0, 0, 0.92) 32%, rgba(0, 0, 0, 0.54) 58%, rgba(0, 0, 0, 0.18) 80%, rgba(0, 0, 0, 0.07) 100%);
  mask-image: radial-gradient(86% 52% at 46% 13%, #000 0%, rgba(0, 0, 0, 0.92) 32%, rgba(0, 0, 0, 0.54) 58%, rgba(0, 0, 0, 0.18) 80%, rgba(0, 0, 0, 0.07) 100%);
}

/* The only motion on the site.
   The percentages are of this element, which is 152% of the plate
   because of the -26% inset, so the 13.2% of travel below is 20% of
   the hero's width: 288px at 1440. Opacity runs 0.06 to 1.00, so the
   lit shelves go out and come back rather than just dimming, and the
   loop is 12s so a dim-to-bright transit takes about four seconds.

   Brightness and travel are deliberately out of phase. An ease-in-out
   has zero slope at every keyframe, so wherever the two line up the
   light is briefly neither moving nor changing, and at the loop's seam
   that dead moment is several seconds long. So the travel turns at 0%
   and 67% while the brightness peaks at 42% and bottoms at 85%: at
   every point in the loop at least one of them is moving. */
@keyframes heroGlow {
  0%   { transform: translate3d(-6.6%, 4.0%, 0) scale(0.88); opacity: 0.46; }
  34%  { transform: translate3d(5.4%, -3.6%, 0) scale(1.20); opacity: 0.92; }
  42%  { transform: translate3d(6.1%, -1.8%, 0) scale(1.17); opacity: 1.00; }
  67%  { transform: translate3d(6.6%, 3.2%, 0) scale(1.02); opacity: 0.58; }
  85%  { transform: translate3d(-1.2%, 4.5%, 0) scale(0.93); opacity: 0.06; }
  100% { transform: translate3d(-6.6%, 4.0%, 0) scale(0.88); opacity: 0.46; }
}

/* Scrolled past, so there is nothing to see: stop the loop rather than
   leave the compositor animating a layer nobody is looking at. */
.hero.is-still .hero-glow,
.hero.is-still .hero-glint { animation-play-state: paused; }

/* Reduced motion gets the frame the loop spends longest near, so the
   hero still looks lit rather than switched off. */
@media (prefers-reduced-motion: reduce) {
  .hero-glow {
    animation: none;
    transform: translate3d(1.0%, -0.8%, 0) scale(1.06);
    opacity: 0.86;
  }
  /* The lights are already on and nothing crosses the glass. */
  .hero-glint { display: none; }
  .hero-lightson { animation: none; opacity: 0; }
}

/* A vignette in reverse: darkest under the text block, opening up
   toward the edges so the shelves still read. This is what holds the
   subline's contrast now that the warm light runs brighter. */
.hero-card::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background:
    radial-gradient(56% 56% at 50% 47%, rgba(10, 14, 26, 0.76) 0%, rgba(10, 14, 26, 0.54) 45%, rgba(10, 14, 26, 0.08) 82%),
    linear-gradient(180deg, rgba(10, 14, 26, 0.26) 0%, rgba(10, 14, 26, 0.05) 42%, rgba(10, 14, 26, 0.58) 100%);
}

.hero-card .hero-inner {
  max-width: 760px;
  margin: 0 auto;
  text-align: center;
}
.hero-card .hero-ctas { justify-content: center; }

/* --- Option B: text beside the photo, photo left ungraded --- */

.hero-split {
  display: grid;
  grid-template-columns: 1.02fr 0.98fr;
  gap: var(--s-6);
  align-items: center;
}
.hero-split .hero-lede { margin-left: 0; margin-right: 0; }
.hero-split .hero-title { font-size: clamp(2.5rem, 4.6vw, 4.1rem); }

.hero-photo {
  position: relative;
  isolation: isolate;
  aspect-ratio: 4 / 5;
  border-radius: var(--radius-card);
  overflow: hidden;
  border: 1px solid var(--line-warm);
  background-color: #0A0E1A;
}
/* No text sits on this one, so it runs brighter and warmer than the
   hero plate. Only enough shading to seat it against the page. */
.hero-photo::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -2;
  background: url("public/hero-kitchen.jpg") center / cover no-repeat;
  filter: brightness(1.45) saturate(1.06);
}
.hero-photo::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(195deg, rgba(10, 14, 26, 0) 38%, rgba(10, 14, 26, 0.52) 100%);
}

/* The play mark on the hero's second button. Sized to the cap height
   of the label beside it rather than to the button. */
.btn-play {
  width: 13px;
  height: 13px;
  margin-right: -2px;
  fill: currentColor;
  flex: none;
}

/* ============================================================
   Video lightbox
   ============================================================ */

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: grid;
  place-items: center;
  padding: var(--s-3);
}
.lightbox[hidden] { display: none; }

.lightbox-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(10, 14, 26, 0.85);
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
  animation: lightboxIn 220ms var(--ease) both;
}

/* A VERTICAL VIDEO IS SIZED BY THE HEIGHT IT HAS, NOT THE WIDTH.

   Sized by width, a 9:16 embed on a laptop is either taller than the
   window or pillarboxed in a 16:9 box with black down both sides. So
   the panel's WIDTH is derived from the height available: 82dvh of
   room, times 9/16, and never wider than the window can hold. The
   frame is then simply 9:16 and the iframe fills it exactly, so there
   are no bars on any axis.

   82dvh rather than 100: the close button sits 48px above the panel
   and the dialog carries its own padding, and both have to fit on a
   short laptop screen. dvh rather than vh so a phone's address bar
   sliding away does not leave the video cropped. */
.lightbox-panel {
  position: relative;
  width: min(calc(82dvh * 9 / 16), calc(100vw - 2 * var(--s-3)));
  animation: lightboxPanelIn 260ms var(--ease) both;
}

.lightbox-frame {
  position: relative;
  aspect-ratio: 9 / 16;
  overflow: hidden;
  border-radius: var(--radius-card);
  border: 1px solid var(--line-warm);
  background: #000;
  box-shadow: 0 40px 90px rgba(0, 0, 0, 0.6);
}
/* A broadcast segment is 16:9, where the walkthrough is a phone. Only
   the two measurements change; everything else about the dialog is the
   same object. */
.lightbox-wide .lightbox-panel {
  width: min(calc(82dvh * 16 / 9), calc(100vw - 2 * var(--s-3)));
}
.lightbox-wide .lightbox-frame { aspect-ratio: 16 / 9; }

.lightbox-frame iframe,
.lightbox-frame video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
}

.lightbox-close {
  position: absolute;
  top: -48px;
  right: 0;
  width: 36px;
  height: 36px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 1px solid rgba(241, 245, 249, 0.18);
  border-radius: 999px;
  background: rgba(241, 245, 249, 0.07);
  color: var(--white);
  cursor: pointer;
  transition: background-color 200ms var(--ease), border-color 200ms var(--ease);
}
.lightbox-close:hover {
  background: rgba(241, 245, 249, 0.14);
  border-color: rgba(241, 245, 249, 0.3);
}
.lightbox-close svg {
  width: 15px;
  height: 15px;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  fill: none;
}

/* The page behind must not scroll under an open dialog. */
.is-locked, .is-locked body { overflow: hidden; }

@keyframes lightboxIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@keyframes lightboxPanelIn {
  from { opacity: 0; transform: translate3d(0, 10px, 0) scale(0.985); }
  to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .lightbox-backdrop,
  .lightbox-panel { animation: none; }
}

@media (max-width: 640px) {
  .lightbox { padding: var(--s-2); }
  .lightbox-close { top: -44px; }
  .lightbox-frame { border-radius: 18px; }
  /* The close button and the smaller padding leave a little more room
     for the video than a desktop window does. */
  .lightbox-panel { width: min(calc(84dvh * 9 / 16), calc(100vw - 2 * var(--s-2))); }
}

/* --- 1. Intro ------------------------------------------------------ */

.intro {
  position: relative;
  overflow: hidden;
  min-height: 85vh;
  display: grid;
  align-items: center;
  padding: var(--sec-gap) 0;
}

.intro-split {
  display: grid;
  grid-template-columns: 1.05fr 0.95fr;
  align-items: center;
  gap: var(--s-5);
}

.intro-title {
  font-size: clamp(2.1rem, 3.9vw, 3.4rem);
  line-height: 1.07;
  max-width: 15ch;
}

.intro-lede {
  margin: var(--s-3) 0 0;
  max-width: 46ch;
}

.intro-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-3);
  margin-top: var(--s-4);
}

/* A button that reads as a link, because that is what it does next to
   the primary beside it. */
.intro-watch {
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: 0.92rem;
  cursor: pointer;
}

.intro-phone { justify-self: center; }

/* The phone and the cards share one positioning context so the cards
   can overlap the frame without being clipped by it. */
.intro-stage {
  position: relative;
  width: max-content;
  margin: 0 auto;
}

/* HIS WORDS, UNDER THE PHONE, WHILE HE SAYS THEM. The clip is silent, so
   this is how the walk reads with the sound off. It holds its height
   whether or not a line is showing, because a bar that appears and
   disappears would shunt the phone up and down the page five times in the
   first twelve seconds. */
.intro-captions {
  min-height: 3.2em;
  margin: var(--s-4) auto 0;
  max-width: 30ch;
  text-align: center;
  font-size: 1.06rem;
  line-height: 1.45;
  font-weight: 500;
  color: #F4F7FB;
  opacity: 0;
  transition: opacity 180ms ease;
}

.intro-captions.is-on { opacity: 1; }

/* the two that describe the screen rather than quote him */
.intro-captions.is-aside {
  font-weight: 400;
  font-style: italic;
  color: var(--muted);
}

@media (max-width: 720px) {
  .intro-captions { font-size: 0.98rem; min-height: 3.6em; }
}

@media (prefers-reduced-motion: reduce) {
  .intro-captions { transition: none; }
}

.intro-caption {
  margin: var(--s-3) 0 0;
  min-height: 1.5em;
  text-align: center;
  font-size: 0.82rem;
  color: var(--muted);
}

/* Glass cards beside the phone. They sit outside the frame's overflow,
   overlapping it by about a third, and each one arrives at its own
   second of the recording. */
.intro-callouts {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.intro-callout {
  position: absolute;
  margin: 0;
  padding: 10px 14px;
  border: 1px solid rgba(241, 245, 249, 0.14);
  border-radius: 12px;
  background: rgba(16, 21, 31, 0.78);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  box-shadow: 0 18px 36px rgba(0, 0, 0, 0.45);
  font-size: 0.86rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  white-space: nowrap;
  opacity: 0;
  transform: translateY(10px);
  transition: opacity 420ms var(--ease), transform 420ms var(--ease);
}
.intro-callout.is-in { opacity: 1; transform: none; }
.intro-callout.is-accent { color: var(--blue); }

/* Left, then right, each a step further down the frame. INTRO_CALLOUTS
   carries two cards since the clip was trimmed, so the third rule below
   matches nothing today. It stays: without it a third card added later
   would have no top at all and would stack on the first. */
.intro-callout.is-left { left: -86px; }
.intro-callout.is-right { right: -86px; }
.intro-callout.is-left:nth-of-type(1) { top: 14%; }
.intro-callout.is-right:nth-of-type(2) { top: 46%; }
.intro-callout.is-left:nth-of-type(3) { top: 76%; }

/* These two blocks MUST stay below the rules above. Same specificity,
   so source order decides, and when they sat higher up the file the
   insets silently never applied: the right-hand card was 47px outside
   the window at 760 and nothing said so.

   Below 1024 the phone column is too narrow for a card to hang off the
   frame, so they sit just inside it instead, which cannot overflow
   whatever the text says. */
@media (max-width: 1024px) {
  .intro-callout { font-size: 0.8rem; padding: 8px 12px; }
  .intro-callout.is-left { left: 4px; }
  .intro-callout.is-right { right: 4px; }
}

/* Below this the phone is the whole of the composition. The brief said
   600, but between 600 and 720 the intro is still two columns and a
   card in the 76% slot lands on the buttons: measured 22x41px of
   overlap at 660 and at 700, clear at 760. That was measured when a
   third card sat there; the rule stays so the slot is safe to use. */
@media (max-width: 720px) {
  .intro-callouts { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .intro-callout { transition: none; }
}

.phone-frame,
.phone-still {
  position: relative;
  width: 300px;
  max-width: 100%;
  /* The recording's own shape, so the box is the right size before
     either the clip or its poster has arrived. */
  aspect-ratio: 390 / 844;
  margin: 0 auto;
}
.phone-frame {
  overflow: hidden;
  /* A dark frame, not a lit one, so the phone sits on the navy rather
     than glowing against it. */
  border: 1.5px solid #2A3344;
  border-radius: 38px;
  background: #000;
  box-shadow: 0 34px 70px rgba(0, 0, 0, 0.62);
}
.phone-frame video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
/* The standby still is a frame of the recording, so it wears the same
   frame the clip does rather than one of its own. */
.phone-still img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border: 1.5px solid #2A3344;
  border-radius: 38px;
  filter: drop-shadow(0 30px 60px rgba(0, 0, 0, 0.62));
}


/* THE WALKTHROUGH, STANDING BEHIND THE RECORDING.

   The same object the kitchen section used to hold, at 273x485 against
   the recording's 300x649, offset down and to the right so it reads as
   a second screen behind the first rather than a card beside it. The
   numbers come off the hero as measured at 1280 and 1440, where the
   container is capped so both are the same: 70px of the poster is
   behind the phone, 203px of it shows, its play mark lands 77px clear
   of the phone's edge so it is never half covered, the mark sits 23px
   below the accent callout rather than under it, and 24px is still
   free before the split ends.

   Absolutely positioned, which keeps it out of the stage's max-content
   width: the recording sits where it always did and nothing shifts
   when the poster loads.

   Desktop only, and 1280 is where the room actually appears: the
   container is capped at 1280, so from there up the hero is the same
   size at every width and the poster clears the split by 23px. At
   1100 it hangs 19px past the edge and the hero clips it, and pulling
   it in far enough to fit puts the play mark behind the phone. So
   below 1280 it goes, and the quiet link in the actions carries the
   walkthrough instead. */
.intro-walk {
  /* --peek is the strip of poster that shows past the phone, --tuck
     the part hidden behind it. Everything else is derived, so moving
     the poster is one number and not five. */
  --peek: 203px;
  --tuck: 70px;

  display: none;
  position: absolute;
  top: 24%;
  right: calc(var(--peek) * -1);
  width: calc(var(--peek) + var(--tuck));
}

.intro-walk .kitchen-stage {
  border-radius: 30px;
  box-shadow: 0 26px 56px rgba(0, 0, 0, 0.62);
}

/* Dimmer than the section's 0.55. The poster is a lit photograph and
   the recording beside it is a black screen, so at the old value the
   second phone read as the main one. */
.intro-walk .kitchen-poster { opacity: 0.34; }
.intro-walk button.kitchen-stage:hover .kitchen-poster { opacity: 0.5; }

/* THE PLAY MARK AND THE LABEL ARE CENTRED ON --peek, NOT ON THE FRAME.
   A third of the frame is behind the phone, so anything centred on the
   frame lands half hidden: the label came out reading "in a real
   kitchen" with the first two words lost behind the recording. */
.intro-walk .kitchen-play {
  left: auto;
  right: calc(var(--peek) / 2);
  transform: translate(50%, -50%);
  width: 46px;
  height: 46px;
}
.intro-walk button.kitchen-stage:hover .kitchen-play {
  transform: translate(50%, -50%) scale(1.06);
}
.intro-walk .kitchen-play svg { width: 17px; height: 17px; margin-left: 2px; }

.intro-walk .kitchen-label {
  left: auto;
  right: 0;
  width: var(--peek);
  top: calc(50% + 40px);
  padding: 0 10px;
  font-size: 0.72rem;
  line-height: 1.4;
}
.intro-walk .kitchen-dot { color: var(--muted); }

@media (min-width: 1280px) {
  .intro-walk { display: block; }
  /* The recording moves left to make the room the poster needs on its
     right. The callouts live in the stage, so they travel with it, and
     the longest line of text is still 134px clear of the nearer one. */
  .intro-phone { position: relative; right: 100px; }
  /* Two openers saying the same words, one of them with a picture. */
  .intro-watch { display: none; }
}

/* --- 2. How it works, as one slim row ------------------------------ */

/* The controls sit in the hero now, close under the recording, so the
   gap above them is a gap inside one block rather than between two. */
.steps-row {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--s-3);
  list-style: none;
  margin: var(--s-4) 0 0;
  padding: 0;
}
.steps-row .step { display: flex; }

/* The same box whether it is a button driving the clip or, with no
   recording to drive, a plain div. Everything a button brings of its
   own is reset here, so the two are indistinguishable until one of
   them is pressed. */
.step-btn {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 5px;
  width: 100%;
  padding: var(--s-3) 0 0;
  margin: 0;
  font: inherit;
  text-align: left;
  color: inherit;
  background: transparent;
  border: 0;
  border-radius: 0;
}

/* The rule above each step is a track, and the fill runs along it with
   the clip. Transform rather than width, so the browser can do it on
   the compositor while a video decodes beside it. */
.step-bar {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  background: var(--line);
  overflow: hidden;
}
.step-fill {
  display: block;
  width: 0%;
  height: 100%;
  /* Blue, like everything else. This was the last amber left on a
     page after the door light went. */
  background: var(--blue);
}
button.step-btn { cursor: pointer; }
button.step-btn:hover .step-head { color: #fff; }
button.step-btn:focus-visible { outline: 2px solid var(--blue); outline-offset: 3px; }

.step-num {
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  color: var(--muted);
  transition: color 240ms var(--ease);
}
.step-head {
  font-size: 0.92rem;
  font-weight: 600;
  line-height: 1.3;
  color: var(--white);
  transition: color 200ms var(--ease);
}
.step-btn .step-copy {
  margin: 0;
  font-size: 0.82rem;
  line-height: 1.5;
  color: var(--muted);
}

/* Lit as the clip runs through this step's own stretch of it. The warm
   rule is the light the door glow uses, so the strip reads as part of
   the recording rather than as a menu beside it. */
.step-btn.is-on .step-num { color: var(--blue-soft); }

/* --- 3. Every vendor, one order ------------------------------------ */

.oneplace-section {
  padding: var(--s-7) 0;
  background: var(--raised);
  border-top: 1px solid var(--line-warm);
  /* No rule on the way out: the warm light at the top of the closing
     pair below is the next thing the eye meets, and two lines that
     close together read as a seam. */
  text-align: center;
}
.oneplace-head { max-width: 62ch; margin: 0 auto; }
.oneplace-lede { margin: var(--s-3) auto 0; max-width: 56ch; }

.oneplace-figure { max-width: 760px; margin: var(--s-5) auto 0; }
/* The only unsized box left on the page: without this the figure is
   zero tall until the SVG lays out, and everything under it jumps.
   The ratio is the viewBox, so the two have to move together. */
.oneplace-figure svg {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 760 / 296;
}

.fig-tile rect { fill: var(--raised-2); stroke: var(--line); stroke-width: 1; }
.fig-rule rect { fill: rgba(241, 245, 249, 0.17); }
/* The vendors, named. Set like the rest of the drawing rather than
   like a logo: one weight, one colour, no marks. */
.fig-vendor {
  fill: var(--white);
  font-family: inherit;
  font-size: 14px;
  font-weight: 500;
  letter-spacing: -0.01em;
}
.fig-vendor-out { font-size: 13px; }

/* One line by default; the phone rule below swaps them. */
.fig-v-stack { display: none; }
.fig-card { fill: var(--navy); stroke: rgba(59, 130, 246, 0.38); stroke-width: 1.4; }
.fig-card-head { fill: var(--white); font-family: inherit; font-size: 17px; font-weight: 600; }
.fig-row rect { fill: rgba(241, 245, 249, 0.22); }
.fig-save { fill: rgba(59, 130, 246, 0.14); }
.fig-save-text { fill: var(--blue-soft); font-family: inherit; font-size: 13px; font-weight: 600; }
.oneplace-points {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--s-3);
  list-style: none;
  max-width: 900px;
  margin: var(--s-5) auto 0;
  padding: 0;
  text-align: left;
}
.oneplace-points li {
  padding-top: var(--s-2);
  border-top: 1px solid var(--line-warm);
  font-size: 1rem;
  font-weight: 500;
  line-height: 1.5;
  color: var(--white);
}

/* --- 3. Questions owners ask ---------------------------------------- */

.faq-section { padding: var(--sec-gap) 0; }

/* Full width and last on the page, so the list is the section rather
   than the right-hand half of one. */
.faq-title {
  text-align: center;
  margin-bottom: var(--s-5);
}
.faq-list { max-width: 760px; margin: 0 auto; }

.faq-aside {
  max-width: 760px;
  margin: var(--s-4) auto 0;
  color: var(--muted);
  font-size: 0.92rem;
  text-align: center;
}

/* <details> rather than a hand-built accordion: the keyboard and the
   screen reader behaviour come with it. */
.faq-item {
  border-top: 1px solid var(--line);
}
.faq-item:last-child { border-bottom: 1px solid var(--line); }

.faq-item summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  padding: var(--s-3) 0;
  cursor: pointer;
  list-style: none;
  font-size: 1.02rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  transition: color 200ms var(--ease);
}
.faq-item summary::-webkit-details-marker { display: none; }
.faq-item summary:hover { color: var(--blue-soft); }

/* The marker is drawn, not typed, so it can rotate without reflowing
   the line it sits on. */
.faq-item summary::after {
  content: "";
  flex: none;
  width: 11px;
  height: 11px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(45deg) translate(-2px, -2px);
  opacity: 0.6;
  transition: transform 240ms var(--ease);
}
.faq-item[open] summary::after {
  transform: rotate(225deg) translate(-2px, -2px);
}

.faq-item p {
  margin: 0 0 var(--s-3);
  max-width: 54ch;
  color: var(--muted);
  line-height: 1.65;
}

.guarantee-title {
  max-width: 18ch;
  margin: 0 auto;
  font-size: clamp(2rem, 4.2vw, 3.2rem);
  line-height: 1.08;
}
.guarantee-line {
  margin: var(--s-3) auto 0;
  max-width: 52ch;
  font-size: 1.02rem;
  line-height: 1.65;
  color: var(--muted);
}
/* ============================================================
   /audit
   ============================================================ */

.audit-hero {
  position: relative;
  padding: var(--s-6) 0;
  overflow: hidden;
}
/* The same grain /story carries, for the same reason. */
.audit-hero::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0.035;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
}
.audit-hero > .container { position: relative; z-index: 1; }

.audit-split {
  display: grid;
  grid-template-columns: 1fr 460px;
  gap: var(--s-6);
  align-items: start;
}
.audit-title {
  margin: 0;
  max-width: 13ch;
  font-size: clamp(2.3rem, 4.6vw, 3.7rem);
  line-height: 1.05;
}
.audit-sub {
  margin: var(--s-3) 0 0;
  max-width: 48ch;
}

.audit-checks {
  list-style: none;
  margin: var(--s-4) 0 0;
  padding: 0;
  display: grid;
  gap: 12px;
}
.audit-checks li {
  position: relative;
  padding-left: 30px;
  font-size: 0.99rem;
  color: var(--white);
}
/* A tick drawn in CSS: two borders on a rotated box. One less file,
   and it takes the accent colour without a second asset. */
.audit-checks li::before {
  content: "";
  position: absolute;
  left: 4px;
  top: 0.42em;
  width: 6px;
  height: 11px;
  border: solid var(--blue-soft);
  border-width: 0 2px 2px 0;
  transform: rotate(42deg);
}

/* The form, lifted off the page. This is the thing the page is for. */
.audit-card {
  padding: var(--s-4);
  background: var(--raised);
  border: 1px solid var(--line-warm);
  border-radius: 20px;
  box-shadow: 0 34px 70px rgba(0, 0, 0, 0.5);
}

/* --- the form itself -------------------------------------------------
   These rules came through the /audit rebuild unchanged: the submit
   path they dress is the one that is already proven, so the markup
   hooks and the states it sets stay exactly as they were. */

.audit-form {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--s-2);
  text-align: left;
}
.field { display: flex; flex-direction: column; gap: 6px; }
.field-wide { grid-column: 1 / -1; }

.audit-form label {
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--muted);
}
.audit-form input {
  width: 100%;
  min-height: 46px;
  padding: 12px 15px;
  font-family: inherit;
  font-size: 0.95rem;
  color: var(--white);
  background: var(--raised-2);
  border: 1px solid var(--line);
  border-radius: 12px;
  transition: border-color 200ms var(--ease), box-shadow 200ms var(--ease);
}
.audit-form input:focus {
  outline: none;
  border-color: rgba(59, 130, 246, 0.65);
  box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
}
.audit-form .btn { width: 100%; }
.audit-form button[disabled] { opacity: 0.55; cursor: default; }

/* Said to a screen reader, not drawn. Clipped rather than hidden,
   because display:none and visibility:hidden both take it out of the
   accessibility tree, which is the one place it is meant to be. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* The honeypot. Off the page rather than display:none, because the
   crude bots this catches skip anything that is plainly hidden. */
.form-bot {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

/* Last thing read before the button is pressed */
.form-guarantee {
  grid-column: 1 / -1;
  margin: var(--s-2) 0 0;
  text-align: center;
  font-size: 0.95rem;
  font-weight: 700;
  line-height: 1.45;
  color: var(--white);
}

/* The quieter half of the offer, under the louder one. */
.form-shadow {
  margin: 10px 0 0;
  font-size: 0.84rem;
  line-height: 1.55;
  color: var(--muted);
  text-align: center;
}

.form-status {
  grid-column: 1 / -1;
  margin: 0;
  min-height: 1.4em;
  font-size: 0.88rem;
  color: var(--muted);
}
.form-status.is-ok    { color: #4ADE80; }
.form-status.is-error { color: #F87171; }

.sample-card {
  max-width: 920px;
  margin: var(--s-5) auto 0;
  padding: var(--s-4);
  background: var(--raised);
  border: 1px solid var(--line);
  border-radius: 18px;
}
.sample-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--s-2);
  margin-bottom: var(--s-3);
  padding-bottom: var(--s-3);
  border-bottom: 1px solid var(--line);
}
/* A table with five rows does not need to fight a phone: it scrolls
   sideways inside its own card rather than squeezing to nothing. */
.sample-scroll { overflow-x: auto; }
.sample-table th,
.proof-line {
  max-width: 920px;
  margin: var(--s-4) auto 0;
  font-size: 0.95rem;
  color: var(--muted);
  text-align: center;
}

/* ============================================================
   /story: a letter
   ============================================================ */

@font-face {
  /* Seventeen characters of Caveat, which is all the two names use.
     Served from here rather than from Google: the subset URL Google
     hands out is a session-ish one that can rotate, and 8 KB is not
     worth a third-party connection. optional, like the other two, so
     it can never move the signature after paint. */
  font-family: "Signature";
  src: url("public/caveat-signature.woff2") format("woff2");
  font-weight: 500;
  font-display: optional;
}

/* The letter's last line, set like the quote it used to be, in the
   place it actually belongs. The two paragraphs share a wrapper so
   they stack in the grid's second column rather than wrapping back
   under the date. */
.letter-para-payoff { margin-top: var(--s-5); }
.story-close { padding-top: var(--s-4); }

/* ============================================================
   /press
   ============================================================ */

/* TALL ENOUGH THAT THE FOOTER STARTS AT THE FOLD, which is what keeps
   this page's layout shift at zero.

   config.js builds the card list after the first paint, because the
   cards come from PRESS and the list is empty in the markup. The list
   went from nothing to 289px and carried the footer 263px down the
   screen with it, inside an 844px viewport: CLS 0.133 on a page that
   had been 0. It had always done this; the stylesheet at
   fonts.googleapis.com used to delay the first paint long enough that
   the cards were already in place by the time anything was drawn, and
   self-hosting the faces took that accidental cover away.

   Reserving a height for the list would fix one card and break on two.
   Reserving the SECTION means the footer begins at the fold whatever
   the list does, so the growth happens below the viewport and is not a
   shift anybody sees. svh, not dvh: dvh changes as a phone's address
   bar slides away, which would be a layout shift of its own.

   It also suits the page. With a single entry the old section ended
   two thirds of the way up the screen and the footer sat in the middle
   of the window, which read as a page that had failed to load. */
/* THE MIN-HEIGHT IS A CLS FIX, not a look. config.js writes the list
   after the first paint, so the page gets taller once the cards land
   and the footer moves. Reserving a screen's worth keeps the footer
   off the first screen until that has happened, and a footer nobody
   can see cannot shift.

   The extra 48px is the margin that makes it true. At exactly
   100svh - 76px the footer's top edge lands ON the fold, which scored
   a 0.001 rather than the zero this page is held to. */
.press-section {
  position: relative;
  padding: var(--s-6) 0 var(--s-7);
  overflow: hidden;
  --reserve: calc(100svh - 76px + 48px);
  min-height: var(--reserve);
}

/* THE BACKDROP IS THE HEIGHT OF THE RESERVE, NOT OF THE LIST. At
   inset: 0 it grows when the cards land, and .bg-ring, whose top is a
   percentage of it, slides a couple of pixels with it: a 0.004 on a
   page held to zero. Anchoring it to the same reserve the section is
   already holding open means nothing about it depends on how many
   pieces are in the list. The light belongs to the top of the page
   anyway, rather than stretched down a list of any length. */
.press-section > .bg {
  bottom: auto;
  height: var(--reserve);
}
.press-section > .container { position: relative; z-index: 1; }

.press-head { max-width: 820px; margin: 0 auto var(--s-6); }
.press-title-main {
  margin: 0;
  font-size: clamp(2.6rem, 7vw, 5rem);
  line-height: 1;
}
.press-sub { margin: var(--s-3) 0 0; max-width: 46ch; }

.press-list {
  display: grid;
  gap: var(--s-2);
  max-width: 820px;
  margin: 0 auto;
}

/* The whole card is the link, so there is one target rather than a
   headline a thumb has to find. */
.press-card {
  display: block;
  padding: var(--s-4);
  background: var(--raised);
  border: 1px solid var(--line);
  border-radius: 18px;
  color: inherit;
  transition: border-color 220ms var(--ease), background 220ms var(--ease);
}
.press-card:hover,
.press-card:focus-visible {
  background: var(--raised-2);
  border-color: var(--line-warm);
}

.press-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--s-2);
  margin: 0 0 12px;
}
.press-outlet {
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--blue-soft);
}
.press-date { font-size: 0.82rem; color: var(--muted); }

.press-title {
  margin: 0;
  font-size: clamp(1.15rem, 2vw, 1.4rem);
  font-weight: 500;
  line-height: 1.35;
  letter-spacing: -0.015em;
  color: var(--white);
}
.press-note {
  margin: 10px 0 0;
  font-size: 0.95rem;
  line-height: 1.65;
  color: var(--muted);
}
.press-cue {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  margin-top: var(--s-3);
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--blue-soft);
}
.press-card:hover .press-cue { text-decoration: underline; }

.press-empty {
  max-width: 820px;
  margin: 0 auto;
  font-size: 1.05rem;
  color: var(--muted);
}

/* ============================================================
   Backdrop

   ONE SYSTEM, AND IT IS DRAWN, NOT PHOTOGRAPHED.

   What was here before: six small WebP files of warm light thrown out
   of focus, stretched across headers and bands at whatever size the
   section happened to be. A 1600x460 image across a 2560px header on a
   2x screen is being asked for four times the pixels it has, and that
   is what made them read as cheap. The fix is not a bigger file. A
   photograph of nothing in particular, blurred, will look blurred at
   every size you ship it at.

   So nothing here is a raster. Three layers of CSS:

     the grid    1px rules on a 72px pitch, masked to fade out before
                 they reach an edge. A hairline is a hairline at any
                 density, which is the whole point: it cannot soften.
                 It also says "software" without a stock photograph of
                 a laptop having to say it.

     the core    one soft pool of ChampOS blue. The position is the
                 only thing that changes between placements, so no two
                 sections look stamped from the same sheet while the
                 system stays one system.

     the orbit   a thin ellipse, which is the mark's own ring at the
                 scale of a page. images/ChampOS-Icon-Color.svg draws a
                 lit core with a ring around it; this is that, large
                 and quiet enough to register as structure rather than
                 as a logo somebody enlarged.

   The homepage hero keeps its photograph. It is now the only
   photographic background on the site, which is what makes it read as
   a decision instead of as one of six smudges.

   Nothing animates, so prefers-reduced-motion has nothing to switch
   off, and every layer is absolutely positioned behind its section's
   content, so none of it can move anything.
   ============================================================ */

.bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
  /* Where the light sits. Overridden per placement, below. */
  --gx: 50%;
  --gy: 8%;
}

/* The grid. Masked on both axes so it never collides with an edge:
   a rule that stops dead at the viewport looks like a bug. */
.bg::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image:
    linear-gradient(to right, rgba(241, 245, 249, 0.032) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(241, 245, 249, 0.032) 1px, transparent 1px);
  background-size: 72px 72px;
  background-position: center top;
  -webkit-mask-image: radial-gradient(118% 92% at var(--gx) var(--gy), #000 12%, transparent 72%);
  mask-image: radial-gradient(118% 92% at var(--gx) var(--gy), #000 12%, transparent 72%);
}

/* The core. Two stops rather than one, so the falloff has a shoulder
   and does not read as a flat disc. */
.bg::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(62% 66% at var(--gx) var(--gy),
      rgba(59, 130, 246, 0.17) 0%,
      rgba(59, 130, 246, 0.07) 38%,
      transparent 72%),
    radial-gradient(34% 40% at var(--gx) var(--gy),
      rgba(157, 192, 245, 0.10) 0%,
      transparent 70%);
}

/* The orbit. A border, not an image, so the stroke stays one device
   pixel honest at any zoom. scaleY tips the circle into the ellipse the
   mark draws; the rotation keeps it off the horizontal. */
.bg-ring {
  position: absolute;
  top: var(--gy);
  left: var(--gx);
  width: min(128vw, 1180px);
  aspect-ratio: 1;
  margin: -64vw 0 0 min(-64vw, -590px);
  border: 1px solid rgba(125, 178, 251, 0.11);
  border-radius: 50%;
  transform: rotate(-17deg) scaleY(0.4);
  -webkit-mask-image: linear-gradient(to bottom, #000 30%, transparent 92%);
  mask-image: linear-gradient(to bottom, #000 30%, transparent 92%);
}
/* The second ring is the only thing that makes the first read as an
   orbit rather than as a stray ellipse. */
.bg-ring::after {
  content: "";
  position: absolute;
  inset: 13%;
  border: 1px solid rgba(125, 178, 251, 0.07);
  border-radius: 50%;
}

/* WHERE THE LIGHT SITS, per placement. The only per-section variable.
   /audit puts the form card in the right-hand column, so its light
   stays left of the thing somebody has to read and type into. */
.bg-shelf { --gx: 63%; --gy: 2%; }
.bg-audit { --gx: 20%; --gy: 4%; }
.bg-press { --gx: 48%; --gy: 2%; }

/* The closing bands are short. The light sits on the top edge, where
   the section begins, and the orbit is not worth drawing inside 200px
   of height: it would only ever show as a flat line. */
.bg-band { --gx: 42%; --gy: -24%; }
.bg-band .bg-ring { display: none; }
.bg-band::before { opacity: 0.6; }

/* The content has to sit over it. These sections were drawn before
   there was anything behind them. */
.intro > .container,
.letter-section > .container,
.audit-hero > .container,
.press-section > .container,
.intro,
/* A phone is narrower than the grid's own pitch is designed for, and a
   72px cell across 390px is six columns of visible scaffolding. */
@media (max-width: 760px) {
  /* Full width on a phone: at 390 the column IS the card's
     width, so right-aligning it would only shave 30px off one
     side and leave the caption floating. */
  .letter-shot { max-width: none; margin: 0; }
  .letter-caption { text-align: left; }
  .letter-title { margin-bottom: var(--s-3); }

  .bg::before { background-size: 56px 56px; }
  .bg-ring { width: 150vw; margin-left: -75vw; }
}

/* The prose spans the note and the pictures under it. */
.letter-para > p,
/* --- The guarantee, as one band ------------------------------------ */

/* Still the close on /shelf and /story. It came out with the dead
   /audit rules by accident; this is it back, unchanged. */
.guarantee-band {
  position: relative;
  /* The last thing before the footer, so it is given room to land. */
  padding: var(--s-7) 0 var(--s-8);
  text-align: center;
}
.guarantee-band > .container {
  position: relative;
  z-index: 1;
}
.guarantee-band .btn { margin-top: var(--s-4); }

/* ============================================================
   The walkthrough, on /shelf and nowhere else
   ============================================================ */

/* A ROW, BECAUSE THE FILM IS 9:16. A poster across the full measure
   stands about 1450px tall, which is taller than the laptop reading
   it: the heading would be off the top before the poster had finished.
   Held to a phone's width with the words beside it, the whole section
   is about as tall as the poster and nothing is cropped.

   Three grid items, not two. The call to action is its own cell so it
   can sit under the words on a laptop and under the POSTER on a phone,
   which is where a thumb expects it after watching something. */
/* THE SAME OBJECT AS THE PHONE IN THE HERO: the border, the radius and
   the shadow are .phone-frame's, because both are a 9:16 screen on the
   same navy and two treatments for one shape read as two things. */
.kitchen-stage {
  position: relative;
  display: block;
  width: 100%;
  margin: 0;
  padding: 0;
  aspect-ratio: 9 / 16;
  overflow: hidden;
  background: #000;
  border: 1.5px solid #2A3344;
  border-radius: 38px;
  cursor: pointer;
  box-shadow: 0 34px 70px rgba(0, 0, 0, 0.62);
}
button.kitchen-stage:focus-visible { outline: 2px solid var(--blue); outline-offset: 3px; }

.kitchen-poster {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: inherit;
  opacity: 0.55;
  transition: opacity 300ms var(--ease), transform 600ms var(--ease);
}
button.kitchen-stage:hover .kitchen-poster { opacity: 0.68; transform: scale(1.015); }

.kitchen-play {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 66px;
  height: 66px;
  border-radius: 50%;
  background: var(--blue);
  box-shadow: 0 14px 34px rgba(0, 0, 0, 0.5);
  transition: transform 260ms var(--ease);
}
button.kitchen-stage:hover .kitchen-play { transform: translate(-50%, -50%) scale(1.06); }
.kitchen-play svg { width: 25px; height: 25px; fill: #fff; margin-left: 3px; }

/* Under the play mark, not at the foot of the frame: on a 9:16 box the
   foot is 250px below the thing it names. */
.kitchen-label {
  position: absolute;
  left: 0;
  right: 0;
  top: calc(50% + 48px);
  text-align: center;
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--white);
}

/* --- /press: the three kinds of card -------------------------------

   One shape, three behaviours. An article card is a link, a video card
   is a button that opens the dialog, and a piece that has not run yet
   is neither. They share .press-card so the rail reads as one list
   rather than three widgets; what differs is the cue at the foot and,
   for the placeholder, that it does not respond to a cursor at all. */

/* A button carries its own font, padding and centring, none of which
   should survive into a card. */
button.press-card {
  width: 100%;
  margin: 0;
  font: inherit;
  text-align: left;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
}
button.press-card:focus-visible { outline: 2px solid var(--blue); outline-offset: 3px; }

/* NOT A TARGET, so it does not light up under a cursor. Dimmer than
   the cards around it, which is what says "not yet" without a label
   having to do all the work. */
.press-card-soon { background: rgba(16, 21, 31, 0.5); }
.press-card-soon:hover,
.press-card-soon:focus-visible {
  background: rgba(16, 21, 31, 0.5);
  border-color: var(--line);
}
/* Shorter than a card with a headline in it. At the full card padding
   it is a box with two words adrift in it, which reads as something
   that failed to load rather than as a piece that has not run yet. */
.press-card-soon {
  padding-top: var(--s-3);
  padding-bottom: var(--s-3);
}
.press-card-soon .press-meta { margin-bottom: 0; }

/* Sits where a date sits, because it is standing in for one. */
.press-soon {
  padding: 3px 10px;
  border-radius: 999px;
  border: 1px solid var(--line);
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
}

/* The play mark, drawn rather than fetched: a circle and a triangle
   are two borders, and at 22px an SVG would be a request for nothing. */
.press-play {
  position: relative;
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--blue);
}
.press-play::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-40%, -50%);
  border-style: solid;
  border-width: 5px 0 5px 8px;
  border-color: transparent transparent transparent #fff;
}
button.press-card:hover .press-play { background: var(--blue-soft); }

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

/* No border and no panel: the footer is the same navy as the page, and
   the only line in it is the divider under the top row. */
.footer {
  background: var(--navy);
  padding: var(--s-5) 0 32px;
}

.footer-top {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3) var(--s-4);
  padding-bottom: var(--s-3);
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}

/* The footer's logo is gone from all five pages that share this
   stylesheet: the same mark already sits in the header, two rows up
   on a phone. why-relay, demo and operator-home keep theirs, out of
   styles.css, which is frozen. */

.footer-links { display: flex; flex-wrap: wrap; gap: var(--s-4); }
.footer-links a {
  font-size: 15px;
  color: rgba(255, 255, 255, 0.8);
  transition: color 200ms var(--ease);
}
.footer-links a:hover { color: #fff; }

/* Copyright on the left, the five icons on the right, and on a
   phone the two stack in that order. */
.footer-bottom {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-2) var(--s-3);
}

.footer-socials {
  display: flex;
  align-items: center;
  margin: var(--s-3) -10px 0 0;
}
/* 44px square at every width: the icon inside is 19px and the rest
   is the tap target. The negative margin above pulls that padding
   back so the row still ends on the page's own edge. */
.footer-socials a {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: 10px;
  color: var(--muted);
  transition: color 200ms var(--ease);
}
.footer-socials a:hover,
.footer-socials a:focus-visible { color: var(--white); }
.footer-socials svg {
  display: block;
  width: 19px;
  height: 19px;
  fill: currentColor;
}

.footer-legal {
  margin: var(--s-3) 0 0;
  font-size: 0.8rem;
  /* Quieter than --muted, which is doing other work on the page,
     but not so quiet it drops under 4.5:1. This is 4.73:1. */
  color: #6E7F99;
}

/* ============================================================
   Scroll reveal
   ============================================================ */

.reveal {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 420ms var(--ease), transform 420ms var(--ease);
}
.reveal.is-in { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  .reveal { opacity: 1; transform: none; }
}

/* ============================================================
   Responsive
   ============================================================ */

@media (max-width: 1024px) {
  .hero-split { gap: var(--s-5); }
}

@media (max-width: 860px) {
  /* 80px between sections here rather than 120. */
  :root { --sec-gap: 40px; }

  :root { --s-7: 96px; --s-8: 112px; }

  .hero-split {
    grid-template-columns: 1fr;
    gap: var(--s-5);
  }
  /* Photo above the words once stacked, so the page still opens on an image */
  .hero-split .hero-photo { order: -1; aspect-ratio: 3 / 2; }
  .hero-split .hero-inner { text-align: center; }
  .hero-split .hero-lede { margin-left: auto; margin-right: auto; }
  .hero-split .hero-ctas { justify-content: center; }



  .log-grid { grid-template-columns: 1fr; gap: var(--s-3); }
  .log-photo { min-height: 0; aspect-ratio: 3 / 2; }

  .letter-section { padding: var(--s-5) 0 var(--s-4); }
  .story-glow { height: 300px; }

  .letter-title { max-width: none; margin-bottom: var(--s-4); }
  .letter-shot { max-width: none; }
  .letter-frame, .letter-frame::after { border-radius: 16px; }

  .letter { margin-top: var(--s-5); }
  /* One column, and the date goes above its paragraph instead of
     beside it. */
  .letter-para {
    grid-template-columns: 1fr;
    gap: var(--s-1);
    margin-bottom: var(--s-4);
  }
  .letter-para::before { padding-top: 0; text-align: left; }
  .letter-para p { font-size: 1.04rem; line-height: 1.78; }
  .letter-para p.letter-lede { font-size: 1.16rem; line-height: 1.6; }

  .letter-para-payoff { margin-top: var(--s-4); }
  .letter-sign { padding-left: 0; margin-top: var(--s-5); }
  .sign-face { width: 60px; height: 60px; }
  .story-close { padding-top: var(--s-3); }

}

@media (max-width: 640px) {
  .container { padding: 0 20px; }

  /* The inline links go; the menu button takes them. The audit button
     stays on the bar, because it is the one thing that must survive. */
  .nav-links a:not(.btn) { display: none; }
  .nav-toggle { display: inline-flex; }
  .nav-logo img { height: 24px; }
  .nav-links .btn { min-height: 36px; padding: 8px 15px; font-size: 0.82rem; }

  /* Narrow enough that the headline has to come down hard, or
     "Shelf buys it for less." breaks across three lines. */
  .hero-title,
  .hero-split .hero-title { font-size: clamp(1.95rem, 7.6vw, 5rem); line-height: 1.08; }
  .hero-lede { font-size: 0.98rem; }

  .hero-card {
    min-height: 480px;
    padding: var(--s-5) 0;
  }

  /* Portrait crops into the brightest part of the art and the text
     block is taller relative to the frame, so the darkening has to be
     wider and deeper here than the desktop radial is. Measured: this
     is what keeps the subline above 4.5:1 at the brightest frame. */
  .hero-card::after {
    background:
      radial-gradient(84% 52% at 50% 50%, rgba(10, 14, 26, 0.90) 0%, rgba(10, 14, 26, 0.72) 44%, rgba(10, 14, 26, 0.14) 84%),
      linear-gradient(180deg, rgba(10, 14, 26, 0.30) 0%, rgba(10, 14, 26, 0.05) 32%, rgba(10, 14, 26, 0.58) 100%);
  }
  .hero-ctas { flex-direction: column; align-items: stretch; }
  .hero-ctas .btn { width: 100%; }


  /* Words first, phone under them. */
  .intro { min-height: 0; }
  .intro-split { grid-template-columns: 1fr; gap: var(--s-4); }
  .intro-phone { order: 2; }
  .phone-frame, .phone-still { width: 260px; }
  .phone-frame { border-radius: 32px; }

  /* The three steps stack, and the rule that lights the active one
     runs along the top of each rather than between columns. */
  .steps-row { grid-template-columns: 1fr; gap: var(--s-2); margin-top: var(--s-3); }

  .oneplace-section { padding: var(--s-6) 0; }
  .oneplace-points { grid-template-columns: 1fr; gap: var(--s-2); }
  /* The figure is 760 units wide against a 350px column, so everything
     in it lands at about half size. The shapes survive that; type at
     13px does not, so the labels are set larger in the drawing's own
     units to come out the same size on screen as they do on a desktop. */
  .fig-card-head { font-size: 29px; }
  .fig-save-text { font-size: 19px; }
  /* Same correction as the labels: the drawing lands at about half
     size on a phone, so its type is set larger in its own units. */
  .fig-vendor { font-size: 23px; }
  .fig-vendor-out { font-size: 21px; }

  /* Performance Foodservice goes onto two lines here and nowhere else.
     Two lines of 23 fill the tile's 52 units on their own, so the four
     price-list bars come off at this width: they are 5 units, which is
     under three pixels once the drawing is scaled to a phone, and they
     were the least legible thing in it. The two on the output side are
     untouched, where the names are short and the room is there. */
  .fig-v-wide { display: none; }
  .fig-v-stack { display: block; }
  /* Every bar, not just the four on the left: leaving the output pair
     in made the two sides of the drawing look like different
     treatments rather than one. */
  .fig-rule { display: none; }

  .faq-title { margin-bottom: var(--s-4); }
  .faq-aside { max-width: none; }

  .guarantee-band { padding: var(--s-6) 0 var(--s-7); }
  .guarantee-title { max-width: none; }
  .intro-lede { font-size: 0.98rem; }
  .page-intro { min-height: 0; }

  .press-section { padding: var(--s-5) 0 var(--s-6); }
  .press-head { margin-bottom: var(--s-5); }
  .press-card { padding: var(--s-3); }

  .footer { padding: var(--s-4) 0 var(--s-3); }
  .footer-top { flex-direction: column; align-items: flex-start; gap: var(--s-3); }
  .footer-links { flex-direction: column; gap: 14px; }
  .footer-bottom { align-items: flex-start; gap: 0; }
  .footer-socials { margin-left: -10px; }

  .log-panel { padding: var(--s-3); border-radius: 22px; }
  .log-photo { border-radius: 22px; }
  .log-rows li { grid-template-columns: auto 1fr; gap: 12px; }
  /* The form comes first on a phone, straight after the headline,
     because it is what the page is for. The reasons to fill it in go
     under it. */
  .audit-hero { padding: var(--s-5) 0 var(--s-6); }
  .audit-split { grid-template-columns: 1fr; gap: var(--s-4); }
  .audit-title { max-width: none; }
  .audit-sub { max-width: none; font-size: 1rem; }
  .audit-words { display: contents; }
  .audit-title { order: 1; }
  .audit-card { order: 2; padding: var(--s-3); }
  .audit-sub { order: 3; }
  .audit-checks { order: 4; margin-top: 0; }

  .sample-section { padding: var(--s-6) 0 var(--s-5); }
  .sample-card { padding: var(--s-3); }
  .audit-form { grid-template-columns: 1fr; }
  .form-note { font-size: 0.78rem; }

  .footer-row { justify-content: center; text-align: center; }
}

@media (max-width: 760px) {
  /* One column. The margin becomes a strip above its paragraph and the
     pictures run across it instead of stacking down a gutter that is
     no longer there. */
  .letter-media { margin: var(--s-4) auto var(--s-5); }
  .letter-media .kitchen-stage { width: 100%; max-width: 340px; }

  .letter-para { grid-template-columns: 1fr; gap: var(--s-2); }
  .letter-para::before { grid-row: 1; text-align: left; }
  .letter-para > p,
  .letter-para > div { grid-column: 1; grid-row: auto; }

  .para-shots {
    grid-row: auto;
    flex-direction: row;
    align-items: flex-start;
    margin: 0 0 var(--s-2);
  }
  .para-shot img { width: 104px; height: 104px; }
  .para-shot figcaption { text-align: left; }

  .fs {
    max-width: none;
    justify-content: flex-start;
    overflow-x: auto;
    transform: none;
    scrollbar-width: none;
  }
  .fs::-webkit-scrollbar { display: none; }
  .fs-f { width: 78px; height: 139px; }
}

/* ============================================================
   The demo video, as a card in the page
   ============================================================ */

/* Phones only, and the mobile block at the foot of this file is what
   turns it on. From 768 the quiet link on /shelf opens the dialog and
   from 1280 the poster behind the recording does, so this is the one
   width where the video has nowhere of its own to be.

   It plays where it stands. A dialog over a page that is already a
   single column buys nothing on a phone. */
/* The same width as the phone it shares a track with: 70% of the
   screen, 273px at 390, 400-odd down. It was 60% when it was the only
   thing on its row, but two widths in one carousel make the picture
   change size as you swipe between them.

   vw, not a percentage: a percentage here is of the container, which
   has already given up 20px of gutter on each side, and that came out
   at 54% of the screen rather than 60. */
.video-card {
  display: none;
  width: min(70vw, 280px);
  margin: 0;
}

/* THE TWO PICTURES ARE ONE TRACK ON A PHONE AND TWO PLAIN CHILDREN OF
   .intro-phone EVERYWHERE ELSE. display:contents is what lets one set
   of markup be both: above 768 neither the track nor its slides is a
   box, so .intro-stage lays out as a direct child of .intro-phone,
   which is where it has always been. */
.intro-media,
.media-slide { display: contents; }

.media-dots { display: none; }

/* The card's own section on home, where the hero used to trail off
   into bare page. Below 768 only; the desktop home page keeps the
   quiet watch link in its hero and does not show this at all. */
.media-band { display: none; }

.video-card-btn {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: 9 / 16;
  padding: 0;
  overflow: hidden;
  background: #0D1420;
  border: 1px solid var(--line);
  border-radius: 16px;
  cursor: pointer;
}
.video-card-btn:focus-visible {
  outline: 2px solid var(--blue);
  outline-offset: 3px;
}

/* Nothing is cropped now: the card and the frame are both 9:16, so
   the whole picture is on screen. */
.video-card-poster {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* What the label is legible against. */
.video-card-btn::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(10, 14, 26, 0) 40%, rgba(10, 14, 26, 0.8) 100%);
}

.video-card-play {
  position: absolute;
  top: 50%;
  left: 50%;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  transform: translate(-50%, -50%);
  background: var(--blue);
  border-radius: 50%;
  box-shadow: 0 8px 24px rgba(10, 14, 26, 0.5);
  transition: transform 200ms var(--ease);
}
.video-card-btn:hover .video-card-play { transform: translate(-50%, -50%) scale(1.06); }
.video-card-play svg {
  width: 18px;
  height: 18px;
  margin-left: 2px;
  fill: #fff;
}

.video-card-label {
  position: absolute;
  right: 12px;
  bottom: 10px;
  left: 12px;
  z-index: 1;
  font-size: 0.72rem;
  font-weight: 600;
  line-height: 1.3;
  text-align: left;
  color: var(--white);
}
.video-card-dot { color: var(--muted); }

/* Playing. The card is already the shape of the film, so the player
   drops straight into the box the poster was using and nothing on the
   page moves at all. */
.video-card-frame {
  position: relative;
  width: 100%;
  aspect-ratio: 9 / 16;
  overflow: hidden;
  background: #000;
  border: 1px solid var(--line);
  border-radius: 16px;
}
.video-card-frame iframe,
.video-card-frame video {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
}

@media (prefers-reduced-motion: reduce) {
  .video-card-play { transition: none; }
  .video-card-btn:hover .video-card-play { transform: translate(-50%, -50%); }
}


/* ============================================================
   PHONES, UNDER 768
   ============================================================ */

/* The mobile pass, in one block and last, so it has the final word on
   anything the blocks above set for a narrow screen.

   768 rather than the 640 the bar used to switch at. The header
   cannot be "the logo and the menu button" at one width and three
   competing calls to action at the next, and the rest of this is what
   a small tablet wants as much as a phone. Nothing here reaches 768,
   so every width from there up is as it was. */
@media (max-width: 767px) {

  /* --- 1. One savings-audit call to action on a screen ----------- */

  /* The bar is the logo and the menu button. The audit pill used to
     sit here as well, which put it on screen beside the hero's own
     button, and with the menu open a third copy under both. The menu
     keeps the offer as a link among the links. */
  .nav-links { display: none; }
  .nav-toggle { display: inline-flex; }
  .nav-logo img { height: 24px; }

  /* --- 2. The menu over the page, not through it ----------------- */

  /* It was a panel in the flow of the bar: no background of its own
     and nothing lifting it, so the hero read straight through the
     links. Fixed, the full screen, in the navy. */
  .nav-menu {
    position: fixed;
    inset: 0;
    z-index: 60;
    flex-direction: column;
    gap: 0;
    margin: 0;
    padding: calc(var(--nav-h) + var(--s-2)) 20px var(--s-5);
    background: var(--navy);
    border-top: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
  }
  /* The bar rides above the panel, so the logo and the close mark
     stay where they were rather than disappearing under it. Both sit
     in the header's stacking context, so this one number settles it. */
  .nav-row { position: relative; z-index: 61; }

  /* AND THE BAR HAS TO BE HELD THERE. Locking the page puts
     overflow:hidden on the root, which leaves a sticky bar with no
     scrollport to stick to: it drops back to its static place at the
     top of the document, so opening the menu 600px down the page took
     the logo and the close button to y -600 with it. A phone has no
     Escape key, so that was a panel with no way out. Fixed for as
     long as the panel is up, and sticky again the moment it is not.
     Nothing moves on screen either way: the panel covers the viewport
     the whole time. */
  .is-menu-open .nav {
    position: fixed;
    top: 0;
    right: 0;
    left: 0;
  }
  .nav-menu a:not(.btn) {
    padding: 16px 2px;
    font-size: 1.05rem;
    border-bottom: 1px solid var(--line);
  }
  .nav-menu a:not(.btn):last-child { border-bottom: 0; }

  /* --- 3. The video, as a card ----------------------------------- */

  .video-card { display: block; }
  /* The link the card replaces: on /shelf it would be the same offer
     twice, once as a sentence and once as a picture. */
  .intro-watch { display: none; }

  /* --- The hero's two pictures, side by side --------------------- */

  /* Stacked, the walkthrough sat above the recording and the hero was
     twice as tall as it needed to be; whichever came second was mostly
     scrolled past before anyone looked at it. One track, one picture
     at a time, the walkthrough first and the recording a swipe away.

     The snapping is all here rather than in script, so the swipe works
     whether or not shelf.js arrives. */
  /* The column has to stretch, or the track cannot measure itself.
     .intro-phone is a grid item with justify-self:center, which sizes
     it to its content: the slides' 100% then resolved against the two
     phones side by side and the track came out 525px wide inside a
     335px page. min-width:0 lets it shrink below that content too. */
  .intro-phone {
    justify-self: stretch;
    min-width: 0;
  }

  .intro-media {
    display: flex;
    margin-top: var(--s-3);
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scroll-behavior: smooth;
    overscroll-behavior-x: contain;
    scrollbar-width: none;
  }
  .intro-media::-webkit-scrollbar { display: none; }
  /* It is a scrollable region, so it takes focus and arrow keys. */
  .intro-media:focus-visible {
    outline: 2px solid var(--blue);
    outline-offset: 6px;
    border-radius: 12px;
  }

  /* Centred both ways. The two pictures are the same width but not
     the same shape: the card is the film's 9:16 and the recording is
     the phone's 390 by 844, which is 106px taller at the same width.
     The track takes the taller of the two, so the card is centred in
     it rather than hung from the top with all the slack underneath. */
  .media-slide {
    display: flex;
    flex: 0 0 100%;
    align-items: center;
    justify-content: center;
    scroll-snap-align: center;
    scroll-snap-stop: always;
  }

  /* The spoken lines belong to the recording, so they stay blank while
     the walkthrough is the one on screen. The box holds its height
     either way, which is what it was already for. */
  .intro-phone.is-walkthrough .intro-captions { opacity: 0; }

  /* The line under the track describes whichever picture is up, and
     the two are not the same length: one line against two, which
     moved everything below it on every swipe. Two lines' worth,
     always. */
  .intro-caption { min-height: 3.2em; }

  /* Which one is showing, and a way to change it that is not a swipe.
     34px of tap target around a 7px dot. */
  .media-dots {
    display: flex;
    justify-content: center;
    gap: 2px;
    margin-top: var(--s-1);
  }
  .media-dot {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    padding: 0;
    background: none;
    border: 0;
    cursor: pointer;
  }
  .media-dot::before {
    content: "";
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: rgba(241, 245, 249, 0.26);
    transition: background 200ms var(--ease), transform 200ms var(--ease);
  }
  .media-dot[aria-current="true"]::before {
    background: var(--blue);
    transform: scale(1.3);
  }
  .media-dot:focus-visible {
    outline: 2px solid var(--blue);
    outline-offset: 2px;
    border-radius: 10px;
  }

  /* --- 4. The hero's actions ------------------------------------- */

  .hero-ctas {
    flex-direction: column;
    align-items: stretch;
    gap: var(--s-2);
    margin-top: var(--s-3);
  }
  .hero-ctas .btn-primary { width: 100%; }
  /* The second action is the quieter of the two, so it stops being a
     button of the same weight and width as the first. */
  .hero-ctas .btn-ghost {
    width: auto;
    align-self: center;
    min-height: 40px;
    padding: 9px 20px;
    font-size: 0.9rem;
  }
  .hero-aside { margin-top: var(--s-2); font-size: 0.78rem; }

  .intro-actions {
    flex-direction: column;
    align-items: stretch;
    gap: var(--s-2);
    margin-top: var(--s-3);
  }
  .intro-actions .btn { width: 100%; }

  /* --- 5. Nothing takes a screen to itself ----------------------- */

  /* THE HERO IS THE FIRST SCREEN, ALL OF IT.

     It had been cut to the height of its own text, which was the fix
     for a screen of nothing under the words: at 375 the page ended at
     y463 and the footer sat on the first screen under it. The card
     now fills that stretch, so the hero can have the screen back.

     100svh on the section, and the card inside it takes the bar's
     height off the top before centring. The section starts at y0,
     under the transparent bar, so a floor of 100svh - 73 would have
     ended 73px short of the fold and shown the top of whatever came
     next; the floor is the whole screen, and the padding is what puts
     the words in the middle of the part you can actually see.

     svh, not vh: vh is the tallest the viewport gets, so with the
     browser's own chrome showing, a vh floor hangs the bottom of the
     hero below the fold and the page opens looking already scrolled.

     min-height:0 on the card overrides the 520px floor it carries for
     desktop, which on a screen shorter than that would push the
     footer back up into view. */
  .hero { min-height: 100svh; }
  .hero-card {
    min-height: 0;
    padding: calc(var(--nav-h) + var(--s-2)) 0 var(--s-2);
  }

  /* /shelf's hero, the same rule. It runs to 1528px at 375 off its own
     content, so this floor never binds there; it is here so the two
     heroes answer to one number rather than one of them happening to
     be tall enough. */
  .intro { min-height: calc(100svh - var(--nav-h)); }

  /* The card between the hero and the footer. No height cap on this
     one: nothing underneath it has to share the screen, so it keeps
     the component's own 70% and reads as big as it can. */
  .media-band {
    display: block;
    padding: var(--s-5) 0;
  }
  .media-band .video-card { margin-inline: auto; }

  /* 12 to 15% off, measured at 375 and 390 against the sizes these
     were: the hero title 31.2 to 27.2, the /shelf title 33.6 to 29.1,
     section titles 29.6 to 25.6, the guarantee 32 to 27.8. */
  .hero-title,
  .hero-split .hero-title { font-size: clamp(1.7rem, 7vw, 2.6rem); line-height: 1.08; }
  .intro-title { font-size: clamp(1.82rem, 7.5vw, 2.8rem); }
  .section-title { font-size: clamp(1.6rem, 6.6vw, 2.4rem); }
  .guarantee-title { font-size: clamp(1.74rem, 7.1vw, 2.6rem); }

  /* THE RECORDING AND ITS WORDS ARE ONE SCREENFUL.

     70% of the width was the only rule, and at 375 that made the phone
     568px tall with 138px of furniture under it: dots, then his words
     as he says them, then the line naming the clip. 706px of a 667px
     screen, so the phone's top left the screen before the transcript
     arrived and the two could never be read together.

     So the width comes off the HEIGHT that is going spare, not off the
     screen's width. 260px is what is not the phone's, measured:

        73   the bar, which is sticky and solid and sits on top of the
             first 73px of whatever has been scrolled under it. The
             first cut left this out, the arithmetic said everything
             fitted, and the screenshot showed the top of the phone
             under the logo.
        42   the dots, 8 of margin and a 34 tap target
        72   the transcript, 16 of margin and 3.6em held open
        58   the line naming the clip, 16 and 3.2em
        15   spare

     The rest is the phone's, and 390/844 turns that height back into
     the width that produces it. Nothing is cropped: the frame scales
     whole, which is the one thing it must not do.

     375x667: a 188px phone, 407 tall, and the four of them come to
     652 of 667. 390x844: 269px, 582 tall, 827 of 844. The 70% rule
     still almost wins at 390, so the common phone barely moves; it is
     the 667-tall screens that come down.

     The card shares the width because it shares the track, and two
     widths in one carousel resize the picture on every swipe. */
  .phone-frame,
  .phone-still,
  .intro-media .video-card {
    width: min(70vw, 280px, calc((100svh - 260px) * 390 / 844));
  }

  /* And a scroll that lands on the track stops below the bar rather
     than under it. */
  .intro-media { scroll-margin-top: calc(var(--nav-h) + var(--s-1)); }

  /* The furniture, tightened. 40px above the transcript and 24 above
     the caption are the desktop rhythm, and together they were 64px of
     the height the phone needed. */
  .intro-captions { margin-top: var(--s-2); }
  .intro-caption { margin-top: var(--s-2); }

  /* The gaps. s-6 and s-7 between sections are a desktop rhythm; on a
     phone they are the long blank stretches, and the worst of them
     was the 128px under the last call to action. */
  .intro { padding: var(--s-4) 0 var(--s-5); }
  .oneplace-section { padding: var(--s-5) 0; }
  .guarantee-band { padding: var(--s-5) 0; }

  /* --- The page fills the screen ---------------------------------- */

  /* A floor under the whole page, so no page can end above the fold
     and leave bare navy under its footer. Home needed this when its
     hero was the height of its text; with the hero back to a full
     screen and the card under it, home clears the fold on its own and
     there is nothing left over for the hero to take. It stays as the
     floor for any page that ever comes up short.

     main's children are told not to shrink. Without that, a flex
     column with min-height on the body would be free to squeeze a
     section to make things fit. */
  body {
    display: flex;
    flex-direction: column;
    min-height: 100svh;
  }
  main {
    display: flex;
    flex-direction: column;
    flex: 1 0 auto;
  }
  main > * { flex-shrink: 0; }
  .hero { flex-grow: 1; }

  /* --- 6. The footer, two columns -------------------------------- */

  .footer { padding: var(--s-4) 0 var(--s-3); }
  .footer-top {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--s-2);
    padding-bottom: var(--s-2);
  }
  /* One flat row. Four links down one side was most of the footer's
     height; two columns of two fixed the height but read as a block
     of cells with a column of air down the middle. All four fit
     across every width this block covers, so they go on one line.

     flex-direction is set here on purpose: the 640 block above turns
     this column, and without it back to row these stack again on
     anything narrower than 640. */
  .footer-links {
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    gap: 10px 18px;
  }
  .footer-links a { font-size: 14px; }
  /* The copyright and the icons share a line. Stacked, they were two
     more rows on a footer that was already a column of six. */
  .footer-bottom {
    align-items: center;
    justify-content: space-between;
    gap: var(--s-2);
  }
  .footer-legal { margin-top: var(--s-2); font-size: 0.75rem; }
  .footer-socials { margin: var(--s-2) -10px 0 0; }
}

@media (prefers-reduced-motion: reduce) {
  .intro-media { scroll-behavior: auto; }
  .media-dot::before { transition: none; }
  .media-dot[aria-current="true"]::before { transform: none; }
}
