
    /* =====================================================================
       SELF-HOSTED TYPOGRAPHY.

       These are the EXACT WOFF2 binaries Google Fonts was serving to this
       site, byte for byte, now served from our own origin. Not the upstream
       full-charset release: that is 352 KB for Inter alone against the 48 KB
       subset production actually receives, and swapping it in would have been
       a 694% font-byte regression sold as an optimisation. Provenance, SHA-256
       and the reasoning are in fonts/README.md.

       The descriptors below are REPRODUCED from the stylesheet production was
       served, not composed. Google declares one variable file several times,
       once per requested weight, so that is what is written here -- five
       blocks for Inter, three for JetBrains Mono. Writing `font-weight: 400
       800` instead would be inventing an axis range the captured
       configuration never declared, and the point of this phase is that
       nothing about the rendering changes.

       Two subsets of Inter, not seven. A codepoint audit of all 66 pages
       against Google's own unicode-range values found exactly two required:
       latin (16 codepoints, 1,087 occurrences) and greek (2 -- a real sigma
       in Case Study #1's RTK note, and an icon glyph on progress-tracking
       that Phase 2 replaces). The other 34 non-ASCII codepoints on this site
       -- 88 diamonds, 83 arrows, 66 burgers, 63 check marks -- are covered by
       NO Inter subset and already render from a system font, so Phase 2's
       switch to SVG icons costs nothing here and saves nothing here.

       unicode-range keeps the greek file lazy exactly as it was: the two
       pages that need it fetch 18,996 B more, and the other 64 fetch the same
       79,688 B they do in production today.

       No italic face: the stylesheet production receives requests none, so
       <em> and <i> are synthesised from the roman -- which is today's
       behaviour, preserved deliberately.
       ===================================================================== */
    @font-face { font-family: 'Inter'; font-style: normal; font-weight: 400; font-display: swap;
      src: url('/fonts/inter/Inter-latin-variable.woff2') format('woff2');
      unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
    @font-face { font-family: 'Inter'; font-style: normal; font-weight: 500; font-display: swap;
      src: url('/fonts/inter/Inter-latin-variable.woff2') format('woff2');
      unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
    @font-face { font-family: 'Inter'; font-style: normal; font-weight: 600; font-display: swap;
      src: url('/fonts/inter/Inter-latin-variable.woff2') format('woff2');
      unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
    @font-face { font-family: 'Inter'; font-style: normal; font-weight: 700; font-display: swap;
      src: url('/fonts/inter/Inter-latin-variable.woff2') format('woff2');
      unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
    @font-face { font-family: 'Inter'; font-style: normal; font-weight: 800; font-display: swap;
      src: url('/fonts/inter/Inter-latin-variable.woff2') format('woff2');
      unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
    @font-face { font-family: 'Inter'; font-style: normal; font-weight: 400; font-display: swap;
      src: url('/fonts/inter/Inter-greek-variable.woff2') format('woff2');
      unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF; }
    @font-face { font-family: 'Inter'; font-style: normal; font-weight: 500; font-display: swap;
      src: url('/fonts/inter/Inter-greek-variable.woff2') format('woff2');
      unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF; }
    @font-face { font-family: 'Inter'; font-style: normal; font-weight: 600; font-display: swap;
      src: url('/fonts/inter/Inter-greek-variable.woff2') format('woff2');
      unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF; }
    @font-face { font-family: 'Inter'; font-style: normal; font-weight: 700; font-display: swap;
      src: url('/fonts/inter/Inter-greek-variable.woff2') format('woff2');
      unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF; }
    @font-face { font-family: 'Inter'; font-style: normal; font-weight: 800; font-display: swap;
      src: url('/fonts/inter/Inter-greek-variable.woff2') format('woff2');
      unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF; }
    @font-face { font-family: 'JetBrains Mono'; font-style: normal; font-weight: 400; font-display: swap;
      src: url('/fonts/jetbrains-mono/JetBrainsMono-latin-variable.woff2') format('woff2');
      unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
    @font-face { font-family: 'JetBrains Mono'; font-style: normal; font-weight: 500; font-display: swap;
      src: url('/fonts/jetbrains-mono/JetBrainsMono-latin-variable.woff2') format('woff2');
      unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
    @font-face { font-family: 'JetBrains Mono'; font-style: normal; font-weight: 600; font-display: swap;
      src: url('/fonts/jetbrains-mono/JetBrainsMono-latin-variable.woff2') format('woff2');
      unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }

    /* =====================================================================
       GROUND TRUTH design tokens.

       Every value below is declared HERE and nowhere else. A page that needs
       a colour, a radius, a step of space or a type size takes it from this
       block; if the value it wants is not here, the answer is to add it here
       rather than to write a number into the page. That is the whole point:
       the drift this system is replacing came from 1,382 inline style
       attributes that each invented their own value, which is how a palette
       of 20 tokens turned into 84 distinct colours and 10 border radii in the
       published HTML.

       THE ONE RULE THAT IS NOT ABOUT TIDINESS -- BRAND COLOUR IS NOT DATA
       COLOUR. The engine renders cut in cyan and fill in amber. The interface
       was ALSO using cyan for the eyebrow, the link, the CTA, the active nav
       item and the checklist tick, so on a page carrying a cut/fill figure a
       reader had no way to tell which cyan carried meaning. The figures
       cannot move -- they are rasters out of the processing pipeline, and
       they are correct. So the INTERFACE moves instead: --accent is now brand
       only (the mark, the primary CTA), --interactive carries links and
       focus, and the --data-* ramp is reserved for legends and keys that have
       to match a raster. Chrome never uses a --data-* token.
       ===================================================================== */
    :root {
      /* --- surfaces -------------------------------------------------- */
      --bg: #0a0e16; --bg-soft: #0d1220; --card: #111726; --card-2: #0e1420;
      --border: #1e2635; --border-soft: #171d2b;
      /* The two gradient cores. Previously written straight into .hero-bg
         and .industry-img, which is why they were invisible to any audit of
         the palette. */
      --depth-1: #133247; --depth-2: #16324a;

      /* --- brand ------------------------------------------------------ */
      /* --accent is the logo mark and the primary CTA. Nothing else. */
      --accent: #22d3ee; --accent-2: #38bdf8; --accent-ink: #04141a;

      /* --- interaction ------------------------------------------------ */
      /* Links, focus rings, active nav. Held apart from --accent so a page
         can carry a cyan data figure without competing with its own chrome.
         The value is --accent-2, already in this palette -- a role split,
         not a new hue. */
      --interactive: #38bdf8; --interactive-hover: #22d3ee;

      /* --- text -------------------------------------------------------- */
      --text: #f5f7fa; --muted: #9aa7b8; --dim: #7d8b9e;

      /* --- chips ------------------------------------------------------- */
      --chip-blue: #10233a; --chip-blue-fg: #38bdf8;
      --chip-amber: #2a1d0d; --chip-amber-fg: #f0a94e;
      --chip-teal: #0c2420; --chip-teal-fg: #2dd4bf;
      --chip-violet: #1e1a33; --chip-violet-fg: #a78bfa;

      /* --- data / measurement (RESERVED) ------------------------------- */
      /* For figure legends, keys and swatches that must agree with a raster
         the engine produced. --data-cut and --data-fill are the pipeline's
         own colours; a legend swatch drawn with them matches the image beside
         it. Never use one of these for a button, a border or an eyebrow. */
      --data-cut: #22d3ee; --data-fill: #f0a94e; --data-neutral: #9aa7b8;
      --data-elev-low: #2dd4bf; --data-elev-high: #e2896b;

      /* --- state ------------------------------------------------------- */
      /* Values taken from what the application already renders, so a status
         means the same thing on the marketing site and in the product. */
      --state-ok: #7dd3a8; --state-warn: #f0a94e;
      /* Long-form body copy. Lighter than --muted, darker than --text: it
         was a literal #c3cede in five Help declarations. */
      --reading: #c3cede;
      --code-bg: rgba(148, 163, 184, 0.14);
      --state-error: #f87171; --state-withheld: #7d8b9e;

      /* --- type -------------------------------------------------------- */
      --font: 'Inter', ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
      --mono: 'JetBrains Mono', ui-monospace, monospace;
      /* The mono face is the MEASUREMENT VOICE: coordinates, units, deltas,
         run ids, technical captions. It is not a decorative alternative to
         the sans. */
      --t-display: clamp(34px, 4.6vw, 56px);
      --t-h1: clamp(26px, 3vw, 38px);
      --t-h2: 26px; --t-h3: 20px; --t-h4: 16.5px;
      --t-body-lg: 17px; --t-body: 15px; --t-sm: 14px; --t-xs: 13px;
      --t-micro: 11.5px;
      --lh-tight: 1.08; --lh-snug: 1.2; --lh-normal: 1.6; --lh-relaxed: 1.7;
      --tr-tight: -0.02em; --tr-wide: 0.14em;
      --w-regular: 400; --w-medium: 500; --w-semibold: 600;
      --w-bold: 700; --w-black: 800;

      /* --- space (4px base) -------------------------------------------- */
      --s-1: 4px;  --s-2: 8px;  --s-3: 12px; --s-4: 16px; --s-5: 20px;
      --s-6: 24px; --s-8: 32px; --s-10: 40px; --s-12: 48px;
      --s-16: 64px; --s-20: 80px;

      /* --- radius ------------------------------------------------------ */
      /* Four, down from the ten the built pages currently contain. --r-lg is
         12px on purpose: it is already the dominant card radius (239 uses),
         so adopting this scale in Phase 3 moves the fewest pixels. */
      --r-sm: 4px; --r-md: 8px; --r-lg: 12px; --r-pill: 999px;

      /* --- elevation --------------------------------------------------- */
      --sh-1: 0 1px 2px rgba(0,0,0,0.30);
      --sh-2: 0 4px 12px rgba(0,0,0,0.35);
      --sh-3: 0 12px 32px rgba(0,0,0,0.45);

      /* --- motion ------------------------------------------------------ */
      /* Three durations and one easing. Everything animates opacity and
         transform only. The reduced-motion block further down neutralises
         all of it. */
      --dur-1: 120ms; --dur-2: 220ms; --dur-3: 400ms;
      --ease: cubic-bezier(0.2, 0.6, 0.2, 1);

      /* --- layout ------------------------------------------------------ */
      --wrap-max: 1240px; --measure: 640px;
    }
    * { box-sizing: border-box; }
    html, body { margin: 0; padding: 0; background: var(--bg); color: var(--text); font-family: var(--font); -webkit-font-smoothing: antialiased; }
    a { color: inherit; text-decoration: none; }
    /* WCAG 1.4.1: an in-text link must not rely on colour alone. Its
       contrast against surrounding body text is 1.35:1, far under the
       3.0 minimum, so it needs a non-colour cue. Scoped to paragraph
       text -- nav and buttons are distinguishable by position/shape. */
    p a { text-decoration: underline; text-underline-offset: 2px; }
    img { max-width: 100%; display: block; }
    .wrap { max-width: 1240px; margin: 0 auto; padding: 0 40px; }
    @media (max-width: 780px) { .wrap { padding: 0 20px; } }

    /* Nav */
    nav.top { position: sticky; top: 0; z-index: 50; background: rgba(10,14,22,0.85); backdrop-filter: blur(14px); border-bottom: 1px solid var(--border-soft); }
    nav.top .row { display: flex; align-items: center; gap: 36px; padding: 16px 40px; }
    @media (max-width: 780px) { nav.top .row { padding: 14px 20px; gap: 16px; flex-wrap: wrap; } }
    .logo { display: flex; align-items: center; gap: 10px; font-weight: 700; font-size: 18px; flex-shrink: 0; }
    .logo .dot { color: var(--accent); }
    .navlinks { display: flex; gap: 26px; flex: 1; font-size: 14.5px; color: var(--muted); flex-wrap: wrap; }
    .navlinks a:hover, .navlinks a.active { color: var(--accent); }
    .navcta { display: flex; align-items: center; gap: 18px; margin-left: auto; }
    /* A BUTTON MUST BE ABLE TO GET NARROWER THAN ITS LABEL.

       white-space:nowrap here was the single root cause of BOTH pricing
       overflow symptoms, measured:

         at 320px  "How we compare - including what we do not do" laid out
                   366px wide and was the ONLY uncontained element on the page;
         at 1024px "Start free 7-day trial" gave each plan card a min-content
                   width of 254px (200px label + 2x26px padding + border), and
                   because `1fr` means `minmax(auto, 1fr)`, four such cards
                   forced the .grid-4 track total to 1016px inside a 929px
                   wrap -- the page laid out 1056px in a 1009px viewport.

       The comparison table, blamed for both for months, is not at fault: it
       is correctly clipped by its own overflow-x:auto wrapper (226px at 320).

       `normal` does not make anything wrap that has room -- it only lets a
       label fold instead of pushing the page sideways. Verified across all 66
       pages at 1440/1024/768/430/375: zero buttons wrap that did not need to.
       text-align:center keeps a folded label balanced rather than ragged. */
    .btn { display: inline-flex; align-items: center; gap: 8px; padding: 10px 18px; border-radius: 7px; font-weight: 600; font-size: 14px; white-space: normal; text-align: center; }
    .btn-primary { background: var(--accent); color: var(--accent-ink); border: 0; cursor: pointer; }
    .btn-secondary { background: transparent; border: 1px solid var(--border); color: var(--text); cursor: pointer; }
    .loginlink { color: var(--muted); font-size: 14.5px; }
    .loginlink:hover { color: var(--text); }

    /* Hero */
    .hero { position: relative; padding: 90px 0 70px; overflow: hidden; }
    .hero-bg { position: absolute; inset: 0; background: linear-gradient(180deg, rgba(10,14,22,0.55) 0%, rgba(10,14,22,0.92) 85%, var(--bg) 100%), radial-gradient(ellipse at 70% 20%, var(--depth-1) 0%, var(--bg) 60%); z-index: -1; }
    .eyebrow { font-family: var(--mono); font-size: 12px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--accent); margin: 0 0 16px; font-weight: 600; }
    h1.hero-h { font-size: clamp(34px, 4.6vw, 56px); font-weight: 800; line-height: 1.08; letter-spacing: -0.02em; margin: 0 0 20px; }
    h1.hero-h .cy { color: var(--accent); }
    .hero-sub { font-size: 17px; line-height: 1.6; color: var(--muted); max-width: 620px; margin: 0 0 28px; }
    .hero-ctas { display: flex; gap: 12px; flex-wrap: wrap; }

    .badge { display: inline-flex; align-items: center; font-family: var(--mono); font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase; padding: 3px 8px; border-radius: 4px; font-weight: 600; }

    /* Sections */
    section.sec { padding: 70px 0; }
    section.sec.alt { background: var(--bg-soft); }
    .sec-eyebrow { font-family: var(--mono); font-size: 11.5px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--accent); margin: 0 0 12px; font-weight: 600; }
    h2.sec-h { font-size: clamp(26px, 3vw, 38px); font-weight: 700; letter-spacing: -0.02em; margin: 0 0 14px; }
    .sec-sub { font-size: 15.5px; color: var(--muted); max-width: 640px; line-height: 1.6; margin: 0 0 40px; }

    /* Cards / grid */
    .grid { display: grid; gap: 18px; }
    /* EVERY fr TRACK IN THIS STYLESHEET HAS A FLOOR OF 0, deliberately, and
       three separate page overflows have now been traced to a track that did
       not: the pricing grid (Phase 5A), the footer and .step-check (found by
       the all-66-page narrow sweep the footer fix required, 6px over at a
       280px viewport on how-it-works). Treat a bare `1fr` here as a bug.

    /* minmax(0, 1fr), not 1fr. `1fr` is shorthand for `minmax(auto, 1fr)`,
       and `auto` as a track MINIMUM resolves to the item's min-content width --
       so one card with a long unbreakable label silently widens the whole grid
       past its container instead of being squeezed. That is grid blowout, and
       it is what put pricing.html 47px over the viewport at 1024. A floor of 0
       lets the tracks share the space they actually have; nothing moves when
       the content already fits. */
    .grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
    .grid-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
    @media (max-width: 900px) { .grid-2, .grid-3, .grid-4 { grid-template-columns: minmax(0, 1fr); } }

    /* THE FOUR PLAN CARDS ARE NOT A FOUR-UP FEATURE GRID.

       .grid-4 goes four columns from 901px up, and at 1024 that gave each plan
       card 218px of track. Measured at that width: the eyebrow "Pay for what
       you map" broke over two lines, "From $49" broke between the word and the
       number, six of seven feature rows ran to three lines, and the "Best for
       construction" flag -- absolutely positioned at top:-12px, sized for one
       line -- wrapped onto two and sat on top of the FLEET eyebrow beneath it.
       A price that breaks in half is a pricing page failing at its only job.

       A plan card needs about 250px to keep a price on one line and a feature
       row to two, so below 1180px these go 2x2 instead. That also gives the
       tablet band a real layout: .grid-4 stacked all four into a single column
       at 900px, which on a 768px iPad made the visitor scroll four screens to
       compare four plans. */
    .plan-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
    @media (max-width: 1180px) { .plan-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
    @media (max-width: 640px)  { .plan-grid { grid-template-columns: minmax(0, 1fr); } }

    /* FOUR CARDS, ONE TABLE.

       Measured at 1280 before this rule: the four prices started 221 / 180 /
       180 / 180 px down their cards (Scout's description runs two lines longer
       than the others), and the four feature lists started 380 / 306 / 306 /
       284 -- a 96px spread. Comparing "what do I actually get" meant hunting
       for the list four times. The flex column each card used could not fix
       that, because flex aligns within a card and knows nothing about its
       neighbours.

       subgrid does: each card takes its four rows -- header, price, features,
       action -- from the parent, so the four cards share them. grid-auto-rows
       carries the pattern rather than grid-template-rows, because at 2-up the
       cards form two rows of four tracks and each pair must align on its own.

       The flex column stays as the fallback and is NOT dead code: without
       subgrid the cards are exactly what they were before, ragged but correct.
       The inline style that used to carry display:flex had to come out of the
       markup first -- an inline declaration beats any stylesheet rule, so the
       @supports block could never have won against it. */
    .plan-card { display: flex; flex-direction: column; gap: 18px; }
    .plan-action { margin-top: auto; display: flex; flex-direction: column; gap: 6px; }
    .plan-action .btn { justify-content: center; }
    .plan-aside { font-size: 12px; color: var(--muted); text-align: center;
                  display: inline-flex; align-items: center; justify-content: center; gap: 4px; }
    .plan-aside:hover { color: var(--text); }

    /* THE PRICE LINE.

       Measured at 1280 before this: Scout's "From $49" set at 36px/800 was
       171px wide in a 225px content box, so the unit beside it -- "/ 250
       photos" -- occupied TWO line boxes with the word "photos" stranded on the
       second. A price that breaks in the middle of its own unit is the one
       thing a pricing page cannot do.

       Two changes, neither of which touches a figure: "From" is a qualifier
       rather than part of the number, so it is set at reading size; and the
       unit is kept whole, so when the line runs out the unit moves down intact
       instead of splitting. flex-wrap, not nowrap -- the line is allowed to
       fold, it is just not allowed to fold mid-unit. */
    .plan-price { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 7px; }
    .plan-price .amount { font-size: 36px; font-weight: 800; letter-spacing: -0.02em; line-height: 1.05; }
    .plan-price .amount-word { font-size: 30px; letter-spacing: -0.01em; }
    .plan-price .qual { font-size: 15px; font-weight: 600; color: var(--muted); }
    .plan-price .unit { font-size: 13px; color: var(--muted); white-space: nowrap; }

    /* THE COMPARISON TABLE, WHILE IT IS SCROLLING SIDEWAYS.

       The table is 640px at its narrowest and its wrapper scrolls it: at a
       320px viewport the wrapper is 226px, so roughly a third of the table is
       visible at a time. That part works -- but once the reader pushes across
       to the Fleet column, the row label has gone off the left edge and the
       cell says "10,000" with nothing to say what of. Pinning the row header
       keeps the question on screen while the answer moves.

       The header needs its own background or the cells slide under it in
       transparency, and z-index 2 on the corner cell so it stays above both
       the pinned column and the pinned header row. */
    .cmp th[scope="row"] { position: sticky; left: 0; background: var(--card); z-index: 1; }
    .cmp thead th:first-child { position: sticky; left: 0; background: var(--card); z-index: 2; }
    .cmp th[scope="colgroup"] { position: sticky; left: 0; background: var(--card); z-index: 1; }
    /* The pinned column needs an edge, otherwise the scrolled cells appear to
       start mid-word against it. */
    .cmp th[scope="row"]::after, .cmp thead th:first-child::after {
      content: ""; position: absolute; top: 0; right: 0; bottom: -1px; width: 1px;
      background: var(--border-soft); }
    @supports (grid-template-rows: subgrid) {
      .plan-grid { grid-auto-rows: auto auto 1fr auto; }
      .plan-card { display: grid; grid-row: span 4; grid-template-rows: subgrid; }
      /* the action row is the last track, so it no longer needs pushing */
      .plan-action { margin-top: 0; }
    }
    .card { background: var(--card); border: 1px solid var(--border); border-radius: 12px; padding: 26px; }
    /* --- icons -------------------------------------------------------
       Geometry comes from ICON_REGISTRY, size comes from here. A builder
       that wants a different size asks for a tier; it does not write
       width="17" into an <svg>. */
    .df-icon { display: block; flex-shrink: 0; }
    .df-icon-sm { width: 18px; height: 18px; }
    .df-icon-md { width: 22px; height: 22px; }
    .df-icon-lg { width: 28px; height: 28px; }

    /* ONE CHIP SURFACE, NOT FOUR.
       The chips used to carry four tints -- blue, amber, teal, violet -- and
       the emoji sitting in them brought their own unrelated colours, so a
       green herb sat in a violet square and an orange crane in an amber one.
       Two of those tints are also DATA colours: amber is fill and teal is the
       low end of the elevation ramp, which is exactly the confusion the token
       block forbids. The chip is now one quiet surface and the icon inherits
       one interaction colour.
       The four chip-* classes are kept and neutralised rather than removed
       from six builders: this is the icon phase, and rewriting the cards'
       markup belongs to Phase 3. */
    .icon-chip { width: 40px; height: 40px; border-radius: var(--r-md);
                 display: grid; place-items: center; margin-bottom: 16px;
                 background: var(--card-2); border: 1px solid var(--border);
                 color: var(--interactive); }
    .chip-blue, .chip-amber, .chip-teal, .chip-violet {
                 background: var(--card-2); color: var(--interactive); }

    /* --- shared components -------------------------------------------
       Each of these replaces a literal that was repeated across builders.
       Nothing here is a utility class: .mt-26 would have moved the problem
       out of the HTML without fixing it. */

    /* An icon trailing inline link text needs its own alignment; .btn is
       already an inline-flex row with a gap, so it must not get a margin. */
    a:not(.btn) .df-icon, button:not(.btn) .df-icon {
      display: inline-block; vertical-align: -0.16em; margin-left: 0.35em; }

    /* The eyebrow opens with a survey mark instead of a font glyph. The mark
       is declared at each site as an icon token, so the decoration belongs to
       the component rather than to a rewrite of the rendered page.
       .industry-tag is the same pattern under a different name. */
    .eyebrow, .sec-eyebrow, .industry-tag {
      display: flex; align-items: center; gap: var(--s-2); }

    /* A live/status marker. It was a literal #2dd4bf, which is --data-elev-low:
       the elevation ramp's low end, used to mean "this is on". */
    /* A STATUS BADGE IS ONE LINE.

       This rule set a colour and nothing else, so the span stayed `display:
       inline` while the icon inside it is `display: block` (.df-icon, which is
       right for every other use). Measured on security.html, all seven
       instances: 3 line boxes, 35px tall, 640px wide -- the badge took the
       whole line, with the icon stranded on a line of its own above the word
       LIVE. inline-flex puts them back on one baseline row and lets the badge
       be only as wide as its contents. --state-ok stays the semantic colour,
       and the word LIVE stays the thing that carries the state: the icon is
       aria-hidden, so colour is not the only signal. */
    .badge-live { display: inline-flex; align-items: center; gap: 5px;
                  color: var(--state-ok); vertical-align: baseline; }

    /* IN-TEXT LINKS ARE INTERACTION, NOT BRAND. 21 of them carried
       style="color:var(--accent)" -- the same cyan as the primary CTA, the
       logo and (in the figures) a cut volume. --interactive exists to hold
       that job apart. */
    .link { color: var(--interactive); }
    .link:hover { color: var(--interactive-hover); }

    /* A row of calls to action. Was display:flex;flex-wrap:wrap;gap:12px;
       margin-top:26px, seven times. 26px is not on the scale; this is --s-6
       (24px), a deliberate 2px move onto it. */
    .cta-row { display: flex; flex-wrap: wrap; gap: var(--s-3); margin-top: var(--s-6); }

    /* A measured figure and its label. The label is the MEASUREMENT VOICE --
       mono because it carries units and run data, not to look technical. */
    .stat-value { font-size: 28px; font-weight: var(--w-black); }
    .stat-label { color: var(--muted); font-family: var(--mono); font-size: 11px;
                  letter-spacing: 0.06em; margin-top: 6px; }

    /* A PASS badge on the QA rows. It was written as a literal #2dd4bf, which
       is BOTH --chip-teal-fg and --data-elev-low: a data colour doing duty as
       a status chip, which is exactly what the token block forbids. It is a
       success state, so it takes the success token. */
    .badge-pass { background: rgba(125, 211, 168, 0.14); color: var(--state-ok); }
    /* WARN and NOTE were the same amber, hand-inlined as a literal #f0a94e in
       three places across two case studies. They are not the same thing: a
       WARN is a result the run flagged (80% DSM coverage over a reflective
       pad and a pond), a NOTE is a limit we are declaring about what the run
       could establish at all (no surveyed control, so no residuals exist to
       report). --state-withheld is the token for exactly that second case --
       not a softer warning, a different claim. Both stay at full text weight;
       nothing about a disclosure gets quieter. */
    .badge-warn { background: rgba(240, 169, 78, 0.14); color: var(--state-warn); }
    .badge-note { background: rgba(125, 139, 158, 0.16); color: var(--state-withheld); }

    /* =====================================================================
       THE CASE-STUDY SYSTEM.

       Three pages told the same story three ways: stat cards with 28px inline
       font sizes, two of them with prose where the third had a spec table, a
       QA list built from fourteen hand-inlined flex rows, and -- on two of the
       three -- not one image between the headline and a 680px live iframe.

       The rule these components encode: a figure on a case study is worth
       exactly as much as the artifact it can be read back from. So every
       measured number either carries its source or sits beside the thing that
       proves it.
       ===================================================================== */

    /* The headline figures. Not .grid-4: that collapses straight from four
       columns to one at 900px, and a stat card now carries a source line, so
       four of them stacked put roughly 900px of preamble between the headline
       and the first piece of evidence on a tablet. Two up in the middle. */
    .cs-metrics { grid-template-columns: repeat(4, minmax(0, 1fr)); }
    @media (max-width: 1080px) { .cs-metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
    @media (max-width: 520px)  { .cs-metrics { grid-template-columns: minmax(0, 1fr); } }

    /* The line under a measured figure that says where it came from. */
    .stat-note { margin-top: 8px; font-size: 11.5px; line-height: 1.5; color: var(--dim); }

    /* The flight, the outputs, the survey panel: one table shape for all
       three, because they are the same kind of statement. */
    .cs-spec { width: 100%; border-collapse: collapse; font-size: 13.5px; }
    .cs-spec th { text-align: left; font-weight: 500; color: var(--dim);
                  padding: 8px 14px 8px 0; border-top: 1px solid var(--border-soft);
                  vertical-align: top; }
    .cs-spec td { text-align: right; color: var(--text); padding: 8px 0;
                  border-top: 1px solid var(--border-soft); vertical-align: top; }
    .cs-spec tr:first-child th, .cs-spec tr:first-child td { border-top: 0; padding-top: 0; }
    /* Below 480px a label and a right-aligned value collide in the middle of
       the card. They stack instead -- measured, not assumed: "Aircraft /
       camera" against "DJI M4E - 20.9 MP (5280x3956)" leaves 40px of gap at
       375px and none at all at 320. */
    @media (max-width: 480px) {
      .cs-spec th, .cs-spec td { display: block; text-align: left; padding-right: 0; }
      .cs-spec th { padding: 10px 0 1px; }
      .cs-spec td { padding: 0 0 10px; border-top: 0; }
      .cs-spec tr:first-child th { padding-top: 0; }
    }

    /* A figure and what it is. The live portal is a figure too: it is the
       primary evidence on the page, not an embellishment beside the text. */
    .cs-figure { margin: 0; }
    .cs-frame { border: 1px solid var(--border); border-radius: var(--r-lg);
                overflow: hidden; background: var(--card-2); }
    .cs-figure img, .cs-figure iframe { display: block; width: 100%; border: 0; }
    .cs-figure figcaption { margin-top: 10px; font-size: 13px; line-height: 1.6;
                            color: var(--dim); }
    .cs-figure figcaption b { color: var(--muted); font-weight: 600; }
    .cs-figure-foot { margin-top: 10px; display: flex; align-items: baseline;
                      justify-content: space-between; flex-wrap: wrap; gap: 6px 16px; }
    .cs-figure-foot figcaption { margin-top: 0; flex: 1 1 22em; }

    /* The QA list. Fourteen copies of the same six inline declarations became
       one rule; the row is a list item because it is one. */
    .cs-checks { list-style: none; margin: 0; padding: 0; }
    .cs-checks li { display: flex; align-items: flex-start; gap: 12px;
                    padding: 11px 0; font-size: 13.5px; line-height: 1.6;
                    color: var(--muted); border-top: 1px solid var(--border-soft); }
    .cs-checks li:first-child { border-top: 0; padding-top: 0; }
    .cs-checks li:last-child { padding-bottom: 0; }
    .cs-checks .badge { flex-shrink: 0; margin-top: 3px; }
    .cs-checks b { color: var(--text); font-weight: 600; }

    /* =====================================================================
       THE DOCUMENTATION LAYER  (Help Center: 1 landing + 40 articles).

       WHY THIS IS HERE AND NOT IN build_help.py. It used to be a 7,780-byte
       <style> block emitted into every one of the 41 Help pages -- 312 KB of
       byte-identical CSS, 33% of all the HTML the Help Center ships, on a site
       whose whole stylesheet is 45 KB and content-hashed precisely so that a
       visitor downloads it once. Every declaration in it was used by all 41
       pages, which is the definition of shared. Page-specific quirks stay
       local; this was never one.

       The class names are unchanged on purpose: the landing page's search and
       filter script addresses .art-card, .help-cat, .filter-chip and [hidden]
       by those names, and renaming a class to tidy a stylesheet is how working
       behaviour breaks silently.
       ===================================================================== */

    /* --- landing ------------------------------------------------------- */
    section.sec.help-hero { padding: 64px 0 30px; }
    section.sec.paths-sec { padding: 8px 0 0; }
    section.sec.help-body { padding: 26px 0 70px; }
    .help-search { margin-top: 24px; max-width: 560px; position: relative; }
    .help-search input { width: 100%; padding: 13px 16px 13px 44px; font: inherit; font-size: 15px;
      color: var(--text); background: var(--card); border: 1px solid var(--border);
      border-radius: var(--r-md); outline: none; }
    .help-search input:focus-visible { border-color: var(--interactive);
      outline: 2px solid var(--interactive); outline-offset: 1px; }
    .help-search .mag { position: absolute; left: 14px; top: 50%; transform: translateY(-50%);
      color: var(--dim); display: flex; pointer-events: none; }

    .help-filters { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 16px; }
    .filter-chip { font: inherit; font-family: var(--mono); font-size: 11.5px; letter-spacing: .06em;
      text-transform: uppercase; color: var(--muted); background: transparent; border: 1px solid var(--border);
      border-radius: var(--r-pill); padding: 7px 13px; cursor: pointer;
      transition: border-color var(--dur-1) var(--ease), color var(--dur-1) var(--ease); }
    .filter-chip:hover { border-color: var(--interactive); color: var(--interactive); }
    .filter-chip.on { border-color: var(--interactive); color: var(--accent-ink); background: var(--interactive); }
    .filter-chip:focus-visible { outline: 2px solid var(--interactive); outline-offset: 2px; }
    .help-count { font-family: var(--mono); font-size: 11.5px; color: var(--dim); margin: 0 0 14px;
      letter-spacing: .04em; }

    /* A category heading is a chip, a title and a count on one row. The count
       was the last uncontained element on the site: the row is a flex line and
       its items default to min-width:auto, so at a 280px viewport
       "11 articles" pushed help.html 27px past the screen. The row wraps, the
       title may shrink, and the count never has to. */
    .help-cat { padding: 32px 0; border-top: 1px solid var(--border-soft); }
    .help-cat:first-of-type { border-top: 0; }
    .help-cat-head { display: flex; align-items: center; flex-wrap: wrap; gap: 8px 12px; margin: 0 0 18px; }
    .help-cat-head h2 { font-size: 21px; font-weight: 700; margin: 0; min-width: 0; }
    .help-cat-head .count { font-family: var(--mono); font-size: 11.5px; color: var(--dim);
      white-space: nowrap; }

    .art-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; }
    @media (max-width: 1000px) { .art-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
    @media (max-width: 680px) { .art-grid { grid-template-columns: minmax(0, 1fr); } }
    a.art-card { display: block; background: var(--card); border: 1px solid var(--border);
      border-radius: var(--r-md); padding: 18px 20px;
      transition: border-color var(--dur-1) var(--ease), transform var(--dur-1) var(--ease); }
    a.art-card:hover { border-color: var(--interactive); transform: translateY(-2px); }
    a.art-card h3 { font-size: 15.5px; font-weight: 650; margin: 0 0 7px; color: var(--text); }
    a.art-card p { font-size: 13px; color: var(--muted); line-height: 1.55; margin: 0; }
    a.art-card .art-meta { font-family: var(--mono); font-size: 11px; color: var(--dim);
      margin: 10px 0 0; letter-spacing: .04em; }
    .help-empty { color: var(--dim); font-size: 14.5px; padding: 20px 0; display: none; }
    /* a.art-card sets display:block, which beats the user-agent [hidden] rule --
       without this the filter "hides" cards that stay perfectly visible. */
    [hidden] { display: none !important; }

    /* --- learning paths ------------------------------------------------ */
    .paths-head h2 { font-size: 21px; font-weight: 700; margin: 0 0 6px; }
    .paths-head p { font-size: 14px; color: var(--muted); margin: 0 0 20px; max-width: 620px; line-height: 1.6; }
    .paths-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; }
    @media (max-width: 1000px) { .paths-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
    @media (max-width: 680px) { .paths-grid { grid-template-columns: minmax(0, 1fr); } }
    .path-card { background: var(--card); border: 1px solid var(--border);
      border-radius: var(--r-md); padding: 20px 22px; }
    .path-card h3 { font-size: 16px; font-weight: 700; margin: 0 0 4px; }
    .path-who { font-family: var(--mono); font-size: 11px; letter-spacing: .06em; text-transform: uppercase;
      color: var(--accent); margin: 0 0 10px; }
    .path-outcome { font-size: 13.5px; line-height: 1.6; color: var(--muted); margin: 0 0 14px; }
    .path-steps { margin: 0 0 14px; padding-left: 20px; }
    .path-steps li { font-size: 13.5px; line-height: 1.62; margin: 0 0 5px; color: var(--reading); }
    .path-steps a { color: var(--reading); }
    .path-steps a:hover { color: var(--interactive); }
    .path-foot { font-family: var(--mono); font-size: 11px; color: var(--dim); margin: 0; letter-spacing: .04em; }

    /* --- the article shell --------------------------------------------- */
    /* READING WIDTH. Measured before this: the prose column was 680px at
       15.5px Inter, whose average glyph here is 8.15px -- 83 characters to a
       line, well past the 65-78 a documentation column should hold. 600px is
       73.6. The figures do NOT shrink with it; see below. */
    .art-wrap { max-width: 680px; }
    .crumbs { font-size: 12.5px; color: var(--dim); margin: 0 0 18px;
              display: flex; flex-wrap: wrap; align-items: center; gap: 2px 7px; }
    .crumbs a { color: var(--muted); }
    .crumbs a:hover { color: var(--interactive); }
    /* --border (#1e2635) on --bg (#0a0e16) rendered the separator invisible:
       quiet is the brief, absent is a broken trail. Measured on the page. */
    .crumbs .sep { color: var(--dim); }
    .crumbs .here { color: var(--dim); }
    .art-eyebrow { font-family: var(--mono); font-size: 11.5px; letter-spacing: .12em;
      text-transform: uppercase; color: var(--accent); font-weight: 600; margin: 0 0 10px; }
    h1.art-h { font-size: clamp(27px, 3.2vw, 36px); font-weight: 800; letter-spacing: -0.02em;
      line-height: 1.14; margin: 0 0 14px; }
    .art-lede { font-size: 16.5px; line-height: 1.6; color: var(--muted); margin: 0 0 24px; }
    .art-metabar { display: flex; flex-wrap: wrap; gap: 6px 16px; font-family: var(--mono);
      font-size: 11.5px; color: var(--dim); letter-spacing: .04em; margin: 0 0 24px;
      padding-bottom: 16px; border-bottom: 1px solid var(--border-soft); }
    .art-metabar .in-app { color: var(--muted); display: inline-flex; align-items: center; gap: 4px; }
    .art-metabar .in-app:hover { color: var(--interactive); }

    /* ON THIS PAGE. Built from the article's real H2s, and only for articles
       with enough of them to need one -- measured across the library, 16 of
       40. A contents list on a three-minute article with two headings is
       furniture. No script: it is a <nav> of anchors, so it works with
       JavaScript off and is keyboard navigable by construction. */
    .art-toc { margin: 0 0 30px; padding: 14px 18px; border: 1px solid var(--border-soft);
      border-radius: var(--r-md); background: var(--card-2); }
    .art-toc h2 { font-family: var(--mono); font-size: 11px; letter-spacing: .12em;
      text-transform: uppercase; color: var(--dim); margin: 0 0 9px; font-weight: 600; }
    .art-toc ol { list-style: none; margin: 0; padding: 0; display: flex;
      flex-direction: column; gap: 6px; counter-reset: toc; }
    .art-toc li { display: flex; gap: 9px; font-size: 13.5px; line-height: 1.5; counter-increment: toc; }
    .art-toc li::before { content: counter(toc); font-family: var(--mono); font-size: 11px;
      color: var(--dim); flex-shrink: 0; padding-top: 2px; }
    .art-toc a { color: var(--muted); }
    .art-toc a:hover { color: var(--interactive); }
    /* A heading jumped to from the contents must not land under the sticky nav. */
    .art-body h2[id] { scroll-margin-top: 80px; }

    .art-body h2 { font-size: 19.5px; font-weight: 700; margin: 34px 0 12px; letter-spacing: -0.01em; }
    .art-body h3 { font-size: 16px; font-weight: 650; margin: 24px 0 8px; }
    .art-body p { font-size: 15.5px; line-height: 1.72; color: var(--reading); margin: 0 0 14px; }
    .art-body ul, .art-body ol { margin: 0 0 16px; padding-left: 22px; }
    .art-body li { font-size: 15.5px; line-height: 1.7; color: var(--reading); margin: 0 0 7px; }
    .art-body strong { color: var(--text); }
    .art-body a { color: var(--interactive); }
    .art-body a:hover { color: var(--interactive-hover); }
    /* Filenames, paths and formats. Monospace here carries a fact -- that the
       string is typed or looked for literally -- and is not applied to prose. */
    .art-body code { font-family: var(--mono); font-size: 13px; background: var(--code-bg);
      color: var(--text); padding: 1px 5px; border-radius: 4px; word-break: break-word; }

    /* PROCEDURES. Only where the content is genuinely sequential: the
       troubleshooting shape's "what to check" and "how to fix it" lists, which
       are ordered steps by construction. Ordinary prose lists keep bullets. */
    .art-body ol.steps { list-style: none; padding-left: 0; counter-reset: step;
      display: flex; flex-direction: column; gap: 12px; margin: 0 0 18px; }
    /* The badge sits absolutely in the li's reserved left padding (26px badge
       plus the 12px gap) and the step text is ordinary inline flow. Not a grid
       and not flex: either one turns every inline child of the li (strong, a,
       em, code) into an item of its own, and the first lands in the badge
       column on top of the number. Measured 2026-09-30: 13 of the 62 step
       items on 6 help pages carried such a child. */
    .art-body ol.steps > li { counter-increment: step; position: relative;
      padding-left: 38px; margin: 0; }
    .art-body ol.steps > li::before { content: counter(step); font-family: var(--mono);
      font-size: 11.5px; font-weight: 700; color: var(--interactive); background: var(--card);
      border: 1px solid var(--border); border-radius: var(--r-sm);
      width: 26px; height: 26px; display: flex; align-items: center; justify-content: center;
      position: absolute; left: 0; top: 1px; }

    /* CALLOUTS. A note and a warning are different claims, so they do not look
       the same: --border for an aside, --state-warn for a consequence. Neither
       borrows a data-ramp colour, and neither leans on colour alone -- the
       label is a real word in the markup and the box carries role="note". */
    .callout { border: 1px solid var(--border); background: var(--card);
      border-left: 3px solid var(--border); border-radius: var(--r-sm);
      padding: 13px 18px; margin: 0 0 18px; }
    .callout p { margin: 0; font-size: 14.5px; line-height: 1.6; color: var(--muted); }
    .callout-label { font-family: var(--mono); font-size: 10.5px; letter-spacing: .12em;
      text-transform: uppercase; color: var(--dim); margin: 0 0 5px; font-weight: 600; }
    .callout-warn { border-left-color: var(--state-warn); }
    .callout-warn .callout-label { color: var(--state-warn); }

    /* FIGURES. Evidence, so they are allowed to be wider than the prose on a
       roomy viewport rather than shrinking with it. At 1000px and up a plain
       figure returns to the 678px it occupied before the reading column was
       narrowed; a wide comparison render goes past that again, because two
       panels side by side in a 600px box are unreadable. */
    .art-fig { margin: 26px 0 30px; display: flex; flex-direction: column; gap: 10px; }
    .art-fig img { display: block; width: 100%; height: auto; border-radius: var(--r-md); }
    .art-fig figcaption { font-family: var(--mono); font-size: 12.5px; line-height: 1.6;
      color: var(--dim); letter-spacing: .01em; }
    .art-fig figcaption b { color: var(--muted); font-weight: 600; }
    @media (min-width: 1000px) {
      .art-fig { width: 113%; margin-left: -6.5%; }
      .art-fig.wide { width: 140%; margin-left: -20%; }
    }

    .related { margin-top: 44px; padding-top: 24px; border-top: 1px solid var(--border-soft); }
    .related h2 { font-family: var(--mono); font-size: 11.5px; letter-spacing: .12em;
      text-transform: uppercase; color: var(--dim); margin: 0 0 14px; font-weight: 600; }
    .related-terms { font-size: 14.5px; color: var(--muted); margin: 0; }
    .art-foot { margin-top: 26px; font-size: 13.5px; color: var(--dim); }
    .cta-card { margin-top: 38px; background: var(--card); border: 1px solid var(--border);
      border-left: 3px solid var(--accent); border-radius: var(--r-md); padding: 22px 24px;
      display: flex; align-items: center; justify-content: space-between; gap: 18px; flex-wrap: wrap; }
    .cta-card h2 { margin: 0 0 4px; font-size: 17px; font-weight: 700; }
    .cta-card p { margin: 0; color: var(--muted); font-size: 14px; }

    /* The menu button holds both icons and shows one. State comes from
       aria-expanded -- the attribute a screen reader already reads -- so there
       is no second source of truth to fall out of step, and the script no
       longer rewrites the button's innerHTML on every toggle. */
    #nav-burger .nav-ico-close { display: none; }
    #nav-burger[aria-expanded="true"] .nav-ico-menu { display: none; }
    #nav-burger[aria-expanded="true"] .nav-ico-close { display: block; }
    .card h3 { font-size: 16.5px; font-weight: 700; margin: 0 0 8px; }
    .card p { font-size: 14px; color: var(--muted); line-height: 1.6; margin: 0; }

    /* Legal prose */
    .legal-notice { padding: 16px 20px; border: 1px solid var(--border); border-radius: 8px; background: rgba(240,169,78,0.08); font-size: 13px; color: var(--chip-amber-fg); line-height: 1.6; margin-bottom: 40px; }
    .legal h2 { font-size: 20px; font-weight: 700; margin: 36px 0 10px; }
    .legal h2:first-of-type { margin-top: 0; }
    .legal p { font-size: 15px; line-height: 1.7; color: var(--muted); margin: 0; }
    .legal a { color: var(--accent); }

    /* Spec list */
    .spec-list { display: flex; flex-direction: column; }
    .spec-row { display: flex; align-items: baseline; justify-content: space-between; column-gap: 12px; padding: 14px 0; border-bottom: 1px solid var(--border-soft); font-size: 14.5px; }
    .spec-row .k { color: var(--muted); }
    .spec-row .v { font-family: var(--mono); font-weight: 600; color: var(--text); }
    .spec-row .v .note { font-family: var(--font); font-weight: 400; color: var(--dim); font-size: 12.5px; margin-left: 6px; }
    .spec-row a.link { white-space: nowrap; flex-shrink: 0; }

    /* Checklist */
    ul.check { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px 24px; }
    @media (max-width: 700px) { ul.check { grid-template-columns: minmax(0, 1fr); } }
    ul.check li { display: flex; align-items: flex-start; gap: 9px; font-size: 14.5px; color: var(--muted); }
    ul.check li::before { content: "✓"; color: var(--accent); font-weight: 700; flex-shrink: 0; }

    /* Industry block */
    .industry-block { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 48px; align-items: center; padding: 56px 0; border-bottom: 1px solid var(--border-soft); }
    @media (max-width: 900px) { .industry-block { grid-template-columns: minmax(0, 1fr); } }
    .industry-img { border-radius: 12px; overflow: hidden; border: 1px solid var(--border); aspect-ratio: 4/3; background: linear-gradient(135deg, var(--depth-2), var(--bg)); display: flex; align-items: center; justify-content: center; color: var(--dim); font-family: var(--mono); font-size: 12px; letter-spacing: 0.08em; text-transform: uppercase; }
    .industry-img img { width: 100%; height: 100%; object-fit: cover; display: block; }
    .industry-tag { font-family: var(--mono); font-size: 11.5px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--accent); margin-bottom: 10px; font-weight: 600; }
    .industry-block h3 { font-size: 26px; font-weight: 700; margin: 0 0 12px; letter-spacing: -0.01em; }
    .industry-block p.desc { color: var(--muted); font-size: 14.5px; line-height: 1.6; margin: 0 0 20px; }

    /* Steps */
    .step { display: grid; grid-template-columns: 340px minmax(0, 1fr); gap: 40px; align-items: center; padding: 40px 0; border-bottom: 1px solid var(--border-soft); }
    @media (max-width: 900px) { .step { grid-template-columns: minmax(0, 1fr); } }
    /* The decorative numeral owns a band above the content: at line-height 1
       it spans y 16..56 of the card and the 64px top padding starts the
       checklist below it, so the two boxes cannot meet. With the old uniform
       24px padding the 48x53 numeral painted over the checklist's right
       column (1,408 px^2 on card 01 at 1440, measured 2026-09-30). It is
       still a muted mono numeral in the top-right, and it takes no clicks. */
    .step-card { background: var(--card); border: 1px solid var(--border); border-radius: 12px; padding: 64px 24px 24px; position: relative; }
    .step-num { position: absolute; top: 16px; right: 20px; font-size: 40px; line-height: 1; font-weight: 800; color: var(--border); font-family: var(--mono); pointer-events: none; user-select: none; }
    .step-check { list-style: none; padding: 0; margin: 0 0 14px; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px 16px; }
    .step-check li { font-size: 13px; color: var(--muted); display: flex; gap: 7px; }
    .step-check li::before { content: "✓"; color: var(--accent); }
    .step-note { font-size: 12px; color: var(--dim); border-top: 1px solid var(--border-soft); padding-top: 12px; margin-top: 12px; }
    .step-num-lbl { font-family: var(--mono); font-size: 13px; color: var(--accent); font-weight: 700; }
    .step-body h3 { font-size: 22px; font-weight: 700; margin: 8px 0 12px; }
    .step-body p { color: var(--muted); font-size: 14.5px; line-height: 1.6; }

    /* Screen-reader-only text. Not display:none -- that would remove it from
       the accessibility tree, which is the opposite of the point. The clip
       rect + 1px box is the standard technique that keeps it announced. */
    .sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
               overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0; }

    /* Skip link. Hidden until focused, then pinned top-left over the nav.
       Uses :focus (not :focus-visible) deliberately -- it must appear for the
       keyboard user who tabbed to it regardless of how the browser is feeling
       about heuristics. */
    .skip-link { position: absolute; left: -9999px; top: 0; z-index: 200;
                 background: var(--accent); color: var(--accent-ink); padding: 12px 18px;
                 border-radius: 0 0 8px 0; font-weight: 700; font-size: 14px; text-decoration: none; }
    .skip-link:focus { left: 0; }

    /* Visible focus everywhere. The design had no focus style at all, so a
       keyboard user could not see where they were on any page. */
    a:focus-visible, button:focus-visible, input:focus-visible,
    select:focus-visible, textarea:focus-visible, [tabindex]:focus-visible {
      outline: 3px solid var(--accent); outline-offset: 2px; border-radius: 3px;
    }

    /* Honour a reduced-motion preference site-wide. */
    @media (prefers-reduced-motion: reduce) {
      *, *::before, *::after {
        animation-duration: 0.01ms !important; animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important; scroll-behavior: auto !important;
      }
    }

    /* Mobile hamburger nav */
    .nav-burger { display: none; background: none; border: 1px solid var(--border); border-radius: 7px; color: var(--text); width: 42px; height: 38px; cursor: pointer; font-size: 18px; line-height: 1; align-items: center; justify-content: center; flex-shrink: 0; }
    .nav-mobile { display: none; }
    @media (max-width: 780px) {
      nav.top .row { flex-wrap: nowrap; }
      .navlinks, .navcta { display: none; }
      .nav-burger { display: inline-flex; margin-left: auto; }
      .nav-mobile.open { display: flex; flex-direction: column; gap: 4px; padding: 10px 20px 18px; border-top: 1px solid var(--border-soft); }
      .nav-mobile a { display: block; padding: 10px 2px; font-size: 15px; color: var(--muted); border-bottom: 1px solid var(--border-soft); }
      .nav-mobile a:last-child { border-bottom: 0; }
      .nav-mobile a.active { color: var(--accent); }
      .nav-mobile a.btn-primary { color: var(--accent-ink); border-bottom: 0; margin-top: 10px; text-align: center; }
    }

    /* Footer */
    footer { border-top: 1px solid var(--border-soft); padding: 56px 0 30px; }
    /* THE FOOTER WAS THE NARROWEST THING ON THE SITE, ON ALL 66 PAGES.

       Measured before this rule, on about.html, at a series of viewport widths:

         viewport   280   300   305   308   320   335
         page       309   309   309   309   309   320   <- pinned, not reflowing
         over       +44   +24   +19   +16    +4     0

       A page whose scroll width does not move as the viewport shrinks is not
       laying out badly, it is not laying out at all -- and that is the
       signature of grid blowout. `1fr` is `minmax(auto, 1fr)`, and `auto` as a
       track MINIMUM is the item's min-content. Measured: column one's
       min-content is 149.45px and it comes entirely from the footer's own logo
       lockup (.logo is display:flex, so the mark and the wordmark will not
       separate); the widest link column's is 107.38px. With the 32px gap the
       two-column footer demands 288.83px of content box whatever the viewport
       says, and below a 328.8px viewport there is not that much left.

       Two changes, and the second is needed because the first alone is not
       enough. A track floor of 0 stops the GRID pushing the page (verified: at
       a 280px viewport the overflow goes to zero) -- but it does it by
       squeezing the logo into a 96.5px track, where the wordmark folds under
       the mark. So below the width where two honest columns fit, the footer
       takes one column instead. 360px rather than the theoretical 329: at 329
       the columns sit at their min-content, meaning every link is broken at
       its longest word, which is two columns of debris rather than a layout.

       Nothing at 375px and above changes. There is no overflow:hidden here and
       there must not be -- clipping would hide the symptom and leave the
       footer genuinely wider than the screen. */
    .foot-grid { display: grid; grid-template-columns: minmax(0, 1.6fr) repeat(4, minmax(0, 1fr)); gap: 32px; margin-bottom: 40px; }
    /* Five columns need a stop between "roomy" and "two-up", or the link text
       in Learn/Solutions wraps mid-word around 900px. */
    @media (max-width: 1040px) { .foot-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
    @media (max-width: 800px) { .foot-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
    @media (max-width: 360px) { .foot-grid { grid-template-columns: minmax(0, 1fr); gap: 26px; margin-bottom: 30px; } }
    .foot-col h4 { font-family: var(--mono); font-size: 11px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--dim); margin: 0 0 16px; }
    .foot-col ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
    .foot-col a { font-size: 14px; color: var(--muted); }
    .foot-col .foot-h { font-size: 11px; font-weight: 700; letter-spacing: .08em;
                        text-transform: uppercase; color: var(--dim);
                        margin: 0 0 10px; }
    .foot-col a:hover { color: var(--accent); }
    .foot-bottom { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 16px; padding-top: 28px; border-top: 1px solid var(--border-soft); font-family: var(--mono); font-size: 11.5px; color: var(--dim); letter-spacing: 0.04em; }
    .foot-bottom a { color: var(--dim); }
    .foot-bottom a:hover { color: var(--accent); }
