/* ══════════════════════════════════════════════════════════════════════════
   CrewSafety Marketing — "Command Center"
   Premium dark, luminous. Sora display + IBM Plex Sans body. Depth, glow,
   glass, orchestrated motion. Committed dark identity (single theme by design).
   Drop-in replacement: preserves the prior class/token API so templates render
   unchanged — only the skin is new. Blueprint-grid + monospace spec-plates are
   gone (they read as engineering docs, not marketing).
   ══════════════════════════════════════════════════════════════════════════ */
/* Sora/IBM Plex Sans are loaded via <link rel="preconnect"> + a real
   <link rel="stylesheet"> in each page's own <head> (marketing_base.html,
   and each auth page directly) -- faster than an @import here, which is
   render-blocking and can't be discovered by the browser's preloader until
   this file itself has already downloaded. This used to duplicate that
   with a same-URL @import as a belt-and-suspenders fallback; every page
   that loads this stylesheet now has its own <link> first, so the
   fallback was dead weight, not a safety net. */

:root {
  /* Tells the browser to render native widgets (select popups, scrollbars,
     date pickers) in dark mode. Without this, a manually dark-styled
     <option> background is ignored by some engines' native dropdown
     popup (which is OS/browser-chrome, not page-painted) while the light
     text color still leaks through - exactly the washed-out white-popup/
     light-text bug this site hit. This is the real fix; the .field select
     option rule below is a belt-and-suspenders fallback. */
  color-scheme: dark;

  /* ── Canvas & surfaces (charcoal, faint teal-green undertone) ── */
  /* 2026-08-26: lightened from #060B09 → #111814 per B2B dark-mode ergonomics
     research. Pure/near-black causes halation for ~40–50% of visitors with
     astigmatism (light bleeds around glyphs on a dilated iris). Charcoal
     absorbs light more evenly while the teal undertone stays. WCAG contrast
     against --text-primary (#E2EDE8) is still >11:1, well above the 4.5:1
     floor. Borders alpha-bumped to stay visible against the lighter canvas. */
  --bg:            #111814;
  --bg-inset:      #0E1A14;
  --bg-elevated:   rgba(255,255,255,.038);   /* glass card */
  --bg-elevated-2: rgba(255,255,255,.062);   /* glass, stronger */
  --border:        rgba(233,245,240,.11);
  --border-strong: rgba(233,245,240,.20);

  /* ── Luminous green accent ── */
  --green-700: #0E5F4C;
  --green-600: #16A085;
  --green-500: #2DD4A0;      /* deep brand green */
  --green-400: #34E5AD;      /* THE glow green */
  --green-300: #7CF3CE;
  --green-100: #C9FBEA;

  --accent-bg:       var(--green-400);
  --accent-bg-hover: var(--green-300);
  --accent-text:     var(--green-400);
  --accent-on:       #032018;   /* ink on green buttons */

  /* secondary "steel" → cool cyan-teal, used sparingly */
  --steel-700:#1E3A46; --steel-600:#245968; --steel-500:#3E8296;
  --steel-400:#5FB5CE; --steel-300:#8ED2E4; --steel-200:#BFE7F1;
  --link:       var(--steel-400);
  --link-hover: var(--steel-300);

  /* ── Text ── */
  /* 2026-08-26: softened from bright #EAF6F1 → ergonomic #E2EDE8. Maximum
     contrast (#FFF on #000) triggers halation; off-white absorbs less light
     at the glyph edge, preserving letterform definition on dark surfaces.
     Secondary lifted from #9FB6AD → #A8B8B2 for ≥4.5:1 against new canvas. */
  --text-primary:   #E2EDE8;
  --text-secondary: #A8B8B2;
  --text-muted:     #6E8078;
  --ink-950:#111814; --ink-900:#0E1A14; --ink-850:#0E1815; --ink-800:#122019;
  --ink-700:#1A2B25; --ink-600:#26332C; --ink-400:#6E8078; --ink-200:#A8B8B2; --ink-050:#E2EDE8; --ink-000:#fff;

  /* ── Signal (status) ── */
  --success:#34E5AD; --success-500:#34E5AD; --success-600:#16A085;
  --warning:#F0B450; --warning-500:#F0B450; --warning-700:#8F5A14;
  --danger:#FF6B6B;  --danger-500:#FF6B6B;  --danger-600:#C93338;

  /* ── Atmospheric glow / texture ── */
  --glow-green: rgba(52,229,173,.16);
  --glow-teal:  rgba(95,181,206,.10);
  --grid-line:  rgba(52,229,173,.05);      /* kept for token compat; barely used */
  --grain-dot:  rgba(255,255,255,.015);
  --shadow-color: 3, 12, 9;
  --glass-blur: 14px;

  /* ── Type ── */
  --font-display: 'Sora', 'Segoe UI', system-ui, sans-serif;
  --font-body:    'IBM Plex Sans', 'Segoe UI', system-ui, sans-serif;
  --font-mono:    ui-monospace, 'Cascadia Code', 'SF Mono', Consolas, monospace;

  --text-display-1: clamp(2.9rem, 2.1rem + 3.6vw, 5.2rem);
  --text-display-2: clamp(2.1rem, 1.6rem + 2.2vw, 3.4rem);
  --text-h1: clamp(1.9rem, 1.5rem + 1.4vw, 2.6rem);
  --text-h2: clamp(1.5rem, 1.3rem + .9vw, 2rem);
  --text-h3: clamp(1.2rem, 1.1rem + .4vw, 1.45rem);
  --text-h4: 1.15rem;
  --text-body-lg: 1.1875rem;
  --text-body: 1.02rem;
  --text-body-sm: .9375rem;
  --text-caption: .8125rem;

  --lh-tight: 1.04; --lh-snug: 1.18; --lh-body: 1.65; --lh-caption: 1.5;

  /* ── Space / radius / motion ── */
  --space-1:4px; --space-2:8px; --space-3:12px; --space-4:16px; --space-5:24px;
  --space-6:32px; --space-7:48px; --space-8:64px; --space-9:96px; --space-10:128px;
  --radius-sm:8px; --radius-md:14px; --radius-lg:22px; --radius-full:999px;
  /* 760px at the 16.3px body size measured out to 82 characters per line —
     well past the 60-75 range prose stays comfortable in, and it showed on
     the two longest-form pages (Terms, Privacy) and the Pricing FAQ. 680px
     lands at ~73ch. --measure is the same idea expressed directly, for prose
     that sits inside the WIDE container rather than in its own narrow one;
     see .prose below. */
  --container-max:1200px; --container-max-narrow:680px;
  /* One step wider than --container-max, for the four-across pricing table
     only. At 1200 the four cards resolve to 258x750 — roughly a 1:3 column,
     which reads cramped; 1320 lifts them to ~294 at the same gap. */
  --container-max-wide:1320px;
  /* 58ch, not 68ch, and the difference matters. The CSS `ch` unit is the
     advance width of "0", which in IBM Plex Sans is ~1.2x the average
     character width — so a `max-width:68ch` block actually fits ~82
     characters per line, which is exactly the too-wide measure found on
     Terms, Privacy and the Pricing FAQ (all of which were already capped
     at 68ch and still measured 82). 58ch lands at ~70 real characters,
     inside the 60-75 range. Verified by canvas text metrics on the live
     page, not by the nominal number. */
  --measure: 58ch;
  /* Sora's "0" is narrower relative to its average character than IBM
     Plex Sans's is, so the SAME ch value buys more real characters in
     display type than in body type — 58ch measured 70 characters in
     Plex and 76 in Sora-700. Display copy needs its own number rather
     than sharing --measure. This is the same ch-is-not-a-character trap
     as above, one level down. */
  --measure-display: 52ch;
  --container-pad: clamp(1.25rem, 4vw, 3rem);
  --dur-fast:.16s; --dur:.28s; --dur-slow:.5s;
  --ease: cubic-bezier(.22,.61,.36,1);
  --focus-ring: 0 0 0 3px rgba(52,229,173,.45);
  --shadow-sm: 0 1px 2px rgba(3,12,9,.5);
  --shadow-md: 0 12px 34px -12px rgba(3,12,9,.7);
  --shadow-lg: 0 30px 70px -24px rgba(3,12,9,.8);
}

*,*::before,*::after{ box-sizing:border-box; }
html{ -webkit-text-size-adjust:100%; scroll-behavior:smooth; }
@media (prefers-reduced-motion: reduce){ html{ scroll-behavior:auto; } }

body{
  margin:0;
  background:var(--bg);
  color:var(--text-primary);
  font-family:var(--font-body);
  font-size:var(--text-body);
  line-height:var(--lh-body);
  /* 2026-08-26: +0.012em letter-spacing. On dark backgrounds, character
     edges bleed into each other (halation) — a small spacing increase keeps
     the gap between letters visible for astigmatic users without affecting
     the display headings, which have their own tighter tracking. */
  letter-spacing:0.012em;
  -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
  overflow-x:hidden;
  position:relative;
}
/* Atmosphere: layered radial glows fixed behind everything + fine grain */
body::before{
  content:""; position:fixed; inset:0; z-index:-2; pointer-events:none;
  background:
    radial-gradient(60% 50% at 78% -8%, var(--glow-green), transparent 60%),
    radial-gradient(48% 45% at 8% 12%, var(--glow-teal), transparent 55%),
    radial-gradient(70% 60% at 50% 118%, rgba(22,160,133,.10), transparent 60%);
}
body::after{
  content:""; position:fixed; inset:0; z-index:-1; pointer-events:none; opacity:.5;
  background-image:radial-gradient(var(--grain-dot) 1px, transparent 1px);
  background-size:3px 3px;
}

/* ── Layout ───────────────────────────────────────────────────────────── */
.container{ width:100%; max-width:var(--container-max); margin-inline:auto; padding-inline:var(--container-pad); }
.container-narrow{ width:100%; max-width:var(--container-max-narrow); margin-inline:auto; padding-inline:var(--container-pad); }
.container--wide{ max-width:var(--container-max-wide); }
/* ── Editorial split ──────────────────────────────────────────────────────
   For a section that is a heading plus long-form prose, sitting on the
   full-width spine. Prose needs a reading measure, but a measure alone in a
   1200px section is a column of text with half a screen of nothing next to
   it, which is what the first attempt at the spine fix produced on About.
   The heading takes the other column: claim at display size on the left,
   argument beside it. The lead column is sticky on tall sections so the
   heading and, in the letter's case, the signature stay with the text.
   Ratio favours the body since that is where the reading happens. */
/* .78 rather than a round number: at the 1200 spine it lands the body
   column on --measure exactly, so the prose fills its track and there is no
   leftover strip between the text and the right edge of the section. */
.editorial{ display:grid; grid-template-columns:minmax(0,.78fr) minmax(0,1fr);
  gap:var(--space-7) var(--space-8); align-items:start; }
.editorial__body{ max-width:var(--measure); }
.editorial__body > :last-child{ margin-bottom:0; }
@media (min-width:1025px){
  .editorial--letter .editorial__lead{ position:sticky; top:calc(68px + var(--space-6)); }
}
@media (max-width:1024px){
  .editorial{ grid-template-columns:1fr; gap:var(--space-5); }
}

/* THE CONTENT SPINE.
   About and Compare were alternating .container (1200) and .container-narrow
   (760) section by section — About ran 760/1200/760/1200/760/760 — which
   moves the left edge of the page in and out by ~220px as you scroll. The
   outer spine should not move; the MEASURE of the prose inside it should.
   .prose holds a comfortable reading width inside the full-width container,
   so a text-only section keeps the same left edge as the grid section above
   it. Reach for .container-narrow only for a page that is prose end to end
   (Terms, Privacy), never to narrow one section of a wider page. */
.prose{ max-width:var(--measure); }
.prose.text-center, .text-center > .prose, .prose--center{ margin-inline:auto; }
/* Section rhythm. Padding-block applies top AND bottom, so two stacked
   sections put double these numbers between their content: the previous
   96px/128px scale meant 192-256px of dead space at every section boundary,
   which read as the page being mostly empty. Tightened by roughly a third
   across the scale, keeping the same relative steps between sm/base/lg. */
.section{ padding-block:var(--space-8); }
.section-sm{ padding-block:var(--space-6); }
.section-lg{ padding-block:calc(var(--space-8) + var(--space-5)); }
.section-inset{ position:relative; }
.section-inset::before{
  content:""; position:absolute; inset:0; z-index:-1;
  background:linear-gradient(180deg, transparent, rgba(255,255,255,.018) 12%, rgba(255,255,255,.018) 88%, transparent);
  border-block:1px solid var(--border);
}
/* No max-width here on purpose (previously 52ch): `ch` is sized off
   THIS element's own (body-sized) font, but the h2 inside almost always
   renders at .display-2 size -- a big bold font fits far fewer characters
   per line than the same ch-width suggests, so the old cap forced long
   display headlines into 4-6 short lines with a large unused gutter
   beside them, confirmed live on Home's "how it works" intro and several
   others. Letting this fill its container (.container or .container-narrow,
   whichever it's nested in) lets the heading wrap at a sane line count;
   an accompanying .lede still keeps its own comfortable reading width via
   its independent max-width:60ch + margin-inline:auto below. */
.section-header{ margin-bottom:var(--space-7); }
.section-header.text-center{ margin-inline:auto; }

.grid{ display:grid; gap:var(--space-5); }
.grid-2{ grid-template-columns:repeat(auto-fit,minmax(300px,1fr)); }
.grid-3{ grid-template-columns:repeat(auto-fit,minmax(240px,1fr)); }
.grid-4{ grid-template-columns:repeat(auto-fit,minmax(200px,1fr)); }
/* grid-compare forces exactly 2 columns for A vs B comparison cards, 
   never 3 even on ultrawide screens, so the pairs remain side-by-side. */
.grid-compare { grid-template-columns: 1fr; }
@media (min-width: 700px) {
  .grid-compare { grid-template-columns: repeat(2, 1fr); }
}
/* Grid's default align-items:stretch forces every card in a row to match
   the tallest one -- right for a card family with genuinely similar copy
   length (feature grids, pricing tiers), wrong for a two-up "their
   approach / our approach" pair where one side is reliably shorter: the
   short card gets stretched into a tall box with a dead gap at the
   bottom instead of sizing to its own content. Opt in per-grid with this
   modifier rather than changing .grid-2's default, since plenty of
   existing .grid-2 usage wants the stretch. */
.grid--top{ align-items:start; }

/* Phase 2 of the marketing-content-architecture plan's conversion pass:
   two small shared components used by all 5 /compare/<slug> pages via
   templates/_breadcrumb.html and each page's own "Choose CrewSafety
   if..." / "Choose {competitor} if..." checklist. Added here (not
   redefined per page like .home-shot-frame/.faq-item are) because these
   are genuinely new and shared by design, not a pre-existing page-scoped
   pattern being reused. */
.compare-breadcrumb{ display:flex; flex-wrap:wrap; align-items:center; gap:var(--space-2); font-size:var(--text-body-sm); color:var(--text-secondary); margin-bottom:var(--space-4); }
.compare-breadcrumb a{ color:var(--text-secondary); }
.compare-breadcrumb a:hover{ color:var(--green-300); }
.compare-breadcrumb__sep{ color:var(--text-muted); }
.compare-breadcrumb__current{ color:var(--text-primary); font-weight:600; }

.compare-checklist{ list-style:none; margin:var(--space-4) 0 0; padding:0; display:flex; flex-direction:column; gap:var(--space-3); }
.compare-checklist li{ padding-left:1.6em; position:relative; }
.compare-checklist li::before{ content:"\2713"; position:absolute; left:0; color:var(--green-400); font-weight:700; }

.row{ display:flex; gap:var(--space-4); }
.row-wrap{ flex-wrap:wrap; }
.row-center{ justify-content:center; }
.row-between{ justify-content:space-between; }
.stack{ display:flex; flex-direction:column; gap:var(--space-3); }
.divider-v{ width:1px; align-self:stretch; background:var(--border); }

/* ── Typography ───────────────────────────────────────────────────────── */
/* 2026-08-26: font-weight 800 → 700 and letter-spacing -.03em → -.025em.
   Ultra-heavy weights on dark surfaces cause character bleed (halation) at
   tight tracking — the gradient fill already provides visual weight without
   needing max weight. Slightly wider tracking keeps letterforms distinct
   at large sizes on low-luminance backgrounds. */
.display-1{ font-family:var(--font-display); font-weight:700; font-size:var(--text-display-1); line-height:var(--lh-tight); letter-spacing:-.025em; margin:0; text-wrap:balance;
  background:linear-gradient(176deg, #FFFFFF 30%, #B9D8CE 78%, #7FC9B3);
  -webkit-background-clip:text; background-clip:text; color:transparent; }
.display-2{ font-family:var(--font-display); font-weight:700; font-size:var(--text-display-2); line-height:var(--lh-snug); letter-spacing:-.02em; margin:0; text-wrap:balance; color:var(--text-primary); }
h1,h2,h3,h4{ font-family:var(--font-display); color:var(--text-primary); margin:0; text-wrap:balance; }
h1{ font-size:var(--text-h1); font-weight:700; letter-spacing:-.02em; line-height:var(--lh-snug); }
h2{ font-size:var(--text-h2); font-weight:700; letter-spacing:-.01em; line-height:var(--lh-snug); }
h3{ font-size:var(--text-h3); font-weight:600; line-height:1.3; }
h4{ font-size:var(--text-h4); font-weight:600; }

.kicker{ display:inline-flex; align-items:center; gap:.55em; font-family:var(--font-body); font-weight:600;
  font-size:var(--text-caption); letter-spacing:.14em; text-transform:uppercase; color:var(--accent-text); margin-bottom:var(--space-4); }
.kicker::before{ content:""; width:.5em; height:.5em; border-radius:50%; background:var(--green-400);
  box-shadow:0 0 12px 1px var(--green-400); flex:none; }
.text-center .kicker, .section-header.text-center .kicker{ justify-content:center; }

.lede{ font-size:var(--text-body-lg); line-height:1.55; color:var(--text-secondary); margin:var(--space-4) 0 0; max-width:60ch; }
.lede.text-center{ margin-inline:auto; }
.body-lg{ font-size:var(--text-body-lg); line-height:1.6; }
.body-sm{ font-size:var(--text-body-sm); }
.caption{ font-size:var(--text-caption); color:var(--text-muted); line-height:var(--lh-caption); }
.mono{ font-family:var(--font-mono); font-variant-numeric:tabular-nums; }
p{ margin:0 0 var(--space-4); }
/* ── Widow guard ──────────────────────────────────────────────────────────
   Headings have carried text-wrap:balance since the start; body prose
   carried nothing, and it showed — 20 paragraphs across six pages were
   ending on a last line under 45% of their longest line, several on a
   single trailing word.

   `balance`, not `pretty`, and that is a measured result rather than a
   preference. Both values were A/B'd against every paragraph on Features
   and Home. On blocks of six lines or fewer, balance is decisively better —
   the same paragraphs went 11%->98%, 15%->93%, 18%->81%, 45%->90%,
   57%->100%. On blocks of seven lines or more the two measured IDENTICAL,
   every time: Chrome caps balance at six lines and falls back, and pretty's
   reflow budget is too small to move a long block's tail at all. So pretty
   buys nothing this content can use, and balance buys a lot.

   The six-line cap is also the safety rail. Balance equalises every line,
   which is right for a lede or a caption and would look odd on a ten-line
   paragraph — and a ten-line paragraph is exactly what the browser declines
   to balance. Engines with neither value fall back to today's wrapping.

   What this CANNOT fix: a paragraph over six lines that ends on one short
   word. Those need a word added or cut, not a CSS property. */
p, .lede, .body-lg, .body-sm, li, dd, dt, summary, figcaption,
.caption, .field-hint, .screen-cap, .photo-cap, .proof-label,
.stat-label, .big-cap, .breadth-item{ text-wrap:balance; }
a{ color:var(--link); text-decoration:none; transition:color var(--dur-fast) var(--ease); }
a:hover{ color:var(--link-hover); }

.text-primary{color:var(--text-primary);} .text-secondary{color:var(--text-secondary);}
.text-muted{color:var(--text-muted);} .text-accent{color:var(--accent-text);}
.text-link{color:var(--link);} .text-success{color:var(--success);}
.text-warning{color:var(--warning);} .text-danger{color:var(--danger);}
.text-center{ text-align:center; }

/* ── Buttons ──────────────────────────────────────────────────────────── */
.btn{ --_b:transparent; display:inline-flex; align-items:center; justify-content:center; gap:.55em;
  font-family:var(--font-body); font-weight:600; font-size:var(--text-body-sm); line-height:1;
  padding:.85em 1.4em; border-radius:var(--radius-sm); border:1px solid var(--_b); cursor:pointer;
  transition:transform var(--dur-fast) var(--ease), box-shadow var(--dur) var(--ease), background-color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
  white-space:nowrap; text-decoration:none; }
.btn:focus-visible{ outline:none; box-shadow:var(--focus-ring); }
.btn-primary{ background:var(--accent-bg); color:var(--accent-on); font-weight:700;
  box-shadow:0 10px 30px -8px rgba(52,229,173,.5); }
.btn-primary:hover{ background:var(--accent-bg-hover); color:var(--accent-on); transform:translateY(-2px) translate(var(--mx,0px),var(--my,0px));
  box-shadow:0 0 34px -2px rgba(52,229,173,.55), 0 14px 36px -10px rgba(52,229,173,.6); }
.btn-primary.is-magnetic{ transform: translateY(-2px) translate(var(--mx, 0px), var(--my, 0px)); }
.btn-secondary{ background:rgba(255,255,255,.04); color:var(--text-primary); border-color:var(--border-strong); backdrop-filter:blur(6px); }
.btn-secondary:hover{ color:var(--text-primary); border-color:var(--green-400); background:rgba(52,229,173,.08); transform:translateY(-2px); }
.btn-ghost{ background:transparent; color:var(--text-secondary); }
.btn-ghost:hover{ color:var(--text-primary); }
.btn-lg{ padding:1.05em 1.7em; font-size:var(--text-body); border-radius:var(--radius-md); }
.btn-sm{ padding:.6em 1em; font-size:var(--text-caption); }
.btn-block{ width:100%; }
.btn.is-disabled,.btn:disabled{ opacity:.5; pointer-events:none; }
/* nowrap above is right for short labels, but it turns any long one into
   horizontal page overflow instead of a taller button. `.btn--wrap` is the
   explicit escape hatch for a label that's really a sentence; the query below
   is the safety net so no button can ever push a phone sideways. */
.btn--wrap{ white-space:normal; text-align:center; }
@media (max-width:600px){ .btn{ white-space:normal; text-align:center; } }
.btn:active{ transform: translateY(0) scale(0.98); }

/* ── Cards (glass) ────────────────────────────────────────────────────── */
.card{ position:relative; background:var(--bg-elevated); border:1px solid var(--border); border-radius:var(--radius-md);
  padding:var(--space-6); backdrop-filter:blur(var(--glass-blur)); -webkit-backdrop-filter:blur(var(--glass-blur));
  box-shadow:var(--shadow-md); overflow:hidden; }
.card::before{ content:""; position:absolute; inset:0 0 auto 0; height:1px;
  background:linear-gradient(90deg, transparent, rgba(255,255,255,.18), transparent); }
.card--accent-top{ border-top:1px solid transparent; }
.card--accent-top::before{ height:2px; background:linear-gradient(90deg, transparent, var(--green-400), transparent); box-shadow:0 0 16px var(--green-400); }
.card--steel-top::before{ background:linear-gradient(90deg, transparent, var(--steel-400), transparent); box-shadow:0 0 16px var(--steel-400); }
.card--interactive{ transition:transform var(--dur) var(--ease), border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease); }
.card--interactive:hover{ transform:translateY(-5px); border-color:var(--border-strong);
  box-shadow:var(--shadow-lg), 0 0 40px -12px var(--glow-green); }
.card-icon{ display:inline-grid; place-items:center; width:46px; height:46px; border-radius:12px; margin-bottom:var(--space-4);
  color:var(--green-400); background:rgba(52,229,173,.1); border:1px solid rgba(52,229,173,.22);
  box-shadow:inset 0 0 18px rgba(52,229,173,.14); }

/* ── Badges ───────────────────────────────────────────────────────────── */
.badge{ display:inline-flex; align-items:center; gap:.4em; font-size:var(--text-caption); font-weight:600;
  padding:.35em .8em; border-radius:var(--radius-full); border:1px solid var(--border-strong);
  background:rgba(255,255,255,.04); color:var(--text-secondary); }
.badge-dot::before{ content:""; width:.5em; height:.5em; border-radius:50%; background:currentColor; }
.badge-green{ color:var(--green-300); border-color:rgba(52,229,173,.35); background:rgba(52,229,173,.1); }
.badge-steel{ color:var(--steel-300); border-color:rgba(95,181,206,.35); background:rgba(95,181,206,.1); }
.badge-success{ color:var(--success); border-color:rgba(52,229,173,.35); background:rgba(52,229,173,.1); }
.badge-warning{ color:var(--warning); border-color:rgba(240,180,80,.35); background:rgba(240,180,80,.1); }
.badge-danger{ color:var(--danger); border-color:rgba(255,107,107,.35); background:rgba(255,107,107,.1); }
.badge-neutral{ color:var(--text-secondary); }
a.badge:hover{ border-color:var(--green-400); color:var(--green-300); }

/* ── Nav (sticky glass) ───────────────────────────────────────────────── */
.site-nav{ position:sticky; top:0; z-index:50; background:rgba(6,11,9,.7); backdrop-filter:blur(16px) saturate(150%);
  -webkit-backdrop-filter:blur(16px) saturate(150%); border-bottom:1px solid var(--border); }
.nav-inner{ display:flex; align-items:center; justify-content:space-between; gap:var(--space-5);
  height:68px; max-width:var(--container-max); margin-inline:auto; padding-inline:var(--container-pad); }
.nav-logo{ display:inline-flex; align-items:center; gap:.6rem; font-family:var(--font-display); font-weight:800;
  font-size:1.12rem; letter-spacing:-.02em; color:var(--text-primary); }
.nav-logo:hover{ color:var(--text-primary); }
.nav-logo-mark{ color:var(--green-400); filter:drop-shadow(0 0 8px rgba(52,229,173,.6)); }
.nav-links{ display:flex; align-items:center; gap:var(--space-2); }
.nav-link{ padding:.5em .85em; border-radius:var(--radius-sm); color:var(--text-secondary); font-size:var(--text-body-sm); font-weight:500; }
.nav-link:hover,.nav-link.is-active{ color:var(--text-primary); background:rgba(255,255,255,.05); }
.nav-actions{ display:flex; align-items:center; gap:var(--space-3); }
/* Returning customers arrive at the marketing URL out of habit (bookmark,
   search) and need a way in. Kept as a quiet text link so it never competes
   with Start free, which is the CTA that has to win. */
.nav-login{ color:var(--text-secondary); font-size:var(--text-body-sm); font-weight:500;
  padding:.5em .4em; border-radius:var(--radius-sm); white-space:nowrap; }
.nav-login:hover{ color:var(--text-primary); }
.nav-toggle{ display:none; align-items:center; justify-content:center; width:40px; height:40px; border-radius:var(--radius-sm);
  background:rgba(255,255,255,.04); border:1px solid var(--border-strong); color:var(--text-primary); cursor:pointer; }
@media (max-width:1024px){
  .nav-links{ position:fixed; inset:68px 0 auto 0; flex-direction:column; align-items:stretch; gap:var(--space-1);
    padding:var(--space-4) var(--container-pad) var(--space-6); background:rgba(8,16,13,.96); backdrop-filter:blur(18px);
    border-bottom:1px solid var(--border); transform:translateY(-8px); opacity:0; pointer-events:none; transition:opacity var(--dur) var(--ease), transform var(--dur) var(--ease); }
  .nav-links[data-state="open"]{ transform:none; opacity:1; pointer-events:auto; }
  .nav-link{ padding:.9em 1em; font-size:1rem; }
  /* This block used to be display:none, which hid Start free (and now Log in)
     from every phone visitor — the drawer offered navigation and no way to
     convert. Both are stacked full-width at the bottom of the drawer instead. */
  .nav-actions{ flex-direction:column; align-items:stretch; gap:var(--space-3);
    margin-top:var(--space-4); padding-top:var(--space-4); border-top:1px solid var(--border); }
  .nav-login{ padding:.9em 1em; font-size:1rem; text-align:center;
    border:1px solid var(--border-strong); }
  .nav-actions .btn{ width:100%; justify-content:center; padding-block:.9em; }
  .nav-toggle{ display:inline-flex; }
}

/* ── Footer ───────────────────────────────────────────────────────────── */
.site-footer{ border-top:1px solid var(--border); background:rgba(255,255,255,.015); margin-top:var(--space-9); }
.site-footer .container{ padding-block:var(--space-8); }
/* FOUR columns, not five. Five link columns inside the 1104px content box
   left each one ~175px, and support@crewsafetyhq.com needs ~189px, so the
   address broke mid-word and the whole strip read as crammed. Dropping the
   "Get started" column (its two lines were a duplicate of Start free and a
   reassurance line that belongs with the brand copy) leaves brand + three
   link columns at ~219px each, which the address clears on one line. */
.footer-grid{ display:grid; grid-template-columns:1.6fr repeat(3,1fr); gap:var(--space-6) var(--space-7); }
.footer-free-note{ margin:0; color:var(--text-muted); }
.footer-sf-badge{ margin-top:var(--space-2); }

/* Narrow: the brand block spans the row (it is prose, not a link list), and
   at phone width the contact column spans too, so the two email addresses
   get the full width and still don't wrap. Rows land 1 / 2-up / 1. */
@media (max-width:1080px){
  .footer-grid{ grid-template-columns:repeat(3, minmax(0,1fr)); }
  .footer-grid > :first-child{ grid-column:1 / -1; }
}
@media (max-width:768px){
  .footer-grid{ grid-template-columns:repeat(2, minmax(0,1fr)); }
  .footer-grid > :last-child{ grid-column:1 / -1; }
}
.footer-col-title{ font-family:var(--font-display); font-size:var(--text-caption); font-weight:600; letter-spacing:.08em;
  text-transform:uppercase; color:var(--text-muted); margin-bottom:var(--space-4); }
.footer-links{ list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:var(--space-2); }
/* support@crewsafetyhq.com is a single unbreakable token wider than a
   phone-width footer column, so it ran 18px past the right edge of a 375px
   viewport. It went unnoticed because the columns used to be sized by
   `1fr` (= minmax(AUTO,1fr)), whose min-content floor quietly widened the
   whole grid to fit it; pinning the columns to minmax(0,1fr) above removed
   that accidental cushion and turned a hidden grid overflow into a visible
   page overflow. Let the address wrap instead of either. */
.footer-links a{ color:var(--text-secondary); font-size:var(--text-body-sm); overflow-wrap:anywhere; }
.footer-links a:hover{ color:var(--green-300); }
.footer-consent-link{ appearance:none; border:0; padding:0; background:transparent; color:var(--text-secondary); cursor:pointer; font:inherit; font-size:var(--text-body-sm); text-align:left; }
.footer-consent-link:hover,.footer-consent-link:focus-visible{ color:var(--green-300); }
.footer-bottom{ display:flex; flex-wrap:wrap; gap:var(--space-3); justify-content:space-between;
  margin-top:var(--space-7); padding-top:var(--space-5); border-top:1px solid var(--border);
  color:var(--text-muted); font-size:var(--text-caption); }

/* Optional analytics is an explicit visitor choice, never a first-paint
   overlay. Keep it above the site shell without obscuring the navigation. */
.marketing-consent{ position:fixed; z-index:80; right:var(--space-5); bottom:var(--space-5); left:var(--space-5); max-width:760px; margin:auto; display:flex; align-items:center; justify-content:space-between; gap:var(--space-5); padding:var(--space-5); border:1px solid var(--border-strong); border-radius:var(--radius-md); background:rgba(11,14,19,.98); box-shadow:0 18px 56px rgba(0,0,0,.42); }
.marketing-consent[hidden]{ display:none; }
.marketing-consent__body h2{ margin:0 0 var(--space-2); font-family:var(--font-display); font-size:var(--text-body); }
.marketing-consent__body p{ max-width:58ch; margin:0; color:var(--text-secondary); font-size:var(--text-body-sm); line-height:1.55; }
.marketing-consent__body a{ color:var(--green-300); }
.marketing-consent__eyebrow{ margin-bottom:var(--space-2)!important; color:var(--green-300)!important; font-size:var(--text-caption)!important; font-weight:700; letter-spacing:.1em; text-transform:uppercase; }
.marketing-consent__actions{ display:flex; flex:0 0 auto; flex-wrap:wrap; justify-content:flex-end; gap:var(--space-2); }
@media (max-width:680px){ .marketing-consent{ bottom:0; right:0; left:0; max-width:none; flex-direction:column; align-items:stretch; border-right:0; border-bottom:0; border-left:0; border-radius:var(--radius-md) var(--radius-md) 0 0; } .marketing-consent__actions{ justify-content:stretch; } .marketing-consent__actions .btn{ flex:1 1 190px; } }

/* ── Forms ────────────────────────────────────────────────────────────── */
.field{ display:flex; flex-direction:column; gap:var(--space-2); margin-bottom:var(--space-4); }

.field-label{ font-size:var(--text-body-sm); font-weight:600; color:var(--text-primary); }
.field-label .optional{ color:var(--text-muted); font-weight:400; }
.field-hint{ font-size:var(--text-caption); color:var(--text-muted); }
.field-error{ font-size:var(--text-caption); color:var(--danger); display:none; }
.field.has-error .field-error{ display:block; }
.field input,.field select,.field textarea{
  font-family:var(--font-body); font-size:var(--text-body); color:var(--text-primary);
  background:rgba(255,255,255,.03); border:1px solid var(--border-strong); border-radius:var(--radius-sm);
  padding:.8em 1em; transition:border-color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease); }
/* The closed <select> box picks up --text-primary/the rgba background above
   fine, but browsers render the OPEN option list as a separate native
   popup that doesn't inherit a translucent background - it was falling
   back to the OS default (white), with our light --text-primary on top of
   it, making every option unreadable. Options need an explicit opaque
   dark background. */
.field select option{ background:var(--bg-inset); color:var(--text-primary); }
.field input::placeholder,.field textarea::placeholder{ color:var(--text-muted); }
.field input:focus,.field select:focus,.field textarea:focus{ outline:none; border-color:var(--green-400); box-shadow:var(--focus-ring); }
.field.has-error input,.field.has-error select{ border-color:var(--danger); }
.field-checkbox{ flex-direction:row; align-items:flex-start; gap:var(--space-3); }
.field-checkbox input{ margin-top:.15em; width:18px; height:18px; accent-color:var(--green-400); flex:none; }

/* ── Signature atmosphere: .bg-blueprint is now an aurora glow, not a grid ─ */
.bg-blueprint{ position:relative; isolation:isolate; }
.bg-blueprint::before{ content:""; position:absolute; inset:0; z-index:-1; pointer-events:none;
  background:radial-gradient(80% 120% at 70% -20%, rgba(52,229,173,.14), transparent 60%),
             radial-gradient(60% 100% at 12% 120%, rgba(95,181,206,.09), transparent 55%); }
.bg-blueprint--drift::after{ content:""; position:absolute; inset:0; z-index:-1; pointer-events:none;
  background:radial-gradient(50% 70% at 60% 30%, rgba(52,229,173,.07), transparent 60%);
  animation:auroraBreathe 9s ease-in-out infinite; }
@keyframes auroraBreathe{ 0%,100%{ opacity:.55; } 50%{ opacity:1; } }
.bg-grain{ position:relative; }

/* Bracket frame → glowing inspection frame for screenshots */
.bracket-frame{ position:relative; }
.bracket-frame::before,.bracket-frame::after{ content:""; position:absolute; width:24px; height:24px; z-index:2; pointer-events:none; border-color:var(--green-400); filter:drop-shadow(0 0 6px rgba(52,229,173,.7)); }
.bracket-frame::before{ top:-6px; left:-6px; border-top:2px solid; border-left:2px solid; border-top-left-radius:6px; }
.bracket-frame::after{ bottom:-6px; right:-6px; border-bottom:2px solid; border-right:2px solid; border-bottom-right-radius:6px; }
.bracket-frame--steel::before,.bracket-frame--steel::after{ border-color:var(--steel-400); filter:drop-shadow(0 0 6px rgba(95,181,206,.7)); }

/* Stat plate → glassy stat with luminous number */
.stat-plate{ position:relative; padding:var(--space-5) var(--space-5) var(--space-5) calc(var(--space-5) + 4px);
  background:var(--bg-elevated); border:1px solid var(--border); border-radius:var(--radius-md); overflow:hidden; backdrop-filter:blur(10px); }
.stat-plate::before{ content:""; position:absolute; left:0; top:14%; bottom:14%; width:3px; border-radius:3px;
  background:var(--green-400); box-shadow:0 0 14px var(--green-400); }
.stat-value{ display:block; font-family:var(--font-display); font-weight:800; font-size:1.9rem; line-height:1; letter-spacing:-.02em; color:var(--green-300); font-variant-numeric:tabular-nums; }
.stat-label{ display:block; margin-top:var(--space-2); font-size:var(--text-caption); color:var(--text-secondary); }

/* ── Disclosure ───────────────────────────────────────────────────────── */
.disclosure{ border:1px solid var(--border); border-radius:var(--radius-md); background:rgba(255,255,255,.02); }
.disclosure > summary{ cursor:pointer; list-style:none; padding:var(--space-4) var(--space-5); font-weight:600; color:var(--text-primary);
  display:flex; align-items:center; gap:var(--space-3); }
.disclosure > summary::-webkit-details-marker{ display:none; }
.disclosure > summary::before{ content:""; width:8px; height:8px; border-right:2px solid var(--green-400); border-bottom:2px solid var(--green-400);
  transform:rotate(-45deg); transition:transform var(--dur) var(--ease); flex:none; }
.disclosure[open] > summary::before{ transform:rotate(45deg); }
.disclosure > summary:hover{ color:var(--green-300); }
.disclosure__body{ padding:0 var(--space-5) var(--space-5); color:var(--text-secondary); }
.disclosure__body p{ margin-bottom:0; }

/* ── Motion ───────────────────────────────────────────────────────────── */
.reveal-up,.fade-in{ opacity:0; will-change:transform,opacity; }
.reveal-up{ transform:translateY(24px); transition:opacity var(--dur-slow) var(--ease), transform var(--dur-slow) var(--ease); transition-delay:var(--delay,0s); }
.fade-in{ transition:opacity var(--dur-slow) var(--ease); transition-delay:var(--delay,0s); }
.reveal-up.is-revealed,.fade-in.is-revealed{ opacity:1; transform:none; }
@media (prefers-reduced-motion: reduce){
  .reveal-up,.fade-in{ opacity:1 !important; transform:none !important; transition:none !important; }
  .bg-blueprint--drift::after{ animation:none; }
  .btn,.card--interactive{ transition:none; }
}

/* ══════════════════════════════════════════════════════════════════════════
   STORYTELLING COMPONENTS  (scroll-driven, Apple/synkori-style)
   Minimal copy + animated product "sims". All state animations trigger off the
   `.is-revealed` class the reveal observer already adds — no extra JS needed
   beyond giving a sim the `reveal-up` class so it gets observed.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── Keyword marquee ──────────────────────────────────────────────────── */
.marquee{ overflow:hidden; border-block:1px solid var(--border); padding-block:var(--space-4);
  background:rgba(255,255,255,.012);
  -webkit-mask:linear-gradient(90deg,transparent,#000 7%,#000 93%,transparent);
          mask:linear-gradient(90deg,transparent,#000 7%,#000 93%,transparent); }
.marquee-track{ display:inline-flex; gap:var(--space-7); white-space:nowrap; padding-left:var(--space-7);
  animation:marquee 34s linear infinite; will-change:transform; }
.marquee:hover .marquee-track{ animation-play-state:paused; }
.marquee-item{ display:inline-flex; align-items:center; gap:.6em; font-family:var(--font-display);
  font-weight:600; font-size:.95rem; letter-spacing:.01em; color:var(--text-secondary); }
.marquee-item::before{ content:""; width:5px; height:5px; border-radius:50%; background:var(--green-400); box-shadow:0 0 8px var(--green-400); flex:none; }
@keyframes marquee{ to{ transform:translateX(-50%); } }
@media (prefers-reduced-motion:reduce){ .marquee-track{ animation:none; } }

/* ── Big counting number (the "gap"/proof beats) ──────────────────────── */
.big-num{ font-family:var(--font-display); font-weight:800; line-height:.9; letter-spacing:-.04em;
  font-size:clamp(3.2rem,2rem + 7vw,7.5rem); color:var(--green-300); font-variant-numeric:tabular-nums;
  text-shadow:0 0 60px rgba(52,229,173,.25); }
.big-num.is-danger{ color:var(--danger); text-shadow:0 0 60px rgba(255,107,107,.25); }
.big-cap{ font-family:var(--font-body); font-weight:600; text-transform:uppercase; letter-spacing:.14em;
  font-size:var(--text-caption); color:var(--text-muted); margin-top:var(--space-3); }
.gap-vs{ font-family:var(--font-display); font-weight:700; color:var(--text-muted); font-size:1.1rem; letter-spacing:.2em; }

/* ── Three-act layout ─────────────────────────────────────────────────── */
.act{ display:grid; grid-template-columns:1fr 1fr; gap:clamp(var(--space-6),5vw,var(--space-9));
  align-items:center; padding-block:var(--space-8); }
.act:nth-child(even) .act-copy{ order:2; }
@media (max-width:900px){ .act{ grid-template-columns:1fr; } .act:nth-child(even) .act-copy{ order:0; } }
.act-num{ display:inline-flex; align-items:center; gap:.7em; font-family:var(--font-display); font-weight:800;
  font-size:.78rem; letter-spacing:.32em; color:var(--green-400); margin-bottom:var(--space-4); }
.act-num::before{ content:""; width:26px; height:1px; background:var(--green-400); box-shadow:0 0 8px var(--green-400); }
.act h3{ font-family:var(--font-display); font-weight:700; letter-spacing:-.02em; line-height:1.08;
  font-size:clamp(1.8rem,1.3rem + 2.4vw,3rem); margin:0 0 var(--space-4); color:var(--text-primary); }
.act p{ color:var(--text-secondary); font-size:var(--text-body-lg); max-width:44ch; margin:0; }

/* ── Product "sim" cards (animate through state on reveal) ────────────── */
.sim{ position:relative; border:1px solid var(--border-strong); border-radius:var(--radius-lg);
  background:linear-gradient(180deg, rgba(255,255,255,.04), rgba(255,255,255,.015));
  padding:var(--space-6); box-shadow:var(--shadow-lg); overflow:hidden; backdrop-filter:blur(10px); }
.sim::after{ content:""; position:absolute; inset:0; pointer-events:none; border-radius:inherit;
  box-shadow:inset 0 1px 0 rgba(255,255,255,.06); }
.sim-head{ display:flex; align-items:center; justify-content:space-between; gap:var(--space-3);
  font-family:var(--font-mono); font-size:var(--text-caption); letter-spacing:.06em; text-transform:uppercase; color:var(--text-muted);
  padding-bottom:var(--space-4); margin-bottom:var(--space-4); border-bottom:1px solid var(--border); }
.sim-row{ display:flex; align-items:center; justify-content:space-between; gap:var(--space-4); padding:var(--space-3) 0; }
.sim-row + .sim-row{ border-top:1px solid var(--border); }
.sim-name{ font-weight:600; color:var(--text-primary); }
.sim-sub{ font-size:var(--text-caption); color:var(--text-muted); }
.sim-pill{ font-size:var(--text-caption); font-weight:700; padding:.3em .8em; border-radius:var(--radius-full); white-space:nowrap; }
.pill-ok{ color:var(--green-300); background:rgba(52,229,173,.12); border:1px solid rgba(52,229,173,.3); }
.pill-warn{ color:var(--warning); background:rgba(240,180,80,.12); border:1px solid rgba(240,180,80,.3); }
.pill-bad{ color:var(--danger); background:rgba(255,107,107,.12); border:1px solid rgba(255,107,107,.3); }

/* ACT I — a cert flips ACTIVE → EXPIRED on reveal */
.sim-flip{ position:relative; }
.sim-flip .stack-pill{ position:relative; display:inline-grid; }
.sim-flip .stack-pill > *{ grid-area:1/1; transition:opacity .5s var(--ease) .7s, transform .5s var(--ease) .7s; }
.sim-flip .stack-pill .pill-bad{ opacity:0; transform:translateY(-4px); }
.sim-flip.is-revealed .stack-pill .pill-ok{ opacity:0; transform:translateY(4px); }
.sim-flip.is-revealed .stack-pill .pill-bad{ opacity:1; transform:none; }
.sim-flip .scan{ position:absolute; left:0; right:0; height:2px; top:0; background:linear-gradient(90deg,transparent,var(--danger),transparent); opacity:0; }
.sim-flip.is-revealed .scan{ animation:scanDown 1.1s var(--ease) .3s 1; }
@keyframes scanDown{ 0%{ opacity:0; top:8%;} 15%{opacity:1;} 100%{ opacity:0; top:92%; } }
.sim-flip.is-revealed .sim-flip-row{ animation:rowRed .6s var(--ease) 1s forwards; }
@keyframes rowRed{ to{ background:rgba(255,107,107,.05); box-shadow:inset 3px 0 0 var(--danger); } }

/* ACT II — an alert toast slides in on reveal */
.sim-alert-toast{ display:flex; align-items:center; gap:var(--space-4); padding:var(--space-4);
  border:1px solid rgba(255,107,107,.3); border-radius:var(--radius-md); background:rgba(255,107,107,.06);
  opacity:0; transform:translateY(14px); transition:opacity .55s var(--ease) .5s, transform .55s var(--ease) .5s; }
.sim.is-revealed .sim-alert-toast{ opacity:1; transform:none; }
.sim-avatar{ width:44px; height:44px; border-radius:50%; flex:none; display:grid; place-items:center;
  font-family:var(--font-display); font-weight:700; color:var(--accent-on);
  background:linear-gradient(140deg,var(--green-400),var(--steel-400)); }
.sim-queued{ margin-top:var(--space-4); display:flex; align-items:center; gap:.6em; font-size:var(--text-body-sm);
  color:var(--green-300); opacity:0; transition:opacity .5s var(--ease) 1.2s; }
.sim.is-revealed .sim-queued{ opacity:1; }

/* ACT III — a compliance matrix goes green + CLEARED stamp */
.sim-matrix-grid{ display:grid; grid-template-columns:repeat(8,1fr); gap:8px; }
.sim-dot{ aspect-ratio:1; border-radius:5px; background:rgba(255,107,107,.5); transition:background .45s var(--ease), box-shadow .45s var(--ease); }
.sim.is-revealed .sim-dot{ background:var(--green-400); box-shadow:0 0 10px rgba(52,229,173,.5); }
.sim.is-revealed .sim-dot:nth-child(3n){ transition-delay:.15s; }
.sim.is-revealed .sim-dot:nth-child(3n+1){ transition-delay:.3s; }
.sim.is-revealed .sim-dot:nth-child(4n){ transition-delay:.45s; }
.sim-stamp{ position:absolute; top:var(--space-5); right:var(--space-5); font-family:var(--font-display); font-weight:800;
  font-size:.8rem; letter-spacing:.14em; color:var(--green-300); border:2px solid var(--green-400); border-radius:var(--radius-sm);
  padding:.35em .7em; transform:rotate(-8deg) scale(.6); opacity:0; box-shadow:0 0 20px rgba(52,229,173,.3); }
.sim.is-revealed .sim-stamp{ animation:stampIn .5s var(--ease) 1s forwards; }
@keyframes stampIn{ to{ opacity:1; transform:rotate(-8deg) scale(1); } }
@media (prefers-reduced-motion:reduce){
  .sim-flip .stack-pill .pill-ok,.sim-flip.is-revealed .stack-pill .pill-bad,.sim-alert-toast,.sim-queued{ transition:none; }
  .sim-flip.is-revealed .scan,.sim-flip.is-revealed .sim-flip-row,.sim.is-revealed .sim-stamp{ animation:none; }
  .sim-alert-toast,.sim-queued{ opacity:1; transform:none; }
  .sim-flip.is-revealed .stack-pill .pill-ok{ opacity:0; } .sim-flip.is-revealed .stack-pill .pill-bad{ opacity:1; transform:none; }
  .sim.is-revealed .sim-stamp{ opacity:1; transform:rotate(-8deg); }
}

/* ── How-it-works steps (minimal) ─────────────────────────────────────── */
.steps{ display:grid; grid-template-columns:repeat(auto-fit,minmax(230px,1fr)); gap:var(--space-5); counter-reset:step; }
.step{ position:relative; padding-top:var(--space-6); }
.step::before{ counter-increment:step; content:"0" counter(step); font-family:var(--font-display); font-weight:800;
  font-size:1.4rem; color:var(--green-400); opacity:.85; letter-spacing:-.02em; }
.step h4{ margin:var(--space-3) 0 var(--space-2); font-size:1.05rem; }
.step p{ color:var(--text-secondary); font-size:var(--text-body-sm); margin:0; }
.step::after{ content:""; position:absolute; top:var(--space-6); left:0; width:34px; height:2px; background:var(--green-400); box-shadow:0 0 8px var(--green-400); opacity:.5; transform:translateY(2.1rem); }

/* ══════════════════════════════════════════════════════════════════════════
   REAL APP RECREATIONS — qualifications checklist (home hero), dashboard
   stat cards, and the job-packet builder (Features page).
   ──────────────────────────────────────────────────────────────────────────
   These are not marketing illustrations: chrome and color come directly
   from the real product's own SaaS-mode UI (static/css/crewsafety-theme.css
   + CLAUDE.md's documented palette) — dark solid cards (#111/#050505), #222
   borders, and the product's own accent, which in SaaS mode is the very
   same green as this site's --green-400 (crewsafety-theme.css redefines
   --blue: #34E5AD for saas_mode; the #2F96FF blue only appears in the
   single-company standalone mode, which CrewSafety customers never see).
   Class
   names below intentionally match the real templates' own vocabulary
   (.checklist-item, .check-icon, .emp-tag/.packet-tag, .card-title, …) so
   this reads as the actual component, not a look-alike. Framed by the
   site's own .bracket-frame / .home-shot-frame / .feature-shot-frame
   treatment — the frame is marketing chrome; everything inside it is a
   recreation of the real product screen.
   ══════════════════════════════════════════════════════════════════════════ */
:root{
  --app-bg:      #050505;
  --app-card:    #111111;
  --app-border:  #222222;
  --app-header:  #0c1525;
  --app-muted:   #888888;
  --app-yellow:  #ffcc00;
  /* The product's own success green is #00ff88. On the marketing site it is
     deliberately overridden to the brand green so a "complete" check and the
     brand accent are never two different greens on the same screen. Yellow
     stays as-is: it carries a different meaning, not a different brand. */
  --app-success: #34E5AD;
}

/* shared "device" shell — a header bar (breadcrumb + user avatar) over a
   dark body, standing in for the product's own sidebar-and-header chrome. */
.app-device-header{ height:42px; background:var(--app-header); border-bottom:1px solid var(--app-border);
  display:flex; align-items:center; justify-content:space-between; padding-inline:var(--space-4); }
.app-device-crumb{ font-family:var(--font-body); font-size:.76rem; color:var(--app-muted); }
.app-device-crumb b{ color:var(--green-400); font-weight:600; }
.app-device-avatar{ width:22px; height:22px; border-radius:50%; background:rgba(52,229,173,.15);
  border:1px solid rgba(52,229,173,.35); display:grid; place-items:center; font-size:.6rem; font-weight:700;
  color:var(--green-400); flex:none; }
.app-device-body{ background:var(--app-bg); padding:var(--space-4) var(--space-5); }

/* ── Qualifications checklist (home hero) — same class names as the real
   Qualifications tab (templates/employee_detail.html): a round check-icon,
   a name + detail line, real action-button chrome on the right. One row's
   icon/border/detail-line move through the credential's real lifecycle;
   it never reaches the red "incomplete" state used elsewhere in the app. */
.qual-list{ display:flex; flex-direction:column; gap:var(--space-3); }
.checklist-item{ display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; gap:10px;
  padding:12px 15px; background:rgba(0,0,0,.2); border:1px solid var(--app-border); border-radius:8px;
  transition:border-color .3s var(--ease), background-color .3s var(--ease); }
.checklist-item.completed{ border-color:var(--app-success); background:rgba(52,229,173,.03); }
.checklist-item.expiring{ border-color:var(--app-yellow); background:rgba(255,204,0,.08); }
.checklist-left{ display:flex; align-items:center; gap:12px; flex:1; min-width:0; }
.check-icon{ width:32px; height:32px; border-radius:50%; flex:none; display:flex; align-items:center;
  justify-content:center; font-size:1em; font-weight:bold; transition:background-color .3s, border-color .3s, color .3s; }
.check-icon.complete{ background:rgba(52,229,173,.2); color:var(--app-success); border:2px solid var(--app-success); }
.check-icon.warning{ background:rgba(255,204,0,.2); color:var(--app-yellow); border:2px solid var(--app-yellow); }
.checklist-info .checklist-name{ font-weight:600; font-size:.95em; color:#eee; }
.checklist-info .checklist-date{ font-size:.8em; color:var(--app-muted); margin-top:2px; }
.checklist-right{ display:flex; gap:8px; flex:none; }
.qual-btn-outline{ background:transparent; border:1px solid var(--app-border); color:var(--app-muted);
  border-radius:6px; padding:.4em .9em; font-size:.78rem; font-weight:600; font-family:var(--font-body); cursor:default; }

/* ── Dashboard stat cards (Features) — same border-top accent + uppercase
   card-title convention as the real dashboard's card chrome. Simplified
   from the real TRIR/EMR cards (which also carry a sparkline trend chart
   and an EMR/NCCI toggle) down to the number, trend, and target a
   marketing card has room for. ─────────────────────────────────────────── */
.dash-card-row{ display:grid; grid-template-columns:repeat(3,1fr); gap:var(--space-4); }
@media (max-width:640px){ .dash-card-row{ grid-template-columns:1fr; } }
.dash-stat-card{ background:var(--app-card); border:1px solid var(--app-border); border-top:3px solid var(--green-400);
  border-radius:10px; overflow:hidden; }
.dash-stat-head{ display:flex; align-items:center; justify-content:space-between; gap:var(--space-2);
  padding:9px 14px; border-bottom:1px solid var(--app-border); background:rgba(52,229,173,.04); }
.dash-stat-title{ font-size:.68rem; text-transform:uppercase; letter-spacing:1.5px; color:var(--green-400); font-weight:700; }
.dash-stat-badge{ font-size:.6rem; padding:2px 7px; border-radius:10px; font-weight:700; letter-spacing:.4px;
  text-transform:uppercase; background:rgba(52,229,173,.12); color:var(--app-success); border:1px solid rgba(52,229,173,.3); white-space:nowrap; }
.dash-stat-body{ padding:14px; }
.dash-stat-big{ font-family:var(--font-mono); font-size:1.65rem; font-weight:700; color:#eee; font-variant-numeric:tabular-nums; line-height:1; }
.dash-stat-trend{ font-size:.68rem; font-weight:600; margin-top:6px; }
.dash-stat-trend.good{ color:var(--app-success); }
.dash-stat-target{ font-size:.68rem; color:var(--app-muted); margin-top:3px; }

/* ── Job packet builder (Features) — same search/tag/checklist vocabulary
   as the real Create Job Packet screen (templates/job_packet_create.html):
   a search box that adds a removable employee tag with a supervisor radio,
   then a base-certifications checklist, then Generate Packet. ──────────── */
.packet-search{ background:#0e1a2e; border:1px solid var(--app-border); border-radius:6px; color:var(--app-muted);
  padding:10px 14px; font-size:.86rem; }
.packet-search .packet-caret{ display:inline-block; width:1px; height:1em; background:var(--green-400);
  vertical-align:-.15em; margin-left:1px; animation:packetCaret 1s steps(1) infinite; }
@keyframes packetCaret{ 50%{ opacity:0; } }
.packet-dropdown{ background:#0e1a2e; border:1px solid var(--app-border); border-top:none; border-radius:0 0 6px 6px;
  overflow:hidden; max-height:0; transition:max-height .25s var(--ease); }
.packet-dropdown.open{ max-height:60px; }
.packet-option{ padding:9px 14px; font-size:.84rem; display:flex; justify-content:space-between; align-items:center; }
.packet-option-title{ font-size:.72rem; color:var(--app-muted); }
.packet-tags{ display:flex; flex-direction:column; gap:8px; margin-top:12px; }
.packet-tag{ background:#0e1a2e; border:1px solid var(--app-border); border-radius:8px; padding:10px 14px;
  display:flex; align-items:center; justify-content:space-between; gap:10px; opacity:0; transform:translateY(6px);
  transition:opacity .3s var(--ease), transform .3s var(--ease), border-color .3s, background-color .3s; }
.packet-tag.in{ opacity:1; transform:none; }
.packet-tag.is-supervisor{ border-color:rgba(52,229,173,.4); background:rgba(52,229,173,.05); }
.packet-tag-name{ font-weight:600; font-size:.86rem; color:#eee; }
.packet-tag.is-supervisor .packet-tag-name::before{ content:"* "; color:var(--green-400); }
.packet-tag-title{ font-size:.72rem; color:var(--app-muted); margin-top:1px; }
.packet-tag-right{ display:flex; align-items:center; gap:10px; flex:none; }
.packet-sup{ display:flex; align-items:center; gap:5px; font-size:.72rem; color:var(--app-muted); white-space:nowrap; }
.packet-sup input{ accent-color:var(--green-400); }
.packet-remove{ background:transparent; border:none; color:#555; font-size:1.1em; line-height:1; padding:2px 4px; }
.packet-no-emp{ color:var(--app-muted); font-size:.82rem; font-style:italic; }
.packet-certs{ margin-top:16px; padding-top:14px; border-top:1px solid var(--app-border); max-height:0; opacity:0;
  overflow:hidden; transition:max-height .4s var(--ease), opacity .4s var(--ease); }
.packet-certs.show{ max-height:220px; opacity:1; }
.packet-cert-item{ display:flex; align-items:center; gap:10px; padding:7px 0; border-bottom:1px solid #1a1a1a; font-size:.84rem; color:#ddd; }
.packet-cert-item:last-child{ border-bottom:none; }
.packet-cert-item input{ accent-color:var(--green-400); flex:none; }
.packet-cert-meta{ color:var(--app-muted); font-size:.76rem; margin-left:.4em; }
.packet-build-row{ display:flex; justify-content:flex-end; margin-top:14px; padding-top:14px;
  border-top:1px solid var(--app-border); opacity:0; max-height:0; overflow:hidden; transition:opacity .3s var(--ease); }
.packet-build-row.show{ opacity:1; max-height:60px; }
.packet-build-btn{ background:var(--green-400); color:#001428; border:none; border-radius:6px; padding:.6em 1.1em;
  font-weight:700; font-size:.82rem; cursor:default; transition:transform .15s var(--ease); }
.packet-build-btn.pressed{ transform:scale(.94); }
.packet-summary{ display:none; align-items:center; gap:16px; padding:6px 0; }
.packet-summary.show{ display:flex; }
.packet-pdf-chip{ width:44px; height:44px; border-radius:8px; background:rgba(52,229,173,.12); border:1px solid rgba(52,229,173,.35);
  display:grid; place-items:center; flex:none; font-size:1.2rem; }
.packet-meta{ flex:1; min-width:0; }
.packet-meta-title{ font-weight:600; font-size:.88rem; color:#eee; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.packet-meta-sub{ font-size:.74rem; color:var(--app-muted); margin-top:2px; }
.packet-send-btn{ background:var(--green-400); color:#001428; border:none; border-radius:6px; padding:.55em 1em;
  font-weight:700; font-size:.78rem; cursor:default; white-space:nowrap; transition:background-color .2s; }
.packet-send-btn.sent{ background:var(--app-success); color:#001428; }

/* ══════════════════════════════════════════════════════════════════════════
   HOME HERO · CREDENTIAL LIFECYCLE
   ──────────────────────────────────────────────────────────────────────────
   One credential moving through its real lifecycle: current, renewal window
   opens, renewal scheduled, renewed. Deliberately NOT a screenshot
   recreation — the earlier version wore fake app chrome (a breadcrumb and a
   user avatar), which promised "this is the product" and then showed a flow
   the product does not literally have. This illustrates the BEHAVIOUR
   instead, so it stays honest as the real UI evolves.

   The card holds current status; the log underneath accumulates. Watching
   entries stack up is what sells "it watches over time" — a single row
   mutating in place reads as a glitch.
   ══════════════════════════════════════════════════════════════════════════ */
.cred-demo{ border:1px solid var(--border-strong); border-radius:var(--radius-lg);
  background:var(--bg-inset); box-shadow:var(--shadow-lg); overflow:hidden; }

.cred-card{ display:flex; align-items:center; gap:var(--space-3);
  padding:var(--space-4) var(--space-5); border-bottom:1px solid var(--border); }
.cred-icon{ width:38px; height:38px; border-radius:50%; flex:none; display:grid; place-items:center;
  font-size:1.05rem; font-weight:700; line-height:1;
  transition:background-color .55s var(--ease), border-color .55s var(--ease), color .55s var(--ease); }
.cred-icon.is-ok{  color:var(--green-400);  border:2px solid var(--green-400);  background:rgba(52,229,173,.16); }
.cred-icon.is-due{ color:var(--app-yellow); border:2px solid var(--app-yellow); background:rgba(255,204,0,.16); }
.cred-body{ flex:1; min-width:0; }
.cred-name{ font-weight:700; font-size:1rem; color:var(--text-primary); }
.cred-meta{ font-size:var(--text-body-sm); color:var(--text-muted); margin-top:2px; }
.cred-pill{ flex:none; font-family:var(--font-mono); font-size:.66rem; letter-spacing:.08em;
  text-transform:uppercase; font-weight:700; padding:.45em .85em; border-radius:100px; white-space:nowrap;
  transition:background-color .55s var(--ease), border-color .55s var(--ease), color .55s var(--ease); }
.cred-pill.is-ok{  color:var(--green-400);  border:1px solid rgba(52,229,173,.4); background:rgba(52,229,173,.12); }
.cred-pill.is-due{ color:var(--app-yellow); border:1px solid rgba(255,204,0,.4);  background:rgba(255,204,0,.12); }

.cred-log{ list-style:none; margin:0; padding:var(--space-4) var(--space-5) var(--space-5);
  display:flex; flex-direction:column; gap:var(--space-3); }
.cred-log li{ display:grid; grid-template-columns:9px 4.4em 1fr; align-items:baseline; gap:var(--space-3);
  font-size:var(--text-body-sm); color:var(--text-secondary); line-height:1.45;
  opacity:0; transform:translateY(6px);
  transition:opacity .55s var(--ease), transform .55s var(--ease); }
.cred-log li.in{ opacity:1; transform:none; }
.cred-log li::before{ content:""; width:7px; height:7px; border-radius:50%; background:var(--green-400);
  box-shadow:0 0 8px var(--green-400); transform:translateY(-2px); }
.cred-when{ font-family:var(--font-mono); font-size:.7rem; letter-spacing:.03em; color:var(--text-muted); }
.cred-what b{ color:var(--text-primary); font-weight:600; }

/* Replay lives on the demo, not in the hero button row: the hero's primary
   action has to be signup, and a replay control is not worth a CTA slot. */
.cred-replay{ display:inline-flex; align-items:center; gap:.45em; margin-top:var(--space-4);
  background:none; border:none; padding:0; cursor:pointer;
  font-family:var(--font-mono); font-size:.7rem; letter-spacing:.08em; text-transform:uppercase;
  color:var(--text-muted); transition:color var(--dur) var(--ease); }
.cred-replay:hover{ color:var(--green-400); }

@media (max-width:520px){
  .cred-card{ flex-wrap:wrap; }
  .cred-pill{ order:3; margin-left:calc(38px + var(--space-3)); }
  .cred-log li{ grid-template-columns:9px 1fr; }
  .cred-when{ grid-column:2; }
  .cred-what{ grid-column:2; }
}
@media (prefers-reduced-motion:reduce){
  .cred-icon,.cred-pill,.cred-log li{ transition:none; }
}

/* ══════════════════════════════════════════════════════════════════════════
   REAL PRODUCT SCREENS
   ──────────────────────────────────────────────────────────────────────────
   Captures from the live app, cropped above the browser chrome (see
   scripts note in the photos README). A caption always names what is on
   screen: an unlabelled screenshot makes a visitor work out what they are
   looking at, and most will not bother.
   ══════════════════════════════════════════════════════════════════════════ */
.screen-grid{ display:grid; grid-template-columns:repeat(3,1fr); gap:var(--space-5); margin-top:var(--space-7); }
@media (max-width:980px){ .screen-grid{ grid-template-columns:repeat(2,1fr); } }
@media (max-width:620px){ .screen-grid{ grid-template-columns:1fr; } }

.screen-card{ display:flex; flex-direction:column; gap:var(--space-3); min-width:0; }
.screen-card figure{ margin:0; border:1px solid var(--border-strong); border-radius:var(--radius-md);
  overflow:hidden; background:var(--bg-inset); box-shadow:var(--shadow-md);
  transition:border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease), transform var(--dur) var(--ease); }
.screen-card img{ display:block; width:100%; height:auto; aspect-ratio:16/10; object-fit:cover; object-position:top left; }
.screen-card:hover figure{ border-color:rgba(52,229,173,.4); transform:translateY(-3px);
  box-shadow:0 0 30px -10px var(--glow-green), var(--shadow-lg); }
.screen-cap{ font-size:var(--text-body-sm); color:var(--text-secondary); }
.screen-cap b{ display:block; color:var(--text-primary); font-weight:600; margin-bottom:2px; }
@media (prefers-reduced-motion:reduce){
  .screen-card figure{ transition:none; }
  .screen-card:hover figure{ transform:none; }
}

/* A photograph standing in for the blueprint pattern behind a closing CTA.
   FIRST VERSION OF THIS SCRIM WAS WRONG: two overlapping dark gradients
   (horizontal + vertical) compound multiplicatively, not additively. At
   their combined lightest point (.74 horizontal * .70 vertical) only
   (1-.74)*(1-.70) = ~8% of the photo's original brightness was reaching the
   page — on top of the shared --photo-filter's brightness(.76) darkening it
   again before that. The photo was effectively hidden, not scrimmed.

   Fixed by leaning on a MODERATE single vertical gradient — the copy
   stacks top-to-bottom here, there's no need for a second horizontal
   darkening pass fighting it for the same pixels. Worst-case visibility
   is ~40%, not ~8%. */
.photo-close{ position:relative; isolation:isolate; overflow:hidden; }
.photo-close::before{ content:""; position:absolute; inset:0; z-index:-2;
  background-image:var(--photo,none), var(--photo-placeholder);
  background-size:cover; background-position:var(--photo-pos, center); background-repeat:no-repeat;
  filter:var(--photo-filter); }
.photo-close::after{ content:""; position:absolute; inset:0; z-index:-1;
  background:linear-gradient(180deg, rgba(6,11,9,.62) 0%, rgba(6,11,9,.50) 38%, rgba(6,11,9,.50) 68%, rgba(6,11,9,.70) 100%); }

/* First pass here copied the hero's heavy two-layer text-shadow (a wide
   16px/.85-opacity glow plus a tight .95 core) — the same recipe already
   flagged as making white text look dark/muddy rather than crisp, reused
   without noticing it was the same complaint. Cut to ONE small, tight
   shadow: just enough for edge definition against a photo, not a dark
   halo around every letter. The scrim above picked up the contrast this
   used to lean on the shadow for. */
.photo-close .kicker,
.photo-close h2,
.photo-close .lede{ text-shadow:0 1px 4px rgba(3,8,6,.6); }
/* The real gap wasn't the shadow, it was this: base .lede is
   color:var(--text-secondary) (#9FB6AD, deliberately muted — correct on a
   flat background, everywhere else .lede is used). The hero already boosts
   its .lede to --text-primary for exactly this reason (photo behind it,
   same file, a few hundred lines up) — that boost was never copied here, so
   this section's lede has been rendering in the dim color the whole time
   regardless of what the shadow did. kicker needs no equivalent fix: it's
   var(--accent-text) (bright green) by default site-wide. */
.photo-close .lede{ color:var(--text-primary); }

/* h2 here carries .display-1, which is NOT a solid color — it's a gradient
   (white -> pale sage -> saturated teal-green) clipped to the glyph shapes
   via background-clip:text, color:transparent. Over a flat dark background
   that reads as an intentional white-to-teal headline. Over a photo it's a
   different problem entirely: the gradient's own darker green band loses
   contrast unpredictably depending on what happens to be in the photo
   behind each letter, which is exactly what "still dark" was — parts of
   the headline were nearly disappearing into the photo's mid-tones.

   The hero's h1 carries the identical .display-1 class and never got this
   same complaint, not because it's styled any differently, but because it
   sits over the hero photo's darkest, most uniform patch (the scrim's .90
   left edge) — a gradient that happens to read fine against near-solid-dark
   isn't proof the technique is safe over a photo, it's proof that spot
   got lucky. Overriding to solid --text-primary here removes the luck
   dependency rather than trying to retune a gradient to fit one photo. */
.photo-close h2{
  background:none;
  -webkit-background-clip:initial; background-clip:initial;
  -webkit-text-fill-color:initial;
  color:var(--text-primary);
}
/* btn-secondary is a transparent/outline style elsewhere on the site; with
   the scrim this much lighter it needs its own backing or it reads as bare
   text floating on the photo, per the same fix already applied to the hero's
   Start Free button. */
.photo-close .btn-secondary{ background:rgba(6,11,9,.62); border-color:rgba(233,245,240,.4); }

/* ── Capability groups ──────────────────────────────────────────────────
   Twenty ticks in one flat grid reads as a wall to skim past. The same
   twenty under four headings reads as organised depth, and each heading
   gives a scanner a reason to stop. */
.breadth-group-title{ font-family:var(--font-display); font-size:var(--text-caption); font-weight:700;
  letter-spacing:.1em; text-transform:uppercase; color:var(--green-400); margin-bottom:var(--space-3);
  display:flex; align-items:center; gap:.6em; }
.breadth-group-title::after{ content:""; flex:1; height:1px;
  background:linear-gradient(90deg, rgba(52,229,173,.28), transparent); }
/* ── Capability columns ────────────────────────────────────────────────────
   Two earlier attempts here were both ROW layouts, and both failed the same
   way. First: no rules at all, so every item was a different width. Then:
   an auto-fit grid of cards, which resolved its track count from the
   container rather than the group, giving three different card widths and a
   stranded single card. Then a fixed three-across grid, which made the
   widths uniform but left the four groups landing 3+3, 3+2, 3+1 and 3+2 —
   three of the four ending on a short row.

   The groups hold 6, 5, 4 and 5 items. No row width divides all four
   evenly, so no row layout can ever come out square. Columns can: one group
   per column, each a plain vertical checklist under its own heading, the
   way a spec sheet reads. There is no trailing row to come up short.

   Grid stretch gives all four columns the same height, so the hairlines
   between them run the full depth of the block and it reads as one
   rectangle whether a column holds four items or six. The card chrome is
   gone with the rows: twenty bordered boxes was a lot of weight for what is
   really a list, and the hairline grid carries the structure now.
   ────────────────────────────────────────────────────────────────────── */
.breadth-columns{ display:grid; grid-template-columns:repeat(4, minmax(0,1fr));
  margin-top:var(--space-7); border-block:1px solid var(--border); }
.breadth-group{ margin-top:0; padding:var(--space-6) var(--space-5); min-width:0; }
.breadth-group + .breadth-group{ border-left:1px solid var(--border); }
.breadth-grid{ display:flex; flex-direction:column; gap:var(--space-3); }
.breadth-item{ display:flex; align-items:flex-start; gap:.65em;
  font-size:var(--text-body-sm); color:var(--text-secondary); line-height:1.45; }
.tick{ display:inline-flex; align-items:center; justify-content:center; flex:none;
  width:18px; height:18px; margin-top:.1em; border-radius:50%; background:rgba(52,229,173,.14);
  color:var(--green-400); font-size:.62rem; font-weight:700; }

/* Two columns, then one. The left hairline moves to the even column and the
   groups on the second row pick up a top rule, so the divider grid stays
   closed at every width instead of leaving three-quarters of a box. */
@media (max-width:1024px){
  .breadth-columns{ grid-template-columns:repeat(2, minmax(0,1fr)); }
  .breadth-group + .breadth-group{ border-left:0; }
  .breadth-group:nth-child(even){ border-left:1px solid var(--border); }
  .breadth-group:nth-child(n+3){ border-top:1px solid var(--border); }
}
@media (max-width:600px){
  .breadth-columns{ grid-template-columns:1fr; }
  .breadth-group:nth-child(even){ border-left:0; }
  .breadth-group + .breadth-group{ border-top:1px solid var(--border); }
  .breadth-group{ padding-inline:0; }
}

/* the punchline under the hero demo — hidden until the sequence finishes */
/* max-width added with the measure pass: this is display-weight type at
   1.15rem with no cap of its own, so once the hero stacks below 1024 it
   spanned the full column and ran to 86 characters a line. */
.reveal-line{ opacity:0; transform:translateY(8px); max-width:var(--measure-display);
  transition:opacity .6s var(--ease), transform .6s var(--ease); }
.reveal-wrap.done .reveal-line{ opacity:1; transform:none; transition-delay:.2s; }

@media (prefers-reduced-motion:reduce){
  .checklist-item, .check-icon, .packet-tag, .packet-build-btn, .packet-send-btn{ transition:none; }
  .packet-search .packet-caret{ animation:none; }
  .reveal-line{ opacity:1; transform:none; }
}

/* ══════════════════════════════════════════════════════════════════════════
   TWO WORLDS
   ──────────────────────────────────────────────────────────────────────────
   The site's organizing idea: the real, physical, consequential world a crew
   works in — cranes, rigging, jobsites, hands, weather — set against the
   quiet, dark, precise system that watches over it. Everything in this block
   serves that one contrast. Nothing here re-skins what's above it; the dark
   canvas, the luminous green, Sora + IBM Plex, and the whole spacing/radius/
   shadow token system are kept exactly as they were.

   Two rules run through all of it:
     • GREEN stays reserved for the system — cleared, in good standing,
       product truth. (Unchanged from the existing color philosophy.)
     • STEEL now means the physical world. Photo captions, jobsite-adjacent
       labels, the frames around photography. The steel tokens already
       existed and were barely used; this gives them a job.
   ══════════════════════════════════════════════════════════════════════════ */

:root{
  /* ── Photographic register ──────────────────────────────────────────────
     Warm-neutral, high-contrast, desaturated toward the dark palette so a
     photograph and a product screen can sit near each other on the same
     page without fighting. Applied as a filter rather than baked into the
     files, so the same source photo works in every placement. */
  --photo-filter: saturate(.60) contrast(1.08) brightness(.76);
  --photo-scrim:  linear-gradient(180deg, rgba(6,11,9,.30) 0%, rgba(6,11,9,.58) 46%, rgba(6,11,9,.95) 100%);
  --photo-scrim-x: linear-gradient(90deg, rgba(6,11,9,.94) 0%, rgba(6,11,9,.72) 34%, rgba(6,11,9,.18) 78%, rgba(6,11,9,.34) 100%);

  /* The stand-in that renders until a real photograph exists at the path.
     Deliberately on-brand — a dark plate with steel and green atmosphere —
     so an unshot placement reads as intentional, never as a broken image.
     Raised well above the original draft's contrast: at the first pass the
     gradients were so faint against the near-black canvas that a placement
     read as empty space rather than a designed plate. See
     static/marketing/photos/README.md for the shot list. */
  --photo-placeholder:
      repeating-linear-gradient(135deg, rgba(233,245,240,.035) 0 1px, transparent 1px 34px),
      radial-gradient(115% 90% at 18% 4%,  rgba(95,181,206,.38), transparent 56%),
      radial-gradient(95% 78% at 88% 100%, rgba(52,229,173,.26), transparent 60%),
      linear-gradient(158deg, #223440 0%, #0F1B18 46%, #070C0A 100%);
}

/* ── Photo plate ────────────────────────────────────────────────────────
   Every photograph on the site goes in one of these. `--photo` is set per
   placement to the file's url(); if that file isn't there yet the browser
   simply skips the layer and the placeholder underneath paints instead.
   That's the whole drop-in mechanism — adding real photography is copying
   four JPGs into static/marketing/photos/, with no code change. */
.photo-plate{ position:relative; overflow:hidden; border-radius:var(--radius-lg);
  border:1px solid var(--border); background-color:var(--ink-900); isolation:isolate; }
.photo-plate > *{ position:relative; z-index:2; }
/* Real child elements, NOT ::before/::after. These divs also carry
   .bracket-frame, which draws its corner marks using ::before/::after on the
   SAME element — an element only has one of each, so both rules' properties
   were merging, and bracket-frame's width:24px/height:24px/top:-6px/left:-6px
   silently overrode this layer's inset:0 sizing. Net effect: the actual
   photo was rendering, correctly, inside a shrunk 24x24px box in the corner
   — which looked exactly like a broken image, but was the real photo the
   whole time. Real children can't collide with a pseudo-element, so this
   class of bug can't recur here. Declared after ".photo-plate > *" so their
   explicit position/z-index win over that generic rule (same specificity,
   source order breaks the tie). */
.photo-plate__bg{ position:absolute; inset:0; z-index:0;
  background-image:var(--photo,none), var(--photo-placeholder);
  background-size:cover; background-position:var(--photo-pos, center);
  background-repeat:no-repeat; filter:var(--photo-filter); }
.photo-plate__scrim{ position:absolute; inset:0; z-index:1; pointer-events:none;
  background:linear-gradient(180deg, transparent 42%, rgba(6,11,9,.55) 100%); }
.photo-plate--wide{ aspect-ratio:16/9; }
.photo-plate--tall{ aspect-ratio:4/5; }
.photo-plate--strip{ aspect-ratio:21/9; }
@media (max-width:640px){ .photo-plate--strip{ aspect-ratio:16/10; } }

/* Steel caption — the marker for anything that is ABOUT the physical world.
   Same monospace/uppercase discipline as the product-screen captions, in
   steel instead of muted green-grey, so the two registers stay legible as
   two registers. */
.photo-cap{ display:flex; align-items:center; gap:.7em; margin-top:var(--space-3);
  font-family:var(--font-mono); font-size:var(--text-caption); letter-spacing:.08em;
  text-transform:uppercase; color:var(--steel-400); }
.photo-cap::before{ content:""; width:20px; height:1px; flex:none;
  background:var(--steel-500); box-shadow:0 0 8px rgba(95,181,206,.5); }

/* ── Nav presence ───────────────────────────────────────────────────────
   The one element on screen for 100% of the visit. Past ~40px it tightens
   and takes on a hairline of the brand green — small, but it's the
   difference between "a page with a header" and a system responding. */
.site-nav{ transition:background-color var(--dur) var(--ease),
  border-bottom-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease); }
.site-nav::after{ content:""; position:absolute; left:0; right:0; bottom:-1px; height:1px;
  pointer-events:none; opacity:0; transition:opacity var(--dur) var(--ease);
  background:linear-gradient(90deg, transparent, rgba(52,229,173,.5) 35%, rgba(52,229,173,.5) 65%, transparent); }
.nav-inner{ transition:height var(--dur) var(--ease); }
.site-nav.is-scrolled{ background:rgba(6,11,9,.93); border-bottom-color:var(--border-strong);
  box-shadow:0 14px 38px -26px rgba(3,12,9,.98); }
.site-nav.is-scrolled::after{ opacity:1; }
.site-nav.is-scrolled .nav-inner{ height:60px; }
@media (prefers-reduced-motion:reduce){
  .site-nav,.nav-inner,.site-nav::after{ transition:none; }
}

/* ── MOTION 1 · RESOLVE ─────────────────────────────────────────────────
   The home hero, and the only place this is used. A full-bleed photograph
   of the real world dissolves into the dark product UI at the same screen
   position as you scroll — Two Worlds staged as an interaction instead of
   described in a sentence. `--resolve` (0 → 1) is written by marketing.js
   from scroll position; every value below is derived from it, so the CSS
   holds no timing of its own.

   It defaults to 1 (fully resolved). JS drops it to 0 and takes over only
   once it knows it can — so a failed script, an old browser, or reduced
   motion all land on the finished state instead of an empty hero. */
.hero-resolve{ --resolve:1; position:relative; isolation:isolate; overflow:hidden;
  display:flex; align-items:center; min-height:clamp(600px, 86vh, 920px);
  border-bottom:1px solid var(--border); }
.hero-resolve__photo{ position:absolute; inset:0; z-index:-2; pointer-events:none; }
.hero-resolve__photo::before{ content:""; position:absolute; inset:-2%;
  background-image:var(--photo,none), var(--photo-placeholder);
  background-size:cover; background-position:var(--hero-photo-pos, center 42%); background-repeat:no-repeat;
  filter:var(--photo-filter);
  opacity:calc(1 - .55 * var(--resolve));
  transform:scale(calc(1.07 - .07 * var(--resolve))); }
.hero-resolve__photo::after{ content:""; position:absolute; inset:0;
  background:var(--photo-scrim-x), var(--photo-scrim);
  opacity:calc(.70 + .30 * var(--resolve)); }
/* the system's half. Two rounds of feedback on this element: first that the
   hero read as empty on load (fixed by raising the opacity floor off an
   original .04), then — with that floor still paired with up to 4px of
   blur — that the card looked "blurry and barely visible" rather than
   deliberately dim. Both true: at rest, without a real photo to justify
   a "coming into focus" metaphor, blur just reads as broken. Blur is
   removed entirely.

   Third round: even at .62, CSS opacity composites the WHOLE element —
   including the card's own solid --bg-inset background — so the photo was
   bleeding through the entire card, not just around it, no matter how
   opaque the card's background color was on its own. Fixed at the root:
   the device is opacity:1 always (fully solid, fully legible from first
   paint), and --resolve now only drives a subtle scale-in instead of any
   fade. */
.hero-resolve__device{ opacity:1;
  transform:scale(calc(.975 + .025 * var(--resolve))); }
/* the copy holds still — it's the one fixed point while the world changes */
@media (prefers-reduced-motion:reduce){
  .hero-resolve{ --resolve:1 !important; }
  .hero-resolve__device{ filter:none; transform:none; opacity:1; }
}

/* ── MOTION 3 · RAIL ────────────────────────────────────────────────────
   "It watches over time," made literal: a static line connects the four
   dots, fading out past the last one so it reads as a run trailing off
   rather than hitting a wall.
   This used to be an SVG path animating stroke-dashoffset in from 0, under
   a mask, with the dot living in a separate DOM element (.step::after)
   that had to stay pixel-synced with it via matching translateY math. That
   combination — two independently-positioned elements, one of them
   SVG-masked and dash-animated — was reported live as extra/broken line
   segments that a hard refresh didn't fix, which a plain CSS background on
   a single element can't produce: no dash math, no mask, no second element
   to fall out of sync with. Replaced outright rather than chased further. */
.steps--fixed{ position:relative; }
/* Pinned rather than auto-fit: at auto-fit widths a 4-item set wraps 3+1 and
   leaves an orphan under a full row. Always 4 across, 2x2, or stacked. Wider
   gap than the base .steps/.grid convention (--space-5) at the 4-across
   width specifically — four dense columns read as cramped where two or one
   don't, and there's no rail to visually separate them from at that gap. */
@media (min-width:1024px){ .steps--fixed{ grid-template-columns:repeat(4,1fr); gap:var(--space-7); } }
@media (min-width:600px) and (max-width:1023px){ .steps--fixed{ grid-template-columns:repeat(2,1fr); gap:var(--space-6); } }
@media (max-width:599px){ .steps--fixed{ grid-template-columns:1fr; } }


/* Same technique, icon scale — the About page's four principles draw their
   own icons as they arrive. Every glyph on that page is a stroked path, so
   one attribute selector covers all of them. */
.icon-draw svg [stroke]{ stroke-dasharray:130; stroke-dashoffset:130;
  transition:stroke-dashoffset 1.1s var(--ease); }
.is-revealed .icon-draw svg [stroke]{ stroke-dashoffset:0; }
.is-revealed .icon-draw svg [stroke]:nth-child(2){ transition-delay:.14s; }
.is-revealed .icon-draw svg [stroke]:nth-child(3){ transition-delay:.28s; }
@media (prefers-reduced-motion:reduce){
  .icon-draw svg [stroke]{ transition:none; stroke-dashoffset:0; }
}

/* ── MOTION 2 · DIFFERENTIATED DEMO ENTRANCES ───────────────────────────
   Three scripted product demos run on this site. Same idea, three arrivals,
   so they don't read as one pattern pasted three times:
     home hero     → resolves out of the photograph (above)
     dashboard     → cards cascade up as their numbers start counting
     packet builder→ the frame "wakes" under a single scan, then works
   JS adds .is-playing at the moment each sequence begins. */
/* .is-armed is added by script, and only once it knows it can animate — the
   markup's resting state is the finished one, so a dead script or a browser
   without IntersectionObserver leaves three visible cards, not three
   invisible ones. Same guard as the hero Resolve. */
.demo-cascade.is-armed .dash-stat-card{ opacity:0; transform:translateY(22px) scale(.985);
  transition:opacity .55s var(--ease), transform .55s var(--ease); }
.demo-cascade.is-playing .dash-stat-card{ opacity:1; transform:none; }
.demo-cascade.is-playing .dash-stat-card:nth-child(2){ transition-delay:.13s; }
.demo-cascade.is-playing .dash-stat-card:nth-child(3){ transition-delay:.26s; }

.demo-wake::after{ content:""; position:absolute; inset:0; z-index:4; opacity:0;
  pointer-events:none; border-radius:inherit;
  background:linear-gradient(180deg, transparent 40%, rgba(52,229,173,.18) 50%, transparent 60%); }
.demo-wake.is-playing::after{ animation:demoWake 1.1s var(--ease) 1; }
@keyframes demoWake{
  0%{ opacity:0; transform:translateY(-100%); }
  14%{ opacity:1; }
  100%{ opacity:0; transform:translateY(100%); }
}
@media (prefers-reduced-motion:reduce){
  .demo-cascade.is-armed .dash-stat-card{ opacity:1; transform:none; transition:none; }
  .demo-wake.is-playing::after{ animation:none; }
}

/* ── Cursor parallax ────────────────────────────────────────────────────
   Desktop pointer only. A few degrees, no more — enough that the frame
   feels like an object under glass, not enough to notice as an effect. */
.tilt{ transform:perspective(1200px) rotateX(var(--ty,0deg)) rotateY(var(--tx,0deg));
  transition:transform .45s var(--ease); will-change:transform; }
.tilt.is-tilting{ transition:transform .09s linear; }
@media (prefers-reduced-motion:reduce){ .tilt{ transform:none !important; transition:none; } }

/* ── Proof strip ────────────────────────────────────────────────────────
   Honest product facts, not usage metrics — every number here is a count
   of something on this page or in the data model, checkable by the reader.

   PHASE 2: when a named customer exists, .proof-quote below is a drop-in
   sibling of .proof-figure inside the same .proof-strip — a content change,
   not a redesign. Its styling ships now so that day is a paste, not a
   build. */
.proof-strip{ display:grid; grid-template-columns:repeat(auto-fit,minmax(180px,1fr));
  gap:var(--space-6) var(--space-5); padding-block:var(--space-7); border-block:1px solid var(--border); }
.proof-figure{ position:relative; padding-left:var(--space-5); }
.proof-figure::before{ content:""; position:absolute; left:0; top:.2em; bottom:.4em; width:2px; border-radius:2px;
  background:linear-gradient(180deg, var(--green-400), rgba(52,229,173,.12));
  box-shadow:0 0 14px rgba(52,229,173,.45); }
.proof-num{ display:block; font-family:var(--font-display); font-weight:800; line-height:1; letter-spacing:-.035em;
  font-size:clamp(2.1rem, 1.5rem + 2.2vw, 3.1rem); color:var(--green-300);
  font-variant-numeric:tabular-nums; text-shadow:0 0 46px rgba(52,229,173,.3); }
.proof-num--sm{ font-size:clamp(1.5rem, 1.15rem + 1.4vw, 2.1rem); letter-spacing:-.02em; }
.proof-label{ display:block; margin-top:var(--space-3); font-size:var(--text-body-sm);
  color:var(--text-secondary); max-width:28ch; }
.proof-strip--compact{ grid-template-columns:1fr; padding-block:var(--space-6); }
.proof-strip--compact .proof-figure{ padding-left:var(--space-5); }
.proof-strip--compact .proof-label{ max-width:var(--measure); margin-top:var(--space-2); }
.proof-strip--compact .proof-inline{ font-family:var(--font-display); font-weight:800;
  color:var(--green-300); font-variant-numeric:tabular-nums; }
/* Phase 2 — real quote, real name, once permission exists. Unused today. */
.proof-quote{ grid-column:1/-1; display:flex; gap:var(--space-5); align-items:flex-start;
  padding-top:var(--space-6); border-top:1px solid var(--border); }
.proof-quote blockquote{ margin:0; font-family:var(--font-display); font-weight:600;
  font-size:var(--text-body-lg); line-height:1.5; color:var(--text-primary); max-width:56ch; }
.proof-quote__who{ margin-top:var(--space-3); font-size:var(--text-body-sm); color:var(--text-muted); }
.proof-quote__who b{ color:var(--text-secondary); font-weight:600; }

/* ── Features · alternating rows ────────────────────────────────────────
   Seven identical image-right rows was the page's biggest structural flaw.
   The flip is desktop-only: on a phone the copy leads every time, because
   a stacked photo-then-copy row reads as an orphaned image. */
/* .grid leaves align-items at its initial `normal` (stretch), so both halves
   of a row start at the same top edge and whichever is shorter simply stops
   early. Measured on the live page, the copy column ran 257-509px against a
   247-372px visual — five of the seven rows were off by 114-207px, one side
   ending most of a screen above the other. Centring is already what the
   About page's "working operation" row and the Compare page's stats row
   compute; this brings the seven Features rows onto the same convention.
   Desktop only: once the row stacks there is no second column to centre
   against, and align-items would just be centring each block in its own
   full-width track. */
@media (min-width:900px){
  .feature-row--flip .feature-copy{ order:2; }
  /* Changed from center to start so sticky positioning works */
  .feature-row{ align-items:start; }
}
@media (min-width: 1025px) {
  .feature-copy { position: sticky; top: calc(68px + var(--space-6)); }
}

/* ── Utilities ────────────────────────────────────────────────────────── */
.shadow-sm{ box-shadow:var(--shadow-sm); } .shadow-md{ box-shadow:var(--shadow-md); } .shadow-lg{ box-shadow:var(--shadow-lg); }
.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; }
@media (max-width:768px){ .hide-mobile{ display:none !important; } }
@media (min-width:769px){ .hide-desktop{ display:none !important; } }
::selection{ background:rgba(52,229,173,.28); color:#fff; }

/* ══════════════════════════════════════════════════════════════════════════
   SCREENSHOT ZOOM
   ──────────────────────────────────────────────────────────────────────────
   Product screenshots are captured at full resolution but shown small in the
   layout (a 6-across grid, a 2-column feature row) — legible as "here is the
   dashboard," not as "here is what that number says." [data-zoom] marks any
   frame holding a real screenshot <img>; marketing.js wires it to a shared
   lightbox that shows the same image near its native size. Deliberately NOT
   applied to the two live illustrative recreations (Dashboard's count-up,
   the Job Packet builder) — those have no single frame to enlarge, the DOM
   nodes themselves ARE the content.
   ══════════════════════════════════════════════════════════════════════════ */
[data-zoom]{ position:relative; cursor:zoom-in; }
[data-zoom]:focus-visible{ outline:2px solid var(--green-400); outline-offset:3px; }

/* The hint stays invisible until a visitor is already looking at the frame
   (hover/focus) — a permanent "click to enlarge" badge on six screenshots at
   once reads as UI clutter, not a feature. */
[data-zoom]::after{ content:"⤢  Click to enlarge"; position:absolute; right:10px; bottom:10px; z-index:5;
  padding:.4em .8em; border-radius:100px; background:rgba(6,11,9,.85); border:1px solid rgba(233,245,240,.18);
  color:var(--text-primary); font-family:var(--font-mono); font-size:.66rem; letter-spacing:.04em; white-space:nowrap;
  opacity:0; transform:translateY(4px); pointer-events:none;
  transition:opacity var(--dur) var(--ease), transform var(--dur) var(--ease); }
[data-zoom]:hover::after, [data-zoom]:focus-visible::after{ opacity:1; transform:none; }
/* Touch has no hover — the badge stays on, compacted to an icon so it doesn't
   compete with the caption underneath. */
@media (hover:none){
  [data-zoom]::after{ content:"⤢"; opacity:1; transform:none; right:8px; bottom:8px;
    width:28px; height:28px; padding:0; display:flex; align-items:center; justify-content:center; font-size:.85rem; }
}
@media (prefers-reduced-motion:reduce){ [data-zoom]::after{ transition:none; } }

.lightbox{ position:fixed; inset:0; z-index:200; display:flex; align-items:center; justify-content:center;
  padding:var(--space-6); background:rgba(3,8,6,.93); backdrop-filter:blur(8px);
  opacity:0; pointer-events:none; transition:opacity .25s var(--ease); }
.lightbox.is-open{ opacity:1; pointer-events:auto; }
.lightbox__frame{ display:flex; flex-direction:column; align-items:center; gap:var(--space-3);
  max-width:min(92vw, 1600px); max-height:92vh; }
.lightbox__img{ display:block; max-width:100%; max-height:78vh; width:auto; height:auto; object-fit:contain;
  border-radius:var(--radius-md); border:1px solid var(--border-strong); box-shadow:var(--shadow-lg);
  background:var(--bg-inset); transform:scale(.97); transition:transform .25s var(--ease); }
.lightbox.is-open .lightbox__img{ transform:scale(1); }
.lightbox__caption{ margin:0; text-align:center; font-family:var(--font-mono); font-size:var(--text-caption);
  letter-spacing:.02em; color:var(--text-secondary); max-width:var(--measure); }
.lightbox__close,.lightbox__prev,.lightbox__next{ position:absolute; display:grid; place-items:center;
  width:44px; height:44px; border-radius:50%; background:rgba(255,255,255,.06); border:1px solid var(--border-strong);
  color:var(--text-primary); font-size:1.3rem; line-height:1; cursor:pointer;
  transition:background-color var(--dur) var(--ease), border-color var(--dur) var(--ease); }
.lightbox__close:hover,.lightbox__prev:hover,.lightbox__next:hover{ background:rgba(52,229,173,.14); border-color:rgba(52,229,173,.4); }
.lightbox__close{ top:var(--space-5); right:var(--space-5); }
.lightbox__prev,.lightbox__next{ top:50%; transform:translateY(-50%); width:48px; height:48px; font-size:1.5rem; }
.lightbox__prev{ left:var(--space-5); }
.lightbox__next{ right:var(--space-5); }
@media (max-width:640px){
  .lightbox{ padding:var(--space-4); }
  .lightbox__close{ top:var(--space-3); right:var(--space-3); width:38px; height:38px; }
  .lightbox__prev,.lightbox__next{ width:38px; height:38px; }
  .lightbox__prev{ left:var(--space-3); } .lightbox__next{ right:var(--space-3); }
}
@media (prefers-reduced-motion:reduce){
  .lightbox,.lightbox__img{ transition:none; }
}

/* ══════════════════════════════════════════════════════════════════════════
   AUTH SHELL — marketing-branded chrome for login / forgot / reset / setup,
   saas_mode only. Deliberately NOT the full site-nav/site-footer: those
   assume a marketing page's worth of nav links, and an auth screen's only
   real navigational needs are "get back to the marketing site" and the
   legal links. A slim header + slim footer keep the same visual system
   (tokens, type, glass card) without dragging in irrelevant chrome.
   ══════════════════════════════════════════════════════════════════════════ */
.auth-shell{ min-height:100vh; display:flex; flex-direction:column; }
.auth-shell-header{ padding:var(--space-6) var(--container-pad) 0; display:flex; justify-content:center; }
.auth-shell-main{ flex:1; display:flex; align-items:center; justify-content:center; padding:var(--space-6) var(--container-pad); }
.auth-shell-footer{ padding:var(--space-5) var(--container-pad) var(--space-7); text-align:center; }
.auth-back{ margin-top:var(--space-2); font-size:var(--text-body-sm); color:var(--text-muted); }
.auth-back a{ color:var(--text-secondary); }
.auth-back a:hover{ color:var(--green-300); }

.auth-card{ width:100%; max-width:420px; }
.auth-card .card{ padding:var(--space-6) var(--space-6) var(--space-7); }
.auth-card-title{ font-family:var(--font-display); font-weight:700; font-size:var(--text-h2);
  text-align:center; margin:0 0 var(--space-6); }

.auth-alert{ display:block; padding:var(--space-3) var(--space-4); border-radius:var(--radius-sm);
  font-size:var(--text-body-sm); text-align:center; margin-bottom:var(--space-5); }
.auth-alert--error{ background:rgba(255,107,107,.1); border:1px solid rgba(255,107,107,.35); color:var(--danger); }
.auth-alert--success{ background:rgba(52,229,173,.1); border:1px solid rgba(52,229,173,.35); color:var(--green-300); }

.auth-oauth{ display:flex; flex-direction:column; gap:var(--space-3); margin-bottom:var(--space-5); }
.auth-oauth .btn{ justify-content:flex-start; padding-inline:var(--space-4); gap:var(--space-3); }
.auth-divider{ display:flex; align-items:center; gap:var(--space-3); margin:0 0 var(--space-5);
  color:var(--text-muted); font-size:var(--text-caption); text-transform:uppercase; letter-spacing:.08em; }
.auth-divider::before,.auth-divider::after{ content:""; flex:1; height:1px; background:var(--border); }

.auth-row{ display:flex; align-items:center; justify-content:space-between; gap:var(--space-3);
  margin-bottom:var(--space-6); font-size:var(--text-body-sm); flex-wrap:wrap; }
.auth-remember{ display:flex; align-items:center; gap:.5em; color:var(--text-secondary); cursor:pointer; }
.auth-remember input{ width:16px; height:16px; accent-color:var(--green-400); }

.auth-footnote{ text-align:center; margin-top:var(--space-6); padding-top:var(--space-5);
  border-top:1px solid var(--border); font-size:var(--text-body-sm); color:var(--text-muted); }
.auth-footnote a{ font-weight:600; }

/* ── State page — 404 (and any future error/empty state) ────────────────── */
.state-page{ display:flex; align-items:center; min-height:calc(100vh - 68px - 260px); padding-block:var(--space-9); }
.state-page-code{ font-family:var(--font-display); font-weight:800; line-height:.9; letter-spacing:-.03em;
  font-size:clamp(4rem,3rem + 6vw,8rem); color:var(--text-primary); opacity:.9;
  background:linear-gradient(176deg, #FFFFFF 30%, #B9D8CE 78%, #7FC9B3);
  -webkit-background-clip:text; background-clip:text; color:transparent; margin:0 0 var(--space-4); }
.state-page .lede{ max-width:var(--measure); }
@media (max-width:640px){
  .state-page{ min-height:calc(100vh - 60px - 340px); }
}

/* ── Research library — the /resources "Guides" and "Compare" card grids ──
   Moved here from templates/marketing_resources.html's own <style> block
   (2026-09-18, marketing content architecture Phase 1) so the shared
   .research-feature/.research-item pattern lives in the one stylesheet
   every marketing page already loads, rather than only existing when
   /resources itself is rendered. Bump marketing_base.html's
   marketing.css?v=twN cache-bust whenever this block changes. */
.research-library { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); grid-auto-rows:280px; gap:var(--space-5); align-items:stretch; }
.research-feature, .research-item { height:100%; min-height:0; }
.research-feature { display:flex; flex-direction:column; justify-content:space-between; }
.research-feature__eyebrow { color:var(--green-300); font-family:var(--font-mono); font-size:var(--text-caption); letter-spacing:.08em; text-transform:uppercase; }
.research-feature__title { margin:var(--space-4) 0 var(--space-3); font-family:var(--font-display); font-size:clamp(1.5rem,1.2rem + .8vw,2rem); line-height:1.15; }
.research-feature__link { display:inline-flex; align-items:center; gap:.5em; margin-top:var(--space-5); font-weight:600; }
.research-list { display:contents; }
.research-item { display:flex; flex-direction:column; align-items:flex-start; gap:0; padding:var(--space-5); border:1px solid var(--border); border-radius:var(--radius-md); background:var(--bg-elevated); box-shadow:var(--shadow-md); color:var(--text-primary); transition:background var(--dur) var(--ease), border-color var(--dur) var(--ease), color var(--dur) var(--ease), transform var(--dur) var(--ease); }
.research-item:hover { background:rgba(52,229,173,.08); color:var(--green-200); }
.research-item__type { display:block; margin-bottom:.45em; color:var(--text-muted); font-family:var(--font-mono); font-size:var(--text-caption); letter-spacing:.08em; text-transform:uppercase; }
.research-item__title { display:block; font-family:var(--font-display); font-size:var(--text-h4); font-weight:700; line-height:1.25; }
.research-item__copy { display:block; margin-top:.45em; color:var(--text-secondary); font-size:var(--text-body-sm); line-height:1.45; }
.research-item__arrow { margin-top:auto; color:var(--accent-text); font-size:1.2rem; transition:transform var(--dur) var(--ease); }
.research-item:hover .research-item__arrow { transform:translateX(4px); }
@media(max-width:960px) { .research-library { grid-template-columns:repeat(2,minmax(0,1fr)); grid-auto-rows:260px; } }
@media(max-width:620px) {
  .research-library { grid-template-columns:1fr; grid-auto-rows:auto; }
  .research-feature, .research-item { height:auto; min-height:0; }
  .research-item { padding:var(--space-4); }
}
