/* Web-only layout — plan §3.3, §11.3.
 *
 * Declared in its own `@layer web`, which (being named after the SDS layers are
 * already established in base.css / components.css / compositions.css) sorts ABOVE
 * base · components · motifs · utilities. So web page chrome wins over SDS component
 * rules without specificity fights, exactly as the SDS's own layering discipline
 * intends. Never add an unlayered rule that would fight the SDS, and never reorder
 * or rename the SDS layers. Colours/typography come from SDS role tokens, so this
 * chrome flips with the prefers-color-scheme dark trigger for free.
 *
 * This file owns the page SHELL: the band system the block engine dispatches into
 * (composed.html), the centred content measure, and the header/footer chrome.
 * Block-specific deltas live in assets/css/blocks/<type>.css. */
@layer web {

  /* Full-width page main; bands provide their own full-bleed background + rhythm. */
  .web-main { display: block; }

  /* ── The gold word ────────────────────────────────────────────────────────
     THE one declaration for {accent}…{/accent}. util/inline.html — the single
     renderer for inline authoring markup — stamps .web-accent on every accent
     it emits, so this rule reaches every authored string in every context:
     block titles, leads, claims, prose bodies, pull quotes, and the document
     pages that live outside the block engine entirely.

     It replaces nineteen per-element copies (`.hero__title .sph-accent`,
     `.st__title .sph-accent`, …). That mirroring drifted: any block whose CSS
     forgot its copy silently lost the gold word, and pullquote — the one
     component the SDS scopes itself (components.css: `.sph-pull-quote
     .sph-accent` → --sph-info, the deck's Lonestar quote accent) — rendered
     salmon instead. Keyed on the class rather than on ancestors so a NEW block
     needs no CSS to get this right.

     Deliberately NOT `.sph-accent`: that class is the SDS's, and SDS-compiled
     DeckLang fragments carry it without .web-accent. They keep SDS semantics —
     the compiler stays the one renderer of deck markup (CLAUDE.md rules 1, 8).

     TWO TIERS, ONE PLACE (axe sweep, Aug 2026 — the SDS's own July 2026 rule):
     raw brand gold --sph-primary carries 3.0:1 on white, legal for DISPLAY
     sizes only; gold in running text takes the text-safe --sph-text-accent
     (≥4.5:1, theme-aware). The display override below names every display
     context; tests/accent-color.mjs and the axe exclusion in
     tests/e2e/specs/a11y.spec.ts mirror this exact selector — move all three
     together. */
  .web-accent { color: var(--sph-text-accent); }
  :is(h1, h2, h3, blockquote, .thesis__claim) .web-accent { color: var(--sph-primary); }

  /* ── Display-heading line-break art direction (design review §P2, July 2026) ──
     Balance every multi-line display heading so no line is left with one short
     word. Browsers apply this only up to their line limit (Chromium: 6) and it
     never widens beyond the box, so it is a safe progressive enhancement. Where
     balance alone can't prevent a dangling article ("… a / SPREADSHEET"), join
     the article to its noun with a no-break space in the CONTENT front matter
     (see content/company/_index.md). Blocks own their look; this single rule
     owns the site-wide wrapping policy, so new blocks join the list here. */
  .hero__title, .web-section-head__title, .thesis__claim, .tm__title,
  .ptabs__heading, .ptabs__title, .roi__title, .st__title,
  .carousel__heading, .cta__claim, .web-display-title {
    text-wrap: balance;
  }

  /* ── Shared block display title ──────────────────────────────────────────
     One typography source for the big in-band heading (d2v-process, decklang;
     review a79ba2d). Blocks add their own alignment and margins via their
     block class (.d2v__title, .decklang__title). */
  .web-display-title {
    font-family: var(--sph-font-display);
    font-weight: var(--sph-weight-extrabold);
    font-size: clamp(1.6rem, 4vw, 2.75rem);
    line-height: 1.05;
    letter-spacing: var(--sph-tracking-tight);
    color: var(--sph-text);
    max-width: 20ch;
  }

  /* ── Band system ──────────────────────────────────────────────────────────
     A band is a full-bleed horizontal section. composed.html alternates
     paper / paper-tint by default; a block may set its own band. Backgrounds are
     SDS role tokens, so bands re-bind under the dark trigger. */
  .band {
    padding-block: clamp(2.5rem, 6vw, 5.5rem);
  }
  .band--paper      { background: var(--sph-bg); }
  .band--paper-tint { background: var(--sph-bg-subtle); }
  .band--muted      { background: var(--sph-bg-muted); }
  /* Blocks joined into one band (join = true) share its background; give the
     stacked blocks the same breathing room bands have between each other. */
  .band > .block + .block { margin-block-start: clamp(2rem, 5vw, 3.5rem); }

  /* ── Not-found page (layouts/404.html) ──
     Part of the shell, not a block: a template-rendered page carries no
     front-matter `blocks`, so it never receives a per-block stylesheet. Kept to
     the few rules the hero look needs, on SDS tokens like everything here. */
  .web-notfound__rule {
    border: 0;
    /* Same hairline the hero sets under its title (blocks/hero.css). */
    border-top: 1px solid var(--sph-border);
    margin-block: clamp(1.5rem, 4vw, 2.5rem);
    max-width: 40rem;
  }
  .web-notfound__actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sph-space-6);
    margin-block-start: var(--sph-space-7);
  }
  .web-notfound__alt { color: var(--sph-primary); }
  .web-notfound__nav { margin-block-start: var(--sph-space-9); }
  .web-notfound__nav-title {
    color: var(--sph-text-muted);
    margin-block-end: var(--sph-space-4);
  }
  .web-notfound__list {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sph-space-4) var(--sph-space-7);
    list-style: none;
    margin: 0;
    padding: 0;
  }
  /* Also on the item: the SDS gives list items inside a band a square marker
     by targeting `li` itself, which beats the inherited list-style above and
     would strew stray dots through this horizontal row. */
  .web-notfound__list li { list-style: none; }

  /* Centred content measure — the inner wrapper every band (and simple page)
     puts its content in, so backgrounds go edge-to-edge but content stays aligned. */
  .web-measure {
    max-width: 75rem;
    margin-inline: auto;
    padding-inline: clamp(1rem, 4vw, 3rem);
  }

  /* Shared second-level heading composition. reasons-grid owns the visual
     source; hero variant=secondary reuses it instead of creating another
     near-identical title system. */
  .web-section-head {
    display: grid;
    grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr);
    column-gap: clamp(1.5rem, 4vw, 3rem);
    row-gap: 0;
    /* Keep the left heading pinned to the top of the row. The right item
       bottom-aligns independently below: medium copy sits against the title,
       longer copy rises only as far as the eyebrow, and overlong copy becomes
       the row height so following content moves down. */
    align-items: start;
  }
  .web-section-head__intro {
    grid-column: 1;
    grid-row: 1;
    min-width: 0;
  }
  .web-section-head__aside {
    grid-column: 2;
    grid-row: 1;
    align-self: end;
    min-width: 0;
  }
  .web-section-head__intro > .sph-eyebrow { margin: 0; }
  .web-section-head__title {
    font-family: var(--sph-font-display);
    font-weight: var(--sph-weight-extrabold);
    font-size: clamp(1.6rem, 4vw, 2.75rem);
    line-height: 1.05;
    letter-spacing: var(--sph-tracking-tight);
    color: var(--sph-text);
    margin: 0;
    max-width: 18ch;
  }
  .web-section-head__intro > .sph-eyebrow + .web-section-head__title {
    margin-top: 0.5rem;
  }
  .web-section-head__lead { margin: 0; }
  @media (max-width: 44rem) {
    .web-section-head {
      grid-template-columns: 1fr;
      row-gap: clamp(1.5rem, 4vw, 3rem);
      align-items: start;
    }
    .web-section-head__aside {
      grid-column: 1;
      grid-row: auto;
      align-self: start;
    }
  }

  /* ── Header ───────────────────────────────────────────────────────────────
     Sticky top chrome: brand · nav · CTA. Web-only layout; the CTA is the SDS
     .sph-btn. Three nav items, so it stays single-row and wraps gracefully. */
  /* Anchored blocks (#fragment links, e.g. the webinar page's closing
     "Register now" → #register) land clear of the sticky header. The single
     source for hash-target clearance; the phone/tablet media blocks below
     re-tune it to their header geometry (static / two-row sticky). */
  .block[id] { scroll-margin-top: 6.5rem; }

  .web-header {
    position: sticky;
    top: 0;
    z-index: 10;
    background: color-mix(in srgb, var(--sph-bg) 88%, transparent);
    /* Safari shipped backdrop-filter prefixed long before unprefixed (18);
       without the prefix iOS paints the 88% wash unblurred — content ghosts
       through the sticky chrome. */
    -webkit-backdrop-filter: saturate(1.4) blur(8px);
    backdrop-filter: saturate(1.4) blur(8px);
    border-bottom: 1px solid var(--sph-border);
  }
  .web-header__inner {
    display: flex;
    align-items: center;
    gap: clamp(1rem, 4vw, 2.5rem);
    padding-block: 0.9rem;
  }
  .web-header__actions {
    display: inline-flex;
    align-items: center;
    gap: 0.75rem;
    flex-shrink: 0;
  }
  .web-brand {
    display: inline-flex;
    align-items: center;
    color: var(--sph-text);
    text-decoration: none;
    /* Let the brand grid cell shrink below the logo's intrinsic width instead
       of overflowing under the actions group (seen on zoomed iPhones, whose
       layout viewport is 320pt: the CTA painted over the wordmark). */
    min-width: 0;
  }
  /* SDS brand ensemble (mark + wordmark): black in light, white in dark, swapped on
     the resolved data-theme so it follows the manual toggle (not just the OS). The
     intrinsic 1869×340 ratio (set via width/height attrs) keeps height-only sizing
     from shifting layout. */
  .web-logo__img {
    display: block;
    height: clamp(1.4rem, 2.4vw, 1.85rem);
    width: auto;
    /* Last-resort shrink: if the cell is ever narrower than the ensemble, scale
       the drawing down in-ratio (left-anchored) rather than spilling under the
       CTA. The stacked-CTA arrangement below keeps this from actually binding. */
    max-width: 100%;
    object-fit: contain;
    object-position: left center;
  }
  .web-logo__img--dark { display: none; }
  :root[data-theme="dark"] .web-logo__img--light { display: none; }
  :root[data-theme="dark"] .web-logo__img--dark { display: block; }
  .web-nav {
    display: flex;
    gap: clamp(0.75rem, 2.5vw, 1.75rem);
    margin-inline-start: auto;        /* push nav toward the actions group */
    align-items: center;
  }
  /* Narrow viewports: two intentional rows instead of accidental flex-wrap —
     row 1 = brand left + actions (CTA · theme toggle) right, row 2 = the nav
     links. Chosen over a hamburger (2026-07-19): three links and the primary
     CTA stay visible with zero JS. 62rem ≈ where the single row stops fitting
     with five non-wrapping links (was 56rem with three links; measured 2026-08-15:
     the row overflows the viewport between ~900 and ~965px). */
  @media (max-width: 62rem) {
    .web-header__inner {
      display: grid;
      grid-template-columns: minmax(0, 1fr) auto;
      grid-template-areas:
        "brand actions"
        "nav   nav";
      row-gap: 0.65rem;
      align-items: center;
    }
    .web-brand { grid-area: brand; }
    .web-header__actions { grid-area: actions; justify-self: end; }
    .web-nav {
      grid-area: nav;
      margin-inline-start: 0;
      flex-wrap: wrap;
      column-gap: 1.25rem;
    }
    /* The two-row sticky header is ~7rem tall — the single-row 6.5rem
       clearance would park anchor targets underneath it. */
    .block[id] { scroll-margin-top: 8.5rem; }
  }
  .web-nav__link {
    font-family: var(--sph-font-display);
    font-weight: var(--sph-weight-semibold);
    font-size: var(--sph-fs-sm);
    color: var(--sph-text);
    text-decoration: none;
    padding-block: 0.25rem;
    border-bottom: 2px solid transparent;
    white-space: nowrap;   /* links wrap as units ("Value Intelligence"), never inside */
  }
  .web-nav__link:hover { color: var(--sph-text-accent); }
  .web-nav__link[aria-current="page"] {
    color: var(--sph-text-accent);
    border-bottom-color: var(--sph-text-accent);
  }

  /* Manual light/dark toggle — moon in light, sun in dark. */
  .web-theme-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.25rem;
    height: 2.25rem;
    padding: 0;
    border: 1px solid var(--sph-border);
    border-radius: 999px;
    background: transparent;
    color: var(--sph-text);
    cursor: pointer;
    -webkit-appearance: none;
    appearance: none;
  }
  .web-theme-toggle:hover {
    color: var(--sph-text-accent);
    border-color: var(--sph-text-accent);
  }
  .web-theme-toggle svg { width: 1.15rem; height: 1.15rem; }
  .web-theme-toggle__moon { display: block; }
  .web-theme-toggle__sun { display: none; }
  :root[data-theme="dark"] .web-theme-toggle__moon { display: none; }
  :root[data-theme="dark"] .web-theme-toggle__sun { display: block; }

  /* ── Phone header (after the base chrome rules so equal-specificity
     overrides win by source order) ──────────────────────────────────────────
     The wrapped header is several rows tall — sticky, it would keep a third
     of a zoomed iPhone's 693pt-high viewport covered, over a backdrop-filter
     wash that iOS renders patchily mid-scroll. In normal flow it scrolls away
     with the page: brand, CTA and all five links stay visible at the top (the
     zero-JS promise of the 2026-07-19 no-hamburger decision), and the content
     below is never overlaid. */
  @media (max-width: 44rem) {
    .web-header {
      position: static;
      background: var(--sph-bg);
      -webkit-backdrop-filter: none;
      backdrop-filter: none;
    }
    /* No sticky chrome to clear — just breathing room above the target. */
    .block[id] { scroll-margin-top: 1.25rem; }
  }
  /* Narrow phones (≤27rem; a zoomed iPhone reports 320pt): brand + CTA +
     toggle can no longer share a row — at 393pt they fit with ZERO slack, and
     below that the CTA painted over the logo. Dissolve the actions group into
     the header grid (display: contents) and give the CTA its own full-width
     row between brand and nav. */
  @media (max-width: 27rem) {
    .web-header__inner {
      grid-template-areas:
        "brand toggle"
        "cta   cta"
        "nav   nav";
    }
    .web-header__actions { display: contents; }
    .web-header__actions .sph-btn {
      grid-area: cta;
      justify-self: stretch;
      text-align: center;
    }
    .web-theme-toggle { grid-area: toggle; justify-self: end; }
    /* One type step down buys the five links a 2-row wrap instead of 3. */
    .web-nav__link { font-size: var(--sph-fs-xs); }
  }

  /* ── Footer ───────────────────────────────────────────────────────────────
     Brand tagline lockup · link columns · corporate line. Follows the theme. */
  .web-footer {
    background: var(--sph-bg-muted);
    border-top: 1px solid var(--sph-border);
    padding-block: clamp(2.5rem, 5vw, 4rem);
    margin-top: clamp(2.5rem, 6vw, 5.5rem);
  }
  /* Brand ensemble above the tagline lockup — the header's logo swap-pair
     (.web-logo__img--light/--dark) reused. The mark spans the full width of the tagline
     lockup below it: the brand block hugs the tagline (width: fit-content), and the logo
     stretches to that width (min-width: 100%). width: 0 keeps the SVG's 1869-wide
     intrinsic size from feeding the fit-content measure (which would blow the column
     out); height: auto then rebuilds the height from the 1869×340 ratio, undistorted. */
  .web-footer__brand { width: fit-content; }
  .web-footer__logo { margin-bottom: clamp(0.85rem, 2vw, 1.3rem); }
  .web-footer__logo .web-logo__img { width: 0; min-width: 100%; height: auto; }
  /* The SDS tagline lockup never wraps; size it fluidly so it always fits down to
     mobile (the SDS's own "size the container to fit" guidance), capped below the
     --md primary so it stays a footer-scale brand line. */
  .web-footer .sph-tagline__primary {
    font-size: clamp(var(--sph-fs-md), 4.5vw, var(--sph-fs-xl));
  }
  /* Keep the descriptor on one line; the min-content brand column accommodates
     it. Like the primary above, size it fluidly so the one-line lockup fits the
     measure down to a 320px viewport (bead vqt.4) — the SDS fixes it at
     --sph-fs-sm, which is ~348px wide and only fits from ~380px up. */
  .web-footer .sph-tagline__descriptor {
    white-space: nowrap;
    font-size: clamp(var(--sph-fs-micro), 3.5vw, var(--sph-fs-sm));
    /* Small gold caps → the text-safe gold (SDS a11y rule): raw --sph-primary
       reads at ~2.2:1 on the muted footer ground (axe sweep, Aug 2026). */
    color: var(--sph-text-accent);
  }
  /* Align the two lines to one width. Fit the lockup to its widest line; the
     descriptor (real word-spaces) justify-fills, and the primary — whose squares are
     margin spans, not whitespace, so justify can't stretch it — is distributed with
     flex space-between. Both then share the same left and right edges. */
  .web-footer .sph-tagline { width: fit-content; }
  .web-footer .sph-tagline__descriptor { text-align-last: justify; }
  .web-footer .sph-tagline__primary {
    white-space: normal;
    display: flex;
    align-items: center;
    justify-content: space-between;
  }
  /* Layered composition — three calm, full-width strata: brand identity, the
     navigation map, the corporate line. Stacking the map UNDER the brand
     (rather than beside it) is what lets all five columns share one aligned
     grid row at the 75rem measure — the old side-by-side flex wrapped them
     into two staggered, unaligned rows. */
  .web-footer__top {
    display: grid;
    gap: clamp(2.25rem, 5vw, 3.25rem);
  }
  /* The link columns sit on one equal-track grid, so the titles, their gold
     rules, and every sublist start on common vertical lines. Tracks re-wrap
     5 → 3 → 2 → 1 as the viewport narrows — always aligned, never scattered.
     A hairline separates the map from the brand, mirroring the legal rule
     below it, so the three strata share one rhythm. */
  .web-footer__nav {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 11rem), 1fr));
    gap: 2.25rem 2rem;
    align-items: start;
    border-top: 1px solid var(--sph-border);
    padding-top: clamp(1.75rem, 4vw, 2.5rem);
  }
  /* Each column subgrids its title + sublist onto the nav's rows, so every
     gold title rule in a row sits on ONE shared line even when a long title
     ("VALUE INTELLIGENCE") wraps to two lines — the wrapped title grows the
     shared row and the one-liners bottom-anchor onto the same rule. row-gap 0
     keeps the title→list spacing on the sublist's own margin, not the nav's
     band gap. Browsers without subgrid simply fall back to per-column rows. */
  .web-footcol {
    display: grid;
    grid-template-rows: subgrid;
    grid-row: span 2;
    row-gap: 0;
  }
  .web-footcol__title { align-self: end; }
  .web-footcol__title {
    display: block;
    font-family: var(--sph-font-display);
    font-weight: var(--sph-weight-bold);
    font-size: var(--sph-fs-base);
    line-height: var(--sph-lh-snug);
    letter-spacing: var(--sph-tracking-wider);
    text-transform: uppercase;
    color: var(--sph-text);
    text-decoration: none;
    margin: 0;
    padding-block-end: var(--sph-space-2);
    border-block-end: 2px solid var(--sph-primary);
  }
  .web-footcol__title:hover { color: var(--sph-text-accent); }
  /* A visible branch line and deeper inset make every permanently expanded list read
     as children of its primary section rather than as another flat menu. */
  .web-footcol__sublist {
    list-style: none;
    display: grid;
    align-content: start;
    align-self: start;
    gap: 0.55rem;
    margin: 0.95rem 0 0 0.2rem;
    padding-inline-start: 1.45rem;
    border-inline-start: 1px solid var(--sph-border);
  }
  .web-footcol__sublist li { position: relative; list-style: none; padding-inline-start: 0; }
  /* SDS base.css assigns a square directly to every ul li. Suppress that
     inherited marker so the guide-node below is the only visible bullet. */
  .web-footcol__sublist li::marker { content: ""; }
  .web-footcol__sublist li::before {
    content: "";
    position: absolute;
    inset-inline-start: -1.6rem;
    top: 0.65em;
    width: 0.3rem;
    height: 0.3rem;
    background: var(--sph-primary);
  }
  .web-footcol__sublist a {
    /* -soft, not -muted: 12px links on the muted footer ground are ~2.9:1
       with muted ink — links must clear the 4.5:1 small-text bar (axe sweep,
       Aug 2026). */
    color: var(--sph-text-soft);
    font-size: var(--sph-fs-xs);
    font-weight: var(--sph-weight-regular);
    line-height: var(--sph-lh-snug);
    text-decoration: none;
  }
  .web-footcol__sublist a:hover { color: var(--sph-text-accent); }
  .web-footcol--knowledge { padding-top: 0; }
  .web-footcol--knowledge .sph-btn {
    display: grid;
    justify-items: center;
    white-space: normal;
    line-height: var(--sph-lh-snug);
  }
  .web-footcol--knowledge .sph-btn > span { display: block; }
  /* Reference row (glossary, publications) — sits between the nav columns and
     the legal line, carrying the legal row's top rule so the two read as one
     closing group rather than three stacked bars. */
  .web-footer__reference {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem 1.5rem;
    margin-top: clamp(1.5rem, 4vw, 2.5rem);
    padding-top: 1.25rem;
    border-top: 1px solid var(--sph-border);
    font-size: var(--sph-fs-sm);
  }
  .web-footer__reference a { color: inherit; text-decoration: none; }
  .web-footer__reference a:hover { text-decoration: underline; }
  /* The legal line then follows without a second rule of its own. */
  .web-footer__reference + .web-footer__legal {
    margin-top: 0.75rem;
    padding-top: 0;
    border-top: 0;
  }

  .web-footer__legal {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem 1.5rem;
    align-items: baseline;
    margin-top: clamp(1.5rem, 4vw, 2.5rem);
    padding-top: 1.25rem;
    border-top: 1px solid var(--sph-border);
    /* -soft, not -muted: 14px legal text (and its links) on the muted footer
       ground is ~2.9:1 with muted ink (axe sweep, Aug 2026). */
    color: var(--sph-text-soft);
    font-size: var(--sph-fs-sm);
  }
  .web-footer__legal-links { display: inline-flex; gap: 1rem; }
  .web-footer__legal-links a { color: inherit; text-decoration: none; }
  .web-footer__legal-links a:hover { color: var(--sph-text-accent); }
  .web-footer__legal-spacer { margin-inline-start: auto; }
  /* Trademark notice — the footer's last, quietest line: one type step below
     the legal row, same muted ink, no rule of its own (it belongs to the
     legal stratum, not a fourth layer). */
  .web-footer__notice {
    margin: 0.85rem 0 0;
    /* -soft like the legal row above it, for the same contrast reason. */
    color: var(--sph-text-soft);
    font-size: var(--sph-fs-xs);
    line-height: var(--sph-lh-snug);
  }

  /* ── DeckLang fragment hosts on phones (beads vqt.1 / vqt.2) ──────────────
     The SDS is compiled for a fixed 1920×1080 deck stage and ships zero
     screen breakpoints, so its layout compositions never collapse on a phone.
     The adaptation lives website-side by design: generated presentations load
     only the SDS stylesheets, never this file, so decks cannot be affected.
     (The SDS-side alternative — container queries — is bead vqt.3, postponed.)

     It lives in this always-linked shell file, not a per-block delta, because
     several block types embed compiled fragments and per-block CSS loads only
     on pages using that block. Every element that hosts a compiled fragment
     carries the `decklang-host` marker class (today: the decklang block's
     measure, each carousel page); a future host opts in by adding the class. */
  @media (max-width: 44rem) {
    /* Deck grids are fixed multi-column — stack every one to a single column.
       min-width: 0 lets a former grid/stack cell shrink below its content's
       min-content width, so a wide table scrolls (below) instead of widening
       the page. */
    .decklang-host :is(.sph-grid-2, .sph-grid-3, .sph-grid-4, .sph-grid-5,
        .sph-grid-chart, .sph-grid-matrix) {
      grid-template-columns: 1fr;
    }
    .decklang-host :is(.sph-grid-2, .sph-grid-3, .sph-grid-4, .sph-grid-5,
        .sph-grid-chart, .sph-grid-matrix) > * {
      min-width: 0;
    }

    /* The DATA2VALUE chain is a wide five-bubble brand composition. Although
       the SVG can shrink to a phone, its icons stop carrying useful meaning.
       When explanatory cards are present, make those cards the mobile process:
       hide only the paired chain (standalone chains remain visible), stack the
       cards in source order, and restore the stage names that the bubbles carry
       at desktop widths. */
    .decklang-host .sph-chain-cards > .sph-process-chain {
      display: none;
    }
    .decklang-host .sph-process-cards {
      grid-template-columns: minmax(0, 1fr);
      gap: var(--sph-space-3);
      counter-reset: d2v-step;
    }
    .decklang-host .sph-process-card[data-bubble] {
      grid-column: 1;
      min-width: 0;
      counter-increment: d2v-step;
    }
    .decklang-host .sph-process-card[data-bubble="raw-data"] {
      --d2v-mobile-stage: "Raw data";
    }
    .decklang-host .sph-process-card[data-bubble="partfinder"] {
      --d2v-mobile-stage: "PartFinder";
    }
    .decklang-host .sph-process-card[data-bubble="nlpp"] {
      --d2v-mobile-stage: "NLPP";
    }
    .decklang-host .sph-process-card[data-bubble="opportunity-tracker"] {
      --d2v-mobile-stage: "Opportunity Tracker";
    }
    .decklang-host .sph-process-card[data-bubble="pl"] {
      --d2v-mobile-stage: "P&L";
    }
    .decklang-host .sph-process-card[data-bubble]::before {
      content: counter(d2v-step, decimal-leading-zero) " · " var(--d2v-mobile-stage);
      color: var(--sph-text-muted);
      font-family: var(--sph-font-display);
      font-size: var(--sph-fs-xs);
      font-weight: var(--sph-weight-semibold);
      letter-spacing: var(--sph-tracking-wide);
      line-height: var(--sph-lh-tight);
      text-transform: uppercase;
    }

    /* Deck-tuned tables are sized for projector distance (18px body, 22px row
       anchors, 14–16px cell padding). Step each role down one token so the
       table reads at phone size; th already sits at the 12px floor. */
    .decklang-host .sph-table--deck {
      font-size: var(--sph-fs-sm);
    }
    .decklang-host .sph-table--deck :is(th, td) {
      padding: var(--sph-space-2) var(--sph-space-3);
    }
    .decklang-host .sph-table--deck tbody th,
    .decklang-host .sph-table--deck th[scope="row"] {
      font-size: var(--sph-fs-base);
    }
    .decklang-host .sph-table--deck caption {
      font-size: var(--sph-fs-md);
    }

    /* Safety net: if a table's min-content width still exceeds the phone
       viewport, its immediate wrapper scrolls horizontally instead of
       blowing out the page width. */
    .decklang-host :has(> .sph-table) {
      overflow-x: auto;
    }

    /* The SDS never resets the UA's 40px side margins on <figure> (a slide is
       wide enough for them); in a phone-width column they push the image past
       the page edge. Auto margins keep a narrower-than-column image centred. */
    .decklang-host .sph-image {
      margin-inline: auto;
    }
  }

  /* ── Contrast-safe gold on the web (axe sweep, Aug 2026) ─────────────────
     The SDS's own rule (tokens.css, a11y review July 2026): brand gold
     --sph-primary is 3.0:1 on white — legal for DISPLAY sizes only; small
     gold text takes --sph-text-accent (≥4.5:1, theme-aware). These deck
     components ship small gold labels sized for a projector, where the rule
     predates them; on the web, pin them to the text-safe token. Decks never
     load this file, so slides are untouched. */
  /* .web-main scope, not .decklang-host: deck compositions also reach pages
     outside fragment hosts (the company tier ladder), and decks never render
     a .web-main. */
  .num-tile--cream .num-tile__num,
  .web-main .sph-process-card__label,
  .web-main .sph-tier-ladder__share--label,
  .web-main .sph-team__eyebrow,
  .web-main .sph-table--deck caption {
    color: var(--sph-text-accent);
  }
  /* Same rule, tone-aware: the stat label's SDS default is raw gold behind an
     optional per-block tone. Swap only the gold fallback; authored tones keep
     their color. */
  .sph-stat-block__label {
    color: var(--sph-tone, var(--sph-text-accent));
  }
  /* Muted ink one step short: --sph-text-muted reads at ~4.0:1 on white and
     less on tinted grounds — under the 4.5:1 small-text bar. Web-side bump to
     -soft for the small deck-composition text the axe sweep flagged. */
  .web-main .sph-tier-ladder__name {
    color: var(--sph-text-soft);
  }

  /* Text-entry controls adapt to the measure, never the reverse. Without a
     width, an input's default intrinsic size (WebKit ≈ 15rem at 16px — a
     third wider than Chromium's, which is why only iPhones showed it) is a
     min-content floor that propagates up single-column grids: the contact
     form and the ROI volume row pushed phone pages into sideways scroll, and
     content wider than the layout viewport makes iOS Safari zoom the page
     out. Text-entry only: checks/radios/ranges/buttons keep their intrinsic
     size, and selects stay per-block (the ROI unit select is deliberately
     shrink-wrapped). */
  .web-main :is(input:not([type="checkbox"], [type="radio"], [type="range"],
      [type="submit"], [type="button"], [type="hidden"], [type="image"]), textarea) {
    width: 100%;
    min-width: 0;
  }

  /* SDS buttons carry white-space: nowrap (right for a 1920px stage); on a
     phone a long CTA label must wrap instead of forcing the page wider than
     the viewport (bead c5x — the roi-estimator CTA). Web-only, mobile-only:
     decks never load this file. */
  @media (max-width: 44rem) {
    .sph-btn {
      white-space: normal;
      text-align: center;
    }
  }

  /* Deck-scale SDS type on phones (bead hhz). The SDS fixes these components
     at stage sizes with no breakpoints — .sph-lead at --sph-slide-body-size
     (24px, the deck body), .num-tile title/body at 22px — and web blocks
     consume them directly, so they read oversized on a phone. Each clamp is
     chosen to EQUAL the SDS size at the top of this media query (its vw term
     already exceeds the cap there), so type is pixel-identical to today at
     >=44rem and shrinks fluidly only on true phone widths. Site-wide within
     the web layer on purpose: the same components appear inside DeckLang
     fragments; decks never load this file. */
  @media (max-width: 44rem) {
    .sph-lead {
      font-size: clamp(var(--sph-fs-base), 5vw, var(--sph-slide-body-size));
    }
    /* An inline Data·Value·Impact lockup used AS a display title (the
       "Putting Value Intelligence to work" section head) is a never-wrapping
       brand ensemble ~13.5× its font size wide. At the title's 1.6rem phone
       floor that is ~346px — wider than the 288px measure of a zoomed iPhone
       (320pt layout viewport), and the overflow makes iOS Safari zoom the
       whole page out (the right-edge gutter on the reference screenshots).
       Size it to the measure instead: 6.3vw ≈ (100vw − 2rem side padding)
       ÷ 13.5em with slack; the 1em cap keeps it at the title size wherever
       it already fits. Small DVI marks (eyebrows, card labels) are narrower
       and unaffected. */
    :is(.web-section-head__title, .web-display-title, .hero__title) .sph-dvi--inline {
      font-size: min(1em, 6.3vw);
    }
    .num-tile__title {
      /* --sph-fs-lg == the tile's literal 22px */
      font-size: clamp(var(--sph-fs-md), 4.8vw, var(--sph-fs-lg));
    }
    .num-tile__body {
      font-size: clamp(var(--sph-fs-base), 4.5vw, var(--sph-fs-lg));
    }
  }
}
