/* 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; }

  /* Hash targets need breathing room below the sticky header. The header is about
     --space-10 tall on desktop and may wrap on narrow screens; this token-only
     margin keeps the target eyebrow visible in both cases. */
  .block[id] { scroll-margin-block-start: calc(var(--sph-space-11) + var(--sph-space-7)); }

  /* ── 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 {
    text-wrap: balance;
  }

  /* ── 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__title .sph-accent { color: var(--sph-primary); }
  .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. */
  .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);
    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;
  }
  /* 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;
  }
  .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;
    }
  }
  .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; }

  /* ── 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));
  }
  /* 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 {
    color: var(--sph-text-muted);
    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);
    color: var(--sph-text-muted);
    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;
    color: var(--sph-text-muted);
    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;
    }
  }

  /* 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));
    }
    .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));
    }
  }
}
