/* ==========================================================================
   Pangea × lightfield hybrid — demo stylesheet
   Dark port of lightfield.app's measured system. Standalone; touches nothing
   in ../mirror/.

   Rules this file obeys, from the measured research:
     - ONE hue in the neutral ramp (white), 12 alphas, semantic aliases only.
       No rule below ever writes a raw alpha.
     - ONE accent colour, used sparingly.
     - ONE typeface at ONE weight for prose (Geist Sans 400). Mono for eyebrows.
       Pixel-line face reserved for the hero H1 (the giga signature).
     - THREE easing curves for the whole page.
     - ZERO box-shadow. Depth is drop-shadow + hairline + surface tint + blur.
     - Backdrop blur lives on ::before, never on the element itself.
   ========================================================================== */

/* --------------------------------------------------------------------------
   0. Fonts
   -------------------------------------------------------------------------- */
@font-face {
  font-family: pangeaSans;
  src: url("assets/fonts/geist-sans-400.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: pangeaMono;
  src: url("assets/fonts/geist-mono-400.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: pangeaPixel;
  src: url("assets/fonts/geist-pixel-line.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

/* --------------------------------------------------------------------------
   1. Token layer
   -------------------------------------------------------------------------- */
:root {
  /* One colour, twelve alphas. Bottom steps lifted for dark per the port rules
     (~2x lightfield's border alphas, ~1.5x their surface tints). */
  --n-t0: rgb(255 255 255 / 0.03);
  --n-t1: rgb(255 255 255 / 0.045);
  --n-t2: rgb(255 255 255 / 0.06);
  --n-t3: rgb(255 255 255 / 0.09);
  --n-t4: rgb(255 255 255 / 0.12);
  --n-t5: rgb(255 255 255 / 0.16);
  --n-t6: rgb(255 255 255 / 0.25);
  --n-t7: rgb(255 255 255 / 0.35);
  --n-t8: rgb(255 255 255 / 0.5);
  --n-t9: rgb(255 255 255 / 0.62);
  --n-t10: rgb(255 255 255 / 0.75);
  --n-t11: rgb(255 255 255 / 0.92);

  /* Semantic aliases. Components reference ONLY these.
     Four TEXT levels, every one of them AA or better against the darkest
     backdrop it is used on. The two lowest alphas are non-text only — that is
     the rule that keeps a dark page legible, and the one lightfield does not
     need because their lightest text sits on white. */
  --content-primary: var(--n-t11); /* .92 — 16.5:1 on base, 12.2:1 on a panel */
  --content-secondary: var(--n-t10); /* .75 — 10.6:1 on base,  7.2:1 on a panel */
  --content-tertiary: var(--n-t9); /* .62 —  7.7:1 on base,  5.1:1 on a panel */
  --content-quaternary: var(--n-t8); /* .50 —  5.3:1 on base,  4.7:1 on a card  */
  /* NON-TEXT ONLY below this line */
  --content-hint: var(--n-t7); /* .35 — decorative glyphs, disabled */
  --content-subtle: var(--n-t6); /* .25 — icons at size, rules */
  --content-inverse: #08090b;

  --border-subtle: var(--n-t3);
  --border-moderate: var(--n-t4);
  --border-faint: var(--n-t2);

  --surface-base: var(--n-t0);
  --surface-primary: var(--n-t1);
  --surface-raised: var(--n-t2);

  /* The single accent. Used once or twice per screen, never as body text. */
  --accent: #2bb673;
  --accent-quiet: rgb(43 182 115 / 0.16);
  --accent-line: rgb(43 182 115 / 0.4);

  --bg: #08090b;
  /* The one non-page black. A raised, slightly cooler plate that the Slack
     scenes sit on so they read as their own surface. Two values only: rest and
     hover. Nothing else on the page may write a second black. */
  --plate: #0c0e13;
  --plate-hi: #0e1017;

  /* Type — lightfield's scale, one weight, tracking scaling negatively */
  --t-hero: 40px;
  --t-h2: 28px;
  --t-h3: 21px;
  --t-h4: 19px;
  --t-lg: 17px;
  --t-md: 15px;
  --t-sm: 13px;
  --t-xs: 12px;
  --t-eyebrow: 10px;

  --lh-tight: 1.2;
  --lh-body: 1.5;

  --ls-h2: -0.03em;
  --ls-h3: -0.015em;
  --ls-h4: -0.01em;

  /* Radius — 5-step scale, 6px is the workhorse, nothing above 12 */
  --r-sm: 4px;
  --r-md: 6px;
  --r-lg: 8px;
  --r-xl: 10px;
  --r-2xl: 12px;

  /* Three curves for the entire page */
  --ease-out: cubic-bezier(0, 0, 0.2, 1);
  --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
  --ease-p1: cubic-bezier(0.25, 0.46, 0.45, 0.94);

  /* Two interaction durations */
  --d-press: 0.05s;
  --d-hover: 0.2s;

  --gut: 24px;
  /* A stepped ladder, not a clamp. `clamp(24px, 3.4vw, 56px)` is fluid, which
     sounds right and is actually how the page acquired sub-pixel gutters at
     every width below the clamp ceiling: 43.52px at 1280, 48.96px at 1440.
     Only 1920 was ever a whole number. Sub-pixel spacing was the defect a
     prior review named on the live page and this token was quietly
     reintroducing it. Five steps, all integers, all on the 4px base. */
  --pad-x: 24px;

  /* Small labels sit here, one step up from where they were.

     Not an accessibility fix, and it is worth being exact about that: 0.5
     white on this background measures 5.32:1, which passes AA for normal text.
     It is a legibility one. The eyebrow is 10px uppercase mono carrying 0.1em
     of extra tracking, which is close to the worst case for reading at low
     alpha, and the changelog date is 12px mono. 0.62 takes them to 7.76:1 and
     makes the small type on this page feel deliberate rather than faded. Body
     copy is untouched; only the labels move. */
  --content-label: var(--n-t9);

  /* ONE vertical interval for the whole page.

     The feature column already breathes on a 240px gap and it reads well, so
     that is the interval everything else adopts. Every section pays half a band
     at each end, which means any two adjacent sections sum to exactly one band
     and there is no second rhythm to notice. The live page's failure mode was
     34 distinct vertical values (28% of them sub-pixel); this file has two, and
     the second is derived from the first. No rule below writes a raw
     inter-section value. */
  --band: 240px;
  --sec-pad: calc(var(--band) / 2);
  /* the provenance banner is fixed to the bottom of the viewport and is not a
     section, so the last block on the page clears it explicitly */
  /* No-JS fallback only. The banner is fixed and wraps with its own copy, so
     its height is 38px at >=1200, 81px from ~600-1199, 98px at ~480 and 116px
     at 390 — one static number cannot be true at more than one width, and 64px
     was true at none. demo.js measures the bar and writes the real value back
     over this on load and on resize. */
  --banner-h: 0px; /* landing page ships without the demo banner */
}

/* The gutter ladder. Integers only, so no width can produce a fractional
   gutter the way the old clamp did at every size under its ceiling. */
@media (min-width: 768px) {
  :root {
    --pad-x: 32px;
  }
}
@media (min-width: 1024px) {
  :root {
    --pad-x: 40px;
  }
}
@media (min-width: 1280px) {
  :root {
    --pad-x: 48px;
  }
}
@media (min-width: 1600px) {
  :root {
    --pad-x: 56px;
  }
}

/* --------------------------------------------------------------------------
   2. Base
   -------------------------------------------------------------------------- */
*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  background: var(--bg);
  /* 1:1 gesture-to-distance. No hijack, no snap, no multiplier. */
  scroll-behavior: auto;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--content-primary);
  font-family: pangeaSans, ui-sans-serif, system-ui, sans-serif;
  font-weight: 400;
  font-size: var(--t-md);
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
  /* `clip`, not `hidden`. `overflow-x: hidden` forces the other axis to `auto`,
     which turns <body> into a scroll container — and a sticky element inside a
     scroll container that is not the one actually scrolling never sticks. It
     silently cost us the entire hero pin. `clip` cuts the same overflow without
     creating a scrollport, so sticky resolves against the document as it
     should. Nothing else about the page changes. */
  overflow-x: clip;
}

h1,
h2,
h3,
h4,
p,
ul,
ol,
figure,
blockquote {
  margin: 0;
  font-weight: 400;
}
ul,
ol {
  padding: 0;
  list-style: none;
}
a {
  color: inherit;
  text-decoration: none;
}
button {
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
}
img,
svg {
  display: block;
  max-width: 100%;
}

.container {
  max-width: 1560px;
  margin-inline: auto;
  padding-inline: var(--pad-x);
}

.grid12 {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  column-gap: var(--gut);
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

/* --------------------------------------------------------------------------
   3. Shared type roles
   -------------------------------------------------------------------------- */
.eyebrow {
  display: inline-block;
  font-family: pangeaMono, ui-monospace, monospace;
  font-size: var(--t-eyebrow);
  line-height: 1;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--content-label);
  border: 1px solid var(--border-subtle);
  border-radius: var(--r-sm);
  padding: 5px 7px 4px;
}

.h2 {
  font-size: var(--t-h2);
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-h2);
  color: var(--content-primary);
}

.h3 {
  font-size: var(--t-h3);
  line-height: 1.25;
  letter-spacing: var(--ls-h3);
  color: var(--content-primary);
}

.h4 {
  font-size: var(--t-h4);
  line-height: 1.3;
  letter-spacing: var(--ls-h4);
  color: var(--content-primary);
}

.lead {
  font-size: var(--t-lg);
  line-height: var(--lh-body);
  color: var(--content-tertiary);
}

.body {
  font-size: var(--t-md);
  color: var(--content-secondary);
}

.caption {
  font-size: var(--t-xs);
  line-height: 1.45;
  letter-spacing: -0.19px;
  color: var(--content-tertiary);
}

/* Two-tone paragraph: bold lead reads alone as the whole argument. */
/* 32em lands this at about 64 characters per line. Uncapped it measured 77.5ch
   at 1440 and 83.8ch at 1920, past the 45-75 range where a line stops being
   comfortable to track back from. Every prose block on this page now carries a
   cap; the grid alone was never going to hold one, because a 12-column span
   gets wider with the viewport and the type does not. */
.two-tone {
  font-size: var(--t-lg);
  line-height: var(--lh-body);
  color: var(--content-tertiary);
  max-width: 32em;
}
.two-tone strong {
  font-weight: 400;
  color: var(--content-primary);
}

/* --------------------------------------------------------------------------
   4. Buttons
   -------------------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 34px;
  padding: 0 12px;
  border-radius: var(--r-md);
  font-size: var(--t-sm);
  white-space: nowrap;
  /* 50ms, lightfield's interaction tier: below the threshold where a
     transition reads as a transition, so the control feels connected to the
     finger rather than animated. Transform and filter join the same tier so
     the press and the brightness land together. */
  transition: background-color var(--d-press) var(--ease-out),
    color var(--d-press) var(--ease-out),
    border-color var(--d-press) var(--ease-out),
    filter var(--d-press) var(--ease-out),
    transform var(--d-press) var(--ease-out);
}
/* Glow on approach, press on contact. brightness() rather than a second
   colour token, so the accent count on the page stays at one. */
.btn:hover {
  filter: brightness(1.09);
}
.btn:active {
  transform: scale(0.976);
  filter: brightness(0.97);
}
.btn-primary {
  background: var(--content-primary);
  color: var(--content-inverse);
}
.btn-primary:hover {
  background: #fff;
}
.btn-ghost {
  color: var(--content-secondary);
  border: 1px solid var(--border-subtle);
}
.btn-ghost:hover {
  color: var(--content-primary);
  border-color: var(--border-moderate);
}
.btn-sm {
  height: 26px;
  padding: 0 8px 0 10px;
  font-size: var(--t-xs);
  border-radius: var(--r-md);
}

/* --------------------------------------------------------------------------
   5. Placeholder banner
   -------------------------------------------------------------------------- */
.copy-banner {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 60;
  isolation: isolate;
  border-top: 1px solid var(--border-subtle);
  padding: 10px var(--pad-x) 10px;
  display: flex;
  gap: 10px;
  align-items: baseline;
  flex-wrap: wrap;
}
.copy-banner::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--surface-primary);
  backdrop-filter: blur(17px);
  -webkit-backdrop-filter: blur(17px);
}
.copy-banner b {
  font-weight: 400;
  font-family: pangeaMono, ui-monospace, monospace;
  font-size: var(--t-eyebrow);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--accent);
}
.copy-banner span {
  font-size: var(--t-xs);
  line-height: 1.45;
  color: var(--content-tertiary);
}

/* --------------------------------------------------------------------------
   6. Header — the floating glass pill
      Measured spec, dark-ported: 66px transparent fixed header, 16px inset,
      w-max centred pill, 10px radius, tint + blur on ::before, no border,
      no shadow, ZERO scroll response.
   -------------------------------------------------------------------------- */
.site-header {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 50;
  display: flex;
  width: 100%;
  justify-content: center;
  padding: 16px;
  background: transparent;
  pointer-events: none;
}

.navbar {
  position: relative;
  width: max-content;
  border-radius: var(--r-xl);
  padding: 4px 0;
  isolation: isolate;
  pointer-events: auto;
  display: flex;
  align-items: center;
}
.navbar::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  /* A vertical wash inside the pill instead of a flat tint: brighter at the
     top edge, settling to the measured 4.5% by a third of the way down. The
     glass reads as a lit object rather than a rectangle of fog. */
  background: linear-gradient(
      to bottom,
      rgb(255 255 255 / 0.055) 0%,
      rgb(255 255 255 / 0.012) 34%,
      rgb(255 255 255 / 0) 100%
    ),
    var(--surface-primary);
  backdrop-filter: blur(17px);
  -webkit-backdrop-filter: blur(17px);
  pointer-events: none;
}
/* The hairline. 1px across the pill's top edge, brightest at the centre and
   fading out before the corners, so it reads as light catching a rounded edge
   rather than as a border. Inset 10px on each side is the pill's own radius,
   which is where the top edge stops being flat. */
.navbar::after {
  content: "";
  position: absolute;
  top: 0;
  left: 10px;
  right: 10px;
  height: 1px;
  pointer-events: none;
  background: linear-gradient(
    to right,
    rgb(255 255 255 / 0) 0%,
    rgb(255 255 255 / 0.2) 20%,
    rgb(255 255 255 / 0.28) 50%,
    rgb(255 255 255 / 0.2) 80%,
    rgb(255 255 255 / 0) 100%
  );
}

/* The scrim. A soft band of page-black behind the header so copy dissolves as
   it travels underneath rather than clipping against the pill. It stops short
   of opaque on purpose: the pill's whole trick is blurring live content, and a
   solid band would leave it with nothing to blur. 0.9 -> 0 over 148px. */
.header-scrim {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 148px;
  z-index: 49;
  pointer-events: none;
  background: linear-gradient(
    to bottom,
    rgb(8 9 11 / 0.9) 0%,
    rgb(8 9 11 / 0.72) 32%,
    rgb(8 9 11 / 0.34) 66%,
    rgb(8 9 11 / 0) 100%
  );
}

.nav-logo {
  display: flex;
  align-items: center;
  height: 26px;
  padding-inline: 12px 0;
  font-family: pangeaPixel, ui-monospace, monospace;
  font-size: 15px;
  letter-spacing: 0.02em;
  color: var(--content-primary);
  transition: color var(--d-press) var(--ease-out);
}
.nav-logo:hover {
  color: var(--content-secondary);
}

.nav-list {
  display: flex;
  align-items: center;
  gap: 12px;
  padding-left: 16px;
  padding-right: 4px;
}
.nav-item {
  position: relative;
}
.nav-link {
  display: flex;
  align-items: center;
  height: 25px;
  padding: 0 6px;
  border-radius: var(--r-md);
  font-size: var(--t-xs);
  color: var(--content-secondary);
  /* Named properties, not `all`. `all` was transitioning every animatable
     property this element has, so any future layout or colour change here
     would animate by accident. Colour is the only thing that actually moves,
     and it moves on the 50ms interaction tier like every other control. */
  transition: color var(--d-press) var(--ease-out),
    background-color var(--d-press) var(--ease-out);
}
.nav-link:hover,
.nav-item:hover > .nav-link {
  color: var(--content-primary);
}

/* Hover dropdown — heavier blur than the pill (24 vs 17), 3px gap below it */
.nav-panel {
  position: absolute;
  top: 37px;
  left: -8px;
  width: 240px;
  padding: 4px;
  border-radius: var(--r-xl);
  isolation: isolate;
  opacity: 0;
  visibility: hidden;
  transform: translateY(-2px);
  transition: opacity 0.1s var(--ease-out), transform 0.1s var(--ease-out),
    visibility 0s linear 0.1s;
}
.nav-panel::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  background: var(--surface-primary);
  backdrop-filter: blur(24px);
  -webkit-backdrop-filter: blur(24px);
}
.nav-item:hover .nav-panel,
.nav-panel:hover {
  opacity: 1;
  visibility: visible;
  transform: none;
  transition-delay: 0s;
}
.nav-panel a {
  display: block;
  padding: 7px 8px;
  border-radius: var(--r-md);
  font-size: var(--t-xs);
  color: var(--content-secondary);
  transition: background-color var(--d-press) var(--ease-out),
    color var(--d-press) var(--ease-out);
}
.nav-panel a:hover {
  background: var(--surface-raised);
  color: var(--content-primary);
}
.nav-panel a small {
  display: block;
  font-size: var(--t-xs);
  color: var(--content-quaternary);
}
/* hover bridge so the pointer can travel the 3px gap */
.nav-item::after {
  content: "";
  position: absolute;
  inset: 100% -8px auto;
  height: 12px;
}

.nav-cta {
  margin-right: 4px;
}

.nav-burger {
  display: none;
}

/* --------------------------------------------------------------------------
   7. Hero
   -------------------------------------------------------------------------- */
.hero {
  position: relative;
  /* The pin. 450px of runway below a sticky hero, measured off lightfield
     (section.home-hero pins y 0 -> 450). The hero holds while the page scrolls
     underneath it; the copy fades and drifts 10px on an exact scroll track and
     the panels lag behind it on a ~180ms follow. That two-speed split is what
     gives the hero mass instead of twitch.

     The runway only exists when demo.js adds `.pinning` to <html>: desktop
     width, hero shorter than the viewport, motion not reduced. Every other
     case gets the old static composition, unchanged. */
}
/* The runway has to be CONTENT, not padding. A sticky element is constrained
   to its containing block's *content* box, so `padding-bottom` on .hero adds
   450px of page and exactly 0px of travel — the hero reads as sticky in
   devtools and scrolls away like a normal block. An in-flow ::after is real
   content, so the content box grows and the element has somewhere to go. */
.pinning .hero::after {
  content: "";
  display: block;
  height: var(--pin, 450px);
}
.hero-sticky {
  position: relative;
  min-height: 100vh;
  padding-top: 128px;
  padding-bottom: var(--sec-pad);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.pinning .hero-sticky {
  position: sticky;
  top: 0;
}

/* Aurora — atmosphere, not a feature. Dialled well down from the first pass
   (0.62 -> 0.30 opacity, 34px -> 22px blur, three rays instead of a canvas).
   The cost of a blur is rasterisation area, so the band is shorter as well as
   fainter. Reduced motion gets one static frame; see the guard at the bottom. */
.aurora {
  position: absolute;
  inset: auto 0 0;
  height: 360px;
  pointer-events: none;
  z-index: 0;
  filter: blur(22px);
  opacity: 0.38;
  /* fade both ends so the glow never meets the hero's clip edge */
  -webkit-mask-image: linear-gradient(
    to bottom,
    transparent 0%,
    #000 34%,
    #000 64%,
    transparent 99%
  );
  mask-image: linear-gradient(
    to bottom,
    transparent 0%,
    #000 34%,
    #000 64%,
    transparent 99%
  );
}
/* No animation here, deliberately, and this is the single most expensive line
   that ever ran on this page.

   The blur and the mask live on the PARENT. When these children animated
   inside it, Chrome could not cache anything: it re-rasterised a 22px blur
   across the full 1440x360 band every frame, forever, whether or not anyone
   was scrolling. Measured, two trials each, idle at y=0:

     as shipped                     16.4ms median, 18-30% of frames over budget
     drift removed, blur kept        8.3ms median,  0% over budget
     blur moved to the children     16.4ms median, 20% over  (so it was never
                                                    about where the blur lived)

   8.3ms is lightfield's own measured idle median. What we gave up to get it
   was a 26 to 38 second cycle that travels 52px behind a mask at 0.38 opacity,
   which nobody has ever seen. Atmosphere was never the animation; it is the
   blur, and the blur is still here.

   If motion is ever wanted back, it cannot go on these elements. It has to be
   a transform on a layer that is not inside a filtered parent. */
.aurora i {
  position: absolute;
  bottom: 0;
  display: block;
  height: 320px;
  mix-blend-mode: screen;
}
.aurora i:nth-child(1) {
  left: -10%;
  width: 52%;
  background: radial-gradient(
    62% 66% at 50% 76%,
    rgb(43 182 115 / 0.6),
    rgb(43 182 115 / 0.14) 45%,
    transparent 72%
  );
}
.aurora i:nth-child(2) {
  left: 22%;
  width: 46%;
  background: radial-gradient(
    62% 66% at 50% 76%,
    rgb(46 200 190 / 0.4),
    rgb(46 200 190 / 0.1) 45%,
    transparent 72%
  );
}
.aurora i:nth-child(3) {
  left: 52%;
  width: 54%;
  background: radial-gradient(
    62% 66% at 50% 76%,
    rgb(96 120 220 / 0.3),
    rgb(96 120 220 / 0.08) 45%,
    transparent 72%
  );
}

/* Centred, not top-aligned. The copy block is ~330px against a ~560px panel
   composition; top-aligning it opens a ~230px void beside the button, which is
   exactly the defect the review named on the live page. */
.hero-inner {
  position: relative;
  z-index: 1;
  align-items: center;
  /* .container carries margin-inline:auto, and an auto margin on a flex item
     wins over stretch — without this the grid collapses to max-content and
     every column narrows. .hero-sticky is a column flex container. */
  width: 100%;
}

.hero-copy {
  grid-column: 1 / span 5;
  /* The stack sits high against the panel's top edge rather than floating in
     the middle of the column. The Live in Slack pill is positioned off the
     hero, not this stack, so it stays put. */
  align-self: start;
  padding-top: 88px;
}
/* The Atlas mark. Dense character art, so it needs real size to read as
   marks rather than as a smudge. Luminance was baked to alpha, so it sits
   on any dark surface without a box. */
.hero-mark {
  display: block;
  width: 96px;
  height: auto;
  margin-bottom: 20px;
  opacity: 0.92;
}
/* Which plan this page is depicting. It has to be said where a reader decides
   what they are looking at, which is above the headline, not in the footer. */
.hero-eyebrow {
  margin-bottom: 20px;
}
.hero h1 {
  font-family: pangeaPixel, ui-monospace, monospace;
  font-size: var(--t-hero);
  line-height: 1.14;
  /* No negative tracking. Tightening tracking as size grows is right for a
     proportional face and wrong for this one: a pixel face has its spacing
     designed into the glyph grid, so -0.005em (-0.2px at 40px) just walks the
     letters off that grid. The one largest element on the page also gets full
     white rather than the 0.92 body alpha. */
  letter-spacing: normal;
  max-width: 12em;
  color: #fff;
}
.hero-sub {
  margin-top: 18px;
  max-width: 26em;
  font-size: var(--t-lg);
  line-height: var(--lh-body);
  color: var(--content-tertiary);
}
.hero-actions {
  display: flex;
  gap: 8px;
  margin-top: 28px;
}

/* the pill is the last painted thing in the hero, so its offset IS the
   hero/introducing gap — half a band here, half from the next section */
.hero-pill {
  position: absolute;
  left: var(--pad-x);
  bottom: var(--sec-pad);
  z-index: 2;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 30px;
  max-width: 100%;
  padding: 0 8px 0 10px;
  border-radius: var(--r-md);
  isolation: isolate;
  font-size: var(--t-xs);
  color: var(--content-tertiary);
  transition: color var(--d-press) var(--ease-out);
}
/* Below the grid breakpoint the pill sits in flow at 390px, and an inline-flex
   row sized to max-content is wider than the column it sits in. The label is the
   only shrinkable part, so it is the part that shrinks. */
.hero-pill .lbl {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.hero-pill b,
.hero-pill .dot,
.hero-pill .chev {
  flex: none;
}
.hero-pill::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  background: var(--surface-primary);
  backdrop-filter: blur(17px);
  -webkit-backdrop-filter: blur(17px);
}
.hero-pill:hover {
  color: var(--content-secondary);
}
.hero-pill b {
  font-weight: 400;
  color: var(--content-primary);
}
.hero-pill .dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--accent);
}
.hero-pill .chev {
  color: var(--content-quaternary);
}

/* --------------------------------------------------------------------------
   7b. The hero panel stack — the artefact, not the product running

   Lightfield's panels are transparent PNGs with the perspective baked in.
   There is no CSS substitute: `transform: perspective()` cannot produce their
   interior foreshortening, and a faked tilt reads as a faked tilt. So these
   are straight-on. What DOES port from the measured recipe, and is used here:
     - glass panels, thinner the nearer they are (12% / 9% / 7% white)
     - depth via `filter: drop-shadow`, never box-shadow
     - a masked backdrop-blur sibling behind the two front panels only
     - depth-scaled mouse parallax, base 15px, factors .33 / .50 / .75
     - the fade-blur-in entrance, staggered
   Every figure inside is a blank slot. None has been measured.
   -------------------------------------------------------------------------- */
.hero-stage {
  grid-column: 6 / span 7;
  position: relative;
  height: 600px;
  margin-right: calc(var(--pad-x) * -1);
  container-type: inline-size;
}

.gp {
  position: absolute;
  border-radius: var(--r-xl);
  isolation: isolate;
  opacity: 0;
  animation: fade-blur-in 1.4s cubic-bezier(0.16, 1, 0.3, 1) both;
  /* JS writes --px/--py; the lerp lives in rAF, so no transition here */
  transform: translate3d(var(--px, 0px), var(--py, 0px), 0);
  will-change: transform;
  /* drop-shadow follows the alpha silhouette, so the radius is respected */
  filter: drop-shadow(0 24px 34px rgb(0 0 0 / 0.55));
}
.gp-frost {
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  pointer-events: none;
}
/* Lightfield's panels are white glass at 15/25/40% over a LIGHT gradient, so
   their overlaps barely register. Inverted for dark, near-transparent white
   over #08090b is invisible on its own and, where two panels cross, the sum
   paints a bright rectangle that reads as a rendering artifact rather than as
   depth. So the dark port is a lifted dark fill at 0.9, nearer = lighter, with
   ~10% still transmitting so the frost behind it is doing real work. */
.gp-body {
  position: relative;
  border-radius: inherit;
  border: 1px solid var(--border-moderate);
}

/* Back — the delivered file. Largest, most opaque; nothing behind it to frost.

   The two front panels overlap it, and both tucks land entirely inside the
   sheet's empty bottom band: no glyph and no rule is ever covered. Layering
   should cover empty panel, not content. */
.gp[data-z="0"] {
  z-index: 0;
  top: 0;
  right: 0;
  width: 70%;
  min-width: 430px;
  animation-delay: 0.35s;
}
.gp[data-z="0"] .gp-frost {
  display: none;
}
.gp[data-z="0"] .gp-body {
  background: rgb(17 19 23 / 0.9);
}

/* Middle — the message and the reply, which is the entire setup */
/* Tops are measured against the sheet panel, not guessed: the sheet's last
   painted row ends at ~372 and its box ends at ~440, so both front panels sit
   inside that 68px band and the overlap never crosses a glyph. */
.gp[data-z="1"] {
  z-index: 1;
  top: 388px;
  left: 0;
  width: 46%;
  min-width: 268px;
  animation-delay: 0.55s;
}
.gp[data-z="1"] .gp-body {
  background: rgb(22 25 30 / 0.9);
}

/* Front — the no-approval line. Thinnest glass, nearest the reader. */
.gp[data-z="2"] {
  z-index: 2;
  top: 372px;
  right: 0;
  width: 50%;
  min-width: 280px;
  animation-delay: 0.75s;
}
.gp[data-z="2"] .gp-body {
  background: rgb(27 31 37 / 0.9);
}

/* --- the delivered file --------------------------------------------------- */
/* The bottom band is deliberately deep and deliberately empty: it is the only
   region a front panel is allowed to cross. Composed, not padded by accident. */
.sh {
  padding: 0 0 68px;
  font-size: var(--t-sm);
}
.sh-head {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 12px 16px;
  border-bottom: 1px solid var(--border-faint);
  color: var(--content-secondary);
  font-size: var(--t-xs);
}
.sh-head b {
  font-weight: 400;
  color: var(--content-primary);
}
/* Quaternary, not subtle. These are structural punctuation inside a text run,
   not decoration, and --content-subtle put them at 2.22:1 — below AA and in
   breach of the "NON-TEXT ONLY" rule this file declares at line 78. */
.sh-hash,
.sh-sep {
  color: var(--content-quaternary);
}
.sh-title {
  font-family: pangeaMono, ui-monospace, monospace;
  color: var(--content-primary);
}
.sh-chip {
  margin-left: auto;
  padding: 2px 8px;
  border-radius: 999px;
  border: 1px solid var(--border-subtle);
  font-family: pangeaMono, ui-monospace, monospace;
  font-size: 10px;
  letter-spacing: 0.04em;
  color: var(--content-quaternary);
}

/* Three figures, not four. Two of them are claims about the product; the third
   is a reply rate, which is sample data and says so in its own label as well as
   on the head chip. */
.sh-metrics {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  border-bottom: 1px solid var(--border-faint);
}
.sh-metric {
  padding: 16px 12px 18px 16px;
  border-right: 1px solid var(--border-faint);
}
.sh-metric:last-child {
  border-right: 0;
}
/* Primary, not subtle: --content-subtle is NON-TEXT ONLY and put these at
   2.22:1. The number is the payload, the label under it is quaternary. */
.sh-num {
  display: block;
  font-size: 26px;
  line-height: 1.1;
  letter-spacing: -0.02em;
  color: var(--content-primary);
}
.sh-lab {
  display: block;
  margin-top: 8px;
  font-size: 11px;
  line-height: 1.35;
  color: var(--content-label);
}

/* The sheet itself. Four of the eleven columns, because eleven at this width is
   3px of type; the full set is named in the feature tile and rendered in the
   Slack scene. The last column is the personalization line, which is the one
   the reply-rate figure above is about, so it gets the space. */
.sh-grid {
  padding: 4px 16px 0;
}
.sh-tr {
  display: grid;
  grid-template-columns: 74px 104px 92px minmax(0, 1fr);
  gap: 10px;
  padding: 9px 0;
  border-bottom: 1px solid var(--border-faint);
  font-size: var(--t-xs);
  color: var(--content-secondary);
}
.sh-tr > span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.sh-tr > span:first-child {
  color: var(--content-primary);
}
.sh-th {
  padding-top: 10px;
  font-family: pangeaMono, ui-monospace, monospace;
  font-size: 10px;
  letter-spacing: 0.04em;
  color: var(--content-quaternary);
}
.sh-th > span:first-child {
  color: var(--content-quaternary);
}
.sh-tr:last-child {
  border-bottom: 0;
}
.sh-foot {
  display: flex;
  align-items: baseline;
  justify-content: flex-end;
  gap: 10px;
  margin: 6px 16px 0;
  padding-top: 12px;
  border-top: 1px solid var(--border-faint);
  font-size: var(--t-xs);
  color: var(--content-quaternary);
}
.sh-foot-val {
  min-width: 24px;
  text-align: right;
  font-size: var(--t-md);
  color: var(--content-primary);
}

/* --- the exchange panel: the entire setup, quoted -------------------------- */
.ex {
  padding: 14px 16px 16px;
}
.ex-head {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--border-faint);
}
.ex-head b {
  font-weight: 400;
  font-size: var(--t-sm);
  color: var(--content-primary);
}
.ex-head span {
  font-size: var(--t-xs);
  color: var(--content-quaternary);
}
.ex-row {
  display: flex;
  gap: 10px;
  padding-top: 13px;
}
.ex-av {
  width: 24px;
  height: 24px;
  flex: none;
  border-radius: var(--r-sm);
  display: grid;
  place-items: center;
  font-size: 10px;
  color: #fff;
}
.ex-av.is-bot {
  background: #1c7a5b;
}
.ex-av.is-me {
  background: #3f6fb8;
}
.ex-col {
  min-width: 0;
}
.ex-who {
  display: block;
  font-size: var(--t-xs);
  color: var(--content-quaternary);
}
.ex-say {
  display: block;
  margin-top: 2px;
  font-size: var(--t-sm);
  line-height: 1.4;
  color: var(--content-primary);
}

/* --- the no-approval strip ------------------------------------------------ */
.nb {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 4px 8px;
  padding: 14px 16px;
  font-size: var(--t-xs);
  color: var(--content-tertiary);
}
.nb b {
  font-weight: 400;
  font-size: var(--t-sm);
  color: var(--content-primary);
}
.nb > span:last-child {
  flex: 1 0 100%;
  line-height: 1.45;
}
.nb-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--accent);
  align-self: center;
}

@keyframes fade-blur-in {
  from {
    opacity: 0;
    filter: blur(6px);
  }
  to {
    opacity: 1;
    filter: blur(0);
  }
}

/* --------------------------------------------------------------------------
   8. Section scaffold
   -------------------------------------------------------------------------- */
.section {
  position: relative;
  padding-block: var(--sec-pad);
}
/* The rail head. Two things used to be wrong here at once:
   (1) as a plain grid item it was STRETCHED to the row height, so a 110px
       label owned a 372–950px box and the rest of that box was void;
   (2) it was sticky at top:120 inside that stretched box. Every content column
       on this page is 148–950px against a ~900px viewport, so the pin only
       engaged in the last stretch of each section — which parked the heading
       level with the space BELOW its own content instead of level with it.
   align-self: start fixes both: the head is its own height and always sits on
   the first line of the column it labels. Sticky is gone because there is no
   section here tall enough for it to earn its keep. */
.section-head {
  grid-column: 1 / span 4;
  align-self: start;
}
.section-head .eyebrow {
  margin-bottom: 20px;
}
/* Sticky is earned, not default. The one content column on this page taller
   than a viewport is the changelog (~1,040px), so it is the only rail where
   pinning keeps the left column occupied instead of parking a heading in dead
   space. The sticky lives on the grid ITEM, whose containing block is the full
   grid area, so align-self: start still gives it real travel. */
#changelog .section-head {
  position: sticky;
  top: 120px;
}
/* A rail carrying only eyebrow + heading reads as a label beside a void. The
   deck gives it a second beat so the two columns start together. */
.section-deck {
  margin-top: 18px;
  max-width: 28em;
  font-size: var(--t-md);
  line-height: var(--lh-body);
  color: var(--content-tertiary);
}

/* Introducing. The eyebrow returns to the rail at column 1, where every other
   section on the page starts its head. Parked at column 6 it was the only
   section breaking the page's own rail convention, and it left a 569x382px
   empty region — the largest void on the page — with a 10px eyebrow as its
   only anchor. A lone eyebrow in the rail is thinner than a heading, but it is
   the convention, and the rail reads as a rail. */
#introducing .section-head {
  grid-column: 1 / span 4;
}
.intro-body {
  grid-column: 6 / span 6;
  display: flex;
  flex-direction: column;
  gap: 54px;
}

/* --------------------------------------------------------------------------
   8b. See it run — one autoplaying loop, no scroll linkage

   Lightfield's equivalent is a 19s autoplay webm. Ours is six pre-rendered
   stills of the same Slack thread crossfading on a timer: no scroll binding,
   no pinning, no hijack, and nothing that can be scrubbed past. Reduced motion
   stops the timer on the frame that carries the whole argument.
   -------------------------------------------------------------------------- */
.run {
  padding-block: var(--sec-pad);
}
.run-head {
  max-width: 42em;
  margin-bottom: 44px;
}
.run-head .eyebrow {
  margin-bottom: 20px;
}
.run-head .h2 {
  max-width: 18em;
}
.run-stage {
  position: relative;
}
.run-frames {
  position: relative;
  /* JS writes the real height from the tallest frame; this only stops the
     section collapsing between first paint and that write. */
  min-height: 420px;
}
/* All six frames occupy the same box; exactly one is shown. Crossfade, so there
   is no layout shift and no frame the reader can miss.

   visibility, not opacity alone: an opacity-0 layer is still rendered text and
   still hit-testable, so five hidden copies of the scene were being counted and
   exposed. visibility is held through the fade with a 0s-delayed transition. */
.run-frames .sl-frame {
  position: absolute;
  inset: 0;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.5s var(--ease-in-out), visibility 0s linear 0.5s;
}
.run-frames .sl-frame.is-on {
  opacity: 1;
  visibility: visible;
  transition: opacity 0.5s var(--ease-in-out), visibility 0s;
}
.run-controls {
  display: flex;
  align-items: center;
  gap: 24px;
  margin-top: 22px;
  padding-top: 18px;
  border-top: 1px solid var(--border-faint);
}
.run-caption {
  position: relative;
  flex: 1;
  min-width: 0;
  min-height: 22px;
  font-size: var(--t-md);
  color: var(--content-tertiary);
}
.run-caption span {
  position: absolute;
  inset: 0;
  opacity: 0;
  visibility: hidden;
  transform: translateY(8px);
  transition: opacity 0.355s var(--ease-p1), transform 0.355s var(--ease-p1),
    visibility 0s linear 0.355s;
}
.run-caption span.is-on {
  opacity: 1;
  visibility: visible;
  transform: none;
  transition: opacity 0.355s var(--ease-p1) 0.095s,
    transform 0.355s var(--ease-p1) 0.095s, visibility 0s;
}
.run-steps {
  display: flex;
  gap: 6px;
  flex: none;
}
.run-step {
  padding: 8px 0;
  cursor: pointer;
}
.run-step i {
  display: block;
  width: 26px;
  height: 2px;
  border-radius: 999px;
  background: var(--n-t4);
  transition: background-color var(--d-hover) var(--ease-out);
}
.run-step:hover i {
  background: var(--n-t6);
}
.run-step.is-on i {
  background: var(--accent);
}

/* --------------------------------------------------------------------------
   9. Features — sticky rail + a column of complete Slack windows
   -------------------------------------------------------------------------- */
.features {
  position: relative;
  padding-block: var(--sec-pad);
}
.features-grid {
  align-items: start;
}

/* Parked at the vertical centre of the screen, and it stays there for the whole
   run of tiles. This is lightfield's mechanism exactly: `sticky` with
   `top: 50vh` on an element that is only as tall as its own content.

   The height is the entire point. It used to be `height: 100vh; top: 0`, which
   centred correctly but cost a full viewport of sticky range, because a sticky
   element releases once its own bottom reaches the bottom of its containing
   block. A 900px rail in a 4167px section therefore stopped sticking 900px
   early and rode off the top of the screen while the last tile was still being
   read. At content height it gives up only its own 241px instead.

   The centring now comes from translating the inner block up half its own
   height rather than from justify-content inside a viewport-tall box, so it
   holds whatever the copy length happens to be. */
.feat-rail {
  grid-column: 1 / span 4;
  position: sticky;
  top: 50vh;
  align-self: start;
  height: auto;
}
/* One block, in flow. The section head used to be absolutely positioned at
   top:120 while the copy sat centred, which opened a ~180px void between
   them — the exact defect the design review found on the live page. */
.feat-rail-inner {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  /* Half its own height, so the block's middle lands on the 50vh park line
     rather than its top edge. Content-agnostic: a longer or shorter feature
     paragraph stays centred without a magic number to keep in sync. */
  transform: translateY(-50%);
}
.feat-rail-inner .eyebrow {
  margin-bottom: 16px;
}
.feat-rail-inner .h2 {
  max-width: 12em;
  margin-bottom: 30px;
}

.feat-counter {
  display: flex;
  align-items: baseline;
  gap: 5px;
  margin-bottom: 14px;
  font-family: pangeaMono, ui-monospace, monospace;
  font-size: var(--t-sm);
  line-height: 20px;
}
.feat-counter-window {
  height: 20px;
  overflow: hidden;
}
.feat-counter-roll {
  transition: transform 0.405s var(--ease-p1);
  color: var(--accent);
}
.feat-counter-total {
  color: var(--content-quaternary);
}

.feat-copy-stack {
  position: relative;
  align-self: stretch;
  min-height: 170px;
}
/* Seven of the eight sit at opacity 0 on the same coordinates. visibility takes
   them out of the rendered-text and hit-test layers too, held through the fade
   by the 0s-delayed transition. */
.feat-copy {
  position: absolute;
  inset: 0;
  opacity: 0;
  visibility: hidden;
  transform: translateY(20px);
  transition: opacity 0.355s var(--ease-p1), transform 0.355s var(--ease-p1),
    visibility 0s linear 0.355s;
  pointer-events: none;
}
.feat-copy.is-active {
  opacity: 1;
  visibility: visible;
  transform: none;
  transition: opacity 0.355s var(--ease-p1) 0.095s,
    transform 0.355s var(--ease-p1) 0.095s, visibility 0s;
  pointer-events: auto;
}
.feat-copy h3 {
  font-size: var(--t-h3);
  line-height: 1.25;
  letter-spacing: var(--ls-h3);
  color: var(--content-primary);
}
.feat-copy p {
  margin-top: 12px;
  max-width: 24em;
  font-size: var(--t-lg);
  line-height: var(--lh-body);
  color: var(--content-tertiary);
}

.feat-media {
  /* span 8, not 7, so the column runs to the last grid line. The extra 114px
     pays for the plate's new bezel instead of coming out of the scene: the
     Slack window renders at essentially the size it always did (0.759 scale
     against 0.767 before) while the backdrop around it goes from 10px to 64. */
  grid-column: 5 / span 8;
  display: flex;
  flex-direction: column;
  /* wide enough that two windows never read as one stacked object */
  gap: var(--band);
  padding-block: 12px 0;
}
/* The backdrop plate. The scenes are their own product surface, not page
   background, so each one sits on a black that is a step off the page: #08090b
   -> #0c0e13, four luminance units lighter and slightly cooler. That is enough
   for the eye to separate the window from the page and not enough to spend ink
   on. Depth is a hairline plus the frame's own drop-shadow falling on the
   plate; no box-shadow, per the elevation rule. Radii stay concentric-ish
   inside the 12px ceiling: 12 outside, 8 inside. */
.feat-tile {
  border-radius: var(--r-2xl);
  display: flex;
  /* The bezel is the point, not a detail. Measured off lightfield's product
     plate: their window sits inside roughly 9.9% of its own width in clear
     space on each side. Ours was 10px, which is 1.46%, so the window filled
     the plate edge to edge and the plate read as a border rather than as a
     surface the product is sitting on. Stepped, because a percentage or a
     clamp here would put fractional padding back into the layout. */
  padding: 16px;
  background: var(--plate);
  border: 1px solid var(--border-faint);
  transition: transform var(--d-hover) var(--ease-out),
    border-color var(--d-hover) var(--ease-out),
    background-color var(--d-hover) var(--ease-out);
}
.feat-tile > .sl-frame {
  border-radius: var(--r-lg);
  transition: filter var(--d-hover) var(--ease-out);
}
/* A lift, not a click affordance: the cursor never changes, because these are
   stills and nothing here is pressable. */
.feat-tile:hover {
  transform: translateY(-3px);
  background: var(--plate-hi);
  border-color: var(--border-subtle);
}
.feat-tile:hover > .sl-frame {
  filter: drop-shadow(0 30px 44px rgb(0 0 0 / 0.72));
}
/* The bezel steps with the room available, and the steps are set against the
   breakpoint that actually changes the tile's width: the grid collapses at
   max-width 1024, so the tile goes full-bleed at and below that and drops to
   eight columns above it. Stepping at 1280 instead, as a first pass did, put
   64px on a 781px tile (16% a side) and 64px on an 888px tile (8.6%), which is
   the same number meaning two different things. Below the grid the tile is
   full width and the scene is the scarce thing, so the bezel stays small. */
@media (min-width: 768px) {
  .feat-tile {
    padding: 28px;
  }
}
@media (min-width: 1025px) {
  .feat-tile {
    padding: 56px;
  }
}
@media (min-width: 1440px) {
  .feat-tile {
    padding: 64px;
  }
}

/* --------------------------------------------------------------------------
   10. Proof — the changelog

   There is no testimonial block and no compliance strip. We hold no SOC 2, ISO,
   GDPR or HIPAA, and no customer has been quoted, so neither section exists
   here rather than existing as invented people and unearned badges.
   -------------------------------------------------------------------------- */
.chg-list {
  grid-column: 5 / span 8;
  border-top: 1px solid var(--border-faint);
}
/* A static list, styled as one. The rows carried a trailing "+" and a hover
   fill, which together read as an expandable disclosure; neither expanded
   anything. No affordance without a behaviour behind it. */
.chg-row {
  display: grid;
  /* 90px fits the widest date this list renders with room to spare; 108px was
     reserving space nothing used. 24px gap puts this row on the same gutter as
     the page grid rather than inventing a 20px one for itself. */
  grid-template-columns: 90px 1fr;
  gap: var(--gut);
  align-items: baseline;
  padding: 56px 0;
  border-bottom: 1px solid var(--border-faint);
}
.chg-date {
  font-family: pangeaMono, ui-monospace, monospace;
  font-size: var(--t-xs);
  color: var(--content-label);
}
.chg-title {
  font-size: var(--t-lg);
  color: var(--content-primary);
  /* 97.6 characters per line at 1920 before this cap. The changelog is already
     the densest block on the page at 184 words per 1000px, roughly 2.5x the
     rest of it, so it is the last place that should also carry the longest
     measure. */
  max-width: 30em;
}
.chg-body {
  margin-top: 6px;
  font-size: var(--t-md);
  color: var(--content-tertiary);
  /* was 46em, which is 92 characters at this size, not 46. An em cap reads as
     if it means characters and does not: at roughly 0.5em per character it is
     double. 30em is about 60ch. */
  max-width: 30em;
}

/* --------------------------------------------------------------------------
   11. Closing CTA + footer
   -------------------------------------------------------------------------- */
.cta {
  position: relative;
  padding-block: var(--sec-pad);
  text-align: center;
  overflow: hidden;
  border-top: 1px solid var(--border-faint);
}
.cta .h2 {
  max-width: 16em;
  margin-inline: auto;
}
.cta .btn {
  margin-top: 26px;
}
/* Sits under the CTA on its own line, quieter than the button, so the
   booking link stays the primary action. */
.cta-tel {
  display: block;
  margin-top: 18px;
  font-size: var(--t-sm);
  color: var(--content-tertiary);
  text-decoration: none;
  transition: color 0.12s cubic-bezier(0.4, 0, 0.2, 1);
}
.cta-tel:hover {
  color: var(--content-primary);
}
.cta-glow {
  position: absolute;
  left: 50%;
  bottom: -300px;
  width: 900px;
  height: 480px;
  transform: translateX(-50%);
  background: radial-gradient(
    50% 50% at 50% 50%,
    rgb(43 182 115 / 0.3),
    transparent 70%
  );
  filter: blur(48px);
  pointer-events: none;
}

/* the provenance banner is fixed to the bottom of the viewport; the extra
   bottom pad keeps it off the utility row at the end of the scroll */
.site-footer {
  border-top: 1px solid var(--border-faint);
  padding-block: var(--sec-pad) calc(var(--sec-pad) + var(--banner-h));
}
.footer-grid {
  row-gap: 32px;
}
.footer-brand {
  grid-column: 1 / span 4;
}
/* The Atlas mark stands in for the wordmark here. Smaller than the hero's,
   since the footer is a closing signature rather than a statement. */
.footer-atlas {
  display: block;
  width: 64px;
  height: auto;
  opacity: 0.72;
}
.footer-col {
  grid-column: span 2;
}
.footer-col h4 {
  font-family: pangeaMono, ui-monospace, monospace;
  font-size: var(--t-eyebrow);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--content-label);
  margin-bottom: 14px;
}
.footer-col a {
  display: block;
  padding: 4px 0;
  font-size: var(--t-sm);
  color: var(--content-tertiary);
  transition: color var(--d-press) var(--ease-out);
}
.footer-col a:hover {
  color: var(--content-primary);
}
.footer-utility {
  grid-column: 1 / -1;
  margin-top: 40px;
  padding-top: 20px;
  border-top: 1px solid var(--border-faint);
  display: flex;
  flex-wrap: wrap;
  gap: 18px;
  align-items: center;
  font-size: var(--t-xs);
  color: var(--content-quaternary);
}
.footer-utility a {
  transition: color var(--d-press) var(--ease-out);
}
.footer-utility a:hover {
  color: var(--content-secondary);
}
.footer-utility .spacer {
  margin-left: auto;
}

/* --------------------------------------------------------------------------
   12. Responsive
   -------------------------------------------------------------------------- */
/* No panel overrides between 1025 and 1440: the stack is sized in % of the
   stage with min-widths, so the one deliberate overlap stays inside the run
   report's empty footer-left at every width in that range. An earlier 1280
   override moved the front strip onto the table and covered a row. */

@media (max-width: 1024px) {
  :root {
    --t-hero: 34px;
    --t-h2: 24px;
  }
  .hero-sticky {
    min-height: 0;
    padding-bottom: var(--sec-pad);
  }
  .hero-copy,
  .hero-stage,
  .section-head,
  .intro-body,
  .feat-rail,
  .feat-media,
  .chg-list,
  .footer-brand {
    grid-column: 1 / -1;
  }
  /* Stacked, not overlapped. Below the grid there is no room for a composition
     and an overlap here would put a panel over the run report's own rows. */
  .hero-stage {
    height: auto;
    margin-top: 56px;
    margin-right: 0;
    display: flex;
    flex-direction: column;
    gap: 18px;
  }
  .gp,
  .gp[data-z="0"],
  .gp[data-z="1"],
  .gp[data-z="2"] {
    position: static;
    width: 100%;
    min-width: 0;
    transform: none;
  }
  /* relative, NOT static. The pill's ::before is `position: absolute; inset: 0`
     with a 17px backdrop-filter. `static` removes the pill as a containing
     block, so inset:0 resolved against .hero-sticky and the blur grew to the
     full hero (1024x1380 at 1024px) — painting over the h1, the sub, the CTA
     and all three panels at ~1.2:1. `relative` keeps the pill in flow, which is
     all the media query wanted, and keeps it the containing block for its own
     backdrop. */
  /* align-self + margin, not `left`. As a stretched flex item the pill took the
     full width of .hero-sticky and then `left: var(--pad-x)` pushed all of it
     24-56px past the right edge, where body's overflow-x: hidden quietly ate
     the tail of its own label. Shrink-to-fit and pad with margin instead. */
  .hero-pill {
    position: relative;
    left: 0;
    align-self: flex-start;
    max-width: calc(100% - var(--pad-x) * 2);
    margin: 32px var(--pad-x) 0;
  }
  .run-controls {
    flex-direction: column;
    align-items: flex-start;
    gap: 16px;
  }
  .run-caption {
    position: static;
    min-height: 44px;
  }
  .run-caption span {
    position: absolute;
  }
  .feat-rail {
    position: static;
    height: auto;
    margin-bottom: 28px;
  }
  /* The centring offset only means something against a sticky park line. With
     the rail back in flow it would just pull the block 120px up into whatever
     sits above it. */
  .feat-rail-inner {
    transform: none;
  }
  .feat-copy-stack {
    min-height: 0;
  }
  /* below the rail breakpoint all eight read as a plain list */
  .feat-copy {
    position: static;
    opacity: 1;
    visibility: visible;
    transform: none;
    margin-bottom: 18px;
  }
  .feat-media {
    gap: 72px;
  }
  .intro-body {
    margin-top: 28px;
  }
  .section-head {
    margin-bottom: 32px;
  }
  .footer-col {
    grid-column: span 4;
  }
}

/* The sheet drops to the two columns that carry the argument (who, and the
   personalization line written for them). Four fixed tracks plus their gaps
   are wider than a 390px panel's interior, so below this they would push the
   page sideways. */
@media (max-width: 640px) {
  .sh-tr {
    grid-template-columns: 72px minmax(0, 1fr);
  }
  .sh-tr > span:nth-child(2),
  .sh-tr > span:nth-child(3) {
    display: none;
  }
  .sh-metrics {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .sh-metric:nth-child(2) {
    border-right: 0;
  }
  .sh-metric:last-child {
    grid-column: 1 / -1;
    border-top: 1px solid var(--border-faint);
  }
}

@media (max-width: 800px) {
  .navbar {
    width: calc(100vw - 32px);
  }
  .nav-list {
    display: none;
  }
  .nav-burger {
    display: flex;
    margin-left: auto;
    margin-right: 8px;
    color: var(--content-secondary);
  }
  .gp {
    transform: none !important;
  }
  .chg-row {
    grid-template-columns: 84px 1fr;
    gap: 12px;
  }
  .footer-col {
    grid-column: span 6;
  }
}

/* We honour this; the reference does not. The autoplay loop stops on one
   static frame (demo.js never starts its timer), the aurora paints one static
   frame, and the parallax rAF loop is never bound. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
  .gp {
    opacity: 1;
  }
  .aurora i {
    animation: none;
  }
}

/* ==========================================================================
   13. The Slack scene
       Rendered as live DOM at a fixed 980px design width, then cropped and
       scaled inside .sl-frame. Slack's own brand colours (aubergine rail,
       blue avatar, its blue selected-channel fill) live ONLY inside this
       block — the scene is depicting Slack, so those are its colours, not
       ours. Our single accent is the agent's green. Nothing outside .sl
       writes a chromatic value.
   ========================================================================== */
.sl-frame {
  position: relative;
  width: 100%;
  height: 100%;
  overflow: clip;
  border-radius: var(--r-lg);
  filter: drop-shadow(0 24px 34px rgb(0 0 0 / 0.6));
}
.sl-shot {
  position: absolute;
  top: 0;
  left: 0;
  width: 980px;
  transform-origin: 0 0;
}

.sl {
  --sl-bg: #131518;
  --sl-side: #17191d;
  --sl-rail: #3d1040;
  --sl-line: rgb(255 255 255 / 0.09);
  --sl-line-soft: rgb(255 255 255 / 0.06);
  --sl-txt: rgb(255 255 255 / 0.92);
  --sl-txt-2: rgb(255 255 255 / 0.62);
  --sl-txt-3: rgb(255 255 255 / 0.5);
  --sl-blue: #1a6ba8;
  --sl-green: #2bb673;
  width: var(--sl-w, 980px);
  border: 1px solid var(--border-moderate);
  border-radius: var(--r-lg);
  overflow: clip;
  background: var(--sl-bg);
  font-size: 13px;
  line-height: 1.5;
  color: var(--sl-txt-2);
}

/* titlebar */
.sl-title {
  display: flex;
  align-items: center;
  gap: 7px;
  height: 34px;
  padding-inline: 12px;
  border-bottom: 1px solid var(--sl-line-soft);
  background: #0f1114;
}
.sl-title i {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--n-t4);
}
.sl-title i.r { background: #e0665f; }
.sl-title i.y { background: #dfa440; }
.sl-title i.g { background: #55a84f; }
.sl-title span {
  margin-left: 6px;
  color: var(--sl-txt);
}

.sl-body { display: flex; height: var(--sl-h, 560px); }

/* workspace rail */
.sl-rail {
  width: 52px;
  flex: none;
  padding-top: 10px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  background: var(--sl-rail);
}
.sl-ws {
  width: 32px; height: 32px;
  display: grid; place-items: center;
  border-radius: var(--r-lg);
  background: #5b2260;
  border: 2px solid rgb(255 255 255 / 0.85);
  color: #fff;
  font-size: 13px;
}
.sl-plus {
  width: 26px; height: 26px;
  display: grid; place-items: center;
  border-radius: 50%;
  border: 1px dashed rgb(255 255 255 / 0.3);
  color: rgb(255 255 255 / 0.6);
  font-size: 14px;
}

/* channel sidebar */
.sl-side {
  width: 176px;
  flex: none;
  padding: 10px 8px;
  background: var(--sl-side);
  border-right: 1px solid var(--sl-line-soft);
}
.sl-side-head {
  display: flex; align-items: center; gap: 6px;
  padding: 2px 6px 12px;
  color: var(--sl-txt);
  font-size: 14px;
}
.sl-side-head span { color: var(--sl-txt-3); font-size: 10px; }
.sl-side-sec {
  padding: 10px 6px 4px;
  font-size: 12px;
  color: var(--sl-txt-3);
}
.sl-side-item {
  display: flex; align-items: center; gap: 7px;
  padding: 4px 8px;
  border-radius: var(--r-sm);
  color: var(--sl-txt-2);
}
.sl-side-item.on { background: var(--sl-blue); color: #fff; }
.sl-hash { color: currentColor; opacity: 0.6; }
.sl-pres {
  width: 8px; height: 8px; border-radius: 50%;
  border: 1.5px solid var(--n-t6);
}
.sl-pres.is-app { border-color: var(--sl-green); background: var(--sl-green); }

/* main */
.sl-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.sl-chan-head {
  display: flex; align-items: center; gap: 3px;
  height: 44px;
  padding-inline: 16px;
  border-bottom: 1px solid var(--sl-line-soft);
  color: var(--sl-txt);
  font-size: 15px;
}
.sl-chan-head b { font-weight: 400; }
.sl-panes { flex: 1; min-height: 0; display: flex; }

.sl-chan {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: 12px 0 10px;
  /* bottom-anchored like real Slack; older messages clip off the top rather
     than overflowing upward over the channel header */
  overflow: clip;
}
.sl-chan-msgs { padding-inline: 14px; }
.sl-divider {
  display: flex; align-items: center; gap: 10px;
  margin: 6px 0 12px;
  color: var(--sl-txt-3);
  font-size: 12px;
}
.sl-divider::before, .sl-divider::after {
  content: ""; flex: 1; height: 1px; background: var(--sl-line-soft);
}

/* messages */
.sl-msg { display: flex; gap: 9px; padding: 5px 0; }
.sl-av, .sl-av-spacer {
  width: 30px; height: 30px; flex: none;
  border-radius: var(--r-sm);
  display: grid; place-items: center;
  font-size: 11px;
  color: #fff;
}
.sl-av-me { background: #3f6fb8; }
.sl-av-bot { background: #1c7a5b; }
.sl-av-spacer { background: none; }
.sl-msg-col { min-width: 0; flex: 1; }
.sl-msg-head { display: flex; align-items: baseline; gap: 6px; }
.sl-msg-head b { color: var(--sl-txt); font-weight: 400; }
.sl-app {
  padding: 0 4px;
  border-radius: var(--r-sm);
  background: var(--sl-green);
  color: #08110d;
  font-size: 9px;
  letter-spacing: 0.04em;
}
.sl-time { color: var(--sl-txt-3); font-size: 11px; }
.sl-msg-body { color: var(--sl-txt); }
.sl-mention {
  padding: 1px 3px;
  border-radius: var(--r-sm);
  background: rgb(43 182 115 / 0.18);
  color: var(--sl-green);
}
/* A word the user types back. It is an instruction, so it is set as one. */
.sl-cmd {
  padding: 1px 5px;
  border-radius: var(--r-sm);
  border: 1px solid var(--sl-line);
  background: rgb(255 255 255 / 0.05);
  font-family: pangeaMono, ui-monospace, monospace;
  font-size: 12px;
  color: var(--sl-txt);
}

/* attachment / block kit */
.sl-attach {
  margin-top: 7px;
  padding: 9px 0 9px 12px;
  border-left: 3px solid var(--sl-green);
}
.sl-bk-title { display: flex; align-items: center; gap: 8px; color: var(--sl-txt); font-size: 14px; }
.sl-bk-title b { font-weight: 400; }
/* the sample label rides on the card, so a screenshot of the card carries it */
.sl-chip {
  padding: 1px 7px;
  border-radius: 999px;
  border: 1px solid var(--sl-line);
  font-family: pangeaMono, ui-monospace, monospace;
  font-size: 10px;
  letter-spacing: 0.04em;
  color: var(--sl-txt-3);
}
.sl-note {
  margin-top: 9px;
  color: var(--sl-txt-2);
  font-size: 12px;
  line-height: 1.45;
}

/* the file that arrives */
.sl-file { display: flex; align-items: center; gap: 9px; color: var(--sl-txt); }
.sl-file-ic {
  padding: 3px 6px;
  border-radius: var(--r-sm);
  background: #1c7a5b;
  color: #fff;
  font-family: pangeaMono, ui-monospace, monospace;
  font-size: 10px;
  letter-spacing: 0.04em;
}
.sl-file-name { font-size: 14px; }
.sl-file-meta { color: var(--sl-txt-3); font-size: 12px; }
/* the eleven columns, in the order they ship */
.sl-cols {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  margin-top: 10px;
}
.sl-col {
  padding: 2px 7px;
  border-radius: var(--r-sm);
  border: 1px solid var(--sl-line-soft);
  background: rgb(255 255 255 / 0.03);
  font-family: pangeaMono, ui-monospace, monospace;
  font-size: 11px;
  color: var(--sl-txt-2);
}

/* the logic the agent ran, numbered because the order is the point */
.sl-logic {
  counter-reset: sll;
  margin: 9px 0 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.sl-logic li {
  position: relative;
  padding-left: 24px;
  color: var(--sl-txt);
  font-size: 13px;
  line-height: 1.45;
}
.sl-logic li::before {
  counter-increment: sll;
  content: counter(sll, decimal-leading-zero);
  position: absolute;
  left: 0;
  top: 0;
  font-family: pangeaMono, ui-monospace, monospace;
  font-size: 11px;
  color: var(--sl-green);
}

/* labelled rows inside a card */
.sl-kv {
  display: flex;
  gap: 10px;
  margin-top: 8px;
  font-size: 13px;
}
.sl-kv span {
  flex: none;
  width: 132px;
  color: var(--sl-txt-3);
}
.sl-kv b { font-weight: 400; color: var(--sl-txt); }

.sl-status {
  display: flex; align-items: center; gap: 7px;
  margin-top: 8px;
  color: var(--sl-txt-2);
}
.sl-spin {
  width: 12px; height: 12px; border-radius: 50%;
  border: 2px solid var(--n-t4);
  border-top-color: var(--sl-green);
  animation: sl-spin 0.9s linear infinite;
}
@keyframes sl-spin { to { transform: rotate(360deg); } }

/* A channel that is only a few messages old fills from the day divider
   downward; bottom-anchoring it opens a void under the divider. The composer
   still belongs at the bottom of the pane either way.

   space-between, not flex-start plus `margin-top: auto` on the composer. Both
   put the slack in the same place, but the auto margin resolves to whatever
   fraction of a pixel is left over and shows up in a computed-style audit as
   sub-pixel spacing. Free space distributed by the container is not spacing. */
.sl-anchor-top .sl-chan { justify-content: space-between; }

/* composer */
.sl-comp { padding: 8px 14px 4px; }
.sl-comp-box {
  border: 1px solid var(--sl-line);
  border-radius: var(--r-lg);
  padding: 8px 10px 6px;
}
.sl-comp-ph { display: block; color: var(--sl-txt-3); }
.sl-comp-tools { display: flex; align-items: center; gap: 10px; margin-top: 6px; color: var(--sl-txt-3); }
.sl-comp-send {
  margin-left: auto !important;
  padding: 1px 7px;
  border-radius: var(--r-sm);
  background: #128257;
  color: #fff;
  font-size: 11px;
}

@media (prefers-reduced-motion: reduce) {
  .sl-spin { animation: none; }
}

/* ==========================================================================
   14. Trust — evidence first, claim last
   ========================================================================== */
.trust > .container > .eyebrow {
  margin-bottom: 28px;
}
.trust-grid {
  align-items: start;
  row-gap: 32px;
}
/* No fixed height. The still is mounted with fitHeight, so demo.js writes the
   frame's real height from the scaled scene — a hard height here contradicted
   it, the frame overflowed this box by ~90px, and because .sl-frame carries a
   filter (its own stacking context) it painted over .trust-land below. Letting
   the host wrap its content makes the grid reserve the height the still
   actually takes, and the overlap stops existing. */
.trust-shot {
  grid-column: 1 / span 7;
}
.trust-copy {
  grid-column: 8 / span 5;
  display: flex;
  flex-direction: column;
  gap: 38px;
}
.trust-steps {
  counter-reset: ts;
  display: flex;
  flex-direction: column;
  gap: 16px;
  padding-bottom: 4px;
}
.trust-steps li {
  position: relative;
  padding-left: 30px;
  font-size: var(--t-md);
  color: var(--content-tertiary);
}
.trust-steps li::before {
  counter-increment: ts;
  content: counter(ts, decimal-leading-zero);
  position: absolute;
  left: 0;
  top: 1px;
  font-family: pangeaMono, ui-monospace, monospace;
  font-size: var(--t-xs);
  color: var(--accent);
}
.trust-steps b {
  font-weight: 400;
  color: var(--content-primary);
}
.trust-land {
  margin-top: var(--sec-pad);
  text-align: center;
  font-size: var(--t-h2);
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-h2);
  color: var(--content-primary);
}

/* ==========================================================================
   15. Typed literals

   The experts block that used to live here is gone with its section: the page
   argues from evidence in the reader's industry, not from a named human, so
   there are no expert cards to style and none should come back.
   ========================================================================== */
/* One rule for every literal a reader is meant to type or recognise: the
   words the individual plan answers to. */
.feat-copy code,
.chg-body code {
  font-family: pangeaMono, ui-monospace, monospace;
  font-size: var(--t-xs);
  padding: 1px 5px;
  border-radius: var(--r-sm);
  background: var(--surface-raised);
  color: var(--content-secondary);
}

/* ==========================================================================
   16. Pricing — two tiers, one product

   The tiers carry the audience split (a rep buying for themself, a company
   buying for the floor), which is why the rest of the page never forks its
   messaging. Same positioning in both columns.
   ========================================================================== */
.price-grid {
  grid-column: 5 / span 8;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--gut);
}
.price-card {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--border-subtle);
  border-radius: var(--r-lg);
  background: var(--surface-base);
  padding: 24px 24px 26px;
}
.price-card.is-lead {
  background: var(--surface-primary);
  border-color: var(--border-moderate);
}
.price-tier {
  display: flex;
  align-items: center;
  gap: 8px;
  font-family: pangeaMono, ui-monospace, monospace;
  font-size: var(--t-eyebrow);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--content-label);
}
/* Ties the demo to the tier it depicts. The page shows one plan and this is
   where a reader finds out which, and what the other one adds. */
.price-tag {
  padding: 3px 7px 2px;
  border-radius: var(--r-sm);
  border: 1px solid var(--accent-line);
  color: var(--accent);
}
.price-amount {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin-top: 20px;
}
.price-amount b {
  font-weight: 400;
  font-size: var(--t-h2);
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-h2);
  color: var(--content-primary);
}
.price-amount span {
  font-size: var(--t-sm);
  color: var(--content-quaternary);
}
/* The accent is spent once per card at most, and only where it is carrying an
   offer ("first batch free"). The enterprise line is a fact, not an offer. */
.price-free {
  margin-top: 8px;
  padding-bottom: 22px;
  border-bottom: 1px solid var(--border-faint);
  font-size: var(--t-md);
  color: var(--accent);
}
.price-card.is-lead .price-free {
  color: var(--content-tertiary);
}
/* flex:1 so the list absorbs the height difference between the two cards and
   both CTAs land on the same baseline */
.price-list {
  flex: 1;
  margin-top: 22px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.price-list li {
  position: relative;
  padding-left: 22px;
  font-size: var(--t-md);
  line-height: var(--lh-body);
  color: var(--content-tertiary);
}
.price-list li::before {
  content: "";
  position: absolute;
  left: 2px;
  top: 10px;
  width: 6px;
  height: 1px;
  background: var(--content-subtle);
}
/* A status line, deliberately not a badge. Plain text at the card's quietest
   text level: no border, no pill, no icon, nothing a screenshot could read as a
   seal. The accent is spent elsewhere on this card and does not come here. */
.price-note {
  margin-top: 24px;
  font-size: var(--t-xs);
  color: var(--content-label);
}
/* Tighter than the 32px a CTA takes after a bullet list: the note belongs to the
   card, not to the button, and 32px of air between them reads as a caption on
   the CTA. Both CTAs stay on the same baseline either way, because .price-list
   is flex:1 and absorbs whatever is above them. */
.price-note + .price-cta {
  margin-top: 20px;
}
.price-cta {
  align-self: flex-start;
  margin-top: 32px;
}

@media (max-width: 1024px) {
  .trust-shot,
  .trust-copy,
  .price-grid {
    grid-column: 1 / -1;
  }
}
@media (max-width: 800px) {
  .price-grid {
    grid-template-columns: 1fr;
  }
}

/* ==========================================================================
   14. Legal prose pages — privacy.html, terms.html
   One reading column, numbered sections, nothing else. Adds no colour, no
   type size and no vertical interval that is not already in the token layer;
   the single new value is the reading line-height, because long-form text at
   15px needs more leading than the marketing page's 1.5.
   ========================================================================== */

/* index.html reserves --banner-h at the foot of the page for the fixed
   provenance strip. These pages carry their warning at the TOP instead, in
   flow, so the footer gives that space back. */
.legal-page .site-footer {
  padding-block: var(--sec-pad);
}

.legal-main {
  --lh-prose: 1.7;
  /* the floating header pill ends at 66px; this clears it with room */
  padding-block: 132px var(--sec-pad);
}

/* The reading column, deliberately much narrower than .container: prose wants
   a 65-75 character measure and the 1560px shell would hand it three times
   that. Measured, not guessed: 560px ran 74-78 characters a line, so the
   column is 520px, which lands at ~70. */
.legal-shell {
  width: min(100% - 2 * var(--pad-x), 520px);
  margin-inline: auto;
}

/* --- the unreviewed-draft warning ---------------------------------------- */
/* Same construction as .copy-banner (mono accent label over tertiary body)
   but in flow at the top of the page and tinted, because it is the first
   thing anyone landing here has to read. */
.legal-alert {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-bottom: 56px;
  padding: 16px 18px;
  border: 1px solid var(--accent-line);
  border-radius: var(--r-lg);
  background: var(--accent-quiet);
}
.legal-alert b {
  font-weight: 400;
  font-family: pangeaMono, ui-monospace, monospace;
  font-size: var(--t-eyebrow);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--accent);
}
.legal-alert span {
  font-size: var(--t-sm);
  line-height: 1.55;
  color: var(--content-secondary);
}

/* --- page head ------------------------------------------------------------ */
.legal-head {
  margin-bottom: 48px;
}
.legal-head .eyebrow {
  margin-bottom: 20px;
}
.legal-title {
  font-size: var(--t-h2);
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-h2);
  color: var(--content-primary);
}
.legal-meta {
  margin-top: 14px;
  font-family: pangeaMono, ui-monospace, monospace;
  font-size: var(--t-xs);
  color: var(--content-quaternary);
}

/* --- numbered sections ---------------------------------------------------- */
/* Numbers come from counters, not from the markup, so a section can be cut or
   moved without renumbering by hand. */
.legal-sections {
  display: flex;
  flex-direction: column;
  gap: 40px;
  counter-reset: legal-section;
}
.legal-section {
  counter-increment: legal-section;
  counter-reset: legal-sub;
}
.legal-section > h2 {
  margin-bottom: 14px;
  font-size: var(--t-h4);
  line-height: 1.3;
  letter-spacing: var(--ls-h4);
  color: var(--content-primary);
}
.legal-section > h2::before {
  content: counter(legal-section) ".";
  margin-right: 10px;
  font-family: pangeaMono, ui-monospace, monospace;
  font-size: var(--t-sm);
  color: var(--content-quaternary);
}
.legal-section h3 {
  margin-top: 24px;
  margin-bottom: 8px;
  counter-increment: legal-sub;
  font-size: var(--t-md);
  color: var(--content-primary);
}
.legal-section h3::before {
  content: counter(legal-section) "." counter(legal-sub);
  margin-right: 9px;
  font-family: pangeaMono, ui-monospace, monospace;
  font-size: var(--t-xs);
  color: var(--content-quaternary);
}
.legal-section p {
  font-size: var(--t-md);
  line-height: var(--lh-prose);
  color: var(--content-tertiary);
}
.legal-section p + p,
.legal-section .legal-list + p {
  margin-top: 14px;
}
.legal-section strong {
  font-weight: 400;
  color: var(--content-primary);
}

/* same hairline marker as .price-list, so the page has one bullet in total */
.legal-list {
  display: flex;
  flex-direction: column;
  gap: 9px;
  margin-top: 12px;
}
.legal-list li {
  position: relative;
  padding-left: 18px;
  font-size: var(--t-md);
  line-height: var(--lh-prose);
  color: var(--content-tertiary);
}
.legal-list li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 12px;
  width: 6px;
  height: 1px;
  background: var(--content-subtle);
}
.legal-section a {
  color: var(--content-secondary);
  text-decoration: underline;
  text-decoration-color: var(--border-moderate);
  text-underline-offset: 3px;
  transition: color var(--d-press) var(--ease-out);
}
.legal-section a:hover {
  color: var(--content-primary);
}

/* Every value nobody has supplied is loud on purpose. Nothing here is a real
   entity, address, email or jurisdiction, so none of them is styled as if it
   could be mistaken for one. */
/* `white-space: nowrap` held every placeholder on one line, which is right for
   `saul@pangea.sh` and wrong for the 60-character registered address: at 390px
   that span measured 372.6px wide with its right edge at 396.6, scrolling both
   legal pages 7px sideways. Allowed to wrap, and allowed to break inside a long
   unbroken token so no single string can overflow either.
   0.92em also resolved to 13.8px, a fractional size off the token scale. */
.ph {
  font-family: pangeaMono, ui-monospace, monospace;
  font-size: var(--t-sm);
  color: var(--accent);
  overflow-wrap: anywhere;
}

.legal-back {
  margin-top: 56px;
  padding-top: 24px;
  border-top: 1px solid var(--border-faint);
  font-size: var(--t-sm);
  color: var(--content-quaternary);
}
.legal-back a {
  color: var(--content-tertiary);
  transition: color var(--d-press) var(--ease-out);
}
.legal-back a:hover {
  color: var(--content-primary);
}

@media (max-width: 800px) {
  .legal-main {
    padding-block: 108px var(--sec-pad);
  }
  .legal-alert {
    margin-bottom: 40px;
  }
}

/* ==========================================================================
   15. The motion system

   Four mechanisms, no more. Everything here is transform and opacity only, so
   nothing in this section can trigger layout or paint.

     a. Smooth scroll        demo.js, exponential damp at lerp 0.18. Distance
                             is never touched: one wheel tick of 100px lands at
                             exactly 100px, it just takes ~480ms to settle.
     b. Hero pin             the hero holds for 450px while the page moves.
                             Copy tracks scroll exactly, panels lag ~180ms.
     c. Reveal on entry      20px rise and a fade, 95ms stagger inside a group.
     d. Idle scene motion    a caret and a presence dot in whichever Slack
                             scene is on screen. Nothing off screen animates.

   Curves are the same three the rest of the page uses. No new ones.
   ========================================================================== */

/* --- b. the pin -------------------------------------------------------------
   demo.js writes the scrubbed values as custom properties; the transform here
   is declarative so the compositor owns it. The copy is bound to scroll with
   no smoothing at all (lightfield: opacity 1 -> 0 inside a single frame) while
   the stage is bound through a 180ms follow, so the imagery is always a beat
   behind the words. That lag IS the weight. */
.pinning .hero-copy {
  opacity: var(--hero-o, 1);
  transform: translate3d(0, var(--hero-y, 0px), 0);
  will-change: opacity, transform;
}
/* The stage recedes as well as drifts, and it has to.

   Fading only the copy left the panels burning at full brightness on the right
   with an empty left column beside them, so the screen after the pin read as
   half-missing rather than as a hero on its way out. lightfield does not have
   this problem because their pin runs an image-swap veil to full opacity over
   y112.5 to 281.25, which covers their imagery even earlier than their copy
   fade. Ours goes to 0.25 rather than 0: the panels stay faintly present while
   the next section arrives, which reads as depth instead of a light switch. */
.pinning .hero-stage {
  transform: translate3d(var(--sx, 0px), var(--sy, 0px), 0);
  opacity: var(--stage-o, 1);
  will-change: transform, opacity;
}
/* The pill is the hero's last painted element, so it leaves with the copy
   rather than hanging over the section that follows it. */
.pinning .hero-pill {
  opacity: var(--hero-o, 1);
}
/* The aurora dissolves across the pin the way lightfield's gradient backdrop
   does, so the hero's atmosphere does not survive into the next section. */
.pinning .aurora {
  opacity: calc(0.38 * var(--hero-bg, 1));
}

/* --- c. reveal on entry -----------------------------------------------------
   20px, not 60. The reference moves its section copy exactly one 20px step and
   nothing on this page has earned more than that. The transition is declared
   on the entered state only, so nothing animates during the first paint. */
.reveal {
  opacity: 0;
  transform: translate3d(0, 20px, 0);
}
.reveal.is-in {
  opacity: 1;
  transform: none;
  transition: opacity 0.62s var(--ease-p1) var(--rd, 0ms),
    transform 0.62s var(--ease-p1) var(--rd, 0ms);
}
/* A revealed tile still has to be liftable. `.reveal.is-in` and
   `.feat-tile:hover` are the same specificity and this block is the later of
   the two, so the hover needs to out-rank it explicitly or the lift silently
   stops working the moment a tile finishes entering. */
.feat-tile.reveal.is-in:hover {
  transform: translate3d(0, -3px, 0);
}

/* --- d. idle motion inside the visible scene --------------------------------
   Two marks, both of which a real Slack window has: a caret blinking in the
   composer and the app's presence dot breathing. They run only while the tile
   they belong to is on screen, so at most two scenes are ever animating.

   Deliberately not: drifting the scene inside its own frame. The frames are
   fitted to the pixel and any drift shows as a moving crop edge. */
.feat-tile .sl-comp-ph {
  position: relative;
}
.feat-tile.is-live .sl-comp-ph::after {
  content: "";
  position: absolute;
  left: calc(100% + 2px);
  top: 0.08em;
  width: 1px;
  height: 1.02em;
  background: var(--sl-txt-2);
  animation: sl-caret 1.15s steps(1, end) infinite;
}
@keyframes sl-caret {
  0%,
  46% {
    opacity: 1;
  }
  47%,
  100% {
    opacity: 0;
  }
}
.feat-tile.is-live .sl-pres.is-app {
  animation: sl-breathe 2.9s var(--ease-in-out) infinite;
}
@keyframes sl-breathe {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.42;
  }
}

/* --- the guard --------------------------------------------------------------
   Every one of the four mechanisms is off under reduced motion, and each one
   rests in the state a reader would otherwise have to scroll to reach. The
   pin is never installed at all (demo.js checks before adding `.pinning`), so
   there is no runway and no sticky to unwind here. */
@media (prefers-reduced-motion: reduce) {
  .reveal,
  .reveal.is-in {
    opacity: 1;
    transform: none;
    transition: none;
  }
  .feat-tile,
  .feat-tile > .sl-frame,
  .btn {
    transition: none;
  }
  .feat-tile:hover {
    transform: none;
  }
  .btn:active {
    transform: none;
  }
  .feat-tile.is-live .sl-comp-ph::after,
  .feat-tile.is-live .sl-pres.is-app {
    animation: none;
  }
}
