/* ==========================================================================
   Loredo Labs — global stylesheet
   Ported from tailwind.config.js + src/index.css of the Base44 export.
   Every brand value lives in :root below; nothing downstream hardcodes hex.
   ========================================================================== */

/* --------------------------------------------------------------------------
   0. Self-hosted display face — Clash Display (Fontshare, OFL)
   Used for the hero headline only. No third-party CDN call; the files live in
   assets/fonts/. Inter and Space Grotesk still come from Google Fonts.
   -------------------------------------------------------------------------- */
@font-face {
  font-family: "Clash Display";
  src: url("../fonts/ClashDisplay-500.woff2") format("woff2");
  font-weight: 500;
  font-display: swap;
  font-style: normal;
}
@font-face {
  font-family: "Clash Display";
  src: url("../fonts/ClashDisplay-600.woff2") format("woff2");
  font-weight: 600;
  font-display: swap;
  font-style: normal;
}

/* --------------------------------------------------------------------------
   1. Design tokens
   -------------------------------------------------------------------------- */
:root {
  /* Ink (text / dark surfaces) */
  --ink-950: #0b1220;
  --ink-700: #293241;
  --ink-500: #5b6472;
  --ink-300: #9aa3b2;

  /* Surfaces */
  --surface-0: #ffffff;
  --surface-50: #f7f8fa;
  --surface-100: #f0f2f5;
  /* The one shared "deep" dark, for the features and contact sections. Same
     hue as --ink-950 (220deg), two stops darker. Replaces the Tailwind zinc
     #09090b and the near-black #050507, which were unrelated to the brand
     navy and to each other, and made the contact -> footer join read as a
     seam. Hero and footer stay on --ink-950. */
  --surface-deep: #070c16;

  /* Lines */
  --border: #e4e7ec;
  --border-dark: #293241;

  /* Brand */
  --blue-brand: #1657ff;
  --blue-hover: #3b74ff;
  --blue-100: #e8efff;
  --blue-accent: #007aff;

  /* Status */
  --success: #1f9d6b;
  --warning: #c6821f;

  /* Type */
  /* Space Grotesk is the single site typeface — body, headings and display.
     Inter was dropped from the Google Fonts request; Clash Display is no
     longer referenced but its @font-face blocks and woff2 files are kept, so
     restoring it on the hero/header is a one-line change to --font-display. */
  --font-heading: "Space Grotesk", ui-sans-serif, system-ui, -apple-system, sans-serif;
  --font-body: "Space Grotesk", ui-sans-serif, system-ui, -apple-system, sans-serif;
  --font-logo: "Outfit", ui-sans-serif, system-ui, -apple-system, sans-serif;
  --font-display: "Space Grotesk", ui-sans-serif, system-ui, -apple-system, sans-serif;

  /* Ribbon field palette (hero background) */
  --rf-white: #ffffff;
  --rf-sky: #78b8f9;
  --rf-ultramarine: #5667ff;
  --rf-iris: #4d2ff9;

  /* Fluid type scale */
  --fs-h1: clamp(2.75rem, 5vw, 4.5rem);
  --fs-h2: clamp(2rem, 3.5vw, 3rem);
  --fs-h3: 1.375rem;
  --fs-lead: clamp(1.125rem, 1.5vw, 1.25rem);
  --fs-body: 1.0625rem;
  --fs-sm: 0.9375rem;
  --fs-xs: 0.8125rem;

  /* Elevation — one geometric ramp, three steps. Ink and alpha are separate
     tokens so the same three shadows work on dark surfaces, where the light
     alphas are invisible; dark sections override the four values below rather
     than declaring shadows of their own. */
  --shadow-rgb: 11, 18, 32;
  --shadow-a1: 0.04;
  --shadow-a2: 0.06;
  --shadow-a3: 0.10;
  --shadow-card: 0 1px 2px rgba(var(--shadow-rgb), var(--shadow-a1));
  --shadow-card-md: 0 8px 24px rgba(var(--shadow-rgb), var(--shadow-a2));
  --shadow-card-lg: 0 16px 40px rgba(var(--shadow-rgb), var(--shadow-a3));

  /* Glass — the shared button material. One set of values for light surfaces
     here, re-declared for dark sections alongside the shadow ink below, so a
     button is frosted the same way everywhere without each section inventing
     its own translucency. Alphas are deliberately high on the primary fills:
     a button whose label drops below 4.5:1 is not a style choice. */
  --glass-blur: 14px;
  --glass-border: rgba(11, 18, 32, 0.14);
  --glass-hi: rgba(255, 255, 255, 0.65);   /* inner top edge, the "lit" rim */
  --glass-fill: rgba(255, 255, 255, 0.55);
  --glass-fill-hover: rgba(255, 255, 255, 0.78);

  /* Radii */
  --radius-xs: 6px;            /* the shadcn rounded-md the ported components use */
  --radius-sm: 8px;
  --radius: 12px;
  --radius-md: 16px;
  --radius-lg: 24px;
  --radius-pill: 999px;

  /* Layout */
  --max-content: 1280px;
  --max-prose: 60ch;
  --nav-h: 3.5rem;   /* header-3 h-14 */
  --gutter: 20px;
  /* Shared vertical rhythm. Every full-width content section uses this, so
     section spacing scales together at the one breakpoint instead of each
     section carrying its own pair of numbers. */
  --section-y: 88px;
  /* Depth of the blend band at each section join. */
  --fade-h: clamp(88px, 9vw, 160px);

  /* Motion */
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-snap: cubic-bezier(0.16, 1, 0.3, 1);

  /* shadcn surface tokens for .ui-btn and the nav components. These live here
     rather than only on .hdr so a .ui-btn works anywhere on the page; .hdr,
     .hdr--over-dark, .mobilesheet and .hero3__actions override them locally. */
  --ui-bg: #ffffff;
  --ui-fg: var(--ink-950);
  --ui-muted-fg: var(--ink-500);
  --ui-border: var(--border);
  --ui-input: var(--border);
  --ui-accent: var(--surface-50);
  --ui-popover: #ffffff;
  --ui-card: rgba(255, 255, 255, 0.6);
  --ui-primary: rgba(22, 87, 255, 0.90);         /* --blue-brand, frosted */
  --ui-primary-hover: rgba(59, 116, 255, 0.95);  /* --blue-hover, frosted */
  --ui-primary-fg: #ffffff;
  --ui-ring: var(--blue-brand);
}

@media (min-width: 768px) {
  :root { --gutter: 40px; --section-y: 120px; }
}

/* The dark-field end of the elevation ramp. Same three shadows, re-inked:
   on a dark surface a 4% shadow is not there at all. .vid__facade is included
   because the play button sits on the dark poster, not on the light section. */
.hero3,
.feat,
.ct,
.site-footer,
.vid__facade {
  --shadow-rgb: 5, 9, 38;
  --shadow-a1: 0.22;
  --shadow-a2: 0.35;
  --shadow-a3: 0.50;

  /* Glass on a dark surface: the frost is a white veil rather than a white
     wash, and the rim carries the edge the hairline border no longer can. */
  --glass-border: rgba(255, 255, 255, 0.22);
  --glass-hi: rgba(255, 255, 255, 0.28);
  --glass-fill: rgba(255, 255, 255, 0.10);
  --glass-fill-hover: rgba(255, 255, 255, 0.18);
}
@media (min-width: 1024px) {
  :root { --gutter: 80px; }
}

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

* { margin: 0; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--nav-h) + 16px);
}

body {
  font-family: var(--font-body);
  font-size: var(--fs-body);
  line-height: 1.6;
  color: var(--ink-950);
  background: var(--surface-0);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-heading);
  font-weight: 500;
  line-height: 1.1;
  letter-spacing: -0.01em;
  text-wrap: balance;
}

h1 { font-size: var(--fs-h1); line-height: 1.05; letter-spacing: -0.02em; }
h2 { font-size: var(--fs-h2); }
h3 { font-size: var(--fs-h3); line-height: 1.2; }

p { text-wrap: pretty; }

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

img, svg, video, iframe {
  display: block;
  max-width: 100%;
  height: auto;
}

/* The rule above sets display on svg, which would otherwise beat the `hidden`
   attribute's UA `display: none`. Keep `hidden` authoritative. */
[hidden] { display: none !important; }

button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }

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

/* --------------------------------------------------------------------------
   3. Layout helpers
   -------------------------------------------------------------------------- */
.container {
  width: 100%;
  max-width: var(--max-content);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.section { padding-block: 64px; }
@media (min-width: 768px) { .section { padding-block: 96px; } }
@media (min-width: 1024px) { .section { padding-block: 128px; } }

.section--tight { padding-block: 64px; }
@media (min-width: 768px) { .section--tight { padding-block: 80px; } }

.section--alt { background: var(--surface-50); }
.section--dark { background: var(--ink-950); color: #fff; }
.section--bordered { border-top: 1px solid var(--border); }

.prose-width { max-width: var(--max-prose); }

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 200;
  padding: 12px 20px;
  background: var(--ink-950);
  color: #fff;
  border-radius: 0 0 var(--radius) 0;
}
.skip-link:focus { left: 0; }

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

/* --------------------------------------------------------------------------
   4. Shared text elements
   -------------------------------------------------------------------------- */
.eyebrow {
  display: block;
  font-family: var(--font-body);
  font-size: var(--fs-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ink-300);
  margin-bottom: 16px;
}

.lead {
  font-size: var(--fs-lead);
  line-height: 1.5;
  color: var(--ink-500);
  max-width: var(--max-prose);
}

.muted { color: var(--ink-500); }

/* Diagonal brand stroke (DiagonalStroke.jsx) */
.stroke {
  display: inline-block;
  width: 4px;
  background: var(--blue-brand);
  transform: skewX(-18deg);
  border-radius: 1px;
}
.stroke--sm { height: 16px; }
.stroke--lg { height: 28px; }

/* --------------------------------------------------------------------------
   5. Buttons
   -------------------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-body);
  font-weight: 500;
  font-size: var(--fs-sm);
  padding: 14px 24px;
  border-radius: var(--radius-md);
  border: 1px solid var(--glass-border);
  backdrop-filter: blur(var(--glass-blur)) saturate(1.4);
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(1.4);
  box-shadow: inset 0 1px 0 var(--glass-hi);
  transition: background-color 0.2s var(--ease-out),
              transform 0.2s var(--ease-out),
              box-shadow 0.2s var(--ease-out),
              border-color 0.2s var(--ease-out);
}

.btn--lg { padding: 16px 32px; font-size: 1.0625rem; }

.btn .btn__arrow { transition: transform 0.2s var(--ease-out); }
.btn:hover .btn__arrow { transform: translateX(2px); }

.btn--primary {
  background: rgba(22, 87, 255, 0.90);
  color: #fff;
}
.btn--primary:hover {
  background: rgba(59, 116, 255, 0.95);
  transform: translateY(-1px);
  box-shadow: var(--shadow-card-md);
}
.btn--primary:active { transform: scale(0.98); }

.btn--secondary {
  color: var(--ink-950);
  background: var(--glass-fill);
}
.btn--secondary:hover {
  border-color: var(--ink-300);
  transform: translateY(-1px);
}
.btn--secondary:active { transform: scale(0.98); }

.btn--sm { padding: 9px 16px; font-size: 0.8125rem; border-radius: var(--radius); }

.btn--ghost {
  color: var(--ink-700);
  background: var(--glass-fill);
}
.btn--ghost:hover { color: var(--ink-950); border-color: var(--ink-300); }
.btn--ghost:active { transform: scale(0.98); }


/* --------------------------------------------------------------------------
   6. Logo mark
   -------------------------------------------------------------------------- */
.logo {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  transition: transform 0.3s var(--ease-out);
}
.logo:hover { transform: scale(1.02); }
.logo:active { transform: scale(0.98); }

.logo__img { width: 32px; height: 32px; object-fit: contain; }

.logo__text { display: flex; flex-direction: column; line-height: 1; }

.logo__name {
  font-family: var(--font-logo);
  font-size: 18px;
  font-weight: 600;
  letter-spacing: 0.06em;
  color: var(--ink-950);
}

.logo__sub {
  font-family: var(--font-logo);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.14em;
  color: var(--blue-brand);
}

.logo--light .logo__name { color: #fff; }
.logo--light .logo__img {
  background: #fff;
  border-radius: var(--radius-xs);
  padding: 3px;
}

/* --------------------------------------------------------------------------
   7. shadcn Button port
   A literal translation of the supplied shadcn/button buttonVariants, used by
   header-3 and hero-3. Separate from the site's own `.btn` (footer, legal
   pages), which follows a different scale.
   -------------------------------------------------------------------------- */
.ui-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  white-space: nowrap;
  height: 2.5rem;              /* h-10 */
  padding: 0.5rem 1rem;        /* px-4 py-2 */
  font-family: var(--font-body);
  font-size: 0.875rem;         /* text-sm */
  font-weight: 500;
  /* Glass: rounded, frosted, hairline-bordered. The radius moves off the
     spec's rounded-md (6px) onto the shared 16px so .ui-btn and .btn are one
     shape — they sit on the same pages and read as two controls otherwise. */
  border-radius: var(--radius-md);
  background: var(--ui-primary);
  color: var(--ui-primary-fg);
  border: 1px solid var(--glass-border);
  backdrop-filter: blur(var(--glass-blur)) saturate(1.4);
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(1.4);
  box-shadow: inset 0 1px 0 var(--glass-hi);
  transition: background-color 0.15s var(--ease-out), color 0.15s var(--ease-out),
              border-color 0.15s var(--ease-out), box-shadow 0.15s var(--ease-out);
}
.ui-btn:hover { background: var(--ui-primary-hover); }
.ui-btn:focus-visible {
  outline: 2px solid var(--ui-ring);
  outline-offset: 2px;
}

/* The secondary is the true glass: it is mostly backdrop. */
.ui-btn--outline {
  background: var(--glass-fill);
  color: var(--ui-fg);
  border-color: var(--glass-border);
}
.ui-btn--outline:hover { background: var(--glass-fill-hover); color: var(--ui-fg); }

.ui-btn--icon { width: 2.5rem; height: 2.5rem; padding: 0; }

.ui-btn svg { flex: none; }
.ui-btn__icon-start { width: 1rem; height: 1rem; margin-right: 0.5rem; }  /* size-4 mr-2 */
.ui-btn__icon-end   { width: 1rem; height: 1rem; margin-left: 0.5rem; }   /* size-4 ml-2 */

/* --------------------------------------------------------------------------
   8. header-3
   sticky, transparent until scrolled past 10px, then a blurred translucent
   surface with a bottom border. h-14 bar inside a max-w-5xl container. The
   desktop nav appears at md; below that it is the toggle + sheet.
   -------------------------------------------------------------------------- */
.hdr {
  position: sticky;
  top: 0;
  z-index: 50;
  width: 100%;
  border-bottom: 1px solid transparent;
  transition: background-color 0.25s var(--ease-out),
              border-color 0.25s var(--ease-out);

  /* shadcn surface tokens, scoped. The over-dark variant below swaps only
     these — no structural rule is conditional on the page. */
  --ui-bg: #fff;
  --ui-fg: var(--ink-950);
  --ui-muted-fg: var(--ink-500);
  --ui-border: var(--border);
  --ui-input: var(--border);
  --ui-accent: var(--surface-50);
  --ui-popover: #fff;
  --ui-card: rgba(255, 255, 255, 0.6);
  --ui-primary: rgba(22, 87, 255, 0.90);         /* --blue-brand, frosted */
  --ui-primary-hover: rgba(59, 116, 255, 0.95);  /* --blue-hover, frosted */
  --ui-primary-fg: #fff;
  --ui-ring: var(--blue-brand);
}

.hdr.is-scrolled {
  background: rgba(255, 255, 255, 0.95);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  border-bottom-color: var(--ui-border);
}

/* The home hero is a dark ribbon field and the header is transparent over it
   until scroll, so the token set inverts. Colours only. */
.hdr--over-dark:not(.is-scrolled) {
  --ui-fg: #fff;
  --ui-muted-fg: rgba(255, 255, 255, 0.72);
  --ui-border: rgba(255, 255, 255, 0.22);
  --ui-input: rgba(255, 255, 255, 0.32);
  --ui-accent: rgba(255, 255, 255, 0.12);
  --ui-bg: transparent;
  --ui-ring: #fff;

  /* Ink-tinted glass, not white-tinted. Everywhere else the glass veil is
     white because the label is either dark (light sections) or the backdrop is
     reliably dark (features/contact/footer). Here the label is white and the
     backdrop is the ribbon field, whose pale end is literally #ffffff — a
     white veil there measures 2.31:1. Same material, same blur, border, rim
     and radius; only the tint of the veil inverts, which is what keeps the
     label legible across the whole gradient (6.0:1 at the palest point,
     14.6:1 at the deepest). */
  --glass-fill: rgba(6, 10, 38, 0.42);
  --glass-fill-hover: rgba(6, 10, 38, 0.55);
  --glass-border: rgba(255, 255, 255, 0.28);
  --glass-hi: rgba(255, 255, 255, 0.20);
}

.hdr__nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  max-width: 64rem;            /* max-w-5xl */
  height: 3.5rem;              /* h-14 */
  margin-inline: auto;
  padding-inline: 1rem;        /* px-4 */
}

.hdr__left { display: flex; align-items: center; gap: 1.25rem; }  /* gap-5 */

/* Wordmark slot: hover:bg-accent rounded-md p-2 */
.hdr__brand {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem;
  border-radius: var(--radius-xs);
  color: var(--ui-fg);
  transition: background-color 0.15s var(--ease-out);
}
.hdr__brand:hover { background: var(--ui-accent); }
.hdr__brand-img { display: block; width: 18px; height: 18px; border-radius: 4px; }
.hdr__brand-text {
  font-family: var(--font-logo);
  font-size: 0.8125rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  line-height: 1;
}
.hdr__brand-text span { font-weight: 500; opacity: 0.72; }

/* --- Desktop navigation menu -------------------------------------------- */
.navmenu { display: none; position: relative; }
@media (min-width: 768px) { .navmenu { display: flex; } }

.navmenu__list {
  display: flex;
  align-items: center;
  gap: 0.25rem;                /* space-x-1 */
  list-style: none;
}

.navmenu__item { position: static; }

/* navigationMenuTriggerStyle: h-9 w-max px-4 py-2 text-sm font-medium rounded-md */
.navmenu__trigger,
.navmenu__plain {
  display: inline-flex;
  align-items: center;
  height: 2.25rem;
  padding: 0.5rem 1rem;
  font-family: var(--font-body);
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--ui-fg);
  background: transparent;
  border-radius: var(--radius-xs);
  transition: background-color 0.15s var(--ease-out);
}
.navmenu__trigger:hover,
.navmenu__plain:hover,
.navmenu__trigger[aria-expanded="true"] { background: var(--ui-accent); }

.navmenu__chev {
  position: relative;
  top: 1px;
  margin-left: 0.25rem;
  width: 0.75rem;
  height: 0.75rem;
  transition: transform 0.3s var(--ease-out);
}
.navmenu__trigger[aria-expanded="true"] .navmenu__chev { transform: rotate(180deg); }

/* Viewport: centred under the bar, scales in from 90%. */
.navmenu__content {
  position: absolute;
  top: 100%;
  left: 50%;
  margin-top: 0.375rem;        /* mt-1.5 */
  transform: translateX(-50%);
  z-index: 60;
  overflow: hidden;
  background: var(--ui-popover);
  color: var(--ui-fg);
  border: 1px solid var(--ui-border);
  border-radius: var(--radius-xs);
  box-shadow: var(--shadow-card-lg);

  --ui-accent: var(--surface-50);
  --ui-muted-fg: var(--ink-500);
  --ui-fg: var(--ink-950);
  --ui-border: var(--border);
}
.navmenu__content[hidden] { display: none; }
.navmenu__content {
  opacity: 0;
  transform: translateX(-50%) scale(0.9);
  transition: opacity 0.2s var(--ease-out), transform 0.2s var(--ease-out);
}
.navmenu__content.is-open { opacity: 1; transform: translateX(-50%) scale(1); }

.navmenu__inner { padding: 0.25rem 0.375rem 0.25rem 0.25rem; }

/* Product panel: ul grid w-lg grid-cols-2 gap-2 rounded-md border p-2 shadow */
.navpanel__grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.5rem;
  width: 32rem;                /* w-lg */
  padding: 0.5rem;
  list-style: none;
  background: var(--ui-popover);
  border: 1px solid var(--ui-border);
  border-radius: var(--radius-xs);
  box-shadow: var(--shadow-card);
}

/* Company panel: grid w-lg grid-cols-2 gap-2 — bordered card list + plain list */
.navpanel__split {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.5rem;
  width: 32rem;
}

.navpanel__stack {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;                 /* space-y-2 */
  padding: 0.5rem;
  list-style: none;
  background: var(--ui-popover);
  border: 1px solid var(--ui-border);
  border-radius: var(--radius-xs);
  box-shadow: var(--shadow-card);
}

.navpanel__aside {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: 0.75rem;            /* p-3 */
  list-style: none;
}

.navpanel__foot {
  padding: 0.5rem;
  font-size: 0.875rem;
  color: var(--ui-muted-fg);
}
.navpanel__foot a { font-weight: 500; color: var(--ui-fg); }
.navpanel__foot a:hover { text-decoration: underline; }

/* ListItem: w-full flex flex-row gap-x-2 rounded-sm p-2 hover:bg-accent */
.listitem {
  display: flex;
  flex-direction: row;
  gap: 0.5rem;
  width: 100%;
  padding: 0.5rem;
  border-radius: 0.25rem;
  transition: background-color 0.15s var(--ease-out);
}
.listitem:hover { background: var(--ui-accent); }

.listitem__icon {
  display: grid;
  place-items: center;
  flex: none;
  aspect-ratio: 1;
  width: 3rem;                 /* size-12 */
  background: var(--ui-card);
  border: 1px solid var(--ui-border);
  border-radius: var(--radius-xs);
  box-shadow: var(--shadow-card);
  color: var(--ui-fg);
}
.listitem__icon svg { width: 1.25rem; height: 1.25rem; }  /* size-5 */

.listitem__text {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
}
.listitem__title { font-size: 0.875rem; font-weight: 500; color: var(--ui-fg); }
.listitem__desc { font-size: 0.75rem; line-height: 1.45; color: var(--ui-muted-fg); }

/* Secondary list rows: flex p-2 rounded-md items-center gap-x-2 hover:bg-accent */
.navrow {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem;
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--ui-fg);
  border-radius: var(--radius-xs);
  transition: background-color 0.15s var(--ease-out);
}
.navrow:hover { background: var(--ui-accent); }
.navrow svg { width: 1rem; height: 1rem; flex: none; }

/* --- Header actions ------------------------------------------------------ */
.hdr__actions { display: none; align-items: center; gap: 0.5rem; }
@media (min-width: 768px) { .hdr__actions { display: flex; } }

.hdr__toggle { display: inline-flex; }
@media (min-width: 768px) { .hdr__toggle { display: none; } }

/* MenuToggleIcon: the long path's dash pattern morphs while the whole glyph
   counter-rotates 45deg, so the bar and the hook resolve into a cross. */
.menutoggle__svg {
  width: 1.25rem;
  height: 1.25rem;
  transition: transform 300ms ease-in-out;
}
.menutoggle__morph {
  stroke-dasharray: 12 63;
  transition: stroke-dasharray 300ms ease-in-out, stroke-dashoffset 300ms ease-in-out;
}
[aria-expanded="true"] .menutoggle__svg { transform: rotate(-45deg); }
[aria-expanded="true"] .menutoggle__morph {
  stroke-dasharray: 20 300;
  stroke-dashoffset: -32.42px;
}

/* --- Mobile sheet -------------------------------------------------------- */
.mobilesheet {
  position: fixed;
  top: 3.5rem;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 40;
  display: none;
  flex-direction: column;
  overflow: hidden;
  background: rgba(255, 255, 255, 0.95);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);

  /* The sheet is a light surface on every page, so it must not inherit the
     over-dark token set from the header it lives inside. */
  --ui-bg: #fff;
  --ui-fg: var(--ink-950);
  --ui-muted-fg: var(--ink-500);
  --ui-border: var(--border);
  --ui-input: var(--border);
  --ui-accent: var(--surface-50);
  --ui-card: rgba(255, 255, 255, 0.6);
}
.mobilesheet.is-open { display: flex; }
@media (min-width: 768px) { .mobilesheet { display: none !important; } }

.mobilesheet__inner {
  width: 100%;
  height: 100%;
  padding: 1rem;
  animation: sheetZoomIn 200ms var(--ease-out);
}

@keyframes sheetZoomIn {
  from { opacity: 0; transform: scale(0.97); }
  to   { opacity: 1; transform: none; }
}

.mobilesheet__content {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 0.5rem;
  height: 100%;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}

.mobilesheet__nav { display: flex; flex-direction: column; gap: 0.5rem; width: 100%; }
.mobilesheet__group { font-size: 0.875rem; color: var(--ink-500); }
.mobilesheet__actions { display: flex; flex-direction: column; gap: 0.5rem; }
.mobilesheet__actions .ui-btn { width: 100%; }

body.is-locked { overflow: hidden; }

/* The header is in flow, so pages do not offset for it. */
.site-main { display: block; }

/* --------------------------------------------------------------------------
   8. Footer
   -------------------------------------------------------------------------- */
/* The footer is the tail of the contact section, not a separate slab: same
   --surface-deep ground, same --section-y rhythm, same ambient. Because the
   two now share a background there is no colour step at the join at all — the
   continuity is carried by the glow below rather than by a divider. */
.site-footer {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  background: var(--surface-deep);
  color: #fff;
  padding-block: var(--section-y);
}

/* Content sits above the ambient layers. */
.site-footer > .container { position: relative; }

/* The shared .aurora masks to its TOP-right corner, which is correct for every
   other section but puts this one's brightest point exactly on the contact
   join — the ambient would appear from nothing at the boundary, which is the
   seam under another name. Anchored to the bottom-right here so it fades
   *toward* the join and the two sections meet on flat --surface-deep. */
.site-footer > .aurora {
  --aurora-mask: radial-gradient(ellipse at 100% 100%, #000 22%, transparent 88%);
  opacity: 0.34;
}

/* Continuation of the contact glow-horizon. Same technique — oversized arcs,
   heavy blur, and a mask arc painted LAST so only a lit rim survives — but two
   lit layers instead of three, no white core, and pushed mostly above the
   section so only the bottom of the rim shows. Contact holds the light source;
   this is the far end of the same field, which is what stops the ambient
   dying at the section boundary. Entrance reuses contact's glowIn keyframes;
   the global prefers-reduced-motion rule collapses it to its end state. */
.site-footer__glow {
  position: absolute;
  top: 0;
  left: 50%;
  z-index: -1;
  width: 165%;
  height: 30rem;
  /* Seated far enough down that the arcs' blur has fully fallen off before the
     section's top edge. The section must clip horizontally — the arc is 165%
     wide and would otherwise push the page sideways — and anything still lit
     when it reaches that clip becomes a hard line exactly at the join, which
     is the seam this section exists to remove.

     Do NOT solve that with mask-image on this element: a mask rasterises the
     group at its border box, and the arcs are scaled 1.16-1.21 with a 38-46px
     blur, so they get cut into visible rectangles. Move the glow, don't mask
     it. */
  transform: translate(-50%, 12%);
  pointer-events: none;
  isolation: isolate;
  animation: glowIn 2s cubic-bezier(0.16, 1, 0.3, 1) both;
}

.site-footer__arc {
  position: absolute;
  inset: 0;
  border-radius: 100%;
}
.site-footer__arc--mid  { transform: scale(1.16); background: var(--rf-ultramarine); filter: blur(46px); opacity: 0.26; }
.site-footer__arc--deep { transform: scale(1.21); background: var(--rf-iris);        filter: blur(38px); opacity: 0.22; }
/* Masks to the section's own colour, not to black — same reason as
   .glow__arc--mask over in contact. */
.site-footer__arc--mask { transform: scale(1.14); background: var(--surface-deep);   filter: blur(56px); }

.site-footer__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 48px;
}
@media (min-width: 768px) {
  .site-footer__grid {
    grid-template-columns: 2fr repeat(4, 1fr);
    gap: 32px;
  }
}

.site-footer__tagline {
  margin-top: 16px;
  font-size: 0.875rem;
  color: var(--ink-300);
}

/* NAP phone. Sits in the brand column under the tagline rather than in a link
   column: it is contact information, not navigation, and anything reading the
   footer for NAP should find it beside the locality line, not among the links. */
.site-footer__phone {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-top: 14px;
  font-size: 0.9375rem;
  font-weight: 600;
  color: #fff;
  text-decoration: none;
}
.site-footer__phone svg { width: 16px; height: 16px; color: var(--ink-300); }
.site-footer__phone-label {
  font-size: var(--fs-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ink-500);
}
.site-footer__phone:hover .site-footer__phone-num { text-decoration: underline; }

.site-footer__col-title {
  font-size: var(--fs-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ink-500);
  margin-bottom: 16px;
}

.site-footer__links { display: flex; flex-direction: column; gap: 4px; }

/* Glass hover chips, on the shared radius and fill tokens. The negative inline
   start margin keeps the label flush with its column title while giving the
   link a real target rather than text-only. */
.site-footer__link {
  border-radius: var(--radius-sm);
  padding: 8px 10px;
  margin-inline-start: -10px;
  font-size: 0.875rem;
  color: var(--ink-300);
  transition: color 0.2s var(--ease-out), background-color 0.2s var(--ease-out);
}
.site-footer__link:hover {
  color: #fff;
  background: var(--glass-fill);
}
.site-footer__link:focus-visible {
  outline: 2px solid var(--glass-border);
  outline-offset: 2px;
}

.site-footer__bottom {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  margin-top: 64px;
  padding-top: 32px;
  border-top: 1px solid var(--glass-border);
  font-size: var(--fs-xs);
  color: var(--ink-300);
}
@media (min-width: 768px) {
  .site-footer__bottom { flex-direction: row; }
}

/* --------------------------------------------------------------------------
   9. Persistent CTAs
   -------------------------------------------------------------------------- */
.mobile-cta {
  position: fixed;
  inset: auto 0 0 0;
  z-index: 40;
  display: block;
  padding: 12px var(--gutter) calc(12px + env(safe-area-inset-bottom));
  background: rgba(255, 255, 255, 0.94);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border-top: 1px solid var(--border);
  transform: translateY(110%);
  transition: transform 0.3s var(--ease-out);
}
.mobile-cta.is-visible { transform: translateY(0); }
.mobile-cta .btn { width: 100%; justify-content: center; }

@media (min-width: 768px) { .mobile-cta { display: none; } }

.estimator-fab {
  position: fixed;
  right: 24px;
  bottom: 96px;
  z-index: 40;
  display: none;
  align-items: center;
  justify-content: center;
  width: 52px;
  height: 52px;
  background: var(--ink-950);
  color: #fff;
  border-radius: var(--radius-pill);
  box-shadow: var(--shadow-card-lg);
  transform: translateY(16px);
  opacity: 0;
  transition: transform 0.3s var(--ease-out), opacity 0.3s var(--ease-out),
              background-color 0.2s;
}
.estimator-fab.is-visible { opacity: 1; transform: translateY(0); }
.estimator-fab:hover { background: var(--blue-brand); }

@media (min-width: 768px) { .estimator-fab { display: flex; } }

/* --------------------------------------------------------------------------
   10. Legal documents (privacy policy, terms)
   -------------------------------------------------------------------------- */
.legal__inner { max-width: 48rem; }

.legal__eyebrow {
  font-size: var(--fs-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--blue-brand);
  margin-bottom: 16px;
}

.legal h1 { font-size: clamp(2rem, 4vw, 3rem); margin-bottom: 16px; }

.legal__meta {
  font-size: var(--fs-sm);
  color: var(--ink-500);
  margin-bottom: 32px;
}

.legal h2 {
  font-size: 1.375rem;
  margin-top: 40px;
  margin-bottom: 16px;
}

.legal p,
.legal li {
  font-size: 1rem;
  line-height: 1.7;
  color: var(--ink-700);
}

.legal p + p { margin-top: 12px; }

.legal ul {
  margin-top: 12px;
  padding-left: 1.25rem;
  display: grid;
  gap: 6px;
}

.legal a { color: var(--blue-brand); }
.legal a:hover { text-decoration: underline; }

.legal__lead { margin-bottom: 12px; }

/* Translation disclaimer — Spanish legal pages only */
.legal__notice {
  margin: 32px 0 8px;
  padding: 16px 20px;
  background: var(--surface-50);
  border-left: 3px solid var(--blue-brand);
  border-radius: 0 var(--radius) var(--radius) 0;
}

.legal__notice p {
  font-size: 0.875rem;
  line-height: 1.6;
  color: var(--ink-500);
}

.legal__back {
  margin-top: 64px;
  padding-top: 32px;
  border-top: 1px solid var(--border);
}

.legal__back a { font-weight: 500; }

/* --------------------------------------------------------------------------
   11. Scroll reveal
   Replaces the ScrollReveal.jsx wrapper. Any element can opt in with
   `data-reveal`, and stagger with `style="--reveal-delay: 160ms"`.
   -------------------------------------------------------------------------- */
[data-reveal] {
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 500ms var(--ease-out) var(--reveal-delay, 0ms),
              transform 500ms var(--ease-out) var(--reveal-delay, 0ms);
  will-change: opacity, transform;
}

[data-reveal].is-revealed {
  opacity: 1;
  transform: none;
}

/* If JS never runs, content must still be visible. */
.no-js [data-reveal] {
  opacity: 1;
  transform: none;
}

/* --------------------------------------------------------------------------
   12. Reduced motion
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }

  [data-reveal] { opacity: 1; transform: none; }
}

/* --------------------------------------------------------------------------
   13. hero-3
   The section is the spec's `mx-auto w-full max-w-5xl overflow-hidden pt-16`
   column. The Ribbon Field sits behind it full-bleed rather than inside the
   column — a 64rem-wide strip of gradient on a white page reads as a bug, and
   the gradient spec calls for a full-bleed element. Nothing else about the
   column changes: `overflow-hidden` still lives on it, which is what clips the
   media panel's right-edge bleed.
   -------------------------------------------------------------------------- */
.hero3 {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  background: var(--ink-950);

  /* The header is sticky and therefore in flow, which would leave a white
     band above the field. Pull the hero up under it and add the height back
     as padding, so pt-16 is still measured from below the bar and the
     transparent-until-scrolled header has something dark to sit on. */
  margin-top: calc(var(--nav-h) * -1);

  --fg: #fff;
  --fg-muted: rgba(255, 255, 255, 0.78);
  --fg-border: rgba(255, 255, 255, 0.28);
  --fg-card: rgba(255, 255, 255, 0.1);
}

/* Ribbon Field, layer 0 — static banded gradient. Always painted, so no-JS
   and reduced-motion get the full treatment and the canvas has a base. */
.hero3__bg {
  position: absolute;
  inset: 0;
  z-index: -3;
  /* Stacked radials approximating the mesh: a white corner top-right, pale
     blue through the middle, iris weighting the bottom-left. */
  background:
    radial-gradient(115% 85% at 92% 2%, var(--rf-white) 0%, rgba(255, 255, 255, 0) 46%),
    radial-gradient(85% 75% at 68% 22%, var(--rf-sky) 0%, rgba(120, 184, 249, 0) 58%),
    radial-gradient(120% 115% at 4% 104%, var(--rf-iris) 0%, rgba(77, 47, 249, 0) 72%),
    linear-gradient(214deg, var(--rf-sky) 0%, var(--rf-ultramarine) 46%, var(--rf-iris) 100%);
}

/* Layer 1 — animated canvas, drawn small and upscaled. */
.hero3__canvas {
  position: absolute;
  inset: 0;
  z-index: -2;
  width: 100%;
  height: 100%;
  opacity: 0;
  transition: opacity 600ms var(--ease-out);
  /* The mesh is drawn ~220px wide; the upscale plus this blur is what makes
     the patches read as soft cloud rather than as low-res blocks. The scale
     overscans so the blur has no transparent edge to smear inward. */
  filter: blur(22px);
  transform: scale(1.1);
}
.hero3__canvas.is-ready { opacity: 1; }

/* Layer 2 — spec grain (120px tile, 0.210) over two scrims. The scrims are a
   contrast requirement: the palette's pale end is pure white and the copy is
   white on top of it. */
.hero3__grain {
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-image:
    /* Re-centred and strengthened: the copy is centred now and carries no wash
       of its own, so this is the only thing standing between white type and
       the pale end of the mesh. Wide and soft enough to read as part of the
       gradient rather than as a shape. */
    radial-gradient(88% 62% at 50% 30%,
      rgba(8, 12, 42, 0.40) 0%,
      rgba(8, 12, 42, 0.28) 52%,
      rgba(8, 12, 42, 0) 100%),
    linear-gradient(180deg, rgba(8, 12, 42, 0.06) 0%, rgba(8, 12, 42, 0.04) 55%, rgba(8, 12, 42, 0.20) 100%),
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='120' height='120' filter='url(%23n)' opacity='0.360'/%3E%3C/svg%3E");
  background-size: auto, auto, 120px 120px;
  background-blend-mode: normal, normal, overlay;
}

/* The spec column. */
.hero3__container {
  position: relative;
  width: 100%;
  max-width: 64rem;            /* max-w-5xl */
  margin-inline: auto;
  overflow: hidden;
  padding-top: calc(4rem + var(--nav-h));   /* pt-16, below the sticky bar */
  padding-bottom: 4rem;
}

/* Shades: radial-gradient(20% 80% at 20% 0%, foreground/.1, transparent) */
.hero3__shades {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
  pointer-events: none;
}
.hero3__shades::before {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(20% 80% at 20% 0%, rgba(255, 255, 255, 0.1), transparent);
}

.hero3__copy {
  position: relative;
  z-index: 10;
  display: flex;
  flex-direction: column;
  /* Centred rather than the spec's left column: the card ring now sits behind
     this block, and a centred ring behind left-aligned copy reads as two
     unrelated things that happen to overlap. Both are on the same axis now. */
  align-items: center;
  text-align: center;
  gap: 1.5rem;
  max-width: 46rem;
  margin-inline: auto;
  padding-inline: 1rem;        /* px-4 */
}

/* Readability wash. The ring crosses the copy at this radius, and the card
   faces include a pure-white logo tile — white type over one of those is 1:1.
   So this is a contrast floor, not decoration, and it is measured: at alpha
   .66 over a white card face the headline holds ~6:1.

   A blurred rounded rect rather than a radial gradient. An ellipse wide enough
   to cover the copy box's corners has to extend far past it, so it darkens a
   lot of hero to protect a little text; this covers the box at close to full
   alpha and fades over ~70px, which is both tighter and cheaper.

   z-index -1 inside .hero3__copy, which is a z-index:10 stacking context, so
   it paints above the cards (z-index -1 on .hero3) and below the type. */
.hero3__copy::before {
  content: "";
  position: absolute;
  z-index: -1;
  left: 50%;
  top: 50%;
  width: calc(100% + 120px);
  height: calc(100% + 96px);
  transform: translate(-50%, -50%);
  pointer-events: none;
  background: rgba(6, 10, 38, 0.66);
  border-radius: 56px;
  filter: blur(36px);
}

/* --- Badge --------------------------------------------------------------- */
.hero3__badge {
  display: flex;
  align-items: center;
  gap: 0.75rem;                /* gap-3 */
  width: fit-content;
  padding: 0.25rem;            /* p-1 */
  color: var(--fg);
  background: var(--fg-card);
  border: 1px solid var(--fg-border);
  border-radius: var(--radius-pill);
  padding-right: 0.5rem;
  box-shadow: var(--shadow-card);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}

.hero3__badge-chip {
  padding: 0.125rem 0.375rem;  /* px-1.5 py-0.5 */
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.75rem;          /* text-xs */
  line-height: 1.25;
  background: var(--fg-card);
  border: 1px solid var(--fg-border);
  border-radius: 0.125rem;     /* rounded-xs */
  box-shadow: var(--shadow-card);
}

.hero3__badge-label { font-size: 0.75rem; }

.hero3__badge-rule {
  display: block;
  height: 1.25rem;             /* h-5 */
  border-left: 1px solid var(--fg-border);
}

.hero3__badge-arrow { padding-right: 0.25rem; }        /* pr-1 */
.hero3__badge-arrow svg {
  display: block;
  width: 0.75rem;              /* size-3 */
  height: 0.75rem;
  transform: translateX(-0.125rem);
  transition: transform 150ms var(--ease-out);
}
.hero3__badge:hover .hero3__badge-arrow svg { transform: translateX(0.125rem); }

/* --- Type ---------------------------------------------------------------- */
.hero3__title {
  font-family: var(--font-display);
  /* Departs from the spec's text-4xl/md:text-5xl font-medium. The headline is
     the hero's whole visual weight now that nothing sits behind it: heavy,
     tight, and scaled off the viewport rather than stepped at one breakpoint. */
  font-weight: 700;
  font-size: clamp(2.5rem, 6vw, 5rem);
  line-height: 1.04;
  letter-spacing: -0.035em;
  text-wrap: balance;
  color: var(--fg);
}

.hero3__sub {
  /* Tracking back to normal: the spec's tracking-wider was set against a
     small subhead, and at this size it reads as letter-spaced display type
     rather than as body copy under a headline. */
  font-size: clamp(1rem, 1.5vw, 1.25rem);
  line-height: 1.55;
  letter-spacing: 0;
  max-width: 40rem;
  color: var(--fg-muted);
}

.hero3__actions {
  display: flex;
  align-items: center;
  /* The spec says justify-center; inside a w-fit single row that is identical
     to flex-start. It only differs once the row wraps, where centring would
     indent the buttons away from the left-aligned copy. */
  justify-content: center;
  gap: 0.75rem;
  width: fit-content;
  padding-top: 0.75rem;
  /* Not in the spec, which pairs "Book a Call" with "Get started". Our real
     CTA labels are longer and run off a 390px screen, and the column clips
     rather than scrolls. Wrapping keeps the spec's single row wherever it
     fits and only breaks it when it would otherwise be cut off. */
  flex-wrap: wrap;
}

/* Scaled up from the shared .ui-btn h-10/text-sm. The buttons sit under a
   5rem headline; the spec's control size reads as a form field there. */
.hero3__actions .ui-btn {
  height: 3.25rem;
  padding-inline: 1.5rem;
  font-size: 1rem;
  border-radius: var(--radius-sm);
}
.hero3__actions .ui-btn svg { width: 1.125rem; height: 1.125rem; }

/* Standfirst under the CTAs — the qualifier that keeps the pair of buttons
   from being the last thing on the screen. */
.hero3__note {
  font-size: var(--fs-sm);
  color: rgba(255, 255, 255, 0.88);
}

/* The hero runs on the dark field, so the shadcn tokens invert here. */
.hero3__actions .ui-btn {
  --ui-primary: rgba(255, 255, 255, 0.92);
  --ui-primary-hover: rgba(255, 255, 255, 0.99);
  --ui-primary-fg: var(--ink-950);
  --ui-ring: #fff;
}
.hero3__actions .ui-btn--outline {
  --ui-fg: #fff;
  --ui-input: var(--fg-border);
  --ui-accent: rgba(255, 255, 255, 0.18);

  /* Ink-tinted glass, not white-tinted. Everywhere else the glass veil is
     white because the label is either dark (light sections) or the backdrop is
     reliably dark (features/contact/footer). Here the label is white and the
     backdrop is the ribbon field, whose pale end is literally #ffffff — a
     white veil there measures 2.31:1. Same material, same blur, border, rim
     and radius; only the tint of the veil inverts, which is what keeps the
     label legible across the whole gradient (6.0:1 at the palest point,
     14.6:1 at the deepest). */
  --glass-fill: rgba(6, 10, 38, 0.42);
  --glass-fill-hover: rgba(6, 10, 38, 0.55);
  --glass-border: rgba(255, 255, 255, 0.28);
  --glass-hi: rgba(255, 255, 255, 0.20);
}

/* --- Media --------------------------------------------------------------- */
.hero3__media { position: relative; }

.hero3__glow {
  position: absolute;
  left: -5rem;                 /* -inset-x-20 */
  right: -5rem;
  top: 0;
  bottom: 0;
  transform: translateY(-33.333%) scale(1.2);
  border-radius: var(--radius-pill);
  background: radial-gradient(ellipse at center,
    rgba(255, 255, 255, 0.1), transparent, transparent);
  filter: blur(50px);
  pointer-events: none;
}

.hero3__frame {
  position: relative;
  margin-top: 2rem;            /* mt-8 */
  margin-right: -14rem;        /* -mr-56 */
  padding-inline: 0.5rem;      /* px-2 */
  overflow: hidden;
  -webkit-mask-image: linear-gradient(to bottom, #000 60%, transparent 100%);
          mask-image: linear-gradient(to bottom, #000 60%, transparent 100%);
}
@media (min-width: 640px) { .hero3__frame { margin-top: 3rem; margin-right: 0; } }
@media (min-width: 768px) { .hero3__frame { margin-top: 5rem; } }

.hero3__panel {
  /* Query container for the dashboard inside. This has to sit on the wrapper,
     not on .dash itself — an element is a container for its descendants, never
     for its own properties, so `font-size: 1.25cqw` declared on .dash would
     resolve against the viewport instead. */
  container-type: inline-size;
  position: relative;
  max-width: 64rem;
  margin-inline: auto;
  padding: 0.5rem;             /* p-2 */
  overflow: hidden;
  background: var(--ink-950);
  border: 1px solid var(--fg-border);
  border-radius: var(--radius-sm);   /* rounded-lg */
  box-shadow: var(--shadow-card-lg),
              0 0 0 1px var(--fg-card),
              inset 0 1px 1px rgba(255, 255, 255, 0.1);
}

.hero3__img {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border: 1px solid var(--fg-border);
  border-radius: var(--radius-sm);
}

/* --- Entrance ------------------------------------------------------------
   fade-in + slide-in-from-bottom-10 (2.5rem), 500ms, fill-mode backwards.
   Delays: h1 100, subhead 200, actions 300, badge 500. The media frame is
   slide-in-from-bottom-5 (1.25rem) over 1000ms at 100. */
.animate-in {
  animation: enterUp 500ms var(--ease-out) backwards;
  animation-delay: var(--enter-delay, 0ms);
  --enter-rise: 2.5rem;
}
.animate-in--slow { animation-duration: 1000ms; --enter-rise: 1.25rem; }

@keyframes enterUp {
  from { opacity: 0; transform: translateY(var(--enter-rise)); }
  to   { opacity: 1; transform: none; }
}

.no-js .animate-in { animation: none; }

@media (prefers-reduced-motion: reduce) {
  .animate-in { animation: none; }
  .hero3__canvas { display: none; }
  .mobilesheet__inner { animation: none; }
}

/* --------------------------------------------------------------------------
   14. Hero dashboard mockup
   A close replica of the reference product screenshot, built in markup rather
   than shipped as an image: crisp at any density, no new asset, and the copy
   is real text. Sized in `em` from one `font-size: 1.15cqw`, so it scales with
   its container like a screenshot would. Design width 960px (1.15cqw = 11px).
   The query container is .hero3__panel — see the note there.
   -------------------------------------------------------------------------- */
.dash {
  display: flex;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  font-family: var(--font-body);
  font-size: 1.15cqw;
  line-height: 1.35;
  color: var(--d-fg);
  background: var(--d-bg);
  border-radius: var(--radius-xs);
  letter-spacing: -0.005em;

  --d-bg: #09090b;
  --d-card: #101012;
  --d-line: #1e1e22;
  --d-line-soft: #17171a;
  --d-fg: #ededef;
  --d-mut: #7c7c86;
  --d-dim: #55555e;
  --d-hi: #1a1a1e;
  --d-ok: #3ecf8e;
}

@supports not (container-type: inline-size) { .dash { font-size: 0.7rem; } }

.dash i { font-style: normal; }

/* Small outline glyph standing in for the reference's icon set. */
.d-ico {
  width: 0.95em;
  height: 0.95em;
  flex: none;
  border: 1px solid currentColor;
  border-radius: 0.2em;
  opacity: 0.75;
}
.d-ico--round { border-radius: 50%; }

/* --- Sidebar ------------------------------------------------------------- */
.d-side {
  flex: none;
  width: 15.2%;
  display: flex;
  flex-direction: column;
  padding: 0.65em 0.55em;
  gap: 0.15em;
  border-right: 1px solid var(--d-line);
}

.d-org {
  display: flex;
  align-items: center;
  gap: 0.45em;
  padding: 0.3em 0.35em 0.55em;
  font-size: 0.92em;
  font-weight: 500;
}
.d-org img { width: 1.15em; height: 1.15em; border-radius: 50%; }

.d-quick { display: flex; align-items: center; gap: 0.3em; margin-bottom: 0.5em; }
.d-quick__btn {
  flex: 1;
  display: flex;
  align-items: center;
  gap: 0.4em;
  padding: 0.35em 0.45em;
  font-size: 0.82em;
  font-weight: 500;
  background: var(--d-hi);
  border: 1px solid var(--d-line);
  border-radius: 0.35em;
}
.d-quick__mail {
  display: grid;
  place-items: center;
  width: 1.7em;
  height: 1.7em;
  border: 1px solid var(--d-line);
  border-radius: 0.35em;
  color: var(--d-mut);
}

.d-nav { display: flex; flex-direction: column; gap: 0.05em; }

.d-link {
  display: flex;
  align-items: center;
  gap: 0.45em;
  padding: 0.3em 0.4em;
  font-size: 0.82em;
  color: var(--d-mut);
  border-radius: 0.3em;
}
.d-link--on { color: var(--d-fg); background: var(--d-hi); }

.d-cap {
  padding: 0.75em 0.4em 0.3em;
  font-size: 0.78em;
  color: var(--d-dim);
}

.d-side__foot { margin-top: auto; display: flex; flex-direction: column; gap: 0.05em; }

.d-user {
  display: flex;
  align-items: center;
  gap: 0.45em;
  margin-top: 0.5em;
  padding: 0.4em 0.35em;
  border-top: 1px solid var(--d-line);
}
.d-user__av {
  width: 1.55em;
  height: 1.55em;
  flex: none;
  border-radius: 0.35em;
  background: linear-gradient(140deg, #4d8bff, #7a4dff);
}
.d-user__name { font-size: 0.8em; font-weight: 500; line-height: 1.2; }
.d-user__mail { font-size: 0.7em; color: var(--d-dim); line-height: 1.2; }
.d-user__chev { margin-left: auto; color: var(--d-dim); font-size: 0.7em; }

/* --- Main ---------------------------------------------------------------- */
.d-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }

.d-top {
  display: flex;
  align-items: center;
  gap: 0.6em;
  padding: 0.55em 0.9em;
  font-size: 0.85em;
  border-bottom: 1px solid var(--d-line);
}
.d-top__sep { width: 1px; height: 1em; background: var(--d-line); }
.d-top__right { margin-left: auto; color: var(--d-mut); font-size: 0.95em; }

/* --- Stat cards ---------------------------------------------------------- */
.d-stats {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 0.75em;
  padding: 0.8em 0.9em 0;
}

.d-card {
  padding: 0.7em 0.75em;
  background: var(--d-card);
  border: 1px solid var(--d-line);
  border-radius: 0.5em;
}

.d-card__top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 0.4em;
  font-size: 0.78em;
  color: var(--d-mut);
}

.d-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.2em;
  padding: 0.1em 0.4em;
  font-size: 0.92em;
  color: var(--d-fg);
  border: 1px solid var(--d-line);
  border-radius: 1em;
  white-space: nowrap;
}

.d-card__num {
  margin: 0.3em 0 0.55em;
  font-size: 2.1em;
  font-weight: 600;
  letter-spacing: -0.03em;
  line-height: 1.05;
}

.d-card__note {
  display: flex;
  align-items: center;
  gap: 0.3em;
  font-size: 0.78em;
  font-weight: 500;
}
.d-card__sub { font-size: 0.78em; color: var(--d-mut); }

/* --- Chart --------------------------------------------------------------- */
.d-chart {
  margin: 0.75em 0.9em 0;
  padding: 0.75em 0.85em 0.5em;
  background: var(--d-card);
  border: 1px solid var(--d-line);
  border-radius: 0.5em;
}
.d-chart__head { display: flex; align-items: flex-start; justify-content: space-between; }
.d-chart__title { font-size: 0.88em; font-weight: 500; }
.d-chart__sub { font-size: 0.78em; color: var(--d-mut); }

.d-seg {
  display: flex;
  padding: 0.13em;
  background: var(--d-hi);
  border: 1px solid var(--d-line);
  border-radius: 0.35em;
}
.d-seg span {
  padding: 0.22em 0.55em;
  font-size: 0.75em;
  color: var(--d-mut);
  border-radius: 0.25em;
}
.d-seg span.on { color: var(--d-fg); background: #26262b; }

.d-chart svg { width: 100%; height: 8.2em; margin-top: 0.5em; display: block; }

.d-axis {
  display: flex;
  justify-content: space-between;
  margin-top: 0.25em;
  font-size: 0.68em;
  color: var(--d-dim);
}

/* --- Tab row ------------------------------------------------------------- */
.d-tabs {
  display: flex;
  align-items: center;
  gap: 0.3em;
  padding: 0.8em 0.9em 0.55em;
}
.d-tab {
  display: flex;
  align-items: center;
  gap: 0.3em;
  padding: 0.28em 0.55em;
  font-size: 0.78em;
  color: var(--d-mut);
  border-radius: 0.3em;
}
.d-tab--on { color: var(--d-fg); background: var(--d-hi); }
.d-pillnum {
  padding: 0 0.3em;
  font-size: 0.88em;
  background: #26262b;
  border-radius: 1em;
}
.d-tabs__right { margin-left: auto; display: flex; gap: 0.35em; }
.d-btn {
  display: flex;
  align-items: center;
  gap: 0.3em;
  padding: 0.28em 0.5em;
  font-size: 0.78em;
  color: var(--d-fg);
  border: 1px solid var(--d-line);
  border-radius: 0.35em;
}

/* --- Table --------------------------------------------------------------- */
.d-table { padding: 0 0.9em; }
.d-grid { width: 100%; border-collapse: collapse; font-size: 0.8em; }

.d-grid th {
  padding: 0.45em 0.5em;
  text-align: left;
  font-weight: 400;
  color: var(--d-mut);
  background: var(--d-card);
  border-block: 1px solid var(--d-line);
}
.d-grid th:first-child { border-radius: 0.35em 0 0 0.35em; }
.d-grid th:last-child { border-radius: 0 0.35em 0.35em 0; }

.d-grid td {
  padding: 0.5em 0.5em;
  border-bottom: 1px solid var(--d-line-soft);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.d-grid td:nth-child(3),
.d-grid td:nth-child(4) { color: var(--d-mut); }
.d-grid .num { text-align: right; }
.d-grid .rev { color: var(--d-mut); }

.d-check {
  width: 0.85em;
  height: 0.85em;
  border: 1px solid #3a3a42;
  border-radius: 0.2em;
  display: inline-block;
}
.d-drag { color: var(--d-dim); letter-spacing: -0.15em; }
.d-more { color: var(--d-dim); text-align: right; }

.d-state { display: inline-flex; align-items: center; gap: 0.35em; }
.d-state i {
  width: 0.55em;
  height: 0.55em;
  flex: none;
  border-radius: 50%;
  border: 1px solid var(--d-mut);
}
.d-state--done i { background: var(--d-ok); border-color: var(--d-ok); }

/* --------------------------------------------------------------------------
   15. Why Loredo Labs — the section under the hero
   Editorial split: the origin story in a narrow left column, the thesis and
   the commitments on the right. No icon tiles, no cards — the hero above is
   the loud element, so this one is quiet and precise. Brand blue appears
   exactly once, on the diagonal cut mark, which is the painter's motif the
   original export used as its `.stroke`.
   -------------------------------------------------------------------------- */
.why {
  /* FIX 6: the lone hairline is gone — no other section had one, and the
     reconciled dark palette now carries the transition. */
  position: relative;
  padding-block: var(--section-y);
  background: var(--surface-0);
  --fade-rgb: 7, 12, 22;      /* --surface-deep */
}

/* The hero -> why join ramps on the HERO side, unlike the others. A band on
   why's top would have to start from a fixed colour, and the hero's bottom
   edge is the animated ribbon field — bright indigo at this scroll position,
   near-black elsewhere. Any constant is wrong most of the time, and measured
   against the real thing it looked worse than the hard cut it replaced.
   Ramping to white from the hero side is colour-agnostic: whatever the field
   is doing, it arrives at the next section's white.

   Shorter than the other bands (96px) because the dashboard panel bleeds to
   within ~105px of the hero's bottom edge, and this paints above it. */
.hero3::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 96px;
  z-index: 3;
  pointer-events: none;
  background: linear-gradient(
    180deg,
    rgba(255, 255, 255, 0)    0%,
    rgba(255, 255, 255, 0.06) 22%,
    rgba(255, 255, 255, 0.24) 44%,
    rgba(255, 255, 255, 0.56) 65%,
    rgba(255, 255, 255, 0.85) 83%,
    rgba(255, 255, 255, 1)    100%
  );
}

/* --------------------------------------------------------------------------
   Section joins — blend bands
   Each band sits in the LOWER edge of the upper section and ramps to the
   colour of the section below, so a cut reads as a transition rather than a
   line. It lives on the upper side deliberately: the lower side of three of
   these joins is a dark section whose own top carries something (contact's
   glow horizon, features' heading), and a band there would wash it out.

   The band always falls inside the section's --section-y bottom padding, so
   it never overlaps content, and it is a static gradient on an existing
   element — no extra node, no paint cost during scroll.

   Not applied to hero -> why: the hero's bottom is the dashboard panel, which
   bleeds close to the edge, and a white ramp there reads as the panel
   dissolving rather than as a section join.
   -------------------------------------------------------------------------- */
.why::after,
.feat::after,
.vid::after,
.ct::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: var(--fade-h);
  z-index: 3;
  pointer-events: none;
  /* Eased, not a two-stop linear ramp. A linear gradient has a discontinuous
     derivative at its endpoint, which the eye reads as a band right where the
     join is — the exact artefact this is meant to remove. These stops
     approximate a smoothstep so the ramp arrives at the section colour with
     zero slope. Colours are carried as RGB triplets for the same reason the
     shadow ramp is: the stops need alpha, not a solid. */
  background: linear-gradient(
    180deg,
    rgba(var(--fade-rgb), 0)    0%,
    rgba(var(--fade-rgb), 0.06) 22%,
    rgba(var(--fade-rgb), 0.24) 44%,
    rgba(var(--fade-rgb), 0.56) 65%,
    rgba(var(--fade-rgb), 0.85) 83%,
    rgba(var(--fade-rgb), 1)    100%
  );
}

.why__inner {
  display: grid;
  grid-template-columns: 1fr;
  gap: 48px;
}
@media (min-width: 1024px) {
  .why__inner {
    grid-template-columns: 17rem minmax(0, 1fr);
    gap: 96px;
  }
}

/* --- Left: the origin ---------------------------------------------------- */
/* The story is short and the list is tall, which left a dead column beside it.
   Pinning the story makes the asymmetry intentional: the origin stays with you
   while you read what came out of it. */
@media (min-width: 1024px) {
  .why__aside {
    position: sticky;
    top: calc(var(--nav-h) + 48px);
    align-self: start;
  }
}

.why__eyebrow {
  font-size: 0.75rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--ink-300);
}

.why__story {
  margin-top: 20px;
  font-size: 1rem;
  line-height: 1.7;
  color: var(--ink-700);
  text-wrap: pretty;
}

.why__by {
  margin-top: 20px;
  font-size: 0.8125rem;
  line-height: 1.5;
  color: var(--ink-500);
}
.why__by b { display: block; font-weight: 600; color: var(--ink-950); }

/* --- Right: thesis + commitments ----------------------------------------- */
/* The cut mark. One 4px diagonal, brand blue — the only colour in the block. */
.why__mark {
  display: block;
  width: 4px;
  height: 30px;
  margin-bottom: 26px;
  background: var(--blue-brand);
  border-radius: 1px;
  transform: skewX(-18deg);
}

/* The thesis. First sentence solid, second ghosted: the work was present,
   the system was not. The type says what the sentence says. */
.why__thesis {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(1.875rem, 4.1vw, 3.25rem);
  line-height: 1.08;
  letter-spacing: -0.03em;
  color: var(--ink-950);
  text-wrap: balance;
}
.why__thesis span {
  display: block;
  color: var(--ink-300);
}

.why__list { margin-top: 56px; }

.why__row {
  display: grid;
  grid-template-columns: 1fr;
  gap: 6px;
  padding-block: 22px;
  border-top: 1px solid var(--border);
}
/* Close the list. Without it the index ends mid-air. */
.why__row:last-child { border-bottom: 1px solid var(--border); }
@media (min-width: 640px) {
  .why__row {
    grid-template-columns: 13rem minmax(0, 1fr);
    gap: 32px;
    align-items: start;
  }
}

.why__row-title {
  font-size: 0.9375rem;
  font-weight: 600;
  line-height: 1.45;
  color: var(--ink-950);
}

.why__row-text {
  font-size: 0.9375rem;
  line-height: 1.65;
  color: var(--ink-500);
  text-wrap: pretty;
}

/* --------------------------------------------------------------------------
   16. About page
   Same editorial language as `.why` on the homepage — hairlines, quiet ink,
   one blue cut mark, Clash Display used twice — but a different rhythm, so
   the two pages don't read as the same block. The homepage runs one sticky
   aside beside one list; this runs a document: a lede, then labelled blocks
   stacked down the page with the label held in the left margin.
   -------------------------------------------------------------------------- */
.abt { padding-block: 72px 96px; }
@media (min-width: 768px) { .abt { padding-block: 96px 128px; } }

/* --- Opener -------------------------------------------------------------- */
.abt__head { max-width: 46rem; }

.abt__eyebrow {
  font-size: 0.75rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--ink-300);
}

.abt__title {
  margin-top: 22px;
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(2rem, 4.4vw, 3.25rem);
  line-height: 1.08;
  letter-spacing: -0.03em;
  color: var(--ink-950);
  text-wrap: balance;
}

.abt__lede {
  margin-top: 26px;
  font-size: 1.125rem;
  line-height: 1.65;
  color: var(--ink-500);
  text-wrap: pretty;
}

/* --- Labelled blocks ----------------------------------------------------- */
.abt__block {
  display: grid;
  grid-template-columns: 1fr;
  gap: 20px;
  margin-top: 56px;
  padding-top: 34px;
  border-top: 1px solid var(--border);
}
@media (min-width: 1024px) {
  .abt__block {
    grid-template-columns: 13rem minmax(0, 1fr);
    gap: 64px;
  }
}

.abt__label {
  font-size: 0.75rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--ink-300);
}

.abt__body { max-width: 42rem; }

.abt__p {
  font-size: 1rem;
  line-height: 1.75;
  color: var(--ink-700);
  text-wrap: pretty;
}
.abt__p + .abt__p { margin-top: 18px; }

/* The one signature moment on this page. Same cut mark as the homepage, but
   the line is set solid — the ghosted split belongs to the homepage thesis
   and repeating it would make the two pages read as one. */
.abt__quote {
  margin-top: 36px;
  padding-left: 26px;
  border-left: 0;
  position: relative;
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(1.375rem, 2.5vw, 1.875rem);
  line-height: 1.2;
  letter-spacing: -0.02em;
  color: var(--ink-950);
  text-wrap: balance;
}
.abt__quote::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.18em;
  width: 4px;
  height: 1.1em;
  background: var(--blue-brand);
  border-radius: 1px;
  transform: skewX(-18deg);
}

.abt__by {
  margin-top: 16px;
  font-size: 0.8125rem;
  line-height: 1.5;
  color: var(--ink-500);
}
.abt__by b { font-weight: 600; color: var(--ink-950); }

/* --- Ordered steps ------------------------------------------------------- */
/* Numbered because this genuinely is a sequence — the order is the content. */
.abt__steps { margin-top: 30px; counter-reset: step; }

.abt__step {
  display: grid;
  grid-template-columns: 2.5rem minmax(0, 1fr);
  gap: 18px;
  padding-block: 18px;
  border-top: 1px solid var(--border);
}
.abt__step:last-child { border-bottom: 1px solid var(--border); }

.abt__num {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 0.9375rem;
  line-height: 1.5;
  color: var(--ink-300);
  font-variant-numeric: tabular-nums;
}

.abt__step-title {
  font-size: 0.9375rem;
  font-weight: 600;
  color: var(--ink-950);
}
.abt__step-text {
  margin-top: 4px;
  font-size: 0.9375rem;
  line-height: 1.65;
  color: var(--ink-500);
}

/* --- Plain hairline rows ------------------------------------------------- */
.abt__rows { margin-top: 26px; }

.abt__row {
  display: grid;
  grid-template-columns: 1fr;
  gap: 4px;
  padding-block: 18px;
  border-top: 1px solid var(--border);
}
.abt__row:last-child { border-bottom: 1px solid var(--border); }
@media (min-width: 640px) {
  .abt__row { grid-template-columns: 12rem minmax(0, 1fr); gap: 28px; align-items: start; }
}

.abt__row-title { font-size: 0.9375rem; font-weight: 600; color: var(--ink-950); }
.abt__row-text { font-size: 0.9375rem; line-height: 1.65; color: var(--ink-500); }
.abt__row-title a:hover { color: var(--blue-brand); text-decoration: underline; }

/* --- Client list --------------------------------------------------------- */
.abt__clients {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 12px;
  margin-top: 24px;
  list-style: none;
}
.abt__clients li {
  padding: 7px 14px;
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--ink-700);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
}

/* --- Close --------------------------------------------------------------- */
.abt__cta { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 26px; }

/* --------------------------------------------------------------------------
   17. Features / Services — two 21st.dev specs placed side by side
   Left: the bento feature grid. Right: the Spline card with the mouse-tracked
   spotlight. Neither spec was written to sit beside the other, so the only
   invented rule is the split itself (58/42, header full width above, bento
   internal grid dropped from 4 columns to 2 inside its column). Everything
   within each card follows its spec.
   -------------------------------------------------------------------------- */
.feat {
  position: relative;
  padding-block: var(--section-y);
  overflow: hidden;
  background: var(--f-bg);
  color: var(--f-fg);
  --fade-rgb: 255, 255, 255;  /* --surface-0 */

  /* Re-hued from Tailwind zinc onto the brand's 220deg. Lightness is
     unchanged step for step, so the section reads exactly as designed — only
     the neutral grey cast is gone, which is what made zinc cards on a navy
     page look like a colour bug once --f-bg was reconciled. */
  --f-bg: var(--surface-deep);
  --f-card: #0b0f17;
  --f-card-2: #101621;
  --f-line: #1d2534;
  --f-line-hi: #2d384f;
  --f-fg: #fafafa;
  --f-mut: #a1a1aa;
  --f-dim: #71717a;
}


.feat__inner {
  display: flex;
  flex-direction: column;
  gap: 48px;
  /* Was max-w-7xl with its own 1rem/2rem section padding, which put the
     heading 80px left of every other section's text edge. This is .container's
     geometry, declared inline rather than by class because the section markup
     is a port and keeps its own wrapper. */
  max-width: var(--max-content);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* --- Section header ------------------------------------------------------ */
.feat__head { display: flex; flex-direction: column; gap: 20px; max-width: 48rem; }

.feat__pill {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  width: fit-content;
  padding: 8px 16px;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  color: var(--f-mut);
  background: var(--f-card-2);
  border: 1px solid var(--f-line);
  border-radius: var(--radius-pill);
}
.feat__pill svg { width: 1rem; height: 1rem; color: var(--f-fg); }

.feat__title {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(2.25rem, 4.6vw, 3.5rem);
  line-height: 1.05;
  letter-spacing: -0.03em;
  color: var(--f-fg);
  text-wrap: balance;
}

.feat__sub {
  max-width: 42rem;
  font-size: 1.0625rem;
  line-height: 1.65;
  color: var(--f-mut);
}

/* --- Stacked: bento above, Spline card below ----------------------------- */
/* Full width for each, which is what both specs were drawn for — the bento
   gets its real 4-column grid back, and the scene gets room to read. */
.feat__cols { display: flex; flex-direction: column; gap: 20px; }

/* --- Bento --------------------------------------------------------------- */
.bento {
  display: grid;
  grid-template-columns: 1fr;
  grid-auto-rows: minmax(260px, auto);
  gap: 16px;
}
@media (min-width: 640px) { .bento { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1024px) {
  /* The spec pins rows at 280px; our real copy is taller than its filler, so
     rows may grow. Fixed 280 clipped the outcome rows and the integrations. */
  .bento { grid-template-columns: repeat(4, minmax(0, 1fr)); grid-auto-rows: minmax(280px, auto); }
}

.card {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  overflow: hidden;
  padding: 24px;
  background: var(--f-card);
  border: 1px solid var(--f-line);
  border-radius: var(--radius-md);
  transition: border-color 0.3s var(--ease-out);
}
.card:hover { border-color: var(--f-line-hi); }

@media (min-width: 640px) {
  .card--wide { grid-column: span 2; }
  .card--tall { grid-column: span 2; grid-row: span 2; }
}
.card--tall { padding: 32px; }

.card__chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  width: fit-content;
  padding: 4px 12px;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--f-mut);
  background: var(--f-card-2);
  border: 1px solid var(--f-line);
  border-radius: var(--radius-sm);
}
.card__chip svg { width: 0.875rem; height: 0.875rem; }

.card__icon {
  display: grid;
  place-items: center;
  width: 2.25rem;
  height: 2.25rem;
  color: var(--f-fg);
  background: var(--f-card-2);
  border: 1px solid var(--f-line);
  border-radius: var(--radius-sm);
}
.card__icon svg { width: 1.125rem; height: 1.125rem; }

.card__h {
  margin-top: 16px;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.75rem;
  line-height: 1.1;
  letter-spacing: -0.02em;
  color: var(--f-fg);
}
.card__h--sm { margin-top: 12px; font-size: 0.9375rem; letter-spacing: -0.01em; }
.card__p { margin-top: 6px; font-size: 0.8125rem; line-height: 1.55; color: var(--f-mut); }

/* Feature selector — 2x2 grid of buttons */
.card__grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
  margin-top: 24px;
}

.pick {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
  padding: 14px;
  text-align: left;
  background: var(--f-card-2);
  border: 1px solid var(--f-line);
  border-radius: var(--radius);
  transition: background-color 0.3s var(--ease-out), border-color 0.3s var(--ease-out);
}
.pick:hover { border-color: var(--f-line-hi); }
.pick[aria-selected="true"] { background: #26262b; border-color: #4a4a54; }
.pick svg { width: 1.25rem; height: 1.25rem; color: var(--f-dim); transition: color 0.3s var(--ease-out); }
.pick[aria-selected="true"] svg { color: var(--f-fg); }
.pick b { font-size: 0.75rem; font-weight: 700; color: var(--f-fg); }
.pick span { font-size: 0.6875rem; color: var(--f-mut); }

/* Selected-feature readout */
.readout {
  margin-top: 20px;
  padding: 16px;
  background: var(--f-card-2);
  border: 1px solid var(--f-line);
  border-radius: var(--radius);
}
.readout__lab {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.6875rem;
  color: var(--f-dim);
}
.readout__t { margin-top: 4px; font-size: 1.0625rem; font-weight: 700; color: var(--f-fg); }
.readout__d { margin-top: 4px; font-size: 0.75rem; line-height: 1.5; color: var(--f-mut); }
.readout__s {
  margin-top: 12px;
  font-family: var(--font-display);
  font-size: 1.25rem;
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--f-fg);
}

/* Before / After toggle replacing the spec's "Live" badge */
.toggle { display: flex; padding: 2px; background: var(--f-card-2); border: 1px solid var(--f-line); border-radius: var(--radius-sm); }
.toggle button {
  padding: 4px 10px;
  font-size: 0.6875rem;
  font-weight: 600;
  color: var(--f-mut);
  border-radius: var(--radius-xs);
}
.toggle button[aria-selected="true"] { color: var(--f-fg); background: #2f2f37; }

.rows { display: flex; flex-direction: column; gap: 8px; margin-top: 16px; }
.row {
  padding: 10px 12px;
  text-align: left;
  background: var(--f-card-2);
  border: 1px solid var(--f-line);
  border-radius: var(--radius-sm);
  transition: background-color 0.2s var(--ease-out), border-color 0.2s var(--ease-out);
}
.row:hover { border-color: var(--f-line-hi); }
.row[aria-selected="true"] { background: #26262b; border-color: #4a4a54; }
.row b { display: block; font-size: 0.625rem; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; color: var(--f-dim); }
.row span { display: block; margin-top: 3px; font-size: 0.75rem; line-height: 1.45; color: var(--f-fg); }

/* Integrations */
.ints { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; margin-top: 16px; }
.int {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 12px 6px;
  background: var(--f-card-2);
  border: 1px solid var(--f-line);
  border-radius: var(--radius-sm);
  transition: border-color 0.2s var(--ease-out);
}
.int:hover { border-color: var(--f-line-hi); }
.int svg { width: 1.125rem; height: 1.125rem; color: var(--f-fg); transition: transform 0.2s var(--ease-out); }
.int:hover svg { transform: scale(1.2); }
.int span { font-size: 0.5625rem; line-height: 1.3; text-align: center; color: var(--f-dim); }

/* Terminal / system flow */
.term {
  margin-top: 16px;
  padding: 16px;
  overflow: auto;
  background: #000;
  border: 1px solid var(--f-line);
  border-radius: var(--radius-sm);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.75rem;
  line-height: 1.75;
}
.term__l { display: flex; gap: 10px; }
.term__n { width: 1.25rem; text-align: right; color: #52525b; user-select: none; flex: none; }
.term__c { color: var(--f-mut); white-space: pre; }
.term__c--hi { color: var(--f-fg); }
.term__c--dim { color: #52525b; }

/* --- Spline card --------------------------------------------------------- */
.spline {
  position: relative;
  display: flex;
  flex-direction: column;
  /* Mobile height is content + a fixed scene, not a fixed card height: pinning
     the card at 560 while the scene needs 340 would squeeze the copy instead. */
  height: auto;
  overflow: hidden;
  background: #000;
  border: 1px solid var(--f-line);
  border-radius: var(--radius-md);
}
/* Copy left, scene right — the spec's row layout. The scene takes the larger
   share so the figure reads at size rather than being cropped into a sliver. */
@media (min-width: 768px) { .spline { flex-direction: row; height: 560px; } }
@media (min-width: 1280px) { .spline { height: 620px; } }

.spline__copy {
  position: relative;
  z-index: 10;
  display: flex;
  flex: none;
  flex-direction: column;
  justify-content: center;
  padding: 28px;
}
@media (min-width: 768px) { .spline__copy { flex: 0 1 44%; padding: 40px; } }
@media (min-width: 1280px) { .spline__copy { padding: 56px; } }

.spline__h {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(1.75rem, 3.4vw, 3rem);
  line-height: 1.1;
  letter-spacing: -0.03em;
  background: linear-gradient(to bottom, #fafafa, #a1a1aa);
  -webkit-background-clip: text;
          background-clip: text;
  color: transparent;
  text-wrap: balance;
}

.spline__p { margin-top: 16px; font-size: 0.9375rem; line-height: 1.6; color: #d4d4d8; }

.spline__list { margin-top: 18px; display: grid; gap: 8px; }
.spline__list li { display: flex; gap: 8px; font-size: 0.8125rem; line-height: 1.5; color: var(--f-mut); }
.spline__list svg { flex: none; width: 0.875rem; height: 0.875rem; margin-top: 0.25em; color: var(--f-fg); }

/* Scene holder. Keeps its box whether or not the runtime ever arrives. */
/* Mobile: a fixed 340px stage. At 240 the figure was too small to read as
   something you can interact with, which is the whole reason for keeping the
   real scene over a static fallback. */
.spline__scene {
  position: relative;
  flex: none;
  align-self: stretch;
  height: 340px;
}
@media (min-width: 768px) { .spline__scene { flex: 1 1 56%; height: auto; } }

/* Pin the viewer to the box rather than sizing it with height:100%. The custom
   element has its own intrinsic size, and a percentage height against a
   stretched flex item is not reliably definite — when it resolved to `auto`
   the whole scene column collapsed to the element's intrinsic ~164px. */
.spline__scene spline-viewer {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
}
/* Kill the viewer's "Built with Spline" badge. It sits in the component's
   shadow DOM, so the ::part hook only lands on builds that expose it — the
   JS in site.js strips the node directly as the reliable path. */
.spline__scene spline-viewer::part(logo) { display: none !important; }

/* Fallback painted underneath: a soft figure-shaped glow, so a blocked CDN or
   a reduced-motion visitor still sees a composed card rather than a hole. */
.spline__fallback {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  background:
    radial-gradient(38% 30% at 50% 34%, rgba(255, 255, 255, 0.22), transparent 70%),
    radial-gradient(60% 50% at 50% 60%, rgba(255, 255, 255, 0.08), transparent 75%);
}
.spline__fallback svg { width: 46%; max-width: 190px; height: auto; color: rgba(255, 255, 255, 0.5); }
.spline--ready .spline__fallback { opacity: 0; transition: opacity 500ms var(--ease-out); }

/* The spotlight: a blurred disc following the pointer inside the card. */
.spline__spot {
  position: absolute;
  top: 0;
  left: 0;
  width: 320px;
  height: 320px;
  margin: -160px 0 0 -160px;
  pointer-events: none;
  border-radius: 50%;
  background: radial-gradient(circle at center, rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.12) 45%, transparent 80%);
  filter: blur(28px);
  opacity: 0;
  transition: opacity 200ms var(--ease-out);
  will-change: transform;
  z-index: 5;
}
.spline.is-lit .spline__spot { opacity: 1; }

/* --- Bottom stats -------------------------------------------------------- */
.feat__stats { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
@media (min-width: 768px) { .feat__stats { grid-template-columns: repeat(4, minmax(0, 1fr)); } }

.stat {
  padding: 16px;
  background: var(--f-card);
  border: 1px solid var(--f-line);
  border-radius: var(--radius);
  transition: border-color 0.3s var(--ease-out);
}
.stat:hover { border-color: var(--f-line-hi); }
.stat svg { width: 1rem; height: 1rem; color: var(--f-fg); }
.stat__v {
  margin-top: 12px;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.5rem;
  letter-spacing: -0.02em;
  color: var(--f-fg);
}
.stat__l { margin-top: 4px; font-size: 0.75rem; line-height: 1.45; color: var(--f-mut); }

@media (prefers-reduced-motion: reduce) {
  .spline__spot { display: none; }
  .int:hover svg { transform: none; }
}

/* --------------------------------------------------------------------------
   18. Video — "See it in action"
   Sits between the dark features section and the dark footer, so it runs
   light: the player becomes the only dark object on the page at that point
   instead of a third black band. Same editorial language as `.why` — eyebrow,
   Clash Display headline, one brand cut mark.

   The Drive iframe is NOT in the initial document. It is injected on click, so
   a homepage visit does not pull Google's player, its cookies and its weight
   for a video most visitors never start. A real link to the file sits below
   the frame for anyone with JS off or the embed blocked.
   -------------------------------------------------------------------------- */
.vid {
  position: relative;
  padding-block: var(--section-y);
  background: var(--surface-0);
  --fade-rgb: 7, 12, 22;      /* --surface-deep */
}

.vid__mark {
  display: block;
  width: 4px;
  height: 30px;
  margin-bottom: 24px;
  background: var(--blue-brand);
  border-radius: 1px;
  transform: skewX(-18deg);
}

.vid__eyebrow {
  font-size: 0.75rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--ink-300);
}

.vid__title {
  margin-top: 18px;
  max-width: 20ch;
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(1.875rem, 3.6vw, 3rem);
  line-height: 1.08;
  letter-spacing: -0.03em;
  color: var(--ink-950);
  text-wrap: balance;
}

.vid__body {
  margin-top: 20px;
  max-width: 46rem;
  font-size: 1.0625rem;
  line-height: 1.65;
  color: var(--ink-500);
  text-wrap: pretty;
}

/* --- Player -------------------------------------------------------------- */
.vid__frame {
  position: relative;
  margin-top: 44px;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  background: var(--ink-950);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card-lg);
}

.vid__frame iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

/* The click target that stands in for the player until it is asked for. */
.vid__facade {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 18px;
  width: 100%;
  color: #fff;
  /* Transparent: the poster sits underneath and the ::before scrim below is
     what darkens it. An opaque fill here hides the frame entirely. */
  background: transparent;
}
.vid__facade[hidden] { display: none; }

/* Poster: a real frame from the demo. The frame is a pale, high-key UI
   screenshot, so it is dimmed here as well as scrimmed below — a white play
   control on untouched pale blue has nothing to sit against. */
.vid__poster {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: brightness(0.58) saturate(0.85);
}

/* Scrim over the poster. The frame is a pale UI screenshot, so the white play
   control needs something to sit on — without this it disappears into it. */
.vid__facade::before {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(42% 42% at 50% 50%, rgba(6, 10, 32, 0.55), transparent 74%),
    linear-gradient(180deg, rgba(6, 10, 32, 0.28) 0%, rgba(6, 10, 32, 0.44) 100%);
}
.vid__facade > * { position: relative; z-index: 1; }
.vid__label { text-shadow: 0 1px 12px rgba(6, 10, 32, 0.8); }

.vid__play {
  display: grid;
  place-items: center;
  width: 76px;
  height: 76px;
  color: var(--ink-950);
  background: rgba(255, 255, 255, 0.82);
  border: 1px solid var(--glass-border);
  border-radius: 50%;
  backdrop-filter: blur(var(--glass-blur)) saturate(1.4);
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(1.4);
  box-shadow: var(--shadow-card-lg), inset 0 1px 0 var(--glass-hi);
  transition: transform 0.25s var(--ease-out), box-shadow 0.25s var(--ease-out);
}
.vid__play svg { width: 26px; height: 26px; margin-left: 4px; }
.vid__facade:hover .vid__play { transform: scale(1.07); background: rgba(255, 255, 255, 0.94); }
.vid__facade:focus-visible .vid__play { transform: scale(1.07); }

.vid__label { font-size: 0.9375rem; font-weight: 500; letter-spacing: 0.01em; }
.vid__facade .vid__label { text-shadow: 0 1px 12px rgba(6, 10, 32, 0.8); }

.vid__note {
  margin-top: 16px;
  font-size: 0.8125rem;
  color: var(--ink-500);
}
.vid__note a { color: var(--ink-950); font-weight: 500; }
.vid__note a:hover { color: var(--blue-brand); text-decoration: underline; }

@media (prefers-reduced-motion: reduce) {
  .vid__facade:hover .vid__play,
  .vid__facade:focus-visible .vid__play { transform: none; }
}

/* --------------------------------------------------------------------------
   19. Contact — glow-horizon + interactive globe
   glow-horizon's framer-motion entrance is ported to keyframes: the wrapper
   runs y -100% -> -50%, scaleY 1.5 -> 1, opacity 0 -> 1, blur 15px -> 0 over
   2s on cubic-bezier(.16,1,.3,1); the inner arcs that declare an initialOffset
   run their own y -40% -> 0 on the same curve at 0s / 0.6s / 1.2s. The spec's
   #A558FB is swapped for our ultramarine so the glow stays on-brand; the arc
   count, sizes, blurs and box-shadow are the spec's.
   -------------------------------------------------------------------------- */
.ct {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  padding-block: var(--section-y);
  background: var(--surface-deep);
  color: #fafafa;
  /* Same colour as the footer below: this band exists to flatten the residual
     luminance from the globe glow, which was the last visible step at that
     join even after both sections were reconciled to --surface-deep. */
  --fade-rgb: 7, 12, 22;      /* --surface-deep */
}


/* --- glow-horizon -------------------------------------------------------- */
.glow {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  /* Back to a share of the section, where the horizon sweeps across rather
     than sitting tucked at the top edge. */
  height: 58%;
  isolation: isolate;
  pointer-events: none;
  z-index: -1;
  transform: translateY(-50%) scaleY(1);
  animation: glowIn 2s cubic-bezier(0.16, 1, 0.3, 1) both;
}

@keyframes glowIn {
  from { transform: translateY(-100%) scaleY(1.5); opacity: 0; filter: blur(15px); }
  to   { transform: translateY(-50%) scaleY(1);    opacity: 1; filter: blur(0px); }
}

.glow__arc {
  position: absolute;
  inset: 0;
  border-radius: 100%;
}

/* Sizes, blurs and paint order are the spec's; only the mid hue is ours.
   Order is load-bearing: the black arc is painted LAST so it masks the centre
   and only a lit rim shows. Painting white last fills the whole section. */
.glow__arc--white {
  transform: scale(1.32);
  background: #fff;
  box-shadow: 0 -4px 23px 0 rgba(255, 255, 255, 0.71);
}
.glow__arc--mid  { transform: scale(1.2);  background: var(--rf-ultramarine); filter: blur(31px); }
.glow__arc--deep { transform: scale(1.24); background: var(--rf-iris);        filter: blur(21px); }
.glow__arc--mask { transform: scale(1.2);  background: var(--surface-deep);     filter: blur(51px); }

/* The three arcs that declare an initialOffset slide from -40% to 0. */
.glow__arc--mid,
.glow__arc--deep,
.glow__arc--mask { animation: arcIn 2s cubic-bezier(0.16, 1, 0.3, 1) both; }
.glow__arc--mid  { animation-delay: 0.6s; }
.glow__arc--deep { animation-delay: 0s; }
.glow__arc--mask { animation-delay: 0s; }

@keyframes arcIn {
  from { translate: 0 -40%; }
  to   { translate: 0 0; }
}

/* --- Shell --------------------------------------------------------------- */
.ct__inner {
  /* Same reconciliation as .feat__inner — was max-w-6xl on a 1rem/2rem
     section pad, landing 16px right of the standard edge. */
  max-width: var(--max-content);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* No card. The content sits directly on the section so the glow behind it
   reads as one continuous field instead of being interrupted by a panel. */
.ct__card { position: relative; }

.ct__grid { display: flex; flex-direction: column; min-height: 500px; }
@media (min-width: 768px) { .ct__grid { flex-direction: row; } }

.ct__copy {
  position: relative;
  z-index: 10;
  display: flex;
  flex: 1;
  flex-direction: column;
  justify-content: center;
  padding-block: 40px;
}
@media (min-width: 768px) { .ct__copy { padding-block: 56px; } }

.ct__pill {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  width: fit-content;
  margin-bottom: 24px;
  padding: 5px 12px;
  font-size: 0.75rem;
  color: #a1a1aa;
  border: 1px solid #2f2f37;
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, 0.03);
}
.ct__dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--success);
  animation: ctPulse 2s ease-in-out infinite;
}
@keyframes ctPulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }

.ct__title {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(1.875rem, 3.4vw, 3rem);
  line-height: 1.08;
  letter-spacing: -0.03em;
  color: #fff;
  text-wrap: balance;
}
.ct__title span {
  background: linear-gradient(to right, var(--rf-sky), #b9c6ff);
  -webkit-background-clip: text;
          background-clip: text;
  color: transparent;
}

.ct__body {
  margin-top: 18px;
  max-width: 34rem;
  font-size: 0.9375rem;
  line-height: 1.65;
  color: #a1a1aa;
}

.ct__stats { display: flex; align-items: center; gap: 22px; margin-top: 32px; flex-wrap: wrap; }
.ct__stat p:first-child {
  font-family: var(--font-display);
  font-size: 1.5rem;
  font-weight: 600;
  letter-spacing: -0.02em;
  color: #fff;
}
.ct__stat p:last-child { margin-top: 2px; font-size: 0.75rem; color: #8b8b95; }
/* Dividers only make sense while the three stats sit on one line. CSS cannot
   detect wrap, so they are hidden until the column is wide enough to hold the
   row — otherwise they strand as loose vertical ticks beside stacked stats. */
.ct__rule { display: none; width: 1px; height: 34px; background: #2f2f37; }
@media (min-width: 1280px) { .ct__rule { display: block; } }

.ct__actions { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 34px; }
.ct__actions .ui-btn {
  --ui-primary: rgba(255, 255, 255, 0.92);
  --ui-primary-hover: rgba(255, 255, 255, 0.99);
  --ui-primary-fg: #0b1220;
  --ui-ring: #fff;
}
.ct__actions .ui-btn--outline {
  --ui-bg: rgba(255, 255, 255, 0.06);
  --ui-fg: #fff;
  --ui-input: #3a3a42;
  --ui-accent: rgba(255, 255, 255, 0.12);
}

/* --- Globe --------------------------------------------------------------- */
.ct__globe {
  position: relative;
  display: flex;
  flex: 1;
  align-items: center;
  justify-content: center;
  min-height: 340px;
  padding: 16px;
}
@media (min-width: 768px) { .ct__globe { min-height: 500px; padding: 0; } }

.ct__globe canvas {
  width: 100%;
  max-width: 520px;
  aspect-ratio: 1;
  touch-action: none;
  cursor: grab;
}
.ct__globe canvas.is-grabbing { cursor: grabbing; }

.ct__hint {
  position: absolute;
  bottom: 18px;
  left: 50%;
  transform: translateX(-50%);
  font-size: 0.75rem;
  color: #71717a;
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  /* glow-horizon lands in its rest state with no entrance at all. */
  .glow,
  .glow__arc--mid,
  .glow__arc--deep,
  .glow__arc--mask { animation: none; }
  .glow { transform: translateY(-50%) scaleY(1); opacity: 1; filter: none; }
  .ct__dot { animation: none; }
}

/* --------------------------------------------------------------------------
   20. Aurora background
   The 21st.dev aurora-background ported out of Tailwind arbitrary values into
   plain CSS. Same mechanism: two stacked repeating-linear-gradients at 100deg,
   blurred, with a second copy on ::after that scrolls its background-position
   from 50% to 350% over 60s and composites with mix-blend-mode: difference.
   The radial mask from the top-right corner is the component's showRadialGradient.

   Tailwind's colour variables are written out literally — blue-500 #3b82f6,
   indigo-300 #a5b4fc, blue-300 #93c5fd, violet-200 #ddd6fe, blue-400 #60a5fa.

   Two host requirements: the section needs `position: relative` and
   `overflow: hidden`, and its content needs to sit above z-index -2.
   -------------------------------------------------------------------------- */
.aurora {
  position: absolute;
  inset: -10px;
  z-index: -2;
  pointer-events: none;
  overflow: hidden;
  opacity: 0.62;

  /* isolation + contain are load-bearing for frame rate, not for looks. The
     ::after blends with `difference`, and without isolation its blend backdrop
     is everything painted beneath it in the section — on the hero that is the
     entire .dash panel subtree, which then re-composites every frame. Measured:
     32fps without these, 60fps with. */
  isolation: isolate;
  contain: paint;

  --aurora: repeating-linear-gradient(
    100deg,
    #3b82f6 10%, #a5b4fc 15%, #93c5fd 20%, #ddd6fe 25%, #60a5fa 30%
  );
  --white-gradient: repeating-linear-gradient(
    100deg, #fff 0%, #fff 7%, transparent 10%, transparent 12%, #fff 16%
  );
  --dark-gradient: repeating-linear-gradient(
    100deg, #000 0%, #000 7%, transparent 10%, transparent 12%, #000 16%
  );

  background-image: var(--white-gradient), var(--aurora);
  background-size: 300%, 200%;
  background-position: 50% 50%, 50% 50%;
  filter: blur(10px) invert(1);

  /* Two mask layers, intersected. The radial is the original shape. The linear
     one fades the ambient in over --fade-h from the section's top edge, and it
     is what makes the blend bands actually work: without it the aurora is at
     full strength on the first pixel of the section, so the band above ramps
     to flat --surface-deep while the section itself starts at deep + ambient,
     and the step just moves rather than disappearing. Same root cause as the
     footer's mask anchor, generalised to every join. */
  --aurora-mask: radial-gradient(ellipse at 100% 0%, #000 22%, transparent 88%);
  -webkit-mask-image: var(--aurora-mask), linear-gradient(180deg, transparent 0%, #000 var(--fade-h));
          mask-image: var(--aurora-mask), linear-gradient(180deg, transparent 0%, #000 var(--fade-h));
  -webkit-mask-composite: source-in;
          mask-composite: intersect;
}

.aurora::after {
  content: "";
  position: absolute;
  background-image: var(--white-gradient), var(--aurora);
  /* Twice as wide as the host, translated by half its own width, instead of a
     100%-wide layer with its background-position animated. Same sweep, same
     60s loop — but background-position repaints a blurred, difference-blended
     layer every single frame, where transform is compositor-only. This is the
     single biggest frame-budget win on the page (29 -> 60fps during scroll).
     background-attachment: fixed went with it: it forces a repaint on scroll. */
  top: 0;
  left: 0;
  bottom: 0;
  width: 200%;
  background-size: 100% 100%, 50% 100%;
  mix-blend-mode: difference;
  will-change: transform;
  animation: aurora 60s linear infinite;
}

/* Dark hosts skip the invert and mask with black instead of white. */
.aurora--dark {
  background-image: var(--dark-gradient), var(--aurora);
  filter: blur(10px);
  opacity: 0.48;
}
.aurora--dark::after {
  background-image: var(--dark-gradient), var(--aurora);
}

@keyframes aurora {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-50%, 0, 0); }
}

/* Off-screen sections drop out of the frame rather than merely pausing: pausing
   was measured to save nothing, since a paused layer still costs its GPU render
   pass every frame.

   Be honest about what this buys during scroll: MEASURED AS NO IMPROVEMENT.
   Paired A/B, cull vs pause, 3 runs each: 5.66-6.08 render passes per frame vs
   6.19-6.33, dropped frames 10.3/18.6/13.1% vs 11.0/12.0/14.8% — inside the
   run-to-run noise. It is kept because it is strictly less compositing work
   where nothing is visible (six of these exist, and it matters more on a phone
   than on the machine this was profiled on), not because it fixed scrolling.
   The aurora's real scroll cost is per-VISIBLE-layer; see initAurora().

   The observer's margin is far larger than --fade-h's 160px ceiling, so the
   cull boundary never lands inside the top-fade region the blend bands depend
   on, and joins cannot pop. */
.aurora.is-idle { display: none; }

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

/* Hosts: each needs a positioning context and a clip, and content above the
   aurora's z-index. */
.why,
.vid,
.abt,
.feat,
.section,
.legal { position: relative; overflow: hidden; }

/* `:not(.aurora)` matters: without it this rule overrides the layer's own
   `position: absolute`, `inset: -10px` stops sizing it, and it collapses to
   zero height — present in the DOM, invisible on screen. */
.why > *:not(.aurora),
.vid > *:not(.aurora),
.abt > *:not(.aurora),
.feat > *:not(.aurora),
.ct > *:not(.aurora),
.section > *:not(.aurora),
.legal > *:not(.aurora) { position: relative; }

/* The hero already stacks bg(-3) / canvas(-2) / grain(-1). The aurora sits at
   -1 so it reads over the ribbon field, and DOM order puts the grain above it.
   Lower opacity here so it tints the mesh rather than competing with it. */
.hero3 .aurora { z-index: -1; opacity: 0.18; }

/* --------------------------------------------------------------------------
   21. Hero card field — scroll-morph-hero, load-fired
   The 21st.dev component's intro sequence ported to vanilla: scatter -> line
   -> halo, spring settle, mouse parallax, hover flip. The component's circle
   and arc stages are gone; the field orbits the dashboard panel as a loose
   scatter, deliberately not a ring. Framer's spring is a rAF velocity integrator.

   Deliberately dropped: the virtual-scroll driver and the circle -> arc morph
   it powers. Those attach `wheel` and `touchmove` with preventDefault, which
   inside a hero means the page cannot be scrolled. Firing on load — as asked —
   removes the need for them entirely, so the listeners are never attached.

   Also dropped: the component's own headline and kicker. This hero already has
   both; the cards are a layer, not a replacement.

   Sits at z-index -1 in DOM order after the aurora and before .hero3__grain,
   so the grain scrim still passes over the cards and hero copy keeps its
   contrast.
   -------------------------------------------------------------------------- */
.smh {
  position: absolute;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  pointer-events: none;
  perspective: 1000px;
}

/* The field orbits its own origin, and that origin is the centre of the
   dashboard panel. JS overwrites left/top in px from the measured
   .hero3__panel box; these values are the pre-measure fallback only. Every
   stage derives from this point, so one offset moves them all. */
.smh__stage {
  position: absolute;
  top: 66%;
  left: 50%;
  width: 0;
  height: 0;
}

.smh__card {
  display: block;
  position: absolute;
  width: 60px;
  height: 85px;
  margin: -42.5px 0 0 -30px;   /* centre the card on its own origin */
  transform-style: preserve-3d;
  pointer-events: auto;
  cursor: pointer;
  opacity: 0;
  will-change: transform;
}
@media (max-width: 767px) {
  .smh__card { width: 42px; height: 60px; margin: -30px 0 0 -21px; }
}

/* display:block is load-bearing — these are spans, and position:relative does
   not blockify, so width/height:100% would be ignored and the faces would
   have no box to fill. */
.smh__inner {
  display: block;
  position: relative;
  width: 100%;
  height: 100%;
  transform-style: preserve-3d;
  transition: transform 0.6s cubic-bezier(0.16, 1, 0.3, 1);
}
.smh__card:hover .smh__inner { transform: rotateY(180deg); }

.smh__face {
  display: block;
  position: absolute;
  inset: 0;
  overflow: hidden;
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-card-md);
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
}

.smh__face img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* A dim wash that lifts on hover, per the component's group-hover treatment. */
.smh__face--front::after {
  content: "";
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.04);
  transition: background-color 0.3s var(--ease-out);
}
.smh__card:hover .smh__face--front::after { background: transparent; }

/* Faces built from the assets that actually exist. */
.smh__face--mark {
  display: grid;
  place-items: center;
  background: var(--surface-0);
}
.smh__face--mark img { width: 52%; height: auto; object-fit: contain; }

.smh__face--mark-dark { background: var(--ink-950); }

.smh__face--brand {
  background: linear-gradient(150deg, var(--rf-sky), var(--rf-ultramarine) 55%, var(--rf-iris));
}

/* Back face — the spec's small label + title, filled with a real service. */
.smh__face--back {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 6px;
  text-align: center;
  background: #111116;
  border: 1px solid #2b2b33;
  transform: rotateY(180deg);
}
.smh__kicker {
  font-size: 7px;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--rf-sky);
  margin-bottom: 2px;
}
.smh__label {
  font-size: 9px;
  font-weight: 500;
  line-height: 1.2;
  color: #fff;
}

@media (prefers-reduced-motion: reduce) {
  /* No scatter, no travel, no flip — the ring is simply there. */
  .smh__inner { transition: none; }
  .smh__card:hover .smh__inner { transform: none; }
}
