/* ============================================================
   HTML ASSET — STRUCTURAL TEMPLATE
   ============================================================

   This template is brand-agnostic by design. The cascade is:

     1. RESET                  — normalize browser defaults
     2. TOKENS                 — design tokens (brand interface)
     3. THEME OVERRIDES        — dark-mode token overrides
     4. LAYOUT (PRESCRIBED)    — page shell, grid, content/sidebar
     5. NAVIGATION (PRESCRIBED)— page-nav for both nav positions
     6. MOBILE (PRESCRIBED)    — sticky header + drawer
     7. TYPOGRAPHY (PRESCRIBED)— type scale, prose elements
     8. COMPONENTS             — buttons, badges, avatars, lists,
                                 tables, cards, pagination
     9. SITE FOOTER            — bottom-of-asset footer chrome
    10. RESPONSIVE             — small-viewport adjustments
    11. BRAND OVERRIDES        — landing zone for structural brand
                                 selector overrides (component-level
                                 brand work that token overrides can't
                                 reach — e.g. resizing the logo slot
                                 to fit a brand's wordmark)

   --------------------------------------------------------------
   NAV MODE — multi-page is the DEFAULT
   --------------------------------------------------------------
   Two navigation modes are supported. The default is multi-page.

     • multi-page (DEFAULT): each section of the asset is its own
       HTML file. The .page-nav__links hrefs are file paths
       (e.g. href="./positioning.html"). Each file ships a
       slightly different markup — same nav, same chrome, only the
       .is-active class moves to the link matching the current
       file. There is no scroll-spy in this mode; the JS controller
       gracefully no-ops because there's only one <section> in
       <main.content> per file. <main.content> in each file holds
       the content for that page only.

     • anchor (opt-in): the asset is a single long-scroll page with
       N <section> children in <main.content>, each anchored by
       id. The .page-nav__links hrefs are #section-id anchors. The
       JS controller activates IntersectionObserver scroll-spy and
       smooth-scrolls on link click. Use this when the user
       explicitly asks for a single-page asset, OR when the
       content is short enough that a single scroll is friendlier
       than multiple file loads.

   The CSS is identical for both modes — only the markup pattern
   and the href values differ. The .is-active link style (color
   + dot via ::before pseudo) renders the same in either case.
   --------------------------------------------------------------

   A brand skill applies through TWO insertion points:

     • Section 2 / 3 (top of stylesheet): token overrides. Add a
       second :root block (and matching [data-theme="dark"] block)
       immediately after the template's defaults. CSS variables
       flow through to every subsequent rule, so the position is
       correct here.

     • Section 11 (bottom of stylesheet): structural selector
       overrides. Any brand rule that targets a template selector
       directly (e.g. `.logo-block { width: auto }`) must live
       AFTER the template's component definitions or it loses the
       cascade to them. Section 11 is the designated landing zone.

   The layout, spacing, and structural rules in sections 4–8 are
   the template's IP and should not be edited in place. When a
   brand needs to bend a component's presentation (sizing, colors
   set as literals rather than tokens, backgrounds), the override
   goes in section 11. The structural invariants documented in
   SKILL.md (nav slot order, icon sprite, JS data-attributes)
   should not be overridden anywhere.

   The desktop nav layout is switched via the data-nav-position
   attribute on <body>: "side" (default, 3-column with left rail)
   or "top" (2-column with top bar). Below 640px both collapse to
   the mobile header + drawer.
   ============================================================ */

/* ============================================================
   1. RESET
   ============================================================ */
*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
html {
  background: var(--color-bg-outer);
  min-height: 100%;
  overflow-x: hidden;
}
body {
  font-family: var(--font-family-body);
  color: var(--color-text-primary);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}
button { font: inherit; color: inherit; background: none; border: 0; padding: 0; cursor: pointer; }
ul, ol { list-style: none; margin: 0; padding: 0; }
h1, h2, h3, h4, h5, h6 { margin: 0; font-weight: inherit; font-size: inherit; }
p { margin: 0; }
svg { display: block; }
a { color: inherit; text-decoration: none; }
table { border-collapse: collapse; width: 100%; }
dialog { padding: 0; border: 0; background: transparent; max-width: 100%; max-height: 100%; }

/* ============================================================
   2. TOKENS — Brand Interface
   ============================================================
   These are the tokens a brand skill overrides. Defaults below
   are greyscale stand-ins. Override these in a separate :root
   block AFTER this one to apply brand styling.
   ============================================================ */
:root {
  /* --- Typography ----------------------------------------- */
  --font-family-display: 'Roboto', system-ui, -apple-system, sans-serif;
  --font-family-body: 'Roboto', system-ui, -apple-system, sans-serif;
  --font-weight-light: 300;
  --font-weight-regular: 400;
  --font-weight-medium: 500;
  --font-weight-semibold: 600;
  --font-weight-bold: 700;
  --font-weight-extrabold: 800;

  /* --- Colors -------------------------------------------- */
  /* Surfaces */
  --color-bg-page: #ffffff;
  --color-bg-outer: #ffffff;
  --color-bg-elevated: #ffffff;
  --color-bg-card: rgba(34, 34, 34, 0.1);
  --color-bg-accent: rgba(34, 34, 34, 0.05);
  --color-bg-footer: #222222;
  --color-bg-header: var(--color-bg-elevated);  /* top-nav + mobile-header surface */

  /* Top border on the footer. Default transparent — a dark footer surface
     on a light page surface already creates a clear visual break. A brand
     bridge should override this in [data-theme="dark"] when the footer bg
     goes transparent, so the footer doesn't blend into the body. */
  --color-footer-border: transparent;

  /* Drawer panel surface — must be SOLID OPAQUE in both themes
     so the open drawer fully obscures the page behind it. The
     drawer's role is to be a temporary navigation overlay; if it
     looked translucent the page underneath would compete with the
     drawer's own content. Light mode defaults to white; dark mode
     defaults to a solid lifted-dark surface so it visually clears
     the page color underneath. */
  --color-bg-drawer: #ffffff;
  /* Drawer text/icon color — pairs with --color-bg-drawer. Drawer-
     internal elements (title, nav links, controls) derive their colors
     from this via `currentColor`, so a brand that sets drawer bg to a
     primary color (e.g. nf-blue) and text to white gets a coherent
     branded drawer in both modes without touching internal selectors. */
  --color-text-drawer: #222222;

  /* Card accent variants — colored card surfaces for visual
     emphasis (see .card--accent / .card--feature / .card--on-dark
     in the components section). Defaults are template greyscale;
     a brand bridge overrides these to its palette. */
  --color-card-accent-bg:       #222222;   /* primary accent — brand-painted */
  --color-card-accent-text:     #ffffff;
  --color-card-feature-bg:      #444444;   /* dark feature card */
  --color-card-feature-text:    #ffffff;
  --color-card-on-dark-bg:      #ffffff;   /* white card for placement on dark surface */
  --color-card-on-dark-text:    #222222;

  /* Sidebar card surface — by default mirrors the accent variant
     so sidebar reads as a distinct "takeaway" zone, not a second
     column of default cards. Brand bridges can remap to a
     secondary palette color when one is in play. */
  --color-sidebar-card-bg:      var(--color-card-accent-bg);
  --color-sidebar-card-text:    var(--color-card-accent-text);

  /* Data viz tokens — minimal palette for charts embedded in
     the asset (Gantt, bar, line, etc.). Greyscale defaults; a
     brand bridge maps these to its primary data color. See the
     `data-visualization` reference for the full chart system. */
  --color-data-primary:         #222222;
  --color-data-primary-track:   rgba(34, 34, 34, 0.10);
  --color-data-grid:            rgba(34, 34, 34, 0.15);
  --color-data-text:            #222222;

  /* Text */
  --color-text-primary: #222222;
  --color-text-secondary: rgba(34, 34, 34, 0.5);
  --color-text-inverse: #ffffff;
  --color-text-meta: rgba(34, 34, 34, 0.5);
  --color-text-footer: rgba(255, 255, 255, 0.5);
  --color-text-footer-secondary: rgba(255, 255, 255, 0.5);
  --color-text-link: #222222;
  --color-text-link-hover: #000000;

  /* Navigation links */
  --color-nav-link: rgba(34, 34, 34, 0.5);
  --color-nav-link-hover: #222222;
  --color-nav-link-active: #222222;
  --color-nav-link-marker: #222222;

  /* Side-nav rail background (painted by body::before in side-nav
     mode). Default is TRANSPARENT in both light and dark — the
     rail's right border alone separates it from content. This
     reads as part of the page surface, not a distinct chrome
     surface. A brand that wants a tinted rail can override this
     token; most brands should leave it. */
  --color-nav-rail-bg: transparent;

  /* Buttons */
  --color-button-primary-bg: #222222;
  --color-button-primary-text: #ffffff;
  --color-button-secondary-bg: transparent;
  --color-button-secondary-text: #222222;
  --color-button-secondary-border: #222222;
  --color-button-tertiary-bg: rgba(34, 34, 34, 0.1);
  --color-button-tertiary-text: #222222;

  /* Badges */
  --color-badge-filled-bg: #222222;
  --color-badge-filled-text: #ffffff;
  --color-badge-ghost-bg: rgba(34, 34, 34, 0.1);
  --color-badge-ghost-text: #222222;
  --color-badge-stroke-border: #222222;
  --color-badge-stroke-text: #222222;
  --color-badge-status: #222222;

  /* Avatars */
  --color-avatar-dark-bg: #222222;
  --color-avatar-dark-text: #ffffff;
  --color-avatar-light-bg: rgba(34, 34, 34, 0.1);
  --color-avatar-light-text: rgba(34, 34, 34, 0.5);
  --color-avatar-white-bg: #ffffff;
  --color-avatar-white-text: #222222;

  /* Icon buttons */
  --color-icon-bg: rgba(34, 34, 34, 0.1);
  --color-icon-bg-hover: rgba(34, 34, 34, 0.18);
  --color-icon-fg: #222222;

  /* Lines & shadow */
  --color-border: rgba(34, 34, 34, 0.1);
  --color-divider: rgba(34, 34, 34, 0.1);
  --color-shadow: rgba(0, 0, 0, 0.08);

  /* Logo (stand-in: solid block). A brand skill replaces the
     placeholder content; --logo-color is the fallback fill. */
  --logo-color: #222222;
  --logo-color-inverse: #ffffff;

  /* ============================================================
     LAYOUT TOKENS — PRESCRIBED (template IP)
     ============================================================
     These should NOT be overridden by brand skills. They define
     the structural identity of the template.
     ============================================================ */
  --page-max-width: 1440px;
  --mobile-breakpoint: 640px;
  --tablet-breakpoint: 800px;

  --side-nav-width: 300px;
  --side-nav-width-narrow: 240px;
  --sidebar-width: 300px;
  --sidebar-offset-top: 440px;

  --top-nav-height: 80px;

  --content-padding-x: 40px;
  --content-padding-top: 160px;       /* both nav modes — strapline aligns with asset title */
  --content-padding-bottom: 80px;

  --card-padding: 30px;
  --card-gap: 40px;

  --button-radius: 30px;
  --badge-radius: 10px;
  --nav-link-radius: 20px;

  --icon-button-size: 52px;
  --icon-button-size-small: 40px;
  --icon-glyph-size: 24px;

  /* Logo slot — bounding box for the logo. A brand's wordmark
     fits within this box, scaling proportionally. The nav slot
     width (220px) is the rail's content area: --side-nav-width
     (300px) minus 40px L+R padding. Capping the slot at the
     content area keeps the logo from pushing into the right
     padding, which previously occurred when a wide wordmark
     was rendered. At ≤1200px the rail narrows to 240px; the
     slot stays at 220px via this hard cap. */
  /* Logo slot width derives from the rail's actual content area
     (--side-nav-width minus 40px L + 40px R padding = 80px total).
     Using calc() means the logo automatically scales when the rail
     narrows at the 1200px breakpoint — no separate override needed. */
  --logo-slot-max-width: calc(var(--side-nav-width) - 80px);
  --logo-slot-max-height: 40px;
  --logo-slot-footer-max-width: 220px;
  --logo-slot-footer-max-height: 60px;

  /* --- Type scale (sizes & leading) ---------------------- */
  --font-size-h1: 64px;
  --line-height-h1: 70px;
  --font-size-h2: 30px;
  --line-height-h2: normal;
  --font-size-h3: 24px;
  --line-height-h3: normal;
  --font-size-h4: 22px;
  --line-height-h4: normal;
  --font-size-h5: 20px;
  --line-height-h5: normal;
  --font-size-strapline: 18px;
  --line-height-strapline: 24px;
  --font-size-body: 20px;
  --line-height-body: 30px;
  --font-size-body-sm: 18px;
  --line-height-body-sm: 24px;
  --font-size-meta: 14px;
  --font-size-button: 18px;
  --line-height-button: 42px;
  --font-size-badge: 14px;
  --font-size-pullquote: 64px;
  --line-height-pullquote: 70px;
  --font-size-nav-link: 18px;
  --line-height-nav-link: 22px;

  /* --- Motion --------------------------------------------- */
  --transition-base: 200ms ease;
  --transition-drawer: 300ms cubic-bezier(0.22, 0.61, 0.36, 1);
}

/* ============================================================
   3. THEME OVERRIDES — Dark Mode
   ============================================================
   A brand skill should provide matching [data-theme="dark"]
   token values to maintain dark-mode parity with its brand.
   ============================================================ */
[data-theme="dark"] {
  /* Surfaces — collapsed to a single black across the page.
     Header and footer carry NO separate surface in dark mode;
     the column rule and the body bg do all the work. */
  --color-bg-page: #000000;
  --color-bg-outer: #000000;
  --color-bg-elevated: #1c1c1c;       /* still used by cards, etc. */
  --color-bg-card: rgba(255, 255, 255, 0.06);
  --color-bg-accent: rgba(255, 255, 255, 0.04);
  --color-bg-footer: transparent;     /* footer matches the page */
  --color-bg-header: transparent;     /* mobile-header + top-nav match the page */
  --color-footer-border: rgba(255, 255, 255, 0.12);  /* matches --color-border so footer separates from the body */
  --color-bg-drawer: #2a2a2a;         /* solid lifted dark — opaque overlay */
  --color-text-drawer: #f4f4f4;       /* drawer text — light in dark mode */

  /* Card accent variants — brand-painted variants typically stay
     theme-invariant (the brand color carries identity); the
     greyscale defaults here flip so the dark-mode template still
     looks reasonable without a brand. */
  --color-card-accent-bg:    #f4f4f4;   /* greyscale inverse in dark */
  --color-card-accent-text:  #111111;
  --color-card-feature-bg:   #2a2a2a;   /* slightly lifted dark in dark mode */
  --color-card-feature-text: #f4f4f4;
  --color-card-on-dark-bg:   #f4f4f4;   /* light card on dark surface */
  --color-card-on-dark-text: #111111;

  /* Sidebar tokens mirror the accent — same theme-invariance
     story when a brand bridge sets them. */
  --color-sidebar-card-bg:   var(--color-card-accent-bg);
  --color-sidebar-card-text: var(--color-card-accent-text);

  /* Data viz — dark-mode defaults (inverted) */
  --color-data-primary:         #f4f4f4;
  --color-data-primary-track:   rgba(255, 255, 255, 0.10);
  --color-data-grid:            rgba(255, 255, 255, 0.15);
  --color-data-text:            #f4f4f4;

  --color-text-primary: #f4f4f4;
  --color-text-secondary: rgba(244, 244, 244, 0.5);
  --color-text-inverse: #111111;
  --color-text-meta: rgba(244, 244, 244, 0.5);
  --color-text-footer: rgba(255, 255, 255, 0.5);
  --color-text-footer-secondary: rgba(255, 255, 255, 0.5);
  --color-text-link: #f4f4f4;
  --color-text-link-hover: #ffffff;

  --color-nav-link: rgba(244, 244, 244, 0.5);
  --color-nav-link-hover: #f4f4f4;
  --color-nav-link-active: #f4f4f4;
  --color-nav-link-marker: #f4f4f4;

  /* Dark mode: drop the rail background; the right border alone
     defines the column against the page surface. */
  --color-nav-rail-bg: transparent;

  --color-button-primary-bg: #f4f4f4;
  --color-button-primary-text: #111111;
  --color-button-secondary-bg: transparent;
  --color-button-secondary-text: #f4f4f4;
  --color-button-secondary-border: #f4f4f4;
  --color-button-tertiary-bg: rgba(255, 255, 255, 0.1);
  --color-button-tertiary-text: #f4f4f4;

  --color-badge-filled-bg: #f4f4f4;
  --color-badge-filled-text: #111111;
  --color-badge-ghost-bg: rgba(255, 255, 255, 0.1);
  --color-badge-ghost-text: #f4f4f4;
  --color-badge-stroke-border: #f4f4f4;
  --color-badge-stroke-text: #f4f4f4;
  --color-badge-status: #f4f4f4;

  --color-avatar-dark-bg: #f4f4f4;
  --color-avatar-dark-text: #111111;
  --color-avatar-light-bg: rgba(255, 255, 255, 0.1);
  --color-avatar-light-text: rgba(244, 244, 244, 0.6);
  --color-avatar-white-bg: rgba(255, 255, 255, 0.15);
  --color-avatar-white-text: #f4f4f4;

  --color-icon-bg: rgba(255, 255, 255, 0.1);
  --color-icon-bg-hover: rgba(255, 255, 255, 0.18);
  --color-icon-fg: #ffffff;

  --color-border: rgba(255, 255, 255, 0.12);
  --color-divider: rgba(255, 255, 255, 0.1);
  --color-shadow: rgba(0, 0, 0, 0.5);

  /* Logo stays light regardless of placement in dark mode —
     no "inverse" treatment makes the footer logo look faded. */
  --logo-color: #f4f4f4;
  --logo-color-inverse: #f4f4f4;
}

/* ----------------------------------------------------------------
   BRAND LAYER INSERTION POINT
   A brand bridge inserts its token override block here, immediately
   after the default :root and [data-theme="dark"] blocks above and
   immediately before the layout section below. Programmatic builds
   should anchor on the `4. LAYOUT — PRESCRIBED` comment that follows
   this marker — never on internal token names within the default
   blocks, which can move as new tokens are added.
   ---------------------------------------------------------------- */

/* ---- Good & Prosper brand tokens ---- */
:root {
  --font-family-display: 'IBM Plex Serif', Georgia, 'Times New Roman', serif;
  --font-family-body: 'IBM Plex Serif', Georgia, 'Times New Roman', serif;
  --font-family-mono: 'IBM Plex Mono', ui-monospace, 'SFMono-Regular', Menlo, monospace;
  --gp-ink: #0F3B4F; --gp-teal: #175A73; --gp-paper: #F7F4EC; --gp-sage: #9CC8C0; --gp-sand: #EDE7DA;
  --color-bg-page: #F7F4EC; --color-bg-outer: #F7F4EC; --color-bg-elevated: #FBF9F4;
  --color-bg-card: rgba(15, 59, 79, 0.06); --color-bg-accent: #EDE7DA; --color-bg-footer: #0F3B4F;
  --color-bg-header: #F7F4EC; --color-bg-drawer: #0F3B4F; --color-text-drawer: #F7F4EC;
  --color-card-accent-bg: #175A73; --color-card-accent-text: #F7F4EC;
  --color-card-feature-bg: #0F3B4F; --color-card-feature-text: #F7F4EC;
  --color-card-on-dark-bg: #FBF9F4; --color-card-on-dark-text: #0F3B4F;
  --color-sidebar-card-bg: #0F3B4F; --color-sidebar-card-text: #F7F4EC;
  --color-data-primary: #175A73; --color-data-primary-track: rgba(23, 90, 115, 0.14);
  --color-data-grid: rgba(15, 59, 79, 0.16); --color-data-text: #0F3B4F;
  --color-text-primary: #0F3B4F; --color-text-secondary: rgba(15, 59, 79, 0.66);
  --color-text-inverse: #F7F4EC; --color-text-meta: rgba(15, 59, 79, 0.62);
  --color-text-footer: rgba(247, 244, 236, 0.85); --color-text-footer-secondary: rgba(247, 244, 236, 0.6);
  --color-text-link: #175A73; --color-text-link-hover: #0F3B4F;
  --color-nav-link: rgba(15, 59, 79, 0.55); --color-nav-link-hover: #0F3B4F;
  --color-nav-link-active: #0F3B4F; --color-nav-link-marker: #175A73;
  --color-button-primary-bg: #175A73; --color-button-primary-text: #F7F4EC;
  --color-button-secondary-text: #175A73; --color-button-secondary-border: #175A73;
  --color-button-tertiary-bg: rgba(23, 90, 115, 0.1); --color-button-tertiary-text: #175A73;
  --color-icon-bg: rgba(15, 59, 79, 0.08); --color-icon-bg-hover: rgba(15, 59, 79, 0.16); --color-icon-fg: #0F3B4F;
  --color-border: rgba(15, 59, 79, 0.16); --color-divider: rgba(15, 59, 79, 0.14);
  --logo-color: #0F3B4F; --logo-color-inverse: #F7F4EC;
}
:root[data-theme="dark"] {
  --color-bg-page: #0A2E43; --color-bg-outer: #0A2E43; --color-bg-elevated: #0F3B4F;
  --color-bg-card: rgba(247, 244, 236, 0.07); --color-bg-accent: rgba(247, 244, 236, 0.05);
  --color-bg-footer: transparent; --color-footer-border: rgba(247, 244, 236, 0.14);
  --color-bg-header: #0A2E43; --color-bg-drawer: #175A73; --color-text-drawer: #F7F4EC;
  --color-card-accent-bg: #175A73; --color-card-accent-text: #F7F4EC;
  --color-card-feature-bg: #12475E; --color-card-feature-text: #F7F4EC;
  --color-card-on-dark-bg: #F7F4EC; --color-card-on-dark-text: #0F3B4F;
  --color-sidebar-card-bg: #175A73; --color-sidebar-card-text: #F7F4EC;
  --color-data-primary: #9CC8C0; --color-data-primary-track: rgba(156, 200, 192, 0.16);
  --color-data-grid: rgba(247, 244, 236, 0.16); --color-data-text: #F7F4EC;
  --color-text-primary: #F7F4EC; --color-text-secondary: rgba(247, 244, 236, 0.68);
  --color-text-inverse: #0F3B4F; --color-text-meta: rgba(247, 244, 236, 0.6);
  --color-text-footer: rgba(247, 244, 236, 0.8); --color-text-footer-secondary: rgba(247, 244, 236, 0.55);
  --color-text-link: #9CC8C0; --color-text-link-hover: #F7F4EC;
  --color-nav-link: rgba(247, 244, 236, 0.55); --color-nav-link-hover: #F7F4EC;
  --color-nav-link-active: #F7F4EC; --color-nav-link-marker: #9CC8C0;
  --color-button-primary-bg: #9CC8C0; --color-button-primary-text: #0A2E43;
  --color-button-secondary-text: #9CC8C0; --color-button-secondary-border: #9CC8C0;
  --color-button-tertiary-bg: rgba(156, 200, 192, 0.12); --color-button-tertiary-text: #9CC8C0;
  --color-icon-bg: rgba(247, 244, 236, 0.1); --color-icon-bg-hover: rgba(247, 244, 236, 0.18); --color-icon-fg: #F7F4EC;
  --color-border: rgba(247, 244, 236, 0.14); --color-divider: rgba(247, 244, 236, 0.12);
  --logo-color: #F7F4EC; --logo-color-inverse: #F7F4EC;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --color-bg-page: #0A2E43; --color-bg-outer: #0A2E43; --color-bg-elevated: #0F3B4F;
    --color-text-primary: #F7F4EC; --color-text-secondary: rgba(247, 244, 236, 0.68);
    --color-data-primary: #9CC8C0; --color-nav-link-active: #F7F4EC; --color-border: rgba(247, 244, 236, 0.14);
  }
}

/* ============================================================
   4. LAYOUT — PRESCRIBED
   ============================================================ */

/* Body shell. Caps at --page-max-width and centers within the
   viewport. Body itself is the PAGE surface; html is the OUTER
   surface (above section 1). Body uses CSS grid for the page
   shell, and is positioned so the rail pseudo (side-nav mode)
   can paint the column background without forcing its row to
   100vh — that's what kept the footer below the fold and let
   extra space appear beneath it. */
body {
  max-width: var(--page-max-width);
  margin: 0 auto;
  background: var(--color-bg-page);
  min-height: 100vh;
  min-height: 100dvh;
  display: grid;
  position: relative;
  isolation: isolate;
}

/* The page body holds the main content and (optional) sidebar
   side by side. The grid template depends on data-nav-position. */
.page-body {
  display: grid;
  position: relative;
  width: 100%;
}

/* ---- Side-nav mode (default): 2-column grid ----
   nav rail | (main + sidebar)
   Nav is a column to the LEFT, footer spans both columns.
   ============================================================
   RAIL BACKGROUND VIA PSEUDO
   We paint the left rail's background with a body::before so
   the .page-nav element itself doesn't have to be 100vh tall.
   That removes the constraint that was forcing the grid row to
   100vh (and the footer below the viewport). The pseudo sits
   behind everything (z-index: 0) and pointer-events: none. */
body[data-nav-position="side"] {
  grid-template-columns: var(--side-nav-width) 1fr;
  grid-template-rows: 1fr auto;
  grid-template-areas:
    "nav body"
    "footer footer";
}
body[data-nav-position="side"]::before {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: var(--side-nav-width);
  background: var(--color-nav-rail-bg);
  border-right: 1px solid var(--color-border);
  z-index: 0;
  pointer-events: none;
}
body[data-nav-position="side"] .page-nav { grid-area: nav; }
body[data-nav-position="side"] .page-body { grid-area: body; }
body[data-nav-position="side"] .site-footer { grid-area: footer; }

body[data-nav-position="side"] .page-body {
  grid-template-columns: minmax(0, 1fr) var(--sidebar-width);
}

/* ---- Top-nav mode: stacked, with top bar full-width ---- */
body[data-nav-position="top"] {
  grid-template-columns: 1fr;
  grid-template-rows: auto 1fr auto;
  grid-template-areas:
    "nav"
    "body"
    "footer";
}
body[data-nav-position="top"] .page-nav { grid-area: nav; }
body[data-nav-position="top"] .page-body { grid-area: body; }
body[data-nav-position="top"] .site-footer { grid-area: footer; }

body[data-nav-position="top"] .page-body {
  grid-template-columns: minmax(0, 1fr) var(--sidebar-width);
}

/* Sidebar-optional fallback. When the .page-body has no .sidebar
   child, collapse to a single-column grid so the main content
   takes the full available width. Required for: Gantt-heavy
   sections where the sidebar is dropped to give the chart room,
   wide-canvas reports, or any asset that omits the sidebar
   intentionally. Without this rule the empty .sidebar column
   still reserves its 300px and leaves a hole on the right.
   :has() is widely supported (Chrome/Safari/Firefox 2023+). */
body[data-nav-position="side"] .page-body:not(:has(> .sidebar)),
body[data-nav-position="top"]  .page-body:not(:has(> .sidebar)) {
  grid-template-columns: minmax(0, 1fr);
}

/* Per-section sidebar suppression (SPA mode only).
   When the .is-current section is marked [data-suppress-sidebar],
   hide the sidebar and collapse the page-body grid to a single
   column — for that section's lifetime only. As soon as the SPA
   controller flips .is-current to a different section, the
   :has() selector re-evaluates and the sidebar returns.

   Use this on sections whose content needs the full width:
   wide diagrams, Gantt charts with many stages, side-by-side
   comparisons, tabular content. Mark the section like:

       <section id="journey" data-suppress-sidebar>…</section>

   In anchor (long-form scrollable) mode, no section ever carries
   .is-current (scroll-spy uses .is-active on nav links, not on
   sections themselves), so this rule simply doesn't fire — the
   sidebar stays consistent across the whole scrollable page,
   which is the correct behavior for a continuous-read asset. */
body:has(main.content > section.is-current[data-suppress-sidebar]) .sidebar {
  display: none;
}
body[data-nav-position="side"]:has(main.content > section.is-current[data-suppress-sidebar]) .page-body,
body[data-nav-position="top"]:has(main.content > section.is-current[data-suppress-sidebar]) .page-body {
  grid-template-columns: minmax(0, 1fr);
}

/* Main content column. Padding from edges of the grid cell.
   The 160px top padding causes the strapline to roughly
   bottom-align with the asset title in the nav rail. */
.content {
  padding: var(--content-padding-top) var(--content-padding-x) var(--content-padding-bottom);
  min-width: 0;  /* allow shrinking inside grid */
  position: relative;
  z-index: 1;
}

.content > section + section {
  margin-top: 80px;
}

/* Sidebar — supplementary content. In side-nav mode the sidebar
   is offset from the top so it visually aligns with the body
   prose (after the H1). In top-nav mode the offset is reduced
   since the top bar provides chrome at the top. No horizontal
   padding by default — cards inside fill the column width.
   Mobile breakpoint reintroduces L/R padding. */
.sidebar {
  padding-bottom: var(--content-padding-bottom);
  position: relative;
  z-index: 1;
}
body[data-nav-position="side"] .sidebar {
  padding-top: var(--sidebar-offset-top);
}
body[data-nav-position="top"] .sidebar {
  padding-top: calc(var(--content-padding-top) + 60px);
}

/* ============================================================
   5. NAVIGATION — PRESCRIBED
   ============================================================
   The .page-nav element holds the brand, asset title, nav links,
   and controls. Its visual layout differs between side-nav and
   top-nav modes, but the DOM order of slots stays the same:

     1. .page-nav__brand   (logo)
     2. .page-nav__title   (asset title text)
     3. .page-nav__links   (nav anchor links)
     4. .page-nav__controls(icon buttons)

   The brand layer changes what's INSIDE each slot, not the order.
   ============================================================ */
.page-nav {
  display: flex;
  align-items: stretch;
  gap: 40px;
  position: relative;
  z-index: 2;
}

/* ---- Side-nav mode ----
   No background or border here: the body::before pseudo paints
   the rail. No height: 100vh: that was forcing the grid row to
   100vh and pushing the footer below the fold. Sticky still
   works — the nav scrolls into a pinned position at top: 0
   within its grid cell. */
body[data-nav-position="side"] .page-nav {
  flex-direction: column;
  padding: 60px 40px 20px;
  position: sticky;
  top: 0;
  align-self: start;
  max-height: 100vh;
  gap: 0;
}

body[data-nav-position="side"] .page-nav__brand {
  padding-bottom: 30px;
}

body[data-nav-position="side"] .page-nav__title {
  padding-bottom: 0;
  align-self: stretch;
  text-align: left;
}

body[data-nav-position="side"] .page-nav__links {
  padding-top: 40px;
  display: flex;
  flex-direction: column;
  gap: 20px;
  text-align: left;
  flex: 1;
}

body[data-nav-position="side"] .page-nav__controls {
  display: flex;
  gap: 10px;
  padding-top: 20px;
  border-top: 1px solid var(--color-divider);
  margin-top: 20px;
}

/* ---- Top-nav mode ----
   Top-nav DOES keep the elevated background since the bar
   spans the page horizontally and needs visual containment. */
body[data-nav-position="top"] .page-nav {
  flex-direction: row;
  align-items: center;
  padding: 20px 40px;
  background: var(--color-bg-header);
  border-bottom: 1px solid var(--color-border);
  position: sticky;
  top: 0;
  z-index: 50;
  min-height: var(--top-nav-height);
}

body[data-nav-position="top"] .page-nav__brand {
  flex-shrink: 0;
}

body[data-nav-position="top"] .page-nav__title {
  flex-shrink: 0;
  max-width: 200px;
}

body[data-nav-position="top"] .page-nav__links {
  flex: 1;
  display: flex;
  flex-direction: row;
  justify-content: center;
  gap: 30px;
  text-align: center;
}

body[data-nav-position="top"] .page-nav__controls {
  flex-shrink: 0;
  display: flex;
  gap: 10px;
}

/* Brand slot — bounded logo container */
.page-nav__brand {
  display: flex;
  align-items: center;
}

/* Logo block placeholder. A brand replaces the inner content with
   an <img> or inline <svg>; the slot's bounding box stays. */
.logo-block {
  display: block;
  width: 100%;
  max-width: var(--logo-slot-max-width);
  height: var(--logo-slot-max-height);
  background: var(--logo-color);
  /* Replaced-element image rendering — same slot, real logo. */
}
img.logo-block,
svg.logo-block {
  width: auto;
  height: auto;
  max-width: var(--logo-slot-max-width);
  max-height: var(--logo-slot-max-height);
  background: transparent;
  object-fit: contain;
  object-position: left center;
}

/* Asset title text */
.page-nav__title {
  font-family: var(--font-family-display);
  font-size: 18px;
  font-weight: var(--font-weight-bold);
  line-height: 24px;
  color: var(--color-text-secondary);
  word-break: break-word;
}

/* Nav links list */
.page-nav__links {
  font-family: var(--font-family-body);
  font-size: var(--font-size-nav-link);
  line-height: var(--line-height-nav-link);
}

.page-nav__links a {
  /* Inline-flex + ::before dot is the canonical vertical-nav
     anatomy (see brand-system controls.md "Vertical Text
     Navigation Menu"): an 8×8 status dot sits 10px to the left
     of the text. The slot is reserved on every link — by giving
     the dot a fixed width and `background: transparent` by
     default — so the text positions don't shift when the active
     item changes. Only `background` toggles between states. */
  display: inline-flex;
  align-items: center;
  gap: 10px;
  color: var(--color-nav-link);
  transition: color var(--transition-base);
  cursor: pointer;
}
.page-nav__links a::before {
  content: '';
  display: inline-block;
  flex-shrink: 0;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: transparent;
  transition: background-color var(--transition-base);
}
.page-nav__links a:hover {
  color: var(--color-nav-link-hover);
}
.page-nav__links a[aria-current="true"],
.page-nav__links a.is-active {
  /* Active state is signaled by color + the status dot only. We
     deliberately DO NOT change font-weight on activation — a weight
     change causes the rendered glyph widths to shift, producing a
     visible layout flash when the user clicks a different page and
     the prior/new active items resize. Keep weight constant. */
  color: var(--color-nav-link-active);
}
.page-nav__links a[aria-current="true"]::before,
.page-nav__links a.is-active::before {
  background: var(--color-nav-link-marker);
}

/* ============================================================
   6. MOBILE — PRESCRIBED
   ============================================================
   Below the mobile breakpoint, desktop nav hides and the
   mobile-header + mobile-drawer take over. Above the breakpoint
   these mobile elements are hidden.

   Mobile header structure (from the figma):
     - Logo on the top-left
     - Asset title beneath the logo
     - Menu icon on the top-right
   The header is auto-height (not fixed) so the stacked logo+title
   has room. Mobile drawer holds the section links plus the
   theme-toggle and download controls (since they're not in the
   header itself).
   ============================================================ */
.mobile-header {
  display: none;  /* shown only via the responsive section */
  position: sticky;
  top: 0;
  z-index: 60;
  padding: 15px 20px;
  /* Background matches the page surface. Because the header is
     position: sticky, page content scrolls UNDER it — a transparent
     fill would let the content show through, making the header
     visually incoherent and the title unreadable. Match the page
     surface and keep the border underline for separation. */
  background: var(--color-bg-page);
  border-bottom: 1px solid var(--color-border);
  align-items: flex-start;
  justify-content: space-between;
  gap: 20px;
}

.mobile-header__brand {
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-width: 0;
  flex: 1;
}

.mobile-header__title {
  font-family: var(--font-family-display);
  font-size: 14px;
  font-weight: var(--font-weight-bold);
  line-height: 18px;
  color: var(--color-text-primary);
  word-break: break-word;
}

.mobile-header__controls {
  display: flex;
  gap: 10px;
  flex-shrink: 0;
}

/* Mobile drawer — a <dialog> element shown via .showModal().
   The panel slides in from the RIGHT (figma reference). The
   dialog itself spans the viewport (so its backdrop covers the
   page); the panel is positioned to the right edge. */
.mobile-drawer {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100dvh;
  background: transparent;
  border: 0;
  margin: 0;
  padding: 0;
  z-index: 200;
  display: none;
}
.mobile-drawer[open] { display: block; }
.mobile-drawer::backdrop {
  /* No page dim — the drawer panel itself provides enough visual
     weight via its solid surface and left-edge shadow. A 40% black
     wash competed with the open drawer's content. */
  background: transparent;
}

.mobile-drawer__panel {
  position: absolute;
  /* Right-pinned: leaves a 20% strip on the LEFT (backdrop), panel
     takes 80% of viewport width on the right. Padding mirrors the
     mobile-header's horizontal padding (20px) so the absolutely-
     positioned close icon lands at the exact viewport coordinates
     of the hamburger.
     Background must be SOLID OPAQUE so the page behind doesn't
     bleed through — that's why we use --color-bg-drawer (which a
     brand bridge maps to a solid color in both light and dark mode)
     instead of --color-bg-elevated (which may be translucent in
     dark mode for surfaces meant to feel layered). The left edge
     gets a subtle shadow so the panel reads as floating above the
     page even without a backdrop dim.
     `color: var(--color-text-drawer)` sets the inherited text/icon
     color for the whole panel; internal elements derive their
     colors from `currentColor` so a branded drawer (e.g. blue
     surface with white text) flips together. */
  inset: 0 0 0 20%;
  background: var(--color-bg-drawer);
  color: var(--color-text-drawer);
  box-shadow: -8px 0 24px rgba(0, 0, 0, 0.18);
  padding: 20px 20px 40px;
  display: flex;
  flex-direction: column;
  gap: 30px;
  overflow-y: auto;
  transform: translateX(100%);
  transition: transform var(--transition-drawer);
}
.mobile-drawer[open] .mobile-drawer__panel {
  transform: translateX(0);
}

.mobile-drawer__head {
  /* Reserves vertical space at the top of the panel so the
     title doesn't collide with the absolutely-positioned close
     button. 40px = icon-button--sm height. */
  min-height: 40px;
}

/* Close button is positioned at the exact viewport coords the
   hamburger occupies in the mobile header (top: 15px, right:
   20px) — so toggling open/closed only swaps the glyph, not
   the icon's position. */
.mobile-drawer__close {
  position: absolute;
  top: 15px;
  right: 20px;
  z-index: 1;
}

.mobile-drawer__title {
  font-family: var(--font-family-display);
  font-size: 18px;
  font-weight: var(--font-weight-bold);
  line-height: 24px;
  color: currentColor;      /* inherits --color-text-drawer from .mobile-drawer__panel */
  padding-right: 50px;       /* room for the close icon */
}

.mobile-drawer__links {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.mobile-drawer__links a {
  /* Same status-dot anatomy as desktop .page-nav__links a — see
     that rule above for the rationale. The slot is reserved on
     every link so the text doesn't shift on activation.
     Inactive links use a 65% opacity of the drawer text color
     (derived via color-mix on currentColor) so the active state
     reads as the brightest item without needing a separate
     branded color. */
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-family: var(--font-family-body);
  font-size: var(--font-size-nav-link);
  line-height: var(--line-height-nav-link);
  color: color-mix(in srgb, currentColor 65%, transparent);
  transition: color var(--transition-base);
}
.mobile-drawer__links a::before {
  content: '';
  display: inline-block;
  flex-shrink: 0;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: transparent;
  transition: background-color var(--transition-base);
}
.mobile-drawer__links a:hover,
.mobile-drawer__links a.is-active {
  color: currentColor;     /* brightest = drawer text color at full opacity */
}
.mobile-drawer__links a.is-active::before {
  /* Marker derives from currentColor too — on a branded drawer
     (blue + white text), the marker is white; on a neutral
     drawer (white + dark text), it's the dark text color. */
  background: currentColor;
}

/* Controls block at the foot of the drawer: theme + download. */
.mobile-drawer__controls {
  display: flex;
  gap: 10px;
  margin-top: auto;
  padding-top: 20px;
  /* Divider derives from drawer text color at low opacity so it
     reads correctly on both branded and neutral drawer surfaces. */
  border-top: 1px solid color-mix(in srgb, currentColor 20%, transparent);
}

/* Icon buttons INSIDE the drawer take their colors from the drawer's
   text color, not the page's --color-icon-fg. The drawer surface is
   theme-invariant (a brand bridge typically sets the drawer to brand
   blue in both light and dark mode), so the icons inside it must also
   be theme-invariant — otherwise the icons render dark on the blue
   drawer in light mode but white in dark mode, even though the surface
   under them is the same blue in both. The currentColor inheritance
   keeps the icons in sync with .mobile-drawer__title and the link text. */
.mobile-drawer .icon-button {
  background: color-mix(in srgb, currentColor 12%, transparent);
  color: currentColor;
}
.mobile-drawer .icon-button:hover {
  background: color-mix(in srgb, currentColor 22%, transparent);
}
.mobile-drawer .icon-button svg,
.mobile-drawer .icon-button svg path {
  fill: currentColor;
  color: currentColor;
}

/* ============================================================
   7. TYPOGRAPHY — PRESCRIBED (prose elements)
   ============================================================ */
.strapline {
  font-family: var(--font-family-body);
  font-size: var(--font-size-strapline);
  line-height: var(--line-height-strapline);
  color: var(--color-text-secondary);
  font-weight: var(--font-weight-regular);
}

.h1 {
  font-family: var(--font-family-display);
  font-size: var(--font-size-h1);
  line-height: var(--line-height-h1);
  font-weight: var(--font-weight-bold);
  color: var(--color-text-primary);
  max-width: 1120px;
  margin-top: 20px;
  margin-bottom: 40px;       /* breathing room below hero headline; brands may bump higher via Section 11 */
}

.h2 {
  font-family: var(--font-family-display);
  font-size: var(--font-size-h2);
  line-height: var(--line-height-h2);
  font-weight: var(--font-weight-bold);
  color: var(--color-text-primary);
  max-width: 880px;
  margin-top: 30px;
}

.h3 {
  font-family: var(--font-family-display);
  font-size: var(--font-size-h3);
  line-height: var(--line-height-h3);
  font-weight: var(--font-weight-bold);
  color: var(--color-text-primary);
  margin-top: 30px;
}

.h4 {
  font-family: var(--font-family-display);
  font-size: var(--font-size-h4);
  line-height: var(--line-height-h4);
  font-weight: var(--font-weight-bold);
  color: var(--color-text-primary);
  margin-top: 20px;
}

.h5 {
  font-family: var(--font-family-display);
  font-size: var(--font-size-h5);
  line-height: var(--line-height-h5);
  font-weight: var(--font-weight-bold);
  color: var(--color-text-primary);
  margin-top: 20px;
}

.body {
  font-family: var(--font-family-body);
  font-size: var(--font-size-body);
  line-height: var(--line-height-body);
  color: var(--color-text-primary);
  font-weight: var(--font-weight-regular);
  max-width: 820px;
  margin-top: 10px;
}
.body a {
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}
.body a:hover {
  color: var(--color-text-link-hover);
}

.body-sm {
  font-family: var(--font-family-body);
  font-size: var(--font-size-body-sm);
  line-height: var(--line-height-body-sm);
  color: var(--color-text-primary);
  font-weight: var(--font-weight-regular);
}

.blockquote {
  font-family: var(--font-family-body);
  font-size: var(--font-size-body);
  line-height: var(--line-height-body);
  color: var(--color-text-secondary);
  padding-left: 20px;
  margin-top: 20px;
  max-width: 820px;
}
.blockquote a {
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}

.pullquote {
  display: block;
  padding: 60px 0;
  margin: 0;
}
.pullquote p:first-child {
  font-family: var(--font-family-display);
  font-size: var(--font-size-pullquote);
  line-height: var(--line-height-pullquote);
  font-weight: var(--font-weight-light);
  color: var(--color-text-primary);
  max-width: 800px;
  /* Optical hang for opening quote — hanging-punctuation handles this cleanly
     when the quote starts with a punctuation glyph, and falls back to no-indent
     when it doesn't. The legacy `text-indent: -0.45ch` approach pulled the first
     line out of alignment whenever the quote didn't start with a punctuation
     mark, producing the visible "second line indented" symptom. */
  hanging-punctuation: first;
  text-indent: 0;
}
.pullquote__cite {
  font-family: var(--font-family-body);
  font-size: var(--font-size-body);
  font-weight: var(--font-weight-regular);
  color: var(--color-text-secondary);
  line-height: 42px;
  max-width: 800px;
}

/* ---- Subsection breathing room ----
   Rotating straplines and block-element + h2 sequences are
   subsection markers per the "Long-section scanability"
   guidance in html-asset.md. The base typography margins
   (.strapline has no margin-top; .h2 margin-top = 30px;
   .cards margin-block = 40px) collapse to ~40px for these
   transitions — adequate for continuation but tight for
   "you've crossed into a new region." These adjacent-sibling
   rules give subsection transitions air without affecting
   the first-child case (a strapline or h2 immediately after
   the section's opening tag keeps the template's defaults). */
* + .strapline { margin-top: 60px; }
.cards + .h2,
.table + .h2,
.list  + .h2,
.gantt + .h2,
.chart-line + .h2,
.chart-bar  + .h2,
.chart-scatter + .h2,
.chart-donut + .h2,
.chart-pie + .h2,
.chart-histogram + .h2,
.chart-boxplot + .h2,
.chart-sankey + .h2,
.chart-venn + .h2 {
  margin-top: 60px;
}

/* ============================================================
   8. COMPONENTS
   ============================================================ */

/* ---- Lists (bulleted + numbered, 1/2/3 column) ---- */
.list {
  display: flex;
  flex-direction: column;
  margin-top: 20px;
}
.list__row {
  display: flex;
  gap: 40px;
}
.list--1col .list__row { display: contents; }
.list--3col .list__row { gap: 20px; }
.list__item {
  display: flex;
  align-items: flex-start;
  flex: 1 0 0;
  min-width: 0;
  padding: 20px 10px;
  border-bottom: 1px solid var(--color-divider);
}
.list__marker {
  display: flex;
  align-items: flex-start;
  padding-left: 10px;
  padding-top: 12px;
  flex-shrink: 0;
}
.list--bulleted .list__marker::before {
  content: '';
  display: block;
  width: 5px;
  height: 5px;
  background: var(--color-text-primary);
}
.list--numbered .list__marker {
  font-family: var(--font-family-display);
  font-size: var(--font-size-body-sm);
  line-height: 30px;             /* match .list__lead line-height so baselines align */
  font-weight: var(--font-weight-bold);
  color: var(--color-text-primary);
  letter-spacing: 0.05em;
  padding-top: 0;                /* baseline aligns via line-height, not top padding */
  text-align: center;
  min-width: 24px;
}
.list__text {
  flex: 1 0 0;
  min-width: 0;
  padding-left: 20px;
  display: flex;
  flex-direction: column;
  gap: 5px;
  color: var(--color-text-primary);
}
.list--1col .list__text { padding-right: 120px; }
/* When a list is nested inside a .card, neutralize the body-width
   prose padding above. The card defines its own measure (often
   250–380px in a 2-col card row); the 120px right-padding would
   eat most of it and crush the bullets toward the left edge. The
   mobile breakpoint already zeroes this for narrow viewports; this
   extends the same intent to narrow CONTAINERS. Covers all three
   column variants because anything placed in a card should respect
   the card's measure, not the section's. */
.cards .card .list--1col .list__text,
.cards .card .list--2col .list__text,
.cards .card .list--3col .list__text {
  padding-right: 0;
}
.list__lead {
  font-family: var(--font-family-display);
  font-size: var(--font-size-body-sm);
  line-height: 30px;
  font-weight: var(--font-weight-bold);
}
.list__body {
  font-family: var(--font-family-body);
  font-size: var(--font-size-body-sm);
  line-height: var(--line-height-body-sm);
  font-weight: var(--font-weight-regular);
}

/* ---- Tables ---- */
.table {
  margin-top: 40px;
  table-layout: auto;
}
.table th,
.table td {
  padding: 20px 16px;
  text-align: left;             /* scan top-to-bottom on the left edge of each cell */
  vertical-align: top;          /* multi-line cells align at the top, not the middle */
  border-bottom: 1px solid var(--color-divider);
  font-family: var(--font-family-body);
  font-size: var(--font-size-body-sm);
  line-height: var(--line-height-body-sm);
  color: var(--color-text-primary);
}
.table th {
  font-family: var(--font-family-display);
  font-weight: var(--font-weight-semibold);
  vertical-align: bottom;       /* header row reads as a baseline above the data */
}
.table tbody tr td { min-height: 64px; }   /* lower the floor — top-alignment removes the need for tall cells */

/* ---- Cards ---- */
/* ---- Card grid ----
   Cards lay out as a CSS grid, not unbounded flex. The
   .card-row--Ncol modifier defines how many columns the grid
   has; if more .card children are present than columns, they
   wrap to additional rows. This means the modifier ENFORCES
   the column count — putting 6 cards into a card-row--3col
   produces 3 cards × 2 rows, not 6 squeezed columns.
   --------------------------------------------------------------
   Choosing a layout:
     • card-row--1col       one full-width card per row
     • card-row--2col       up to 2 per row, wraps for the rest
     • card-row--3col       up to 3 per row, wraps for the rest
     • card-row--divided    2 cards, no gap, vertical rule between
   --------------------------------------------------------------
   The HARD CAP is 3 columns. Four cards in one row are squeezed
   to ~22% width each on a 1440px viewport — too narrow for any
   meaningful content. The content-rules section of the
   html-asset reference makes this explicit. */
.cards {
  display: grid;
  gap: var(--card-gap);
  /* Symmetric vertical rhythm. Cards used mid-flow inside body text
     (where the next sibling is a paragraph, list, or heading) need
     matching breathing room above AND below so the row sits as one
     evenly-spaced beat in the prose rhythm. margin-top alone produced
     visibly asymmetric gaps — ~40px above, 0 below — and the
     following paragraph jammed against the bottom card edge. */
  margin-block: 40px;
}
.cards .card {
  min-width: 0;
  background: var(--color-bg-card);
  padding: var(--card-padding);
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.cards .card .h3 { margin-top: 20px; padding-bottom: 10px; }
.cards .card .body { margin-top: 0; padding-bottom: 20px; }

/* Bare <p> fallback inside a card. The reference asks authors to use
   explicit typography classes (.body, .body-sm) on every paragraph; this
   rule is a belt-and-suspenders default so an un-classed <p> renders at
   the brand minimum body size instead of the browser's 16px default.
   Always prefer explicit classes — the cascade reads more clearly. */
.cards .card p:not([class]) {
  font-family: var(--font-family-body);
  font-size: var(--font-size-body-sm);
  line-height: var(--line-height-body-sm);
  color: inherit;
}

.cards.card-row--1col   { grid-template-columns: 1fr; }
.cards.card-row--2col   { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.cards.card-row--3col   { grid-template-columns: repeat(3, minmax(0, 1fr)); }

/* Divided: 2-card surface with a vertical rule between them.
   Hard cap of 2. Extra .card children visually break the pattern;
   the html-asset content rules forbid more than 2. */
.cards.card-row--divided {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0;
}
.cards.card-row--divided .card + .card {
  border-left: 1px solid var(--color-border);
}

/* ---- Card accent variants ----
   Three modifier classes paint a card with a colored surface
   for visual emphasis. All three are designed to coexist on
   the same row as a default (.card) so a single "feature"
   card stands out among informational ones. The tokens flow
   through a brand bridge — defaults below are template
   greyscale; a brand sets them to its palette.

     .card--accent      brand primary surface, light text
     .card--feature     dark surface, light text
     .card--on-dark     light surface, dark text (use on a
                        dark page surface, e.g. dark mode hero)

   Use SPARINGLY. The default posture is intentional restraint:
   one accent card per row, used to mark hero metrics, primary
   recommendations, or summary statements. Painting every card
   defeats the contrast. */
.cards .card--accent {
  background: var(--color-card-accent-bg);
  color: var(--color-card-accent-text);
}
.cards .card--feature {
  background: var(--color-card-feature-bg);
  color: var(--color-card-feature-text);
}
.cards .card--on-dark {
  background: var(--color-card-on-dark-bg);
  color: var(--color-card-on-dark-text);
}

/* Inherit the accent's text color through all prose elements
   inside the card. Lists, headings, body, and link colors are
   all bound to the card's text token so any of the three
   variants reads cleanly. Links keep an underline so they
   remain identifiable even when their color matches body. */
.cards .card--accent .h1,  .cards .card--feature .h1,  .cards .card--on-dark .h1,
.cards .card--accent .h2,  .cards .card--feature .h2,  .cards .card--on-dark .h2,
.cards .card--accent .h3,  .cards .card--feature .h3,  .cards .card--on-dark .h3,
.cards .card--accent .h4,  .cards .card--feature .h4,  .cards .card--on-dark .h4,
.cards .card--accent .h5,  .cards .card--feature .h5,  .cards .card--on-dark .h5,
.cards .card--accent .body,         .cards .card--feature .body,         .cards .card--on-dark .body,
.cards .card--accent .body-sm,      .cards .card--feature .body-sm,      .cards .card--on-dark .body-sm,
.cards .card--accent .strapline,    .cards .card--feature .strapline,    .cards .card--on-dark .strapline,
.cards .card--accent .list__text,   .cards .card--feature .list__text,   .cards .card--on-dark .list__text,
.cards .card--accent .list__lead,   .cards .card--feature .list__lead,   .cards .card--on-dark .list__lead,
.cards .card--accent .list__body,   .cards .card--feature .list__body,   .cards .card--on-dark .list__body,
.cards .card--accent .list__marker, .cards .card--feature .list__marker, .cards .card--on-dark .list__marker {
  /* All prose containers and prose elements inside an accented card
     inherit the card's text color. .list__text is critical here:
     it's an intermediate container that has an explicit
     `color: var(--color-text-primary)` declaration earlier in this
     stylesheet, which would otherwise break the cascade from the
     card down to .list__body and .list__lead. */
  color: inherit;
}
.cards .card--accent a,
.cards .card--feature a,
.cards .card--on-dark a {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 3px;
}
/* List markers inside accent cards: the bullet should also use
   the card's text color so it reads against the colored surface. */
.cards .card--accent  .list--bulleted .list__marker::before,
.cards .card--feature .list--bulleted .list__marker::before,
.cards .card--on-dark .list--bulleted .list__marker::before {
  background: currentColor;
}

/* Badges inside accent cards: the variant tokens (--color-badge-*)
   resolve to the brand primary, which is the same color as the card
   surface itself for .card--accent — so badges render invisibly. Map
   all badge variants inside any accented card to a transparent fill
   with the card's text color as the border and label. This produces
   a consistent outlined pill that reads correctly on the colored
   surface regardless of which badge variant was authored. */
.cards .card--accent  .badge,
.cards .card--feature .badge,
.cards .card--on-dark .badge {
  background: transparent;
  border: 1px solid currentColor;
  color: inherit;
}
/* Status dots in badges inside accent cards should also use
   currentColor — same reason as the bullet rule above. */
.cards .card--accent  .badge .badge__status,
.cards .card--feature .badge .badge__status,
.cards .card--on-dark .badge .badge__status {
  background: currentColor;
}

/* ---- Buttons ----
   The three styles (primary / secondary / tertiary) each expose a
   reveal-on-hover icon. By default .button__icon is collapsed
   (width 0, opacity 0); on hover/focus the button grows by the
   icon width, the icon fades in and slides right. */
.button {
  display: inline-flex;
  align-items: center;
  height: 52px;
  padding: 5px 30px;
  border-radius: var(--button-radius);
  font-family: var(--font-family-display);
  font-size: var(--font-size-button);
  line-height: var(--line-height-button);
  font-weight: var(--font-weight-bold);
  text-align: center;
  cursor: pointer;
  transition: background var(--transition-base), color var(--transition-base), border-color var(--transition-base), padding var(--transition-base);
  white-space: nowrap;
  border: 2px solid transparent;
}
.button + .button { margin-left: 20px; }

/* Bare CTAs need breathing room above. The template's <p> and list
   bottom-margin alone reads tight before a 52px-tall button. Applies
   wherever in .content a .button is the next sibling of a paragraph,
   list, or list block. Buttons wrapped in .cards.card-row carry the
   card-row's own spacing, so this rule only catches bare buttons
   sitting directly between content elements. */
.content p + .button,
.content .list + .button,
.content ul + .button,
.content ol + .button {
  margin-top: 16px;
}

.button__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: var(--icon-glyph-size);
  /* Collapsed by default; reveals on :hover/focus. */
  max-width: 0;
  margin-left: 0;
  opacity: 0;
  overflow: hidden;
  transform: translateX(-6px);
  transition: max-width var(--transition-base), margin-left var(--transition-base), opacity var(--transition-base), transform var(--transition-base);
}
.button__icon svg {
  width: var(--icon-glyph-size);
  height: var(--icon-glyph-size);
  flex-shrink: 0;
}
.button:hover .button__icon,
.button:focus-visible .button__icon {
  max-width: var(--icon-glyph-size);
  margin-left: 10px;
  opacity: 1;
  transform: translateX(0);
}
.button:has(.button__icon) {
  padding-right: 20px;
}

.button--primary {
  background: var(--color-button-primary-bg);
  color: var(--color-button-primary-text);
}
.button--secondary {
  background: var(--color-button-secondary-bg);
  color: var(--color-button-secondary-text);
  border-color: var(--color-button-secondary-border);
}
.button--tertiary {
  background: var(--color-button-tertiary-bg);
  color: var(--color-button-tertiary-text);
}

.button-group {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 30px;
  margin-top: 30px;
}

/* ---- Icon buttons (used in nav controls, mobile header, pagination) ---- */
.icon-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--icon-button-size);
  height: var(--icon-button-size);
  background: var(--color-icon-bg);
  border-radius: 50%;
  color: var(--color-icon-fg);
  transition: background var(--transition-base);
  flex-shrink: 0;
}
.icon-button:hover {
  background: var(--color-icon-bg-hover);
}
/* Icon glyph color and size. We set fill directly from the
   variable (rather than via currentColor) because currentColor
   cascade into inline SVG paths is unreliable across browsers
   — Safari in particular can resolve it before the cascade
   settles, leaving the glyph filled black on a dark surface. */
.icon-button svg {
  width: var(--icon-glyph-size);
  height: var(--icon-glyph-size);
  fill: var(--color-icon-fg);
  color: var(--color-icon-fg);
}
.icon-button svg path,
.icon-button svg use {
  fill: var(--color-icon-fg);
}
.icon-button--sm {
  width: var(--icon-button-size-small);
  height: var(--icon-button-size-small);
}
.icon-button--bare {
  background: transparent;
}

/* Theme toggle visibility — show the icon matching the *target* mode */
[data-theme="light"] .icon-light { display: none; }
[data-theme="dark"]  .icon-dark { display: none; }

/* ---- Badges ---- */
.badge {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 10px 20px;
  border-radius: var(--badge-radius);
  font-family: var(--font-family-display);
  font-size: var(--font-size-badge);
  font-weight: var(--font-weight-bold);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  line-height: 1;
  border: 2px solid transparent;
}
.badge--filled {
  background: var(--color-badge-filled-bg);
  color: var(--color-badge-filled-text);
}
.badge--ghost {
  background: var(--color-badge-ghost-bg);
  color: var(--color-badge-ghost-text);
}
.badge--stroke {
  background: transparent;
  color: var(--color-badge-stroke-text);
  border-color: var(--color-badge-stroke-border);
}
.badge__status {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--color-badge-status);
}
.badge--filled .badge__status {
  background: currentColor;
}

.badge-group {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 30px;
  margin-top: 30px;
}

/* ---- Avatars ---- */
.avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  font-family: var(--font-family-display);
  font-weight: var(--font-weight-extrabold);
  letter-spacing: 0.1em;
  line-height: 1;
  text-align: center;
  flex-shrink: 0;
}
.avatar--sm  { width: 16px; height: 16px; font-size: 9px;  letter-spacing: 0.1em; }
.avatar--md  { width: 32px; height: 32px; font-size: 16px; letter-spacing: 0.1em; }
.avatar--lg  { width: 48px; height: 48px; font-size: 24px; letter-spacing: 0.1em; }

/* Two-character monogram shrinks proportionally */
.avatar--md:has(:nth-child(2)),
.avatar--md.is-double { font-size: 12px; }
.avatar--lg:has(:nth-child(2)),
.avatar--lg.is-double { font-size: 18px; }

.avatar--dark  { background: var(--color-avatar-dark-bg);  color: var(--color-avatar-dark-text); }
.avatar--light { background: var(--color-avatar-light-bg); color: var(--color-avatar-light-text); }
.avatar--white { background: var(--color-avatar-white-bg); color: var(--color-avatar-white-text); }

.avatar-group {
  display: flex;
  align-items: center;
  gap: 30px;
  flex-wrap: wrap;
  margin-top: 30px;
}

/* ---- Pagination pill (prev/next at section boundaries) ---- */
.pagination {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 5px 7px;
  background: var(--color-bg-card);
  border-radius: var(--button-radius);
  margin-top: 60px;
}
.pagination__label {
  font-family: var(--font-family-body);
  font-size: 16px;
  font-weight: var(--font-weight-medium);
  color: var(--color-text-primary);
  padding: 0 10px;
}

/* ---- Sidebar cards — distinct treatment ----
   Sidebar cards default to the brand-accent surface (the same
   token mapping as .card--accent in the main column). This
   differentiates them visually from main-column cards so the
   sidebar reads as a summary aside, not as another column.
   If a brand has multiple palette colors in play, a brand
   bridge can remap --color-sidebar-card-bg / --color-sidebar-card-text
   to a secondary color (e.g., Frontier purple) — see
   bridge-html-asset.md. */
.sidebar .card {
  background: var(--color-sidebar-card-bg);
  color: var(--color-sidebar-card-text);
  padding: var(--card-padding);
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.sidebar .card .h3 { margin-top: 20px; padding-bottom: 10px; }
.sidebar .card .body { margin-top: 0; padding-bottom: 20px; }

/* Cascade the sidebar card's text color through prose elements
   so headings, list items, links etc. all read correctly on
   the accent surface. Mirrors the .card--accent cascade in
   section 8. */
.sidebar .card .h1,  .sidebar .card .h2,  .sidebar .card .h3,
.sidebar .card .h4,  .sidebar .card .h5,
.sidebar .card .body, .sidebar .card .body-sm,
.sidebar .card .strapline,
.sidebar .card .list__text,
.sidebar .card .list__lead, .sidebar .card .list__body,
.sidebar .card .list__marker {
  color: inherit;
}
.sidebar .card a {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 3px;
}
.sidebar .card .list--bulleted .list__marker::before {
  background: currentColor;
}
/* Defensive: badges inside sidebar cards (which auto-paint as
   accent) — same rule as .cards .card--accent .badge above. The
   sidebar skill rule says not to use badges here, but if a producer
   does, render them as outlined-with-currentColor so they're
   readable on the colored sidebar surface. */
.sidebar .card .badge {
  background: transparent;
  border: 1px solid currentColor;
  color: inherit;
}
.sidebar .card .badge .badge__status {
  background: currentColor;
}

/* ---- Gantt chart (embedded data viz) ----
   Stage-indented Gantt per the brand's data-visualization
   reference. Each stage is a stacked group: label → bar →
   description, all left-aligned to the same x-coordinate.
   Stages indent progressively (default 100px per step) to
   convey sequence in time. The bar's `.gantt-bar-fill`
   represents progress for a project in motion, OR planned
   scope for a forward-looking timeline — in either case, the
   empty track is the unfilled remainder.

   Token surface (data-viz tokens defined in section 2):
     --color-data-primary       solid bar fill
     --color-data-primary-track 10% wash for the track
     --color-data-grid          15% wash for axes (unused here)
     --color-data-text          numeric/label text color

   Layout invariants (do NOT change):
     1. Label, bar, and description ALWAYS stack vertically.
     2. All three elements left-align to the same x-coordinate
        within the stage.
     3. The per-stage indent must NOT push the description past
        the content column's right edge. For 6+ stages, set
        --gantt-stage-indent to a smaller value (70px / 50px /
        40px depending on stage count); see html-asset.md.
     4. If even the calibrated indent doesn't fit, drop the
        sidebar OR switch to a horizontal bar chart / vertical
        timeline — see html-asset.md "Gantt layout rules".

   To embed: drop a <div class="gantt"> with N <div class="gantt-stage">
   children. Each stage carries an --i index (CSS var) for the
   indent calculation. The fill width is set via inline style or
   a data-attribute. */
.gantt {
  display: flex;
  flex-direction: column;
  gap: 25px;
  padding: 40px 0;
  --gantt-stage-indent: 100px;        /* default; override on the .gantt element for many-stage charts */
}
.gantt-stage {
  display: flex;
  flex-direction: column;
  gap: 8px;
  /* Indent each stage by --i × --gantt-stage-indent. Both vars
     are settable; --i is per-stage (0, 1, 2, …) and indent is
     per-chart. The indent is applied as left padding so the
     entire stack (label, bar, description) shifts together. */
  padding-left: calc(var(--i, 0) * var(--gantt-stage-indent));
  /* Stage takes the full available width minus its left indent.
     This keeps the bar's available width CONSTANT across all
     stages (the indent moves the bar's start position rightward
     but doesn't subtract additionally from the right edge). The
     prior formula (max-width: 100% - i × indent) intentionally
     narrowed each subsequent stage to mimic a Gantt's diagonal,
     but with 6+ stages on a sidebar-narrowed content column the
     late stages collapsed to unusably-tiny bars. The bar's own
     max-width: 488px still caps the longest bar. */
  max-width: 100%;
}
.gantt-stage__label {
  font-family: var(--font-family-display);
  font-size: 19px;
  line-height: 28px;
  font-weight: var(--font-weight-semibold);
  color: var(--color-data-text, var(--color-text-primary));
  white-space: normal;               /* allow wrap on narrow viewports — don't force overflow */
}
.gantt-stage__bar {
  height: 5px;
  background: var(--color-data-primary-track, rgba(34, 34, 34, 0.10));
  position: relative;
  border-radius: 2.5px;
  overflow: hidden;
  /* Bar fills the stage's available width. Each stage's indent
     pushes its start position rightward, so later stages have
     less horizontal room — that produces the Gantt's natural
     diagonal cascade. No hard pixel cap: on wide columns (e.g.,
     when the sidebar is suppressed for this section) the bars
     extend to fill the available space; on narrow columns they
     shrink to fit. Override --gantt-bar-max-width on the parent
     .gantt to introduce a cap if needed (rare). */
  width: 100%;
  max-width: var(--gantt-bar-max-width, none);
}
.gantt-stage__bar-fill {
  position: absolute;
  inset: 0;
  width: var(--fill, 100%);
  background: var(--color-data-primary, var(--color-text-primary));
  border-radius: inherit;
}
.gantt-stage__description {
  font-family: var(--font-family-body);
  font-size: 15px;
  font-weight: var(--font-weight-semibold);
  line-height: 22px;
  color: var(--color-data-text, var(--color-text-primary));
  opacity: 0.7;
  /* Description matches the bar's width behavior — fills the
     available stage width, capped by --gantt-bar-max-width if set. */
  max-width: var(--gantt-bar-max-width, none);
}

/* ---- Stat card (headline-metric pattern) ----
   When 2+ headline metrics appear in a section, render them as
   stat cards in a card-row rather than burying them in prose.
   Typography matches the data-visualization reference: numeric
   role at 55px Light in the brand's data-primary color; label
   and body in the text role.

   Markup:
     <div class="cards card-row--3col">
       <div class="card card--stat">
         <p class="stat__label">Label</p>
         <p class="stat__value">76%</p>
         <p class="stat__body">Context for the number.</p>
       </div>
       …
     </div>
*/
/* The stat-card container declares container-type so its descendant
   stat-value can read the card's actual inline-size (via cqi units)
   and scale proportionally. The viewport-based stepdown at the bottom
   of this block is intentionally retained as a final fallback for
   browsers without container-query support — but cqi is the primary
   sizing channel and handles the real failure case (narrow card
   columns with a sidebar present, where viewport is fine but the
   card itself is ~220px wide). */
.card.card--stat {
  container-type: inline-size;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.card--stat .stat__label {
  font-family: var(--font-family-body);
  font-size: 14px;
  font-weight: var(--font-weight-medium);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-text-primary);
  opacity: 0.7;
  margin: 0;
}
.card--stat .stat__value {
  font-family: var(--font-family-display);
  font-weight: 300;                                       /* Light */
  /* Container-query scaling: 18cqi = 18% of the card's inline-size.
     A ~220px-wide card yields ~40px; a ~460px 1-col card yields the
     full 55px ceiling. The 34px floor preserves the IBM Plex Light
     identity (below ~32px the Light weight starts reading wispy). */
  font-size: clamp(34px, 18cqi, 55px);
  line-height: 1.05;
  letter-spacing: -0.02em;
  color: var(--color-data-primary, var(--color-text-primary));
  margin: 4px 0 8px;
}
.card--stat .stat__body {
  font-family: var(--font-family-body);
  font-size: 15px;
  line-height: 22px;
  color: var(--color-text-primary);
  opacity: 0.85;
  margin: 0;
}
/* Row-level compact variant for stat values. When ANY stat in the row
   has a long value (6+ characters with units, e.g. "$500K+",
   "18–36 mo", "100%↗"), apply this modifier to the parent row and
   ALL stat values in the row drop together to a smaller clamp band.
   This enforces visual parity across the row — never mix a 55px stat
   with a 40px stat side-by-side. The author makes one judgment per
   row, not per card. */
.cards.card-row--stats-compact .card--stat .stat__value {
  font-size: clamp(28px, 13cqi, 40px);
  line-height: 1.1;
}
/* Viewport-based fallback for browsers without container-query
   support. Container queries reached baseline support in all major
   browsers by mid-2023, but this stepdown is retained as a defensive
   floor for older renderers. Browsers that support cqi ignore these
   because the clamp() above already caps at the same values. */
@media (max-width: 1024px) {
  .card--stat .stat__value { font-size: clamp(34px, 18cqi, 44px); }
  .cards.card-row--stats-compact .card--stat .stat__value { font-size: clamp(28px, 13cqi, 36px); }
}

/* ============================================================
   9. SITE FOOTER
   ============================================================
   Footer content stays within the page's max-width (the body's
   grid cap), but the BACKGROUND extends full-bleed to the
   viewport edges so the dark footer band looks intentional on
   wide screens instead of leaving the outer page surface
   peeking past the footer.

   Technique: clip-path: inset(0 -100vmax) widens the element's
   visual bounds while the layout box stays put. A box-shadow
   with 100vmax spread paints the bg in the expanded clip area.
   The shadow color matches the footer background — it IS the
   background, extended. (Cleaner than a 100vw absolutely-
   positioned pseudo-element which has stacking quirks at the
   page boundaries.) */
.site-footer {
  background: var(--color-bg-footer);
  color: var(--color-text-footer);
  padding: 80px 40px 40px;
  display: flex;
  flex-direction: column;
  gap: 60px;
  position: relative;
  z-index: 1;
  clip-path: inset(0 -100vmax);
  box-shadow: 0 0 0 100vmax var(--color-bg-footer);
}

/* Full-bleed top border. Painted by a pseudo-element rather than
   a plain `border-top`, because a CSS border lives at the layout
   box edge — bounded to the 1440px deck — and would appear as a
   short rule floating inside the dark band on wide viewports.
   The pseudo extends `-100vmax` to either side so it lands inside
   the same clip-path region as the background and reads as a true
   full-bleed separator. Token defaults to transparent so the
   border is invisible in light mode (where the dark footer surface
   already contrasts the page); brand bridges that go transparent
   on the footer in dark mode should set --color-footer-border to a
   visible neutral so the footer doesn't blend into the body. */
.site-footer::before {
  content: '';
  position: absolute;
  top: 0;
  left: -100vmax;
  right: -100vmax;
  height: 1px;
  background: var(--color-footer-border, transparent);
  z-index: 2;
}
body[data-nav-position="side"] .site-footer {
  padding-left: calc(var(--side-nav-width) + 40px);
}

.site-footer__lockup {
  display: flex;
  align-items: center;
  gap: 30px;
  flex-wrap: wrap;
}

.site-footer__logo {
  display: block;
  width: var(--logo-slot-footer-max-width);
  max-width: var(--logo-slot-footer-max-width);
  height: var(--logo-slot-footer-max-height);
  background: var(--logo-color-inverse);
  flex-shrink: 0;
}
img.site-footer__logo,
svg.site-footer__logo {
  width: auto;
  height: auto;
  max-width: var(--logo-slot-footer-max-width);
  max-height: var(--logo-slot-footer-max-height);
  background: transparent;
  object-fit: contain;
  object-position: left center;
}

.site-footer__title {
  font-family: var(--font-family-display);
  font-size: 18px;
  font-weight: var(--font-weight-bold);
  line-height: 24px;
  color: var(--color-text-footer);
  max-width: 200px;
}

.site-footer__boilerplate {
  font-family: var(--font-family-body);
  font-size: var(--font-size-body);
  line-height: var(--line-height-body);
  color: var(--color-text-footer);
  max-width: 820px;
}
.site-footer__boilerplate p + p { margin-top: 20px; }

/* ============================================================
   10. RESPONSIVE
   ============================================================ */

/* ≤1200px: scaling begins.
   - Typography scales down a notch (H1 first)
   - Side-nav rail narrows to keep more room for content
   - 3-col cards/lists drop to 2-col
   - .list--1col padding-right is normalized (120px squeeze
     was uneven against the other variants at this size) */
@media (max-width: 1200px) {
  :root {
    --font-size-h1: 52px;
    --line-height-h1: 60px;
    --side-nav-width: var(--side-nav-width-narrow);
  }
  body[data-nav-position="side"]::before {
    width: var(--side-nav-width-narrow);
  }
  body[data-nav-position="side"] .site-footer {
    padding-left: calc(var(--side-nav-width-narrow) + 40px);
  }
  .list--1col .list__text {
    padding-right: 0;
  }
  /* 3-col cards collapse to 2-col at narrow viewports. Grid syntax,
     not flex — the .cards container is a CSS Grid. */
  .cards.card-row--3col {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .list--3col .list__row { flex-wrap: wrap; }
  .list--3col .list__row .list__item { flex: 1 1 calc(50% - 20px); }
}

/* ≤1024px: right sidebar stacks below content.
   Type steps down further and remaining multi-col cards/lists
   collapse to single column. Side nav rail stays. */
@media (max-width: 1024px) {
  :root {
    --font-size-h1: 40px;
    --line-height-h1: 48px;
    --font-size-h2: 24px;
    --font-size-body: 18px;
    --line-height-body: 26px;
    --font-size-pullquote: 40px;
    --line-height-pullquote: 48px;
  }
  body[data-nav-position="side"] .page-body,
  body[data-nav-position="top"] .page-body {
    grid-template-columns: 1fr;
  }
  body[data-nav-position="side"] .sidebar,
  body[data-nav-position="top"] .sidebar {
    padding-top: 0;
  }
  /* All multi-col card rows collapse to 1-col. Grid syntax —
     overriding the .card-row--Ncol rules from section 8. */
  .cards.card-row--3col,
  .cards.card-row--2col {
    grid-template-columns: 1fr;
  }
  /* Divided pattern: switch the vertical rule to a horizontal one
     when stacked. */
  .cards.card-row--divided {
    grid-template-columns: 1fr;
  }
  .cards.card-row--divided .card + .card {
    border-left: 0;
    border-top: 1px solid var(--color-border);
    padding-top: 30px;
  }
  .list--2col .list__row,
  .list--3col .list__row {
    flex-wrap: wrap;
    gap: 0;
  }
  .list--2col .list__row .list__item,
  .list--3col .list__row .list__item {
    flex: 1 1 100%;
  }
}

/* ≤640px: desktop nav hides, mobile header + drawer take over.
   Side rail pseudo is also suppressed since the entire column
   layout collapses. */
@media (max-width: 640px) {
  body[data-nav-position="side"],
  body[data-nav-position="top"] {
    grid-template-columns: 1fr;
    grid-template-rows: auto 1fr auto;
    grid-template-areas:
      "mobile"
      "body"
      "footer";
  }
  body[data-nav-position="side"]::before {
    display: none;
  }
  .mobile-header { display: flex; grid-area: mobile; }
  .page-nav { display: none; }
  .content {
    padding: 30px 20px 60px;
  }
  body[data-nav-position="top"] .content {
    padding-top: 30px;
  }
  body[data-nav-position="side"] .site-footer,
  body[data-nav-position="top"] .site-footer {
    padding: 60px 20px 40px;
  }
  .site-footer__lockup {
    flex-direction: column;
    align-items: flex-start;
    gap: 20px;
  }
  .sidebar {
    padding-left: 20px;
    padding-right: 20px;
  }
  .pullquote {
    padding: 30px 0;
  }
}

/* Print: hide chrome, render body content */
@media print {
  .page-nav, .mobile-header, .mobile-drawer, .pagination { display: none; }
  body[data-nav-position="side"]::before { display: none; }
  body { display: block; }
  .page-body { display: block; }
  .content, .sidebar { padding: 0; }
  .site-footer { padding: 20px 0; }
}


/* ============================================================
   10. DATA VISUALIZATION — 9 chart patterns ported from
       the Newfangled 2026 brand styleguide. Gantt is in
       Section 8 above; Stat card is in Section 8 above.
   ============================================================
   Every chart uses --color-data-* tokens for color and
   --font-family-display / --font-family-body for type, so
   the patterns theme automatically when a brand bridge
   overrides those tokens.

   Multi-series charts (Sankey) reach for additional brand
   palette tokens (--nf-purple, --nf-green-dark, --nf-orange)
   for series 2/3/4. Those are defined by the brand bridge
   in section 2A overrides.

   Layout convention: every chart wrapper sits inside a
   .chart container that caps max-width to body-prose
   width (820px) and centers via margin: 30px auto. Charts
   inside .cards rows fill their card; the .chart wrapper's
   max-width does not apply there because flex/grid containers
   override it.
   ============================================================ */

.chart {
  max-width: 820px;
  margin: 30px auto;
  padding: 10px 0;
  font-family: var(--font-family-body);
  color: var(--color-data-text, var(--color-text-primary));
}
.chart__title {
  font-family: var(--font-family-display);
  font-weight: 500;
  font-size: 14px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-text-primary);
  opacity: 0.7;
  margin: 0 0 16px;
}
.chart__caption {
  font-family: var(--font-family-body);
  font-size: 13px;
  line-height: 1.45;
  color: var(--color-text-primary);
  opacity: 0.7;
  margin-top: 12px;
  text-align: left;
}

/* ---- DONUT — single-value highlight ring -----------------
   Use when a single percentage / proportion is the headline.
   For multi-segment part-of-whole, use a different pattern
   (typically a horizontal bar with segment labels).
   The donut highlights ONE value against a track. The value
   itself reads at 75px Light in the data-primary color.   */
.chart-donut {
  display: grid;
  place-items: center;
  max-width: 520px;
  margin: 30px auto;
  aspect-ratio: 1 / 1;
  width: 100%;
  min-width: 0;
}
.chart-donut__svg { width: 100%; height: 100%; display: block; }
.chart-donut__track {
  fill: none;
  stroke: var(--color-data-primary-track);
  stroke-width: 4;
}
.chart-donut__seg {
  fill: none;
  stroke: var(--color-data-primary);
  stroke-width: 4;
  stroke-linecap: butt;
  transition: stroke-dashoffset 600ms ease;
}
.chart-donut__value {
  font-family: var(--font-family-display);
  font-weight: 300;
  font-size: 36px;
  fill: var(--color-data-primary);
  text-anchor: middle;
  dominant-baseline: central;
}

/* ---- PIE — single-value highlight, solid fill -----------
   Same role as the donut but filled. Use when the visual
   weight of a solid sector reads better than a ring.    */
.chart-pie {
  display: grid;
  place-items: center;
  max-width: 520px;
  margin: 30px auto;
  aspect-ratio: 1 / 1;
  width: 100%;
}
.chart-pie__svg { width: 100%; height: 100%; display: block; }
.chart-pie__track { fill: var(--color-data-primary-track); }
.chart-pie__seg   { fill: var(--color-data-primary); }
.chart-pie__value {
  font-family: var(--font-family-display);
  font-weight: 300;
  font-size: 28px;
  fill: var(--color-data-primary);
  text-anchor: middle;
  dominant-baseline: central;
}

/* ---- BAR CHART — horizontal, thin track + label below ---
   The Newfangled bar chart is a thin 5px track with the
   filled portion in data-primary, then VALUE and LABEL on
   a single line beneath. Bars stack with 25px gaps between
   rows. Use when comparing magnitudes across 3-8 categories. */
.chart-bar {
  display: flex;
  flex-direction: column;
  gap: 25px;
  max-width: 820px;
  margin: 30px auto;
  padding: 20px 0;
}
.chart-bar__row { display: flex; flex-direction: column; gap: 10px; }
.chart-bar__track {
  height: 5px;
  width: 100%;
  background: var(--color-data-primary-track);
  position: relative;
}
.chart-bar__fill {
  height: 100%;
  background: var(--color-data-primary);
  width: var(--chart-fill, 0%);
  transition: width 800ms cubic-bezier(0.22, 0.61, 0.36, 1);
}
.chart-bar__caption {
  display: flex;
  gap: 10px;
  align-items: baseline;
  padding-top: 5px;
}
.chart-bar__value {
  font-family: var(--font-family-display);
  font-weight: 600;
  font-size: 19px;
  color: var(--color-data-primary);
  white-space: nowrap;
}
.chart-bar__label {
  font-family: var(--font-family-body);
  font-weight: 600;
  font-size: 15px;
  color: var(--color-data-primary);
  flex: 1;
  line-height: 1.35;
}

/* ---- LINE GRAPH — smooth path with point markers --------
   SVG-based for crisp rendering. Axes and gridlines at
   --color-data-grid (15% wash). Path and points in
   --color-data-primary. Value labels above each point
   in 16px IBM Plex Sans SemiBold.
   The SVG uses viewBox="0 0 420 320" with a 30px gutter
   on all sides for labels. Plot area is 360x240 inside. */
.chart-line, .chart-scatter, .chart-histogram, .chart-boxplot {
  display: block;
  max-width: 520px;
  margin: 30px auto;
  width: 100%;
}
.chart-line__svg,
.chart-scatter__svg,
.chart-histogram__svg,
.chart-boxplot__svg {
  width: 100%;
  height: auto;
  display: block;
}
.chart-line__grid,
.chart-scatter__grid,
.chart-histogram__grid,
.chart-boxplot__grid {
  stroke: var(--color-data-grid);
  stroke-width: 1;
  fill: none;
}
.chart-line__path {
  stroke: var(--color-data-primary);
  stroke-width: 2;
  fill: none;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: 0.55;
}
.chart-line__point,
.chart-scatter__point {
  fill: var(--color-data-primary);
}
.chart-line__label,
.chart-scatter__label,
.chart-histogram__label,
.chart-boxplot__label {
  font-family: var(--font-family-display);
  font-weight: 600;
  font-size: 13px;
  fill: var(--color-data-primary);
  text-anchor: middle;
}

/* ---- SCATTER PLOT — point cloud ------------------------
   Same gridline + label system as the line graph. No
   connecting path; just point markers.                    */
.chart-scatter__label--x { text-anchor: middle; }
.chart-scatter__label--y { text-anchor: end; }

/* ---- HISTOGRAM — thin vertical bars on a gridded plot --
   Use for distribution-of-a-variable visuals. Each bin
   renders as a 5px-wide vertical strip — full height in
   the track wash + filled portion in data-primary up to
   the bin's value.                                        */
.chart-histogram__track { fill: var(--color-data-primary-track); }
.chart-histogram__fill  { fill: var(--color-data-primary); }
.chart-histogram__label--x { text-anchor: middle; }
.chart-histogram__label--y { text-anchor: end; }

/* ---- BOX PLOT — five-number summary -------------------
   Stroked box for Q1-Q3 with a light-blue fill. Median
   line crosses horizontally. Whiskers extend to Min/Max.
   Outlier shown as a solid dot. Annotation labels at right
   in 12px IBM Plex Sans (Bold name + Regular value).      */
.chart-boxplot__box-fill { fill: var(--color-data-primary-track); }
/* The box has TOP + BOTTOM edges only (Q3 and Q1 markers). No left/right
   stroke — leaving the sides open is the brand's convention so the box reads
   as a paired pair of horizontal markers rather than a bounded rectangle. */
.chart-boxplot__box-edge { stroke: var(--color-data-primary); stroke-width: 1.5; }
.chart-boxplot__whisker  { stroke: var(--color-data-primary); stroke-width: 1.5; }
.chart-boxplot__median   { stroke: var(--color-data-primary); stroke-width: 1.5; }
.chart-boxplot__outlier    { fill: var(--color-data-primary); }
.chart-boxplot__lead       { stroke: var(--color-data-primary); stroke-width: 1; opacity: 0.4; stroke-dasharray: 2 2; }
.chart-boxplot__annot {
  font-family: var(--font-family-display);
  font-size: 12px;
  fill: var(--color-data-primary);
  dominant-baseline: middle;
}
.chart-boxplot__annot-term  { font-weight: 700; }
.chart-boxplot__annot-value { font-weight: 400; }

/* ---- SANKEY — flow between stages ---------------------
   Four-stage flow with categorical nodes and curved
   connectors. First-stage node colors map to brand
   palette (blue, purple, green-dark, orange); subsequent
   nodes use data-primary or a neutral fill. Connectors
   sit behind nodes at 20% opacity.                       */
.chart-sankey {
  display: block;
  max-width: 820px;
  margin: 30px auto;
  width: 100%;
}
.chart-sankey__svg { width: 100%; height: auto; display: block; }
.chart-sankey__col-axis { stroke: rgba(34, 34, 34, 0.1); stroke-width: 1; }
[data-theme="dark"] .chart-sankey__col-axis { stroke: rgba(255, 255, 255, 0.1); }
.chart-sankey__node--blue    { fill: var(--color-data-primary); }
.chart-sankey__node--purple  { fill: var(--nf-purple, #50246C); }
.chart-sankey__node--green   { fill: var(--nf-green-dark, #1D9A72); }
.chart-sankey__node--orange  { fill: var(--nf-orange, #F36D32); }
.chart-sankey__node--neutral { fill: rgba(34, 34, 34, 0.2); }
[data-theme="dark"] .chart-sankey__node--neutral { fill: rgba(255, 255, 255, 0.2); }
.chart-sankey__flow--blue   { fill: var(--color-data-primary); opacity: 0.2; }
.chart-sankey__flow--purple { fill: var(--nf-purple, #50246C); opacity: 0.2; }
.chart-sankey__flow--green  { fill: var(--nf-green-dark, #1D9A72); opacity: 0.2; }
.chart-sankey__flow--orange { fill: var(--nf-orange, #F36D32); opacity: 0.2; }
.chart-sankey__flow--neutral { fill: rgba(34, 34, 34, 0.1); }
[data-theme="dark"] .chart-sankey__flow--neutral { fill: rgba(255, 255, 255, 0.1); }
.chart-sankey__label {
  font-family: var(--font-family-display);
  font-size: 12px;
  fill: var(--color-text-primary);
}
.chart-sankey__label--name  { font-weight: 700; }
.chart-sankey__label--value { font-weight: 400; }

/* ---- VENN DIAGRAM — two overlapping circles ----------
   Stroked-only rendering (no fills). One circle reads as
   the focal subject (data-primary stroke at full opacity);
   the other reads as context (data-primary stroke at low
   opacity). An optional arrow + value label below points
   at one circle to anchor a specific metric.            */
.chart-venn {
  display: block;
  max-width: 520px;
  margin: 30px auto;
  width: 100%;
}
.chart-venn__svg { width: 100%; height: auto; display: block; }
.chart-venn__circle--muted   { fill: none; stroke: var(--color-data-primary); stroke-width: 1.5; opacity: 0.25; }
.chart-venn__circle--focal   { fill: none; stroke: var(--color-data-primary); stroke-width: 2; }
.chart-venn__arrow { stroke: var(--color-data-primary); stroke-width: 1; fill: var(--color-data-primary); }
.chart-venn__value {
  font-family: var(--font-family-display);
  font-weight: 400;
  font-size: 28px;
  fill: var(--color-data-primary);
  text-anchor: middle;
}

/* Compact 2-up grid for placing two small charts side by side
   inside a card row width. Used in the demo section. */
.chart-grid--2up {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 40px;
  max-width: 820px;
  margin: 30px auto;
}
.chart-grid--2up > .chart-donut,
.chart-grid--2up > .chart-pie,
.chart-grid--2up > .chart-venn,
.chart-grid--2up > .chart-line,
.chart-grid--2up > .chart-scatter,
.chart-grid--2up > .chart-histogram,
.chart-grid--2up > .chart-boxplot { margin: 0; max-width: 100%; }
@media (max-width: 640px) {
  .chart-grid--2up { grid-template-columns: 1fr; gap: 24px; }
}

/* ============================================================
   11. BRAND OVERRIDES — landing zone for structural brand
                          selector overrides
   ============================================================
   Brand bridges that need to override structural selectors
   (rather than just CSS variables) paste their rules HERE,
   immediately before the end of the stylesheet. Token overrides
   stay in their own :root block near the top of this file. See
   SKILL.md "What a brand layer is allowed to change" for
   guidance on when each insertion point applies.
   ============================================================ */
/* ---- brand bridge overrides go here ---- */
@media (min-width: 1100px) {
  :root { --sidebar-width: 240px; --side-nav-width: 260px; }
}
#membership .table th { white-space: nowrap; vertical-align: bottom; }
#membership .table td:first-child { white-space: nowrap; }
#membership .table td:nth-child(2), #membership .table td:nth-child(3) { white-space: nowrap; }
#membership .table td { white-space: nowrap; }
.gp-usd-wrap { display: block; color: var(--color-text-secondary); font-size: 0.9em; }
.gp-gold-note { margin-top: 12px; color: var(--color-text-secondary); }
@media (max-width: 640px) { #membership .table td { white-space: normal; } }
/* ---- Good & Prosper structural overrides ---- */
:root { box-sizing: border-box; padding-top: env(safe-area-inset-top, 0px); padding-bottom: env(safe-area-inset-bottom, 0px); }
html { scroll-padding-top: env(safe-area-inset-top, 0px); }
.mobile-header { top: env(safe-area-inset-top, 0px); }
@media (max-width: 640px) {
  body::after { content: ""; position: fixed; inset: 0 0 auto; height: env(safe-area-inset-top, 0px); z-index: 10; background: var(--color-bg-header); }
}
.logo-block, .site-footer__logo { background: transparent; display: flex; align-items: center; gap: 11px; width: auto; }
.gp-mark { width: 30px; height: 30px; display: block; flex-shrink: 0; }
.site-footer__logo .gp-mark { width: 40px; height: 40px; }
.gp-wordmark { font-family: var(--font-family-display); font-weight: 600; font-size: 20px; letter-spacing: 0.01em; line-height: 1; white-space: nowrap; color: var(--logo-color); }
.site-footer__logo .gp-wordmark { color: #F7F4EC; font-size: 22px; }
.strapline, .page-nav__links a, .mobile-drawer__links a, .table th {
  font-family: var(--font-family-mono);
}
.strapline { text-transform: uppercase; letter-spacing: 0.12em; font-size: 14px; }
.page-nav__links a, .mobile-drawer__links a { font-size: 15px; letter-spacing: 0.02em; }
.table th { font-weight: 500; font-size: 14px; text-transform: uppercase; letter-spacing: 0.06em; }
.table-wrap { overflow-x: auto; max-width: 100%; }
.table-wrap .table { min-width: 640px; }
img, svg { max-width: 100%; }
.gp-venn-label { font-family: var(--font-family-mono); font-size: 15px; fill: var(--color-text-primary); text-anchor: middle; }
.gp-venn-center { font-family: var(--font-family-display); font-size: 20px; font-weight: 600; fill: var(--color-data-primary); text-anchor: middle; }
.gp-chart-note { margin-top: 12px; }

.gp-form { margin-top: 32px; max-width: 820px; display: flex; flex-direction: column; gap: 20px; }
.gp-field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
@media (max-width: 640px) { .gp-field-row { grid-template-columns: 1fr; } }
.gp-field { display: flex; flex-direction: column; gap: 8px; font-family: var(--font-family-body); color: var(--color-text-primary); }
.gp-field > span { font-family: var(--font-family-mono); font-size: 14px; letter-spacing: 0.04em; color: var(--color-text-secondary); }
.gp-field input, .gp-field select, .gp-field textarea {
  font: inherit; font-size: 18px; color: var(--color-text-primary); background: var(--color-bg-elevated);
  border: 1px solid var(--color-border); border-radius: 0; padding: 12px 14px; width: 100%; box-sizing: border-box;
}
.gp-field input:focus, .gp-field select:focus, .gp-field textarea:focus { outline: 2px solid var(--color-data-primary); outline-offset: 1px; }
.gp-hidden { position: absolute; left: -9999px; }
.gp-form-status:empty { display: none; }
.gp-form-status { color: var(--color-text-primary); }
.sidebar .card .button.button--primary, .sidebar .card .button.button--primary:visited { background: #F7F4EC !important; color: #0F3B4F !important; text-decoration: none !important; }

.logo-block, .site-footer__logo { max-width: none; }
body, .site-footer { overflow-x: clip; }



/* Steven photos */
.gp-photo { margin: 30px 0 24px; }
.gp-photo img { display: block; width: 100%; height: auto; max-width: 100%; }
.sidebar .card .gp-portrait { display: block; width: 100%; height: auto; margin: 0 0 6px; }
