/* =========================================================
   CSA Component Library — Color Tokens
   Source of truth: csa-component-library-SPEC.md, section 1
   Naming: --{letter}{ramp}, letter = color family, ramp = 25–800
   No semantic aliases (--csa-blue, --navy, --ink, etc. are retired —
   see the spec's retirement map if porting old page code).
   ========================================================= */
:root {
  /* BLUE */
  --b25:#E1F2FF; --b50:#C4E6FF; --b75:#99D4FF; --b100:#66BEFF; --b200:#33A9FF;
  --b300:#0093FF; --b400:#0072C7; --b500:#00549F; --b600:#003E77; --b700:#102A55; --b800:#0C2040;

  /* ORANGE */
  --o25:#FFF3DE; --o50:#FFE7BD; --o75:#FFDB99; --o100:#FFCA66; --o200:#FFB833;
  --o300:#FFA600; --o400:#FF9100; --o500:#FF7A00; --o600:#F25D00; --o700:#CF4300; --o800:#B03100;

  /* TEAL */
  --t25:#C2FFFF; --t50:#8CFAFA; --t75:#50F0ED; --t100:#14D1D1; --t200:#00B1B4;
  --t300:#00989C; --t400:#1A8488; --t500:#006A69; --t600:#00504F; --t700:#003C3C; --t800:#002828;

  /* PURPLE */
  --p25:#F3E8FF; --p50:#ECD6FF; --p75:#DCB3FF; --p100:#C890FF; --p200:#B170FF;
  --p300:#9962DF; --p400:#8938D9; --p500:#6700CF; --p600:#4D009B; --p700:#2F006E; --p800:#200047;

  /* GREEN */
  --g25:#AAFFDA; --g50:#70FFC2; --g75:#00DF9B; --g100:#00C181; --g200:#00A36F;
  --g300:#008757; --g400:#007141; --g500:#005D31; --g600:#004926; --g700:#00381B; --g800:#002713;

  /* RED */
  --r25:#FFD6CF; --r50:#FFBAAF; --r75:#FF9B8E; --r100:#FF7267; --r200:#FD4D4F;
  --r300:#EE0E34; --r400:#CB002B; --r500:#9D0016; --r600:#760010; --r700:#4F000B; --r800:#380009;

  /* NEUTRAL */
  --n25:#EEF3F8; --n50:#DDE6EF; --n75:#C3D1DE; --n100:#A5B7C6; --n200:#899CAC;
  --n300:#697A89; --n400:#59626F; --n500:#454C5B; --n600:#383C4C; --n700:#282C38; --n800:#191929;

  /* WHITE / BLACK — standalone constants, not steps in a 25–800 ramp */
  --white:#FFFFFF; --black:#101019;
}
/* =========================================================
   CSA Component Library — Typography Tokens
   Source: SPEC section 1 (Typography) + "Two distinct small-label
   components" + "kicker-sub" subsections.

   Font loading is NOT done here (CSS can't add <link> tags) — the
   two required <link> tags live in dist/csa-library.html's <head>
   and must be copied into any page that uses this library:
     <link rel="stylesheet" href="https://use.typekit.net/tsm2vln.css">
     <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Red+Hat+Text:wght@400;500;600;700&display=swap">
   Typekit kit tsm2vln supplies both azo-sans-web and peridot-pe-variable.
   Red Hat Text tops out at weight 700 on Google Fonts (no 900 cut) —
   browsers synthesize the extra boldness for h1–h4 in the fallback case,
   which only matters if Typekit fails to load.

   These must stay live <link> tags in the consuming page's own <head>
   — never copy/inline the kit's underlying @font-face CSS instead.
   Adobe's kit is domain-restricted (only approved domains can actually
   load the font files it points to) and can change its served CSS/font
   URLs over time; a live <link> tracks both automatically, a pasted
   snapshot doesn't and can silently stop working. If the <link> is
   missing, --font/--font-kicker fall back to the next name in their
   stack with no visible error, so this is easy to miss on a quick look.
   ========================================================= */
:root {
  --font: 'azo-sans-web','Red Hat Text',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;
  --font-kicker: "peridot-pe-variable", sans-serif;

  /* Small text sizes — the two steps below body (16px) and .csa-block
     (18px), so the scale reads 12 / 14.5 / 16 / 18. Components use these
     values directly (buttons, .csa-go, tabs, form hints...); pages use
     the matching classes, .csa-block-small and .csa-caption, below.
     14.5 is the library's one deliberate half size — it's what buttons,
     links and card copy were already set at; don't add others. */
  --text-small: 14.5px;
  --text-caption: 12px;
}

body { font-family: var(--font); }

.csa-title {
  color: var(--b800);
  font-weight: 700;
  letter-spacing: -.02em;
  line-height: initial;
  font-size: initial;
}

.csa-subtitle {
  color: var(--n300);
  font-weight: 400;
  letter-spacing: 0;
}

.csa-title:has(+ .csa-subtitle) { margin-bottom: 8px; }

h1.csa-title, h2.csa-title, h3.csa-title, h4.csa-title,
h1.csa-subtitle, h2.csa-subtitle, h3.csa-subtitle, h4.csa-subtitle {
  color: var(--b800);
  letter-spacing: -.02em;
  line-height: 1.12;
  font-weight: 900; /* unified per spec — live pages had a 900/750 split */
}

/* h1 only ever appears inside .hero across all 4 reference pages — there
   is no smaller "generic" h1 anywhere to copy. Their hero h1 clamps run
   38–42px min / 60–76px max; this is the average across all 4, so a
   plain h1 reads at the same punchy scale you'd see on those pages.
   .csa-hero h1 in hero.css layers its own close variant on top (a
   deliberate exception for its own longer heading, not a pattern to
   extend — sizing elsewhere in this library matches this plain h1
   directly rather than any one source page's specific value, since
   this doc outlives the source HTML it was extracted from). */
h1.csa-title, h1.csa-subtitle { font-size: clamp(40px, 5.8vw, 66px); letter-spacing: -.03em; }
h2.csa-title, h2.csa-subtitle { font-size: clamp(28px, 3.4vw, 40px); letter-spacing: -.025em; }
h3.csa-title, h3.csa-subtitle { font-size: 22px; letter-spacing: -.02em; }
h4.csa-subtitle { font-weight: 700; }
h4.csa-title { font-weight: 700; letter-spacing: 0; }

.csa-muted { color: var(--n300); }

.csa-hr { border: none; border-top: 1px solid var(--n50); margin: 0; }
[data-surface="dark"] .csa-hr { border-top-color: var(--b600); }
[data-surface="black"] .csa-hr { border-top-color: var(--n700); }

.csa-block { 
  font-size: 18px; 
  margin-top: 12px; 
  margin-bottom: 12px; 
  line-height: 1.6em; 
  color: var(--n500); 
}

/* Block-small — .csa-block's smaller sibling for short paragraphs: card
   copy, notes, supporting text that's still meant to be read as prose.
   Same shape as .csa-block, scaled down (lighter margins so it sits
   comfortably inside cards). */
.csa-block-small {
  font-size: var(--text-small);
  margin-top: 8px;
  margin-bottom: 8px;
  line-height: 1.55;
  color: var(--n500);
}
[data-surface="dark"] .csa-block-small, [data-surface="black"] .csa-block-small { color: var(--n75); }

/* Caption — small supporting text: dates, bylines, read times, counts,
   footnotes, image captions, helper text. Short lines only; anything
   paragraph-length should be .csa-block-small instead. No margins — it's
   often inline (a <span> in a card footer). The colors are the lightest
   grays that still pass WCAG AA (4.5:1) at 12px: n400 on white/n25,
   n200 on dark/black and their cards (--n300, the usual "muted" gray,
   fails at this size). The [data-surface] copy outranks the surfaces'
   own `[data-surface] p` text colors when the caption is a <p>. */
.csa-caption, [data-surface] .csa-caption {
  font-size: var(--text-caption);
  line-height: 1.5;
  color: var(--n400);
}
[data-surface="dark"] .csa-caption, [data-surface="black"] .csa-caption { color: var(--n200); }

/* Kicker — short punchy section eyebrow label ("THE PROGRAM").
   Surface-aware: orange-700 on light/gradient, orange-500 on dark.

   Which one to use, .csa-kicker or .csa-kicker-sub below: it's chosen by
   the heading it introduces, not by nesting depth alone —
     - Above an h1 or h2 (hero, section-level headings) → .csa-kicker
     - Above an h3, or with no heading at all (a standalone label inside
       a card/component — "Sample card", "Rules") → .csa-kicker-sub
   This isn't enforced by a selector like `.csa-kicker:has(+ h3)` on
   purpose: kicker-sub's no-heading cases mean that wouldn't even be
   correct, just differently incomplete — it'd catch the h3 pairing but
   still render the standalone labels as the big kicker by default,
   trading one class of mistake for another and making the class render
   invisibly-differently by DOM context, unlike every other variant in
   this library (always an explicit class the author picks). */
.csa-kicker {
  font-family: var(--font-kicker);
  font-size: 12px;
  letter-spacing: .2em;
  text-transform: uppercase;
  font-variation-settings: 'wght' 500, 'wdth' 125, 'ital' 0;
  color: var(--o700);
}
[data-surface="dark"] .csa-kicker, [data-surface="black"] .csa-kicker { color: var(--o500); }

/* Kicker-sub — see the usage rule in the .csa-kicker comment above. */
.csa-kicker-sub {
  font-family: var(--font-kicker);
  font-size: 12px;
  letter-spacing: .08em;
  text-transform: uppercase;
  font-variation-settings: 'wght' 400, 'wdth' 125, 'ital' 0;
  color: var(--o700);
}
[data-surface="dark"] .csa-kicker-sub, [data-surface="black"] .csa-kicker-sub { color: var(--o500); }

/* Kicker-blue — a color-only modifier, combined with either base class
   (<span class="csa-kicker csa-kicker-blue"> or
   <span class="csa-kicker-sub csa-kicker-blue">), the same way
   .csa-tag-blue pairs with either tag type. Size, font and spacing stay
   in the base class; this only swaps orange for blue: b500 on
   light/gradient, b200 on dark/black. The dark/black rule needs its own
   selector at the same weight as the base kicker's dark rule above, and
   sits after it, or orange would win on dark surfaces. Not wired into
   .csa-hero/.csa-cta, which have their own always-dark kicker colors
   (hero.css) — a blue modifier inside those is overridden by design. */
.csa-kicker-blue { color: var(--b500); }
[data-surface="dark"] .csa-kicker-blue, [data-surface="black"] .csa-kicker-blue { color: var(--b200); }

/* Rule — small accent bar sitting under a kicker+heading+paragraph
   intro. Extracted as-is for shape/size (.rule, identical across all 4
   reference pages: 44×3px, 2px radius). Color is NOT extracted as-is,
   though: the source used a page-specific teal (var(--csai)) that has
   no equivalent in this system, so it's reassigned to the same orange
   kicker-accent used everywhere else instead — same surface-aware
   split as .csa-kicker (o700 light/gradient, o500 dark/black), since
   the rule is doing the same "accent mark near the kicker" job. */
.csa-rule { width: 44px; height: 3px; border-radius: 2px; margin-top: 8px; margin-bottom: 8px; background: var(--o700); }
[data-surface="dark"] .csa-rule, [data-surface="black"] .csa-rule { background: var(--o500); }
h1:has(+ .csa-rule), h2:has(+ .csa-rule), h3:has(+ .csa-rule),
h4:has(+ .csa-rule), h5:has(+ .csa-rule), h6:has(+ .csa-rule) { margin-bottom: 0; }


/* =========================================================
   CSA Component Library — Spacing / Layout Tokens
   Source: SPEC section 1, "LAYOUT"
   ========================================================= */
:root {
  --maxw: 1200px;
}

.csa-wrap {
  max-width: var(--maxw);
  margin: 0 auto;
  padding: 0 24px;
}

.csa-section {
  padding: 92px 0;
}

/* Named size variants — lighter-weight alternatives to the 92px default
   rhythm, for a band that shouldn't take a full section's worth of
   breathing room (a thin banner, a tight intro strip, an oversized
   closing CTA). */
.csa-section-small  { padding: 32px 0; }
.csa-section-medium { padding: 64px 0; }
.csa-section-large  { padding: 128px 0; }
/* =========================================================
   CSA Component Library — Shadow Tokens
   Source: SPEC section 1 — only two shadow levels exist in real usage.
   ========================================================= */
:root {
  --shadow-sm: 0 1px 2px rgba(12,35,64,.06);
  --shadow:    0 2px 6px rgba(12,35,64,.06), 0 18px 40px -18px rgba(12,35,64,.22);
}
/* =========================================================
   CSA Component Library — Icon glyph (shared primitive)
   A small icon glyph that lives inside a pill/label component (tag,
   badge, ...), recolored via mask + currentColor rather than a plain
   <img>: a raster image can't be recolored by CSS at all, so it'd need
   a separate pre-baked file per color AND per surface AND per state
   (hover/pressed) to track the text color above it — masking tracks
   it automatically, everywhere, for free, from a single flat-glyph
   SVG file. Sized independent of the source SVG's own viewBox via
   mask-size:contain, so any icon file works, not just ones sharing
   one particular icon's dimensions.

   This is the fallback route, not the default: when a component
   already has a pre-colored file variant for the exact color it needs
   (e.g. badges' Calendar-b100/b300/b500.svg), use a plain <img> instead
   — it's the simpler markup and gives exact, designer-controlled
   control over that one file. Reach for this glyph when no matching
   color file exists, or the icon's color needs to track an arbitrary/
   dynamic text color rather than one fixed file per surface.

   Was duplicated verbatim per-component (.csa-tag-icon-glyph,
   .csa-badge-icon-glyph) before being pulled out here — same mechanics,
   just a different custom-property name and size in each copy, which
   is exactly how the two sizes drifted out of sync once. One
   definition now; each component sets only its own size (see
   tags.css/badges.css) scoped under its own ancestor class.

   KNOWN LIMITATION, confirmed by testing earlier in this project:
   mask-image does not render under the file:// protocol in Chrome/
   Safari — it works normally over http/https (a real deployed page is
   unaffected), but silently shows nothing (not an error, just a blank
   glyph) when the HTML file is opened directly by double-click rather
   than served. This matters here specifically because build.mjs builds
   dist/preview/index.html as a standalone "double-click-able bundle"
   for people who won't run a local server — anyone opening it that way
   will see blank icons wherever .csa-icon-glyph is used (the showcase's
   own tag/badge icon demos included). If a consuming page must also
   support being opened straight from disk, don't use this primitive —
   inline that icon's SVG directly with fill="currentColor" instead
   (get the same "recolors with the text automatically" behavior without
   the file:// failure mode; see tags.css's icon markup contract for a
   worked example). Left as mask-image here regardless, since it's the
   right technique for this library's real use case — pages actually
   deployed on cloudsecurityalliance.org, always served over https. */
.csa-icon-glyph {
  display: inline-block; flex-shrink: 0;
  width: 14px; height: 14px;
  background-color: currentColor;
  mask-image: var(--icon-glyph); mask-size: contain; mask-repeat: no-repeat; mask-position: center;
  -webkit-mask-image: var(--icon-glyph); -webkit-mask-size: contain; -webkit-mask-repeat: no-repeat; -webkit-mask-position: center;
}
[hidden] {
  display: none !important;
}

.csa-text-left   { text-align: left !important; }
.csa-text-center { text-align: center !important; }
.csa-text-right  { text-align: right !important; }

.csa-center {
  display: flex;
  flex-direction: column;
  align-items: center;
}

/* Visually hidden, but still read by screen readers and still focusable.
   For text that only assistive tech needs (e.g. naming the target of a
   repeated "View Working Group" link) and for native controls that drive
   a CSS-only component without being seen (the .csa-card-flip checkbox). */
.csa-sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}
/* =========================================================
   CSA Component Library — Spacing Helpers
   Margin and padding utility classes modeled after Bulma's
   spacing helpers. All use !important so they work as
   overrides anywhere in the component stack.

   Naming:  .csa-{m|p}{direction}-{step}
   Types:   m = margin   p = padding
   Dirs:    (none)=all  t=top  b=bottom  l=left  r=right
            x=left+right  y=top+bottom
   Steps:   0=0  1=4px  2=8px  3=12px  4=16px  5=24px  6=48px
   Auto:    margin only — .csa-m{dir}-auto
   ========================================================= */

/* ── Margin — all sides ─────────────────────────────────── */
.csa-m-0    { margin: 0 !important; }
.csa-m-1    { margin: 4px !important; }
.csa-m-2    { margin: 8px !important; }
.csa-m-3    { margin: 12px !important; }
.csa-m-4    { margin: 16px !important; }
.csa-m-5    { margin: 24px !important; }
.csa-m-6    { margin: 48px !important; }
.csa-m-auto { margin: auto !important; }

/* ── Margin — top ───────────────────────────────────────── */
.csa-mt-0    { margin-top: 0 !important; }
.csa-mt-1    { margin-top: 4px !important; }
.csa-mt-2    { margin-top: 8px !important; }
.csa-mt-3    { margin-top: 12px !important; }
.csa-mt-4    { margin-top: 16px !important; }
.csa-mt-5    { margin-top: 24px !important; }
.csa-mt-6    { margin-top: 48px !important; }
.csa-mt-auto { margin-top: auto !important; }

/* ── Margin — bottom ────────────────────────────────────── */
.csa-mb-0    { margin-bottom: 0 !important; }
.csa-mb-1    { margin-bottom: 4px !important; }
.csa-mb-2    { margin-bottom: 8px !important; }
.csa-mb-3    { margin-bottom: 12px !important; }
.csa-mb-4    { margin-bottom: 16px !important; }
.csa-mb-5    { margin-bottom: 24px !important; }
.csa-mb-6    { margin-bottom: 48px !important; }
.csa-mb-auto { margin-bottom: auto !important; }

/* ── Margin — left ──────────────────────────────────────── */
.csa-ml-0    { margin-left: 0 !important; }
.csa-ml-1    { margin-left: 4px !important; }
.csa-ml-2    { margin-left: 8px !important; }
.csa-ml-3    { margin-left: 12px !important; }
.csa-ml-4    { margin-left: 16px !important; }
.csa-ml-5    { margin-left: 24px !important; }
.csa-ml-6    { margin-left: 48px !important; }
.csa-ml-auto { margin-left: auto !important; }

/* ── Margin — right ─────────────────────────────────────── */
.csa-mr-0    { margin-right: 0 !important; }
.csa-mr-1    { margin-right: 4px !important; }
.csa-mr-2    { margin-right: 8px !important; }
.csa-mr-3    { margin-right: 12px !important; }
.csa-mr-4    { margin-right: 16px !important; }
.csa-mr-5    { margin-right: 24px !important; }
.csa-mr-6    { margin-right: 48px !important; }
.csa-mr-auto { margin-right: auto !important; }

/* ── Margin — horizontal (left + right) ─────────────────── */
.csa-mx-0    { margin-left: 0 !important;    margin-right: 0 !important; }
.csa-mx-1    { margin-left: 4px !important;  margin-right: 4px !important; }
.csa-mx-2    { margin-left: 8px !important;  margin-right: 8px !important; }
.csa-mx-3    { margin-left: 12px !important; margin-right: 12px !important; }
.csa-mx-4    { margin-left: 16px !important; margin-right: 16px !important; }
.csa-mx-5    { margin-left: 24px !important; margin-right: 24px !important; }
.csa-mx-6    { margin-left: 48px !important; margin-right: 48px !important; }
.csa-mx-auto { margin-left: auto !important; margin-right: auto !important; }

/* ── Margin — vertical (top + bottom) ──────────────────── */
.csa-my-0    { margin-top: 0 !important;    margin-bottom: 0 !important; }
.csa-my-1    { margin-top: 4px !important;  margin-bottom: 4px !important; }
.csa-my-2    { margin-top: 8px !important;  margin-bottom: 8px !important; }
.csa-my-3    { margin-top: 12px !important; margin-bottom: 12px !important; }
.csa-my-4    { margin-top: 16px !important; margin-bottom: 16px !important; }
.csa-my-5    { margin-top: 24px !important; margin-bottom: 24px !important; }
.csa-my-6    { margin-top: 48px !important; margin-bottom: 48px !important; }
.csa-my-auto { margin-top: auto !important; margin-bottom: auto !important; }

/* ── Padding — all sides ────────────────────────────────── */
.csa-p-0 { padding: 0 !important; }
.csa-p-1 { padding: 4px !important; }
.csa-p-2 { padding: 8px !important; }
.csa-p-3 { padding: 12px !important; }
.csa-p-4 { padding: 16px !important; }
.csa-p-5 { padding: 24px !important; }
.csa-p-6 { padding: 48px !important; }

/* ── Padding — top ──────────────────────────────────────── */
.csa-pt-0 { padding-top: 0 !important; }
.csa-pt-1 { padding-top: 4px !important; }
.csa-pt-2 { padding-top: 8px !important; }
.csa-pt-3 { padding-top: 12px !important; }
.csa-pt-4 { padding-top: 16px !important; }
.csa-pt-5 { padding-top: 24px !important; }
.csa-pt-6 { padding-top: 48px !important; }

/* ── Padding — bottom ───────────────────────────────────── */
.csa-pb-0 { padding-bottom: 0 !important; }
.csa-pb-1 { padding-bottom: 4px !important; }
.csa-pb-2 { padding-bottom: 8px !important; }
.csa-pb-3 { padding-bottom: 12px !important; }
.csa-pb-4 { padding-bottom: 16px !important; }
.csa-pb-5 { padding-bottom: 24px !important; }
.csa-pb-6 { padding-bottom: 48px !important; }

/* ── Padding — left ─────────────────────────────────────── */
.csa-pl-0 { padding-left: 0 !important; }
.csa-pl-1 { padding-left: 4px !important; }
.csa-pl-2 { padding-left: 8px !important; }
.csa-pl-3 { padding-left: 12px !important; }
.csa-pl-4 { padding-left: 16px !important; }
.csa-pl-5 { padding-left: 24px !important; }
.csa-pl-6 { padding-left: 48px !important; }

/* ── Padding — right ────────────────────────────────────── */
.csa-pr-0 { padding-right: 0 !important; }
.csa-pr-1 { padding-right: 4px !important; }
.csa-pr-2 { padding-right: 8px !important; }
.csa-pr-3 { padding-right: 12px !important; }
.csa-pr-4 { padding-right: 16px !important; }
.csa-pr-5 { padding-right: 24px !important; }
.csa-pr-6 { padding-right: 48px !important; }

/* ── Padding — horizontal (left + right) ────────────────── */
.csa-px-0 { padding-left: 0 !important;    padding-right: 0 !important; }
.csa-px-1 { padding-left: 4px !important;  padding-right: 4px !important; }
.csa-px-2 { padding-left: 8px !important;  padding-right: 8px !important; }
.csa-px-3 { padding-left: 12px !important; padding-right: 12px !important; }
.csa-px-4 { padding-left: 16px !important; padding-right: 16px !important; }
.csa-px-5 { padding-left: 24px !important; padding-right: 24px !important; }
.csa-px-6 { padding-left: 48px !important; padding-right: 48px !important; }

/* ── Padding — vertical (top + bottom) ──────────────────── */
.csa-py-0 { padding-top: 0 !important;    padding-bottom: 0 !important; }
.csa-py-1 { padding-top: 4px !important;  padding-bottom: 4px !important; }
.csa-py-2 { padding-top: 8px !important;  padding-bottom: 8px !important; }
.csa-py-3 { padding-top: 12px !important; padding-bottom: 12px !important; }
.csa-py-4 { padding-top: 16px !important; padding-bottom: 16px !important; }
.csa-py-5 { padding-top: 24px !important; padding-bottom: 24px !important; }
.csa-py-6 { padding-top: 48px !important; padding-bottom: 48px !important; }
/* =========================================================
   CSA Component Library — Color Helpers
   Text-color and background-color utility classes for every
   color token defined in tokens/colors.css.

   Naming:  .csa-color-{token}   → color (text)
            .csa-bg-{token}      → background-color
   Tokens:  b/o/t/p/g/r/n + ramp (25–800), plus white & black
   All use !important so they work as overrides anywhere.
   ========================================================= */

/* ── BLUE ───────────────────────────────────────────────── */
.csa-color-b25  { color: var(--b25)  !important; } .csa-bg-b25  { background-color: var(--b25)  !important; }
.csa-color-b50  { color: var(--b50)  !important; } .csa-bg-b50  { background-color: var(--b50)  !important; }
.csa-color-b75  { color: var(--b75)  !important; } .csa-bg-b75  { background-color: var(--b75)  !important; }
.csa-color-b100 { color: var(--b100) !important; } .csa-bg-b100 { background-color: var(--b100) !important; }
.csa-color-b200 { color: var(--b200) !important; } .csa-bg-b200 { background-color: var(--b200) !important; }
.csa-color-b300 { color: var(--b300) !important; } .csa-bg-b300 { background-color: var(--b300) !important; }
.csa-color-b400 { color: var(--b400) !important; } .csa-bg-b400 { background-color: var(--b400) !important; }
.csa-color-b500 { color: var(--b500) !important; } .csa-bg-b500 { background-color: var(--b500) !important; }
.csa-color-b600 { color: var(--b600) !important; } .csa-bg-b600 { background-color: var(--b600) !important; }
.csa-color-b700 { color: var(--b700) !important; } .csa-bg-b700 { background-color: var(--b700) !important; }
.csa-color-b800 { color: var(--b800) !important; } .csa-bg-b800 { background-color: var(--b800) !important; }

/* ── ORANGE ─────────────────────────────────────────────── */
.csa-color-o25  { color: var(--o25)  !important; } .csa-bg-o25  { background-color: var(--o25)  !important; }
.csa-color-o50  { color: var(--o50)  !important; } .csa-bg-o50  { background-color: var(--o50)  !important; }
.csa-color-o75  { color: var(--o75)  !important; } .csa-bg-o75  { background-color: var(--o75)  !important; }
.csa-color-o100 { color: var(--o100) !important; } .csa-bg-o100 { background-color: var(--o100) !important; }
.csa-color-o200 { color: var(--o200) !important; } .csa-bg-o200 { background-color: var(--o200) !important; }
.csa-color-o300 { color: var(--o300) !important; } .csa-bg-o300 { background-color: var(--o300) !important; }
.csa-color-o400 { color: var(--o400) !important; } .csa-bg-o400 { background-color: var(--o400) !important; }
.csa-color-o500 { color: var(--o500) !important; } .csa-bg-o500 { background-color: var(--o500) !important; }
.csa-color-o600 { color: var(--o600) !important; } .csa-bg-o600 { background-color: var(--o600) !important; }
.csa-color-o700 { color: var(--o700) !important; } .csa-bg-o700 { background-color: var(--o700) !important; }
.csa-color-o800 { color: var(--o800) !important; } .csa-bg-o800 { background-color: var(--o800) !important; }

/* ── TEAL ───────────────────────────────────────────────── */
.csa-color-t25  { color: var(--t25)  !important; } .csa-bg-t25  { background-color: var(--t25)  !important; }
.csa-color-t50  { color: var(--t50)  !important; } .csa-bg-t50  { background-color: var(--t50)  !important; }
.csa-color-t75  { color: var(--t75)  !important; } .csa-bg-t75  { background-color: var(--t75)  !important; }
.csa-color-t100 { color: var(--t100) !important; } .csa-bg-t100 { background-color: var(--t100) !important; }
.csa-color-t200 { color: var(--t200) !important; } .csa-bg-t200 { background-color: var(--t200) !important; }
.csa-color-t300 { color: var(--t300) !important; } .csa-bg-t300 { background-color: var(--t300) !important; }
.csa-color-t400 { color: var(--t400) !important; } .csa-bg-t400 { background-color: var(--t400) !important; }
.csa-color-t500 { color: var(--t500) !important; } .csa-bg-t500 { background-color: var(--t500) !important; }
.csa-color-t600 { color: var(--t600) !important; } .csa-bg-t600 { background-color: var(--t600) !important; }
.csa-color-t700 { color: var(--t700) !important; } .csa-bg-t700 { background-color: var(--t700) !important; }
.csa-color-t800 { color: var(--t800) !important; } .csa-bg-t800 { background-color: var(--t800) !important; }

/* ── PURPLE ─────────────────────────────────────────────── */
.csa-color-p25  { color: var(--p25)  !important; } .csa-bg-p25  { background-color: var(--p25)  !important; }
.csa-color-p50  { color: var(--p50)  !important; } .csa-bg-p50  { background-color: var(--p50)  !important; }
.csa-color-p75  { color: var(--p75)  !important; } .csa-bg-p75  { background-color: var(--p75)  !important; }
.csa-color-p100 { color: var(--p100) !important; } .csa-bg-p100 { background-color: var(--p100) !important; }
.csa-color-p200 { color: var(--p200) !important; } .csa-bg-p200 { background-color: var(--p200) !important; }
.csa-color-p300 { color: var(--p300) !important; } .csa-bg-p300 { background-color: var(--p300) !important; }
.csa-color-p400 { color: var(--p400) !important; } .csa-bg-p400 { background-color: var(--p400) !important; }
.csa-color-p500 { color: var(--p500) !important; } .csa-bg-p500 { background-color: var(--p500) !important; }
.csa-color-p600 { color: var(--p600) !important; } .csa-bg-p600 { background-color: var(--p600) !important; }
.csa-color-p700 { color: var(--p700) !important; } .csa-bg-p700 { background-color: var(--p700) !important; }
.csa-color-p800 { color: var(--p800) !important; } .csa-bg-p800 { background-color: var(--p800) !important; }

/* ── GREEN ──────────────────────────────────────────────── */
.csa-color-g25  { color: var(--g25)  !important; } .csa-bg-g25  { background-color: var(--g25)  !important; }
.csa-color-g50  { color: var(--g50)  !important; } .csa-bg-g50  { background-color: var(--g50)  !important; }
.csa-color-g75  { color: var(--g75)  !important; } .csa-bg-g75  { background-color: var(--g75)  !important; }
.csa-color-g100 { color: var(--g100) !important; } .csa-bg-g100 { background-color: var(--g100) !important; }
.csa-color-g200 { color: var(--g200) !important; } .csa-bg-g200 { background-color: var(--g200) !important; }
.csa-color-g300 { color: var(--g300) !important; } .csa-bg-g300 { background-color: var(--g300) !important; }
.csa-color-g400 { color: var(--g400) !important; } .csa-bg-g400 { background-color: var(--g400) !important; }
.csa-color-g500 { color: var(--g500) !important; } .csa-bg-g500 { background-color: var(--g500) !important; }
.csa-color-g600 { color: var(--g600) !important; } .csa-bg-g600 { background-color: var(--g600) !important; }
.csa-color-g700 { color: var(--g700) !important; } .csa-bg-g700 { background-color: var(--g700) !important; }
.csa-color-g800 { color: var(--g800) !important; } .csa-bg-g800 { background-color: var(--g800) !important; }

/* ── RED ────────────────────────────────────────────────── */
.csa-color-r25  { color: var(--r25)  !important; } .csa-bg-r25  { background-color: var(--r25)  !important; }
.csa-color-r50  { color: var(--r50)  !important; } .csa-bg-r50  { background-color: var(--r50)  !important; }
.csa-color-r75  { color: var(--r75)  !important; } .csa-bg-r75  { background-color: var(--r75)  !important; }
.csa-color-r100 { color: var(--r100) !important; } .csa-bg-r100 { background-color: var(--r100) !important; }
.csa-color-r200 { color: var(--r200) !important; } .csa-bg-r200 { background-color: var(--r200) !important; }
.csa-color-r300 { color: var(--r300) !important; } .csa-bg-r300 { background-color: var(--r300) !important; }
.csa-color-r400 { color: var(--r400) !important; } .csa-bg-r400 { background-color: var(--r400) !important; }
.csa-color-r500 { color: var(--r500) !important; } .csa-bg-r500 { background-color: var(--r500) !important; }
.csa-color-r600 { color: var(--r600) !important; } .csa-bg-r600 { background-color: var(--r600) !important; }
.csa-color-r700 { color: var(--r700) !important; } .csa-bg-r700 { background-color: var(--r700) !important; }
.csa-color-r800 { color: var(--r800) !important; } .csa-bg-r800 { background-color: var(--r800) !important; }

/* ── NEUTRAL ────────────────────────────────────────────── */
.csa-color-n25  { color: var(--n25)  !important; } .csa-bg-n25  { background-color: var(--n25)  !important; }
.csa-color-n50  { color: var(--n50)  !important; } .csa-bg-n50  { background-color: var(--n50)  !important; }
.csa-color-n75  { color: var(--n75)  !important; } .csa-bg-n75  { background-color: var(--n75)  !important; }
.csa-color-n100 { color: var(--n100) !important; } .csa-bg-n100 { background-color: var(--n100) !important; }
.csa-color-n200 { color: var(--n200) !important; } .csa-bg-n200 { background-color: var(--n200) !important; }
.csa-color-n300 { color: var(--n300) !important; } .csa-bg-n300 { background-color: var(--n300) !important; }
.csa-color-n400 { color: var(--n400) !important; } .csa-bg-n400 { background-color: var(--n400) !important; }
.csa-color-n500 { color: var(--n500) !important; } .csa-bg-n500 { background-color: var(--n500) !important; }
.csa-color-n600 { color: var(--n600) !important; } .csa-bg-n600 { background-color: var(--n600) !important; }
.csa-color-n700 { color: var(--n700) !important; } .csa-bg-n700 { background-color: var(--n700) !important; }
.csa-color-n800 { color: var(--n800) !important; } .csa-bg-n800 { background-color: var(--n800) !important; }

/* ── WHITE / BLACK ──────────────────────────────────────── */
.csa-color-white { color: var(--white) !important; } .csa-bg-white { background-color: var(--white) !important; }
.csa-color-black { color: var(--black) !important; } .csa-bg-black { background-color: var(--black) !important; }
/* =========================================================
   CSA Component Library — Columns
   Equivalent of Bulma's .columns / .column flex-row system.
   A .csa-columns container distributes .csa-column children
   as equal-width columns by default. Named-fraction and
   12-column-grid width modifiers are available for asymmetric
   layouts. Column spans that sum to 12 always fill the row
   exactly without overflow or gap, regardless of how they're
   mixed (e.g. 3+9, 4+4+4, 8+4, 6+3+3).

   Default gap is 20px, matching the card-grid gap used
   elsewhere in this library. Columns stack to a single
   column below 640px unless .csa-columns-mobile is present.
   ========================================================= */

/* Container */
.csa-columns {
  display: flex;
  flex-wrap: wrap;
  gap: 20px;
}

/* Default column — equal flexible share of available space.
   flex-basis:0 (not auto) is what makes all unsized columns
   exactly equal regardless of their content width. */
.csa-column {
  flex: 1 1 0;
  min-width: 0; /* prevent long non-breaking content from overflowing */
}

/* ── Named-fraction widths ───────────────────────────────
   Each value uses the same formula as the 12-column grid
   below: K/12 × 100% − (12−K)/12 × 20px. Named fractions
   map to their exact 12-column equivalent (half=6, third=4,
   two-thirds=8, quarter=3, three-quarters=9), so mixing
   named and numeric modifiers in the same row composes
   correctly — .csa-column-two-thirds + .csa-column-4 fills
   a row the same way .csa-column-8 + .csa-column-4 does. */
.csa-column-narrow         { flex: 0 0 auto; }
.csa-column-full           { flex: 0 0 100%; }
.csa-column-three-quarters { flex: 0 0 calc(75%      -  5px);       } /* ≡ col-9  */
.csa-column-two-thirds     { flex: 0 0 calc(66.6667% -  6.6667px); } /* ≡ col-8  */
.csa-column-half           { flex: 0 0 calc(50%      - 10px);       } /* ≡ col-6  */
.csa-column-third          { flex: 0 0 calc(33.3333% - 13.3333px); } /* ≡ col-4  */
.csa-column-quarter        { flex: 0 0 calc(25%      - 15px);       } /* ≡ col-3  */

/* ── 12-column integer grid ─────────────────────────────
   Width = K/12 × 100% − (12−K)/12 × 20px, where 20px is
   the container gap. Any set of columns whose spans sum to
   12 will fill the row exactly — e.g. col-3 + col-9,
   col-4 + col-4 + col-4, col-2 + col-6 + col-4. */
.csa-column-1  { flex: 0 0 calc(8.3333%  - 18.3333px); }
.csa-column-2  { flex: 0 0 calc(16.6667% - 16.6667px); }
.csa-column-3  { flex: 0 0 calc(25%      - 15px);       }
.csa-column-4  { flex: 0 0 calc(33.3333% - 13.3333px); }
.csa-column-5  { flex: 0 0 calc(41.6667% - 11.6667px); }
.csa-column-6  { flex: 0 0 calc(50%      - 10px);       }
.csa-column-7  { flex: 0 0 calc(58.3333% -  8.3333px); }
.csa-column-8  { flex: 0 0 calc(66.6667% -  6.6667px); }
.csa-column-9  { flex: 0 0 calc(75%      -  5px);       }
.csa-column-10 { flex: 0 0 calc(83.3333% -  3.3333px); }
.csa-column-11 { flex: 0 0 calc(91.6667% -  1.6667px); }
.csa-column-12 { flex: 0 0 100%; }

/* ── Container modifiers ────────────────────────────────── */
.csa-columns-vcentered  { align-items: center; }
.csa-columns-centered   { justify-content: center; }
.csa-columns-gapless    { gap: 0; }
/* multiline is the default (flex-wrap:wrap is always on),
   but naming it explicitly aids discoverability for authors
   coming from Bulma who expect to set it intentionally. */
.csa-columns-multiline  { flex-wrap: wrap; }

/* ── Responsive — stack on mobile ───────────────────────
   Below 640px all columns collapse to full-width and stack
   vertically. Add .csa-columns-mobile to the container to
   keep columns side-by-side at all widths. */
@media (max-width: 640px) {
  .csa-columns:not(.csa-columns-mobile) {
    flex-direction: column;
  }
  .csa-columns:not(.csa-columns-mobile) > .csa-column,
  .csa-columns:not(.csa-columns-mobile) > [class*="csa-column-"] {
    flex: 0 0 auto;
    width: 100%;
  }
}
/* =========================================================
   CSA Component Library — Grid
   Equivalent of Bulma's .grid / .cell CSS Grid system.

   .csa-grid auto-fills columns using --col-count (default 12).
   --cell-min is calculated as one column's exact share of the
   container width (gap-aware), so auto-fill always creates
   exactly --col-count tracks — at any container width. This
   means spans behave predictably as precise fractions of the
   row (e.g. .csa-cell-col-6 is always half the row on a
   12-col grid), while still being flex-responsive: the track
   formula re-evaluates as the container narrows.

   Use .csa-grid-{2–6} for common equal-column card grids.
   Override --col-count inline for any other count.
   Keep cell spans ≤ --col-count; larger spans wrap to a new
   row since there are no tracks to place them in.
   ========================================================= */

.csa-grid {
  --grid-gap: 20px;
  --col-count: 12;
  /* One column's share of the container: (total + gap) / count - gap.
     max(0px,...) prevents negative values on very narrow containers. */
  --cell-min: max(0px, calc((100% + var(--grid-gap)) / var(--col-count) - var(--grid-gap)));
  display: grid;
  gap: var(--grid-gap);
  grid-template-columns: repeat(auto-fill, minmax(var(--cell-min), 1fr));
}

/* Default cell — 1 column, 1 row */
.csa-cell {
  min-width: 0; /* prevent overflow from non-breaking content */
}

/* ── Column spans ────────────────────────────────────────── */
.csa-cell-col-1  { grid-column: span 1; }
.csa-cell-col-2  { grid-column: span 2; }
.csa-cell-col-3  { grid-column: span 3; }
.csa-cell-col-4  { grid-column: span 4; }
.csa-cell-col-5  { grid-column: span 5; }
.csa-cell-col-6  { grid-column: span 6; }
.csa-cell-col-7  { grid-column: span 7; }
.csa-cell-col-8  { grid-column: span 8; }
.csa-cell-col-9  { grid-column: span 9; }
.csa-cell-col-10 { grid-column: span 10; }
.csa-cell-col-11 { grid-column: span 11; }
.csa-cell-col-12 { grid-column: span 12; }

/* ── Row spans ───────────────────────────────────────────── */
.csa-cell-row-1 { grid-row: span 1; }
.csa-cell-row-2 { grid-row: span 2; }
.csa-cell-row-3 { grid-row: span 3; }
.csa-cell-row-4 { grid-row: span 4; }
.csa-cell-row-5 { grid-row: span 5; }
.csa-cell-row-6 { grid-row: span 6; }

/* ── Column-count presets ────────────────────────────────── */
/* Convenience classes for the most common card-grid counts.
   Any other count: set --col-count via inline style. */
.csa-grid-1 { --col-count: 1; }
.csa-grid-2 { --col-count: 2; }
.csa-grid-3 { --col-count: 3; }
.csa-grid-4 { --col-count: 4; }
.csa-grid-5 { --col-count: 5; }
.csa-grid-6 { --col-count: 6; }

/* ── Cell modifiers ──────────────────────────────────────── */
/* Makes the cell's direct child stretch to the full cell
   height — useful when a .csa-card or similar component
   should match the height of its tallest row-sibling without
   adding height:100% to the component itself. */
.csa-cell-fill {
  display: flex;
  flex-direction: column;
}
.csa-cell-fill > * {
  flex: 1;
}

/* ── Container modifiers ─────────────────────────────────── */
.csa-grid-gapless { --grid-gap: 0px; }

/* ── Responsive breakpoints (mobile-first) ───────────────── */
/* sm ≥576px — grid column counts and cell spans            */
@media (min-width: 576px) {
  .csa-grid-sm-1 { --col-count: 1; }
  .csa-grid-sm-2 { --col-count: 2; }
  .csa-grid-sm-3 { --col-count: 3; }
  .csa-grid-sm-4 { --col-count: 4; }
  .csa-grid-sm-5 { --col-count: 5; }
  .csa-grid-sm-6 { --col-count: 6; }

  .csa-cell-col-sm-1  { grid-column: span 1; }
  .csa-cell-col-sm-2  { grid-column: span 2; }
  .csa-cell-col-sm-3  { grid-column: span 3; }
  .csa-cell-col-sm-4  { grid-column: span 4; }
  .csa-cell-col-sm-5  { grid-column: span 5; }
  .csa-cell-col-sm-6  { grid-column: span 6; }
  .csa-cell-col-sm-7  { grid-column: span 7; }
  .csa-cell-col-sm-8  { grid-column: span 8; }
  .csa-cell-col-sm-9  { grid-column: span 9; }
  .csa-cell-col-sm-10 { grid-column: span 10; }
  .csa-cell-col-sm-11 { grid-column: span 11; }
  .csa-cell-col-sm-12 { grid-column: span 12; }
}

/* md ≥768px */
@media (min-width: 768px) {
  .csa-grid-md-1 { --col-count: 1; }
  .csa-grid-md-2 { --col-count: 2; }
  .csa-grid-md-3 { --col-count: 3; }
  .csa-grid-md-4 { --col-count: 4; }
  .csa-grid-md-5 { --col-count: 5; }
  .csa-grid-md-6 { --col-count: 6; }

  .csa-cell-col-md-1  { grid-column: span 1; }
  .csa-cell-col-md-2  { grid-column: span 2; }
  .csa-cell-col-md-3  { grid-column: span 3; }
  .csa-cell-col-md-4  { grid-column: span 4; }
  .csa-cell-col-md-5  { grid-column: span 5; }
  .csa-cell-col-md-6  { grid-column: span 6; }
  .csa-cell-col-md-7  { grid-column: span 7; }
  .csa-cell-col-md-8  { grid-column: span 8; }
  .csa-cell-col-md-9  { grid-column: span 9; }
  .csa-cell-col-md-10 { grid-column: span 10; }
  .csa-cell-col-md-11 { grid-column: span 11; }
  .csa-cell-col-md-12 { grid-column: span 12; }
}

/* lg ≥1024px */
@media (min-width: 1024px) {
  .csa-grid-lg-1 { --col-count: 1; }
  .csa-grid-lg-2 { --col-count: 2; }
  .csa-grid-lg-3 { --col-count: 3; }
  .csa-grid-lg-4 { --col-count: 4; }
  .csa-grid-lg-5 { --col-count: 5; }
  .csa-grid-lg-6 { --col-count: 6; }

  .csa-cell-col-lg-1  { grid-column: span 1; }
  .csa-cell-col-lg-2  { grid-column: span 2; }
  .csa-cell-col-lg-3  { grid-column: span 3; }
  .csa-cell-col-lg-4  { grid-column: span 4; }
  .csa-cell-col-lg-5  { grid-column: span 5; }
  .csa-cell-col-lg-6  { grid-column: span 6; }
  .csa-cell-col-lg-7  { grid-column: span 7; }
  .csa-cell-col-lg-8  { grid-column: span 8; }
  .csa-cell-col-lg-9  { grid-column: span 9; }
  .csa-cell-col-lg-10 { grid-column: span 10; }
  .csa-cell-col-lg-11 { grid-column: span 11; }
  .csa-cell-col-lg-12 { grid-column: span 12; }
}
/* =========================================================
   CSA Component Library — Surface System
   Source: SPEC section 2. Three surfaces, each with its own card
   treatment and text-color defaults. Applied via data-surface, a
   plain data attribute (not a class) — no csa- prefix needed since
   it can't collide with the host site's own CSS classes.
   ========================================================= */
[data-surface="light"]    { background: #fff; }
/* b50, not b75: the spec's own summary table (section 2) calls for b50
   here, but the literal CSS snippet right below that table in the same
   doc used b75 — an inconsistency within the spec itself. We'd copied
   the snippet; the table is the more deliberate source, so b50 wins. */
[data-surface="gradient"] { background: linear-gradient(120deg, var(--b50) 0%, var(--n25) 35%, var(--n25) 65%, var(--o25) 100%); }
[data-surface="dark"]     { background: var(--b700); color: #fff; }
/* Black — a 4th surface, near-black rather than navy. Text colors match
   the dark (navy) surface exactly, per the fourth surface's own request;
   only the background and card treatment differ (see below). */
[data-surface="black"]    { background: var(--black); color: #fff; }

/* Surface text-color defaults (finalized table) */
[data-surface="light"] h1, [data-surface="light"] h2, [data-surface="light"] h3, [data-surface="light"] h4,
[data-surface="gradient"] h1, [data-surface="gradient"] h2, [data-surface="gradient"] h3, [data-surface="gradient"] h4 {
  color: var(--b800);
}
[data-surface="light"] p, [data-surface="gradient"] p { color: var(--n500); }

[data-surface="dark"] h1, [data-surface="dark"] h2, [data-surface="dark"] h3, [data-surface="dark"] h4,
[data-surface="black"] h1, [data-surface="black"] h2, [data-surface="black"] h3, [data-surface="black"] h4 { color: #fff; }
[data-surface="dark"] p, [data-surface="black"] p { color: var(--n75); }

/* Card — surface-aware background/border
   Light/gradient use --n50 (neutral) borders, not --b50 — corrected
   from the spec's literal text, which called for --b50 here.
   .csa-card-media shares this treatment — same shell, just with an
   image bled to the top edge instead of uniform padding (see cards.css). */
[data-surface] .csa-card, [data-surface] .csa-card-media,
[data-surface].csa-card, [data-surface].csa-card-media { background: var(--n25); border: 1px solid var(--n50); }
[data-surface="gradient"] .csa-card, [data-surface="gradient"] .csa-card-media,
[data-surface="light"][data-tone="neutral"] .csa-card, [data-surface="light"][data-tone="neutral"] .csa-card-media,
[data-surface="gradient"].csa-card, [data-surface="gradient"].csa-card-media,
[data-surface="light"][data-tone="neutral"].csa-card, [data-surface="light"][data-tone="neutral"].csa-card-media { background: #fff; border: 1px solid var(--n50); }
[data-surface="dark"] .csa-card, [data-surface="dark"] .csa-card-media,
[data-surface="dark"].csa-card, [data-surface="dark"].csa-card-media { background: var(--b800); border: 1px solid var(--b600); }
/* Black surface's card is a neutral n800/n700 pair rather than the dark
   surface's navy b800/b600 — n800 is lighter than the near-black page
   background, so the card still reads as a raised, distinct surface. */
[data-surface="black"] .csa-card, [data-surface="black"] .csa-card-media,
[data-surface="black"].csa-card, [data-surface="black"].csa-card-media { background: var(--n800); border: 1px solid var(--n700); }

/* =========================================================
   Tones — a second, optional data attribute that nudges a surface
   one step without becoming a new surface. A tone only changes the
   band and card fills (plus the few inset fills that would otherwise
   vanish into them, in their own component files); every text-color
   and component rule keyed to data-surface keeps applying unchanged.

     <section data-surface="light" data-tone="neutral">  n25 band, white cards
     <section data-surface="dark"  data-tone="deep">     b800 band, b700 cards

   "neutral" replaces the older .csa-bg-n25 hook. That name now belongs
   to color-helpers.css, where it's a plain !important background
   utility with no effect on children, so it's no longer a tone alias:
   use data-tone="neutral" for an n25 band.
   "deep" reverses dark's band/card pairing: the band drops to b800,
   so cards step up to b700 — dark's own b800 cards would disappear
   into it. Anything else that sits on dark in b800 (progress track,
   toggle tracks, disabled pagination) gets the same one-step reversal
   in its own component file.
   ========================================================= */
[data-surface="light"][data-tone="neutral"] { background: var(--n25); }

[data-surface="dark"][data-tone="deep"] { background: var(--b800); }
[data-surface="dark"][data-tone="deep"] .csa-card,
[data-surface="dark"][data-tone="deep"] .csa-card-media,
[data-surface="dark"][data-tone="deep"].csa-card,
[data-surface="dark"][data-tone="deep"].csa-card-media { background: var(--b700); border: 1px solid var(--b600); }
/* =========================================================
   CSA Component Library — Cards
   .csa-card shell (radius/padding/shadow) built fresh: no single
   reference page used one shared card class, they all had bespoke
   one-off card variants (.whycard, .road, .rcard, .step, etc.) with
   radius 14–16px and padding 28–32px — .csa-card synthesizes a
   sensible shared default from that range. Background/border are
   surface-aware — see surfaces.css.
   card-glass replaces the old duplicate-named .hero-card / .involve-card.
   The spec's literal card-glass CSS omitted border-radius, but both of
   its real predecessors had one — .hero-card (standalone, not nested
   in another rounded box) used 16px; .involve-card (nested inside the
   20px-rounded .involve band) used 12px. Default here is 16px, the
   standalone case; if you nest card-glass inside another rounded
   container, scale its radius down to keep corners proportional —
   see the general nesting note this file's radii follow. Set it via
   --glass-radius, not border-radius: the glass's inset edge line
   derives its own corner radius from that value (see below).
   ========================================================= */
.csa-card {
  border-radius: 16px;
  padding: 32px;
  box-shadow: var(--shadow-sm);
}
/* h3 is left unset at the token level (typography.css) on purpose — its
   real usages differ enough (a full card, a reveal panel's compact
   strip, a glass panel, the Surfaces section's demo heading) that they
   don't share a layout, so the size lives per-context here instead of
   forcing one h1/h2-style clamp on all of them. The size itself,
   though, is the same 20px in every one of those contexts on purpose —
   was previously an inline style on every single card heading in the
   docs, drifting between 19px/20px with no real reason; consolidated
   here (see .csa-card-glass/.csa-card-reveal-head below, and #surfaces
   h3 in the docs page's own <style> block) so every context uses the
   same size while keeping its own margin, since spacing genuinely
   differs by layout even though size doesn't. */
.csa-card h3, .csa-card-media-body h3 { font-size: 20px; margin: 12px 0 10px; }

/* =========================================================
   Frost glass — the material shared by .csa-card-glass (and the
   .csa-hero-glass panels built on it, hero.css) and .csa-card-reveal-
   panel. Tuned against CSA's own Illustrator marketing comps, which
   measured as: dark glass DARKENS the backdrop rather than covering it
   (light behind keeps its hue and glows through, instead of the gray
   veil a semi-opaque navy fill gives), light glass is ~60% white over
   the blur, grain is subtle (~0.5–2% luminance), and the edge is one
   even light line set just inside the card's edge. Deliberately a matte
   frost, not Apple-style liquid glass: no refraction, no specular
   highlights. One backdrop-filter per panel; everything else is static.

   Layers:
     element   backdrop blur + brightness (depth) + saturate, plus a thin
               tint fill. The darkening lives IN the backdrop-filter
               because CSS can't blend a pseudo-element with an element's
               own backdrop-filter output — it also caps how bright dark
               glass can ever get, so white text keeps a contrast floor
               on any backdrop.
     ::before  the edge: a 1px line inset 1px from the card's edge (a
               sliver of clear glass between them reads as a bevel), soft
               1px both ways, a faint inner frost, and an optional faint
               hairline on the true edge (--glass-outer, dark only — dark
               glass over near-black otherwise has no visible edge). On
               card-glass a top-to-bottom mask fades it toward the bottom
               (--glass-fade), like light entering from above.
     ::after   grain — a static SVG noise tile split into white and black
               specks (zero-mean, normal blend: texture without tint).
   Both pseudo-elements sit at z-index:-1 inside the panel's own stacking
   context (isolation:isolate) — above its fill, below all content — so
   no child ever needs position/z-index added to stay on top.

   Light vs dark is a set of --glass-* values. DARK IS THE DEFAULT on every
   surface: measured, dark glass holds body-text contrast over almost any
   backdrop (the darkening caps its brightness), while light glass only
   works over lighter imagery — over dark photos it turns gray and body
   text drops to ~2-3:1. Light glass is opt-in: .csa-card-glass-light, or
   .csa-hero-glass-light for the hero panel. The reveal panel is always
   dark — it's a photo card, so its glass is there for contrast against
   the image, not to match the page.

   Because the dark panel can now sit on a light page, it sets its own
   text colors (headings, body, kickers, .csa-go, .csa-hr) instead of
   inheriting the page surface's. Other surface-aware components nested
   inside (buttons, badges, tags, form fields…) key off [data-surface],
   so for those put data-surface="dark" on the glass card itself — the
   glass background still wins over the surface's own background.
   blur()/brightness() are written out literally per tone, not as
   variables: older Safari handled var() inside -webkit-backdrop-filter
   poorly, and these two values are what everything else is tuned around.
   ========================================================= */
.csa-card-glass,
.csa-card-reveal-panel {
  position: relative;
  isolation: isolate;
  background: var(--glass-fill);
}
.csa-card-glass {
  --glass-radius: 16px;
  border-radius: var(--glass-radius);
  box-shadow:
    0 2px 6px rgb(0 0 0 / calc(var(--glass-shadow) * .35)),
    0 28px 60px -28px rgb(0 0 0 / var(--glass-shadow));
  color: #fff;
}
/* .csa-card + .csa-card-glass on one element: the surfaces' card rules
   ([data-surface] .csa-card / [data-surface="dark"] .csa-card, and the
   deep tone's) outrank the glass by specificity and painted a solid card
   fill + 1px border over it — a navy card with the glass's own inset edge
   line drawn inside it, read as an extra layer behind the glass. These
   match those rules' weight (and come later), so glass keeps its own
   thin fill and its border-free edge on any surface. */
.csa-card.csa-card-glass,
[data-surface] .csa-card.csa-card-glass,
[data-surface].csa-card.csa-card-glass { background: var(--glass-fill); border: 0; }
/* Dark (default) text set — the dark-surface colors, applied by the panel
   itself so they hold on any page surface. */
.csa-card-glass h1, .csa-card-glass h2, .csa-card-glass h3, .csa-card-glass h4 { color: #fff; }
.csa-card-glass p { color: var(--n75); }
.csa-card-glass .csa-kicker, .csa-card-glass .csa-kicker-sub { color: var(--o200); } /* o500 dropped to ~4:1 over lighter photo areas; o200 is 6:1+. The glass hero panel keeps hero.css's own kicker color (later file, same weight) */
.csa-card-glass .csa-kicker-blue { color: var(--b100); } /* b200 dropped to ~4.2:1 over lighter photo areas; b100 is 5.3:1+ */
/* Links: white, n75 on hover — same as the reveal card's link (see
   .csa-card-reveal-more-inner .csa-go); b300 read poorly on the glass's
   navy cast. The [data-surface] copies outrank the dark/black surfaces'
   own .csa-go rules, which come later in this file at the same weight;
   the light-glass link rules below still outrank these. */
.csa-card-glass .csa-go, [data-surface] .csa-card-glass .csa-go { color: #fff; }
.csa-card-glass .csa-go:hover, [data-surface] .csa-card-glass .csa-go:hover { color: var(--n75); }
.csa-card-glass .csa-hr { border-top-color: var(--b600); }
/* Light text set — the light-surface colors, for the opt-in light glass.
   Compound selector (.csa-card-glass.csa-card-glass-light) so it outranks
   both the dark set above and any [data-surface="dark"] page rules. */
.csa-card-glass.csa-card-glass-light, .csa-hero-glass-light .csa-card-glass { color: var(--b800); }
:is(.csa-card-glass.csa-card-glass-light, .csa-hero-glass-light .csa-card-glass) :is(h1, h2, h3, h4) { color: var(--b800); }
:is(.csa-card-glass.csa-card-glass-light, .csa-hero-glass-light .csa-card-glass) p { color: var(--n500); }
/* Kickers: card-glass-light only — the light hero panel keeps hero.css's
   own b500 kicker, which this compound selector would otherwise outrank. */
.csa-card-glass.csa-card-glass-light :is(.csa-kicker, .csa-kicker-sub) { color: var(--o700); }
.csa-card-glass.csa-card-glass-light .csa-kicker-blue { color: var(--b500); }
:is(.csa-card-glass.csa-card-glass-light, .csa-hero-glass-light .csa-card-glass) .csa-go { color: var(--b500); }
:is(.csa-card-glass.csa-card-glass-light, .csa-hero-glass-light .csa-card-glass) .csa-go:hover { color: var(--b300); }
:is(.csa-card-glass.csa-card-glass-light, .csa-hero-glass-light .csa-card-glass) .csa-hr { border-top-color: var(--n50); }
/* Same 20px as .csa-card h3 above — own margin only, see that rule's
   comment for why h3 is sized per-context but kept at one shared size. */
.csa-card-glass h3 { font-size: 20px; margin: 8px 0 6px; }

/* Dark glass — the default for card-glass (any surface, including
   .csa-hero-glass-dark) and for the reveal panel. */
.csa-card-glass,
.csa-card-reveal-panel {
  --glass-fill: rgb(0 62 119 / .16);   /* --b600: a thin blue cast, not a cover */
  --glass-line: 196 230 255;           /* --b50: picks up the glass's blue */
  --glass-rim: .23;
  --glass-frost: .05;
  --glass-outer: .06;
  --glass-fade: .5;
  --glass-shadow: .5;
  -webkit-backdrop-filter: blur(8px) brightness(.45) saturate(1.3);
  backdrop-filter: blur(8px) brightness(.45) saturate(1.3);
}
/* Light glass — opt-in. Higher specificity than the dark default above,
   so it wins on any surface. */
.csa-card-glass.csa-card-glass-light,
.csa-hero-glass-light .csa-card-glass {
  --glass-fill: rgb(255 255 255 / .6);
  --glass-line: 255 255 255;
  --glass-rim: .64;    /* higher than dark on purpose: measured, it's the value that reads the same weight as dark's line on white glass */
  --glass-frost: .1;
  --glass-outer: 0;    /* light glass already has a visible edge */
  --glass-fade: .6;
  --glass-shadow: .18;
  -webkit-backdrop-filter: blur(8px) brightness(1.02) saturate(1.3) contrast(1.1);
  backdrop-filter: blur(8px) brightness(1.02) saturate(1.3) contrast(1.1);
}

/* Edge + grain layers (see the Frost glass comment above). */
.csa-card-glass::before, .csa-card-glass::after,
.csa-card-reveal-panel::before, .csa-card-reveal-panel::after {
  content: ""; position: absolute; z-index: -1; pointer-events: none;
}
.csa-card-glass::before {
  inset: 1px;
  border-radius: max(0px, calc(var(--glass-radius) - 1px));
  box-shadow:
    inset 0 0 1px 1px rgb(var(--glass-line) / var(--glass-rim)),
    0 0 1px 0 rgb(var(--glass-line) / calc(var(--glass-rim) * .7)),
    inset 0 0 3px rgb(var(--glass-line) / var(--glass-frost));
  /* The outer hairline is this layer's outline, pushed out to the card's
     true edge, so it fades with the line instead of staying full-strength
     at the bottom. */
  outline: 1px solid rgb(var(--glass-line) / var(--glass-outer));
  outline-offset: 0;
  /* A mask normally also clips whatever spills past its box — here the
     line's outer softness and the outline — so the mask image is drawn
     larger than the box and mask-clip:no-clip lets them through.
     Unprefixed on purpose: -webkit-mask-clip has no no-clip value. */
  mask-image: linear-gradient(to bottom, #000 8px, rgb(0 0 0 / calc(1 - var(--glass-fade))) calc(100% - 8px));
  mask-size: calc(100% + 18px) calc(100% + 18px);
  mask-position: center;
  mask-repeat: no-repeat;
  mask-clip: no-clip;
}
.csa-card-glass::after,
.csa-card-reveal-panel::after {
  inset: 0; border-radius: inherit;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='200' height='200'><filter id='n' x='0' y='0' width='100%' height='100%' color-interpolation-filters='sRGB'><feTurbulence type='fractalNoise' baseFrequency='.65' numOctaves='3' stitchTiles='stitch' result='t'/><feColorMatrix in='t' values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 3 0 0 0 -1.5' result='w'/><feColorMatrix in='t' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -3 0 0 0 1.5' result='b'/><feMerge><feMergeNode in='w'/><feMergeNode in='b'/></feMerge></filter><rect width='100%' height='100%' filter='url%28%23n%29'/></svg>");
  background-size: 200px;
  /* Stated, not left to the default: some host pages reset every
     element's background to no-repeat (cloudsecurityalliance.org's
     application.css does: `*, ::before, ::after { background-repeat:
     no-repeat }`), which left only the first 200px tile of grain. */
  background-repeat: repeat;
  opacity: .04;
}
/* The grain filter's width/height='100%' matter: left out, an SVG filter
   region defaults to 120% of the tile, so stitchTiles joins the noise up
   on a 240px tile that's then cut off at 200px — every tile edge became a
   faint visible seam (seen as a vertical line ~200px into the reveal
   panel). At 100% the noise stitches exactly at the tile's own edges.
   The rect's filter reference is written url%28%23n%29 (parentheses
   percent-encoded) rather than url(/%23n): asset pipelines that rewrite
   CSS url(/...) paths (Rails/Propshaft on cloudsecurityalliance.org)
   don't know this one sits inside a data: URI and turned it into
   url(/%23n), which broke the filter — the grain rendered as a flat
   black tile (no texture, and a seam at every 200px tile edge). The
   browser decodes it back to url(#n) before the SVG is drawn. */

/* Fallbacks: no backdrop-filter support, or the viewer asked the OS for
   reduced transparency — go near-opaque in the old solid colors. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .csa-card-glass,
  .csa-card-reveal-panel { --glass-fill: rgb(12 32 64 / .92); }
  .csa-card-glass.csa-card-glass-light,
  .csa-hero-glass-light .csa-card-glass { --glass-fill: rgb(255 255 255 / .92); }
}
@media (prefers-reduced-transparency: reduce) {
  .csa-card-glass,
  .csa-card-reveal-panel { --glass-fill: rgb(12 32 64 / .92); }
  .csa-card-glass.csa-card-glass-light,
  .csa-hero-glass-light .csa-card-glass { --glass-fill: rgb(255 255 255 / .92); }
}
/* Windows High Contrast drops backgrounds and box-shadows, which would
   leave the panel with no outline at all — give it a real border. */
@media (forced-colors: active) {
  .csa-card-glass { border: 1px solid CanvasText; }
  .csa-card-reveal-panel { border-top: 1px solid CanvasText; }
}

/* Icon sitting at the top of a card — .csa-card + a .csa-icon needs no
   new class, just spacing between the icon and the text below it. */
.csa-card .csa-icon { margin-bottom: 16px; }

/* CTA link — b500 by default (matches the reference pages' .go/.rel
   link color everywhere: .conn .go, .ann .go, .rcard .go, etc.), b300 on
   hover and on dark surfaces (kept in sync with the arrow-link precedent,
   distinct from the o500 used for kickers/buttons on dark). Arrow is
   typed directly into the link text ("Learn more →"), same as the 4 pages.
   On dark/black, resting color is ALSO b300, so hover needs its own
   step lighter (b100) — otherwise hovering would show no visible change
   at all, since :hover's b300 would just match the resting color. */
.csa-go { display: inline-flex; align-items: center; gap: 6px; margin-top: 20px; font-size: var(--text-small); font-weight: 500; color: var(--b500); transition: .15s; }
.csa-go:hover { color: var(--b300); }
[data-surface="dark"] .csa-go, [data-surface="black"] .csa-go { color: var(--b300); }
[data-surface="dark"] .csa-go:hover, [data-surface="black"] .csa-go:hover { color: var(--b100); }

/* Hover card — a .csa-card modifier. Lift + shadow on hover, matching
   the identical pattern used across all 4 reference pages for
   interactive cards (.conn a:hover, .ann:hover, .rcard:hover —
   translateY(-2 to -3px) + var(--shadow)). The reference pages also
   flip border-color to #00549f/b500 on hover, but that's been dropped
   here on purpose (cleaner without it) — not because it's wrong, just
   parked. Revert to `border-color: var(--b500);` in this same :hover
   rule if it comes back. Sub-brand cards (.csa-card-brand-*) were never
   affected either way: they already force border-color: transparent
   explicitly at both rest and :hover to protect their own gradient
   border, so this rule never reached them to begin with. */
.csa-card-hover { position: relative; transition: .18s; cursor: pointer; }
.csa-card-hover:hover { transform: translateY(-2px); box-shadow: var(--shadow); }
/* Stretch the csa-go link to cover the whole card. Any other interactive
   element inside the card needs position:relative + z-index:1 to stay clickable. */
.csa-card-hover .csa-go::after { content: ""; position: absolute; inset: 0; }
/* --shadow is a navy-tinted, low-opacity shadow — reads fine lifting off
   a light/gradient page, but that same navy-on-navy has too little
   contrast to read against the dark surface's own navy background.
   Same offsets/blur/spread, just black and notably more opaque. */
[data-surface="dark"] .csa-card-hover:hover, [data-surface="black"] .csa-card-hover:hover { box-shadow: 0 2px 6px rgba(0,0,0,.25), 0 18px 40px -18px rgba(0,0,0,.55); }

/* Disabled — mutes the entire card (content, background, shadow) via
   opacity so it works on every variant (glass, brand, media, reveal)
   without fighting surface-aware rules. pointer-events:none blocks all
   interaction; the hover override keeps .csa-card-hover from lifting. */
.csa-card-disabled {
  opacity: 0.4;
  pointer-events: none;
}
.csa-card-hover.csa-card-disabled:hover {
  transform: none;
  box-shadow: var(--shadow-sm);
}

/* Raised — a stronger, always-on elevation for a card that needs more
   emphasis than the rest of a grid around it, without changing its
   background/border (those stay exactly what .csa-card's own
   surface-aware rules in surfaces.css already give it — this is a pure
   modifier, always paired with .csa-card). Swaps the base --shadow-sm
   for the bigger --shadow token: the same shadow .csa-card-hover
   reaches for on :hover, just resting instead of hover-triggered. */
.csa-card-raised { box-shadow: var(--shadow); }
/* Same reasoning as .csa-card-hover's own dark/black override above —
   --shadow's navy tint has too little contrast against a dark/black
   background; identical value, reused rather than inventing a new one. */
[data-surface="dark"] .csa-card-raised, [data-surface="black"] .csa-card-raised { box-shadow: 0 2px 6px rgba(0,0,0,.25), 0 18px 40px -18px rgba(0,0,0,.55); }

/* Media card — image fills the top edge-to-edge (16:9 by default), text
   sits below in its own padded body. Unlike .csa-card, the outer shell
   carries no padding of its own — .csa-card-media-body does instead —
   so the image can bleed to the rounded corners. Background/border are
   surface-aware, same rules as .csa-card (see surfaces.css). */
.csa-card-media { border-radius: 16px; overflow: hidden; box-shadow: var(--shadow-sm); }
.csa-card-media-img { aspect-ratio: 16 / 9; overflow: hidden; }
.csa-card-media-img img { width: 100%; height: 100%; object-fit: cover; display: block; }
.csa-card-media-body { padding: 24px; }

/* Reveal card — a photo card whose kicker + heading sit permanently on a
   thin frosted-glass strip pinned to the bottom edge (the image stays
   visible above it). On hover, that strip grows upward to also reveal
   the body copy and CTA. Not surface-aware on purpose — the overlay is
   always dark regardless of the page surface, since its job is contrast
   against the photo, not matching the page. 4:3 by default.
   The reveal itself uses a grid-template-rows 0fr->1fr transition
   rather than max-height, so it grows to fit copy of any length without
   guessing a cutoff or clipping longer paragraphs.
   min-height is a safety floor for narrow widths: aspect-ratio alone
   means a narrow card also gets short, and right before this sits in a
   grid narrow enough to force 1 column, it can get too short for the
   revealed kicker+heading+copy+CTA to fit without clipping. Above this
   floor nothing changes — the card stays a clean 4:3 tile; below it, the
   card just reads slightly more portrait at that one pinch width, and
   the photo still covers cleanly via object-fit. */
.csa-card-reveal { position: relative; width: 100%; aspect-ratio: 4 / 3; min-height: 280px; min-width: 0; border-radius: 16px; overflow: hidden; cursor: pointer; } /* width: 100% makes the inline size definite (locked to the grid track) instead of auto — with width auto, the aspect-ratio + min-height combo derives width FROM the min-height floor (280 * 4/3 = 373px) and overflows into neighboring columns whenever the track is narrower than that; an explicit width removes that feedback loop so min-height only ever grows the card's height, never its width */
.csa-card-reveal img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; }

/* The overlay itself spans the full card (invisible — no background of its
   own) so it can never push its contents past the card's edges; the glass
   background lives on .csa-card-reveal-panel instead, sized to its own
   content and pinned to the bottom via justify-content. This decouples
   "how tall the glass panel wants to be" from "the card's fixed height":
   the kicker+heading never move or clip (flex-shrink: 0), and if the
   revealed paragraph+CTA don't fit the remaining space, only that inner
   block scrolls (see .csa-card-reveal-more-inner) instead of the panel
   spilling above the card and getting sliced off by its rounded corners. */
.csa-card-reveal-overlay {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; justify-content: flex-end;
}
.csa-card-reveal-panel {
  display: flex; flex-direction: column; max-height: 100%;
  padding: 22px 33px 33px; /* less on top than the sides/bottom — the strip's resting height is just kicker+heading, so a full 32px above reads as more empty space than the 32px beside/below actual text. 33, not 32: the 1px bleed below is added back so text sits exactly where it did */
  /* 1px bleed past the card's left/right/bottom edges (the card's own
     overflow:hidden + radius clips it, so it's never visible). Without
     it, a card that lands on a fractional pixel position — the middle
     card of a 3-up grid usually does — can have its backdrop-filter
     region rounded inward on the GPU, leaving one device-pixel column of
     raw, un-darkened photo at the card's edge. Seen as a bright sliver on
     the middle showcase reveal card. */
  margin: 0 -1px -1px;
  /* Glass material and light/dark values: see the Frost glass section
     at the top of this file. The panel's sides and rounded bottom are
     the photo card's own edges, so its edge treatment runs along the top
     only — one horizontal line 1px below the top (no bottom fade: there's
     nothing below it to fade), and the outer hairline top-only too. No
     drop shadow: the card clips it, so it would only smudge the photo. */
  box-shadow: inset 0 1px 0 rgb(var(--glass-line) / var(--glass-outer));
}
.csa-card-reveal-panel::before {
  inset: 1px 0 auto 0; height: 1px;
  background: rgb(var(--glass-line) / var(--glass-rim));
  box-shadow:
    0 0 1px rgb(var(--glass-line) / calc(var(--glass-rim) * .7)),
    0 1.5px 3px rgb(var(--glass-line) / var(--glass-frost));
}
/* Grain that holds still while the panel grows. The shared grain layer
   is sized to its panel, so here — where the panel's height animates on
   hover — the texture was re-laid every frame (crawling upward with the
   top edge, or resampled at a new sub-pixel offset when anchored to the
   bottom), read as shimmer along the card's bottom. Instead the layer is
   a fixed size anchored to the panel's bottom edge, which doesn't move,
   and the panel clips it to its own box (clip-path, not overflow:hidden,
   so the panel doesn't become a scroll container). Measured: zero
   frame-to-frame change in the bottom strip during the reveal. */
.csa-card-reveal-panel { clip-path: inset(0); }
.csa-card-reveal-panel::after {
  top: auto; bottom: 0;
  height: max(100%, 1200px);
  background-position: 0 100%;
}
.csa-card-reveal-head { flex-shrink: 0; }
.csa-card-reveal-overlay h3 { color: #fff; }
/* Same 20px as .csa-card h3 above — margin:8px 0 0 rather than a
   bottom margin, though: this heading sits directly above the
   reveal-on-hover copy, which has its own top spacing
   (.csa-card-reveal-more-inner's padding-top) instead. */
.csa-card-reveal-head h3 { font-size: 20px; margin: 8px 0 0; }
.csa-card-reveal-overlay .csa-kicker-sub { color: var(--o200); } /* same dark glass as .csa-card-glass — matches its o200 kickers */


.csa-card-reveal-more { display: grid; grid-template-rows: 0fr; min-height: 0; transition: grid-template-rows .4s ease; }
/* overflow-y flips from hidden to auto, but not at the moment hover
   starts — DELAYED by the same .4s the row above takes to grow, via a
   discrete-value transition (transition-behavior: allow-discrete lets
   a keyword property like overflow-y participate in `transition` at
   all; duration 0 + a matching delay is what pins the exact moment it
   flips, same idiom as the classic `transition: opacity .3s, visibility
   0s .3s` fade-then-hide trick, just with allow-discrete standing in
   for visibility's built-in special case). Net effect: for the entire
   grow animation, overflow-y reads as hidden no matter what — a card
   whose content will eventually need to scroll just gets clipped while
   the row is still short, exactly like a card that never needs to
   scroll at all, so there's no way for a scrollbar to flash mid-grow.
   The instant the row finishes growing, it flips to auto — content
   that fits shows no scrollbar (nothing overflows), content that
   doesn't becomes scrollable right on cue. Reverting the flip is NOT
   delayed (see the un-hovered rule's `transition-delay: 0s` below) —
   leaving hover should hide any scrollbar immediately, not linger
   through the collapse.
   Needs a browser supporting transition-behavior: allow-discrete
   (Chrome 117+, Safari 17.4+, Firefox 129+ — all well established by
   now). Anywhere that doesn't, the unsupported keyword invalidates
   just this transition declaration — overflow-y itself still applies,
   it just snaps instantly instead of waiting, i.e. the exact flash
   this rule exists to avoid, not a broken layout. */
.csa-card-reveal-more-inner {
  overflow-y: hidden; min-height: 0; max-height: 100%;
  padding-right: 4px; /* gap so the scrollbar thumb doesn't sit flush against the text */
  transition: overflow-y 0s allow-discrete; /* delay: 0s — flip to hidden immediately on leaving hover */
}
/* padding-top only while open, NOT unconditionally: padding can't be
   shrunk by min-height:0 the way content can, so an unconditional
   padding-top:14px here would force this row's collapsed (resting)
   height to a permanent 14px floor instead of true 0. Scoping it to
   :hover means it only ever applies while the row is actually
   growing/open, so the resting collapse stays 0. */
.csa-card-reveal:hover .csa-card-reveal-more-inner {
  overflow-y: auto;
  transition: overflow-y 0s allow-discrete .4s; /* delay: .4s — matches .csa-card-reveal-more's grow duration above */
  padding-top: 14px;
}
.csa-card-reveal-more-inner p { color: var(--n75); }
/* White link, n75 on hover. The panel is always dark glass over a photo
   (regardless of page surface), and the usual dark-surface blue (b300)
   had poor contrast against its navy cast — white reads clearly and
   matches the panel's heading. Hover steps down to n75 (the panel's own
   body-text color) rather than up, since there's nothing brighter than
   white to step to. */
.csa-card-reveal-more-inner .csa-go { color: #fff; }
.csa-card-reveal-more-inner .csa-go:hover { color: var(--n75); }
.csa-card-reveal:hover .csa-card-reveal-more { grid-template-rows: 1fr; }
/* Stretch the csa-go link over the glass panel, same idea as
   .csa-card-hover. Only the panel, not the whole card: the panel is the
   link's containing block (position + backdrop-filter) and its clip-path
   (the grain clip above) also clips hit-testing, so a bigger hit area
   can't reach the photo above it. Its containing block being the panel,
   not the scrolling -more-inner, also keeps it from being clipped while
   the copy is collapsed. */
.csa-card-reveal .csa-go::after { content: ""; position: absolute; inset: 0; }
/* With a link, drop the card-wide pointer so the cursor only turns into
   a hand over the panel (the link's stretched ::after above) — the photo
   above it isn't clickable, and the cursor shouldn't suggest otherwise. */
.csa-card-reveal:has(.csa-go) { cursor: auto; }


/* =========================================================
   Flip card — .csa-card-flip, a card with a front and a back
   that turns over on click (CSS only, no script). Both faces are regular
   .csa-card elements, so they pick up the surface's card colours/border.

   Markup contract (the id/for pair must be unique per card):
     <div class="csa-card-flip">
       <input type="checkbox" class="csa-card-flip-toggle csa-sr-only" id="flip-1" aria-label="More about …">
       <div class="csa-card-flip-inner">
         <div class="csa-card csa-card-flip-front">
           <label class="csa-card-flip-hit" for="flip-1"></label>
           <span class="csa-card-flip-cue" aria-hidden="true"><svg>…</svg></span>
           …front content…
         </div>
         <div class="csa-card csa-card-flip-back">
           <label class="csa-card-flip-hit" for="flip-1"></label>
           <span class="csa-card-flip-cue" aria-hidden="true"><svg>…</svg></span>
           …back content, links included…
         </div>
       </div>
     </div>

   - Click: a visually hidden checkbox holds the flipped state. Each face
     is covered by a <label> for it (.csa-card-flip-hit), so a click
     anywhere on the card flips it; links on the back sit above the label
     and stay clickable. Keyboard: Tab reaches the checkbox (focus ring on
     the card), Space flips it, then Tab reaches the back's links.
   - Hover: the card tilts 12° in the direction it will turn, hinting that
     it flips, and lifts with the library's raised-card shadow (also shown
     on keyboard focus). The corner cue icon shows on touch screens only, where
     there's no hover tilt or pointer cursor to give that hint.
   - Both faces share one grid cell, so the card is as tall as its taller
     face and the two always match; in a .csa-grid row every card
     stretches to the row's tallest.
   - Whichever face is turned away is visibility:hidden (once the turn
     passes halfway), so it can't catch clicks and the back's links aren't
     reachable by Tab until the card is turned over.
   - Reduced motion: no rotation or tilt, the faces cross-fade instead. */
.csa-card-flip { position: relative; perspective: 1200px; }
.csa-card-flip-inner {
  display: grid; height: 100%;
  transform-style: preserve-3d;
  transition: transform .6s cubic-bezier(.3, 1.3, .5, 1); /* slight overshoot as it lands */
}
/* 24px padding rather than .csa-card's 32px — the component's own value,
   same as .csa-card-media-body, to keep a grid of these compact. */
.csa-card-flip-front, .csa-card-flip-back {
  grid-area: 1 / 1; position: relative; padding: 24px;
  -webkit-backface-visibility: hidden; backface-visibility: hidden;
}
.csa-card-flip-back {
  transform: rotateY(180deg);
  display: flex; flex-direction: column; justify-content: space-between;
  visibility: hidden; transition: visibility 0s .3s, box-shadow .3s ease; /* hide once it's turned away */
}
.csa-card-flip-hit { position: absolute; inset: 0; z-index: 1; border-radius: inherit; cursor: pointer; }
.csa-card-flip-back a { position: relative; z-index: 2; }
/* Belt and braces for the click: some browsers (Safari especially) order
   layers inside a 3D-transformed card by depth and can ignore the
   z-index above, letting the text sit over the label — a click on the
   words then selects text instead of flipping. So the face content
   ignores the pointer outright (clicks fall through to the label),
   except links, and the card's text isn't selectable. */
.csa-card-flip-front > :not(.csa-card-flip-hit),
.csa-card-flip-back > :not(.csa-card-flip-hit):not(a) { pointer-events: none; }
.csa-card-flip-front, .csa-card-flip-back { -webkit-user-select: none; user-select: none; }
.csa-card-flip-back .csa-go { margin-top: 16px; align-self: flex-start; }
.csa-card-flip-cue {
  position: absolute; top: 8px; right: 8px; width: 16px; height: 16px; /* inside the 24px padding, clear of content */
  color: var(--b300); opacity: .6; transition: opacity .2s ease; pointer-events: none;
}
.csa-card-flip-cue svg { display: block; width: 100%; height: 100%; }
/* The cue is for touch only — with a mouse the hover tilt and pointer
   cursor already say "this turns over"; touch has neither. */
@media (hover: hover) and (pointer: fine) { .csa-card-flip-cue { display: none; } }

/* Hover tilt (peek), flipped state, and the flipped card's own peek back. */
.csa-card-flip:hover .csa-card-flip-inner { transform: rotateY(12deg); }
.csa-card-flip-toggle:checked + .csa-card-flip-inner { transform: rotateY(180deg); }
.csa-card-flip:hover .csa-card-flip-toggle:checked + .csa-card-flip-inner { transform: rotateY(168deg); }
.csa-card-flip-toggle:checked + .csa-card-flip-inner .csa-card-flip-back { visibility: visible; transition-delay: 0s; }
/* The face turned away still catches clicks unless it's hidden too — so the
   front hides (after the turn) while flipped, leaving the back's link and
   label clickable. */
.csa-card-flip-front { transition: visibility 0s, box-shadow .3s ease; }
.csa-card-flip-toggle:checked + .csa-card-flip-inner .csa-card-flip-front { visibility: hidden; transition-delay: .3s, 0s; }
.csa-card-flip-toggle:focus-visible + .csa-card-flip-inner > * { outline: 2px solid var(--b300); outline-offset: 3px; }

/* Lift shadow while hovered or keyboard-focused — so it's there through the
   tilt and the turn. It sits on the faces, so it tilts and turns with the
   card rather than staying flat. Values are the library's own raised-card
   shadow — --shadow's two layers on light surfaces, and the darker black
   stack .csa-card-hover:hover / .csa-card-raised use on dark and black —
   with one addition: a sideways offset that follows the turn, falling
   left on the front and right on the back (--flip-shadow-dir: -1 / 1;
   4px on the tight layer, 16px on the soft one). */
.csa-card-flip-front { --flip-shadow-dir: -1; }
.csa-card-flip-back  { --flip-shadow-dir: 1; }
.csa-card-flip:hover .csa-card-flip-inner > *,
.csa-card-flip-toggle:focus-visible + .csa-card-flip-inner > * {
  box-shadow:
    calc(var(--flip-shadow-dir) * 4px) 2px 6px rgba(12,35,64,.06),
    calc(var(--flip-shadow-dir) * 16px) 18px 40px -18px rgba(12,35,64,.22);
}
[data-surface="dark"] .csa-card-flip:hover .csa-card-flip-inner > *,
[data-surface="dark"] .csa-card-flip-toggle:focus-visible + .csa-card-flip-inner > *,
[data-surface="black"] .csa-card-flip:hover .csa-card-flip-inner > *,
[data-surface="black"] .csa-card-flip-toggle:focus-visible + .csa-card-flip-inner > * {
  box-shadow:
    calc(var(--flip-shadow-dir) * 4px) 2px 6px rgba(0,0,0,.25),
    calc(var(--flip-shadow-dir) * 16px) 18px 40px -18px rgba(0,0,0,.55);
}

@media (prefers-reduced-motion: reduce) {
  .csa-card-flip-inner { transition: none; }
  .csa-card-flip:hover .csa-card-flip-inner,
  .csa-card-flip-toggle:checked + .csa-card-flip-inner,
  .csa-card-flip:hover .csa-card-flip-toggle:checked + .csa-card-flip-inner { transform: none; }
  .csa-card-flip-back { transform: none; opacity: 0; }
  .csa-card-flip-front, .csa-card-flip-back { transition: opacity .2s ease, visibility 0s .2s, box-shadow .3s ease; }
  .csa-card-flip-toggle:checked + .csa-card-flip-inner .csa-card-flip-front { opacity: 0; transition: opacity .2s ease, visibility 0s .2s, box-shadow .3s ease; }
  .csa-card-flip-toggle:checked + .csa-card-flip-inner .csa-card-flip-back { opacity: 1; transition-delay: 0s; }
}


/* =========================================================
   Brand Cards — one per CSA sub-brand (Cloud, AI, Zero Trust). White-
   background only by design (no [data-surface] variants) — sit below
   card-glass in the showcase, outside the data-surface="light" demo
   wrapper the standard/hover/media/reveal cards use above.
   Built from .csa-card + .csa-card-hover + a .csa-icon, same shell as
   the "Hover cards with CTA" above, plus a per-brand modifier class
   here for background/border color, and .csa-go recolored to match
   each brand's accent instead of the library-wide default blue.

   Background + border are both gradients, drawn with the same
   double-background-clip trick Primary/Secondary buttons use for a
   gradient border (see buttons.css): the fill gradient is clipped to
   padding-box (stops at the border's inner edge), the border gradient
   is clipped to border-box (fills all the way out), and a matching
   `border-width`/transparent `border-color` carves out the ring
   between them. Two things extend that same base trick here:

   1. Non-uniform border-width (4px top, 1px right/bottom/left) instead
      of a uniform ring, so the border-box layer is naturally 4px thick
      only along the top edge.
   2. A hard color-stop at 16px into the border gradient — solid accent
      color from 0–16px, then an immediate jump into the real b100→b50-
      style fade from 16px to 100%. 16px, not 4px, deliberately — .csa-
      card's border-radius is 16px, so the border's actual thickness
      doesn't drop straight from 4px to 1px at the corner; it tapers
      across that whole 16px curve. Stopping the solid color at just
      4px cut it off mid-curve, an awkward seam where the color already
      switched to the b100 gradient start but the border was still
      visibly tapering. Extending the solid color through the full
      16px lets it cover the entire curve, so the gradient only begins
      once the border has settled into its final uniform 1px thickness
      on the straight vertical side — beyond the flat top edge's own
      4px height, nothing is painted there anyway (that's the card's
      interior, no border pixels exist past y:4px in the flat middle),
      so extending the stop to 16px only affects the corners, which is
      exactly where it needed to. (The same 1px-wide top sliver of the
      left/right edges also renders in the solid accent color this way,
      since border-width only controls thickness, not which pixels the
      color gradient touches — negligible at 1px.)

   border-color stays fully transparent in BOTH rest and :hover here,
   set explicitly in the :hover rule too — .csa-card-hover:hover (same
   specificity: one class + :hover) sets border-color to a real blue,
   and it would otherwise win by source order (it comes first in this
   file) and paint an opaque flat border on top of, and hiding, this
   gradient entirely.

   Hover changes the border gradient's colors (not the fill, and not
   the constant top accent) via a plain :hover background-image
   override — this snaps instantly rather than crossfading, since
   background-image can't interpolate between two different gradients
   through a `transition` (the same limition buttons.css works around
   with a ::before opacity crossfade). Left as a snap here: the lift +
   shadow from .csa-card-hover is the dominant, already-smooth hover
   cue, and animating a 1px–4px ring's color on top of that reads as a
   minor enough detail not to be worth the extra overlay complexity —
   revisit with the same overlay technique if that snap ever reads as
   janky rather than crisp. */
.csa-card-brand-cloud {
  border-width: 4px 1px 1px 1px;
  border-style: solid;
  border-color: transparent;
  background-image: linear-gradient(to bottom, var(--b25), var(--white)),
                     linear-gradient(to bottom, var(--b500) 16px, var(--b100) 16px, var(--b50) 100%);
  background-origin: border-box;
  background-clip: padding-box, border-box;
}
.csa-card-brand-cloud:hover {
  border-color: transparent;
  background-image: linear-gradient(to bottom, var(--b25), var(--white)),
                     linear-gradient(to bottom, var(--b500) 16px, var(--b500) 16px, var(--b300) 100%);
}
.csa-card-brand-cloud .csa-kicker-sub { color: var(--b500); }
.csa-card-brand-cloud .csa-go { color: var(--b500); }
.csa-card-brand-cloud .csa-go:hover { color: var(--b300); }

.csa-card-brand-ai {
  border-width: 4px 1px 1px 1px;
  border-style: solid;
  border-color: transparent;
  background-image: linear-gradient(to bottom, var(--o25), var(--white)),
                     linear-gradient(to bottom, var(--o700) 16px, var(--o100) 16px, var(--o50) 100%);
  background-origin: border-box;
  background-clip: padding-box, border-box;
}
.csa-card-brand-ai:hover {
  border-color: transparent;
  background-image: linear-gradient(to bottom, var(--o25), var(--white)),
                     linear-gradient(to bottom, var(--o700) 16px, var(--o700) 16px, var(--o300) 100%);
}
.csa-card-brand-ai .csa-kicker-sub { color: var(--o700); }
.csa-card-brand-ai .csa-go { color: var(--o700); }
.csa-card-brand-ai .csa-go:hover { color: var(--o500); }

.csa-card-brand-zt {
  border-width: 4px 1px 1px 1px;
  border-style: solid;
  border-color: transparent;
  background-image: linear-gradient(to bottom, var(--t25), var(--white)),
                     linear-gradient(to bottom, var(--t500) 16px, var(--t100) 16px, var(--t50) 100%);
  background-origin: border-box;
  background-clip: padding-box, border-box;
}
.csa-card-brand-zt:hover {
  border-color: transparent;
  background-image: linear-gradient(to bottom, var(--t25), var(--white)),
                     linear-gradient(to bottom, var(--t500) 16px, var(--t500) 16px, var(--t200) 100%);
}
.csa-card-brand-zt .csa-kicker-sub { color: var(--t500); }
.csa-card-brand-zt .csa-go { color: var(--t500); }
.csa-card-brand-zt .csa-go:hover { color: var(--t300); }

/* =========================================================
   Quote / Testimonial card — a .csa-card modifier
   (<div class="csa-card csa-card-quote">) styled from the testimonial
   carousel pattern already in use on other CSA pages: a large quote
   glyph, the quote body, then a footer — divider, headshot, name,
   title — below it. The headshot (.csa-card-quote-avatar) is optional;
   the footer's flex layout centers name/title vertically against
   whatever's beside them, so leaving the <img> out just leaves name/
   title on their own with no gap to account for. When it is included,
   it's a plain <img>, not a mask: unlike an icon that needs to recolor
   with surrounding text, a photo is its own fixed image, so there's
   nothing masking would buy here over a plain <img> — see
   base/icon-glyph.css's own comment on when to reach for which.
   ========================================================= */
.csa-card-quote { display: flex; flex-direction: column; }
.csa-card-quote-mark { font-size: 38px; line-height: .7; font-weight: 800; color: var(--o700); }
/* flex:1 (relies on .csa-card-quote's own display:flex/flex-direction:column
   above) is what pins the footer to the same y-position across a row of
   cards stretched to equal height, regardless of how long any one quote
   runs — the body grows to fill the leftover space instead of the footer
   just trailing wherever the text happens to end. */
.csa-card-quote-body { font-size: var(--text-small); color: var(--n500); margin-top: 12px; flex: 1; }
.csa-card-quote-footer {
  display: flex; align-items: center; gap: 12px;
  border-top: 1px solid var(--n25); margin-top: 24px; padding-top: 16px;
}
.csa-card-quote-avatar { width: 48px; height: 48px; flex-shrink: 0; display: block; }
.csa-card-quote-name { font-size: 16px; font-weight: 700; color: var(--b800); }
.csa-card-quote-title { font-size: var(--text-caption); color: var(--n400); margin-top: 2px; }

[data-surface="dark"] .csa-card-quote-body, [data-surface="black"] .csa-card-quote-body { color: var(--n75); }
[data-surface="dark"] .csa-card-quote-footer, [data-surface="black"] .csa-card-quote-footer { border-top-color: var(--b600); }
[data-surface="dark"] .csa-card-quote-name, [data-surface="black"] .csa-card-quote-name { color: #fff; }
[data-surface="dark"] .csa-card-quote-title, [data-surface="black"] .csa-card-quote-title { color: var(--n200); }
/* =========================================================
   CSA Component Library — Buttons
   Source: SPEC section 3, amended per your call: primary is always
   --o500 (no blue variant at all, no surface-conditional switching).
   .btn-gold/.btn-gold-outline are retired.

   4 standardized variants, in emphasis order — use .csa-btn-primary,
   .csa-btn-secondary, .csa-btn-tertiary, or .csa-btn-disabled:
     Primary   — gradient fill, highest emphasis, one per view ideally
     Secondary — o500 outline, works unmodified on light AND dark
     Tertiary  — o500 text only, lowest emphasis, for inline/dense rows
     Disabled  — non-interactive; surface-aware (see its own comment)
   .csa-btn-light and .csa-btn-onnavy are retired — Disabled replaces
   Light, and Secondary replaces Onnavy on dark surfaces (already
   worked there unmodified, so Onnavy was a redundant variant).
   ========================================================= */
/* min-width keeps a short label ("Go", "Save") from looking cramped/
   squat next to longer-labeled buttons in the same row — justify-
   content:center re-centers the label+icon within that wider box
   instead of leaving them hugging the left edge once min-width, not
   content, is what's setting the button's actual width. Per-size
   overrides below scale it with the size variant's own padding/
   font-size rather than one flat value looking oversized on Small or
   negligible on Large. .csa-btn-block's width:100% simply wins over
   this whenever it's larger — min-width is only ever a floor. */
.csa-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 9px;
  padding: 13px 22px; border-radius: 8px;
  font-weight: 400; font-size: var(--text-small);
  border: 1px solid transparent; transition: .18s; cursor: pointer;
  min-width: 120px;
  transition: .35s ease;
}
.csa-btn:hover {
  color: white;
}
.csa-btn:visited {
  color: initial;
}
.csa-btn svg { width: 15px; height: 15px; }
/* Same slot as svg above, for buttons whose icon is a standalone asset
   file (src/assets/icons/*.svg) rather than markup drawn inline — the
   flex row + gap already places it before or after the label by source
   order alone, so no separate icon-button variant class is needed. */
.csa-btn img { width: 15px; height: 15px; display: block; }

/* Gradient fill (o300→o600) + gradient border (o100→o400), both left to
   right — the double-background trick: the fill gradient is clipped to
   padding-box (stops at the border's inner edge) and the border gradient
   is clipped to border-box (fills all the way out), with the 2px
   `border: solid transparent` below carving out the ring between them.
   Border stops are 2 ramp steps down from the fill's (lighter/softer
   than the 1-step-down o200→o500 this shipped with originally — tried
   both side by side and this one won).

   Hover fades to a flat o600 fill + flat o400 border via a ::before
   overlay rather than swapping background-image directly — gradients
   can't cross-fade through a plain `transition`, they'd snap instantly,
   so the overlay's opacity is what actually animates. The border keeps
   its own hover tone (same -2-step relationship the resting o300/o100
   and o600/o400 pairs use) rather than flattening into the exact same
   color as the fill and disappearing. This is a plain opacity crossfade,
   compositor-friendly and with nothing to gate behind
   prefers-reduced-motion, unlike animating background-position.
   The overlay reuses the same double-background-clip trick as the
   element itself (needs its own 2px transparent border for that to
   work — otherwise its padding-box and border-box would be identical
   and the second layer would just paint over the first with no ring
   showing through) — flat "gradients" (two identical color stops) act
   as solid fills through that same clip split. It's inset -2px
   (matching the border width) so it extends out over the gradient
   border too, and sits behind the label text but above the button's own
   gradient background — negative z-index within the local stacking
   context .csa-btn-primary establishes via position: relative +
   z-index:0. */
.csa-btn-primary, .csa-btn-primary:visited { 
  position: relative;
  z-index: 0;
  border-width: 2px;
  background-image: linear-gradient(to right, var(--o300), var(--o600)),
                     linear-gradient(to right, var(--o100), var(--o400));
  background-origin: border-box;
  background-clip: padding-box, border-box;
  color: #fff;
  box-shadow: 0 0 0 0 rgba(242,93,0,0); /* animatable 0-state — see :hover */
  transition: box-shadow .35s ease;
}
.csa-btn-primary::before {
  content: '';
  position: absolute;
  inset: -2px;
  z-index: -1;
  border-radius: inherit;
  border: 2px solid transparent;
  background-image: linear-gradient(var(--o600), var(--o600)),
                     linear-gradient(var(--o400), var(--o400));
  background-origin: border-box;
  background-clip: padding-box, border-box;
  opacity: 0;
  transition: opacity .35s ease;
}
.csa-btn-primary:hover::before { opacity: 1; }
/* Soft glow, faded in alongside the ::before overlay — rgba since
   box-shadow can't reference custom properties through color-mix in
   every supported browser; #F25D00/#FFA600 are o600/o300's literal
   values (see colors.css). o300 reads better than o600 against a dark/
   black page background, so dark surfaces get their own brighter glow. */
/* transition restated on :hover (as .csa-btn-secondary:hover does): host
   pages' own `a:hover { transition: color .3s }` (cloudsecurityalliance.org's
   application.css has one) outranks the plain .csa-btn-primary rule while
   hovered, so the glow snapped on with no fade-in. */
.csa-btn-primary:hover { box-shadow: 0 0 24px 2px rgba(242,93,0,.5); transition: box-shadow .35s ease; }
/* .csa-hero/.csa-cta are the always-dark bespoke backgrounds noted in
   hero.css — they don't carry data-surface, so they need to be listed
   here explicitly too or a primary button inside them (e.g. the hero CTA)
   would silently fall through to the light-surface o600 glow above. */
[data-surface="dark"] .csa-btn-primary:hover,
[data-surface="black"] .csa-btn-primary:hover,
.csa-hero .csa-btn-primary:hover,
.csa-cta .csa-btn-primary:hover {
  box-shadow: 0 0 24px 2px rgba(255,166,0,.5);
}
/* .csa-hero-glass-light is a light glass PANEL sitting inside an
   always-dark .csa-hero, so the rule right above this one (which keys
   off .csa-hero itself) would otherwise give its Primary button the
   dark-surface o300 glow — wrong for a panel that reads as light.
   Same specificity as that rule (two classes), so this only wins
   because it's declared later in the file; keep it below, not above.
   .csa-hero-light is the same situation but for a whole hero background
   rather than a panel floating on one — it's the one .csa-hero variant
   that isn't dark, so it needs this too. */
.csa-hero-glass-light .csa-btn-primary:hover,
.csa-hero-light .csa-btn-primary:hover {
  box-shadow: 0 0 24px 2px rgba(242,93,0,.5);
}

/* transition overrides the base .csa-btn's .18s — Primary's hover fades
   over .35s (its ::before opacity/box-shadow transitions), and at the
   base .18s Secondary's hover was visibly snappier than Primary's right
   next to it, so this matches Primary's pace instead. */
.csa-btn-secondary, .csa-btn-secondary:visited { background: transparent; border: 2px solid var(--o500); color: var(--o500); transition: .35s ease; }
/* Border keeps its own hover tone (2 ramp steps down from the fill,
   same -2-step relationship Primary's hover overlay now uses) instead
   of flattening to the same color as the fill — border-color and
   background-color are plain solid values here, so this needs no
   crossfade workaround, unlike Primary's gradient fill/border. */
.csa-btn-secondary:hover { background: var(--o600); border-color: var(--o400); color: #fff; transition: .35s ease; } 
/* works unmodified on both light and dark surfaces */

/* No fill, no border (kept as a matching 2px transparent so it lines up
   at the same height as Primary/Secondary in a row) — o500 text, and a
   soft 20%-opacity o500 wash fills in on hover. rgba since background
   can't reference --o500 through color-mix in every supported browser;
   #FF7A00 → rgb(255,122,0) (see colors.css). transition overrides the
   base .csa-btn's .18s to match Primary/Secondary's .35s pace. */
.csa-btn-tertiary, .csa-btn-tertiary:visited { background: transparent; border: 2px solid transparent; color: var(--o500); transition: .35s ease; }
/* color restated so the base .csa-btn:hover white (same specificity, earlier) can't win */
.csa-btn-tertiary:hover { background: rgba(255,122,0,.2); color: var(--o500); transition: .35s ease; } /* transition restated for the same host a:hover reason as Primary/Secondary */
/* Persistent underline (not just on :hover) — a text-only button has no
   fill or border to read as clickable at rest, so the line is the
   resting affordance. On hover it retracts away and disappears (the
   wash alone carries the hover cue at that point) rather than staying
   put or growing. A literal border rather than text-decoration: exact,
   consistent thickness/offset across browsers, unlike relying on
   text-decoration-thickness support. Drawn as an absolutely-positioned
   ::after rather than a border on .csa-btn-label itself — a real
   border/padding on an inline element can only sit flush against its
   content box (no sub-pixel gap control), where an absolutely
   positioned line's `bottom` offset can be nudged by exact 1px steps
   independent of the text's own box, with no risk of it ever affecting
   the label's line-height/baseline the way padding or margin tweaks did
   during earlier passes at this. Scoped to a wrapping .csa-btn-label
   span so it sizes to the label text only, not the button's full padded
   flex box (which would sit far below the text) or an icon beside it.
   The retraction animates via transform: scaleX rather than width — a
   compositor property, so it animates smoothly without triggering
   layout/paint on every frame the way animating width would.
   transform-origin: right anchors the RIGHT edge in place, so
   scaleX(1)→scaleX(0) shrinks away from the left, reading as the line
   sliding out and off to the right as it vanishes, rather than
   collapsing in place from both ends or from the left. Eased in with
   cubic-bezier(0.55,0.055,0.675,0.19) instead of the plain `ease` used
   elsewhere in this file — a deliberately custom, more pronounced
   accelerating curve (starts slow, then speeds away toward the end) so
   this one detail reads as intentional/bespoke rather than a stock
   transition, distinct from Primary/Secondary's timing. */
.csa-btn-tertiary .csa-btn-label { position: relative; }
.csa-btn-tertiary .csa-btn-label::after {
  content: '';
  position: absolute;
  left: 0; right: 0; bottom: 1px;
  border-bottom: 1.5px solid currentColor;
  transform: scaleX(1);
  transform-origin: right;
  transition: transform .35s cubic-bezier(0.55,0.055,0.675,0.19);
}
.csa-btn-tertiary:hover .csa-btn-label::after { transform: scaleX(0); }

/* Non-interactive — pointer-events:none so it's inert regardless of
   whether the real element is an <a> or a <button disabled>, plus
   aria-disabled for anything reachable via the same class without the
   native attribute. No :hover rule at all, on purpose (the ask was "no
   hover change"). Surface-aware like the other 3 variants: light pages
   use n50/n300; dark (and the always-dark .csa-hero/.csa-cta, which
   don't carry data-surface — see the Primary glow rule above for why
   those need explicit scoping too) uses b800/b500, since n50 would be
   nearly invisible against a dark background; black uses n800/n500
   instead — matching the n-scale .csa-card already uses for its own
   black-surface background/border in surfaces.css, rather than
   reusing dark's blue-scale tones on a surface that's otherwise
   neutral, not navy. */
.csa-btn-disabled, .csa-btn[aria-disabled="true"] {
  background: var(--n50); color: var(--n300); border: 2px solid transparent;
  cursor: not-allowed; pointer-events: none;
}
[data-surface="dark"] .csa-btn-disabled,
.csa-hero .csa-btn-disabled, .csa-cta .csa-btn-disabled,
[data-surface="dark"] .csa-btn[aria-disabled="true"],
.csa-hero .csa-btn[aria-disabled="true"], .csa-cta .csa-btn[aria-disabled="true"] {
  background: var(--b800); color: var(--b500);
}
[data-surface="black"] .csa-btn-disabled,
[data-surface="black"] .csa-btn[aria-disabled="true"] {
  background: var(--n800); color: var(--n500);
}

.csa-btn-sm { padding: 9px 16px; font-size: 13px; min-width: 96px; }
.csa-btn-sm svg, .csa-btn-sm img { width: 13px; height: 13px; }

.csa-btn-lg { padding: 16px 28px; font-size: 16px; min-width: 140px; }
.csa-btn-lg svg, .csa-btn-lg img { width: 17px; height: 17px; }

.csa-btn-block { display: flex; width: 100%; justify-content: center; }

.csa-buttons { display: flex; flex-wrap: wrap; gap: 8px; }
/* =========================================================
   CSA Component Library — Action
   A surface-aware interactive tile applied to <a> or <button>.
   Looks like a card outline at rest; hover lifts it slightly;
   aria-selected="true" switches to an orange border with a
   warm background tint to signal a chosen/active state.

   Markup contract:
     <a class="csa-action" href="…">…</a>
     <button type="button" class="csa-action" aria-selected="false">…</button>

   Selected state is driven by aria-selected="true" — the ARIA
   attribute for selected items in listboxes, grids, and tab lists.
   Toggle it in JS; every visual change below is keyed off that
   attribute alone, so the JS contract is one attribute flip.
   ========================================================= */

/* Container — stacks actions in a single full-width column */
.csa-actions {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.csa-actions .csa-action { width: 100%; }

.csa-action {
  /* Layout */
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 14px 16px;
  /* Shape */
  border-radius: 12px;
  /* n50, not n75 — matches the resting border this library already uses
     elsewhere on white/n25 (.csa-pagination-btn, pagination.css); n75
     read a step darker than that established convention. */
  border: 1px solid var(--n50);
  /* Color */
  background: rgba(255,255,255,.02);
  color: inherit;
  text-decoration: none;
  /* Button reset — makes <button> render identically to <a> */
  font: inherit;
  -webkit-appearance: none;
  appearance: none;
  cursor: pointer;
  /* Transitions — border-left-width is listed alongside border-color so
     the selected state's thin-to-thick left border animates in rather
     than snapping; without it only the color fades. */
  transition: border-color .15s, border-left-width .15s, box-shadow .15s, background .15s;
  text-align: left;
  font-size: 90%;
  line-height: 1.2;
}

/* Hover — border steps one ramp darker, gains a subtle lift */
.csa-action:hover {
  border-color: var(--n100);
  box-shadow: var(--shadow-sm);
}

/* Focus ring for keyboard navigation */
.csa-action:focus-visible {
  outline: 2px solid var(--b500);
  outline-offset: 2px;
}

/* ── Selected state ─────────────────────────────────────── */
/* No :hover override once selected — border-color already wins on this
   rule by source order (declared after .csa-action:hover, same
   specificity), and the shadow doesn't change on hover either. A second
   color/lift shift on top of a tile that's already reading as "selected"
   would be a redundant signal. */
.csa-action[aria-selected="true"] {
  border-color: var(--o700);
  background: var(--o25);
  border-left-width: 4px;
  box-shadow: var(--shadow);
}

/* ── Arrow modifier ─────────────────────────────────────── */
/* color on ::after feeds currentColor into the chevron's borders,
   tracking every state the parent border goes through. */
.csa-action-arrow::after {
  content: '';
  display: block;
  width: 6px;
  height: 6px;
  border-right: 1.5px solid currentColor;
  border-top: 1.5px solid currentColor;
  transform: rotate(45deg);
  margin-left: auto;
  flex-shrink: 0;
  color: var(--n75);
}
.csa-action-arrow:hover::after                              { color: var(--n100); }
.csa-action-arrow[aria-selected="true"]::after              { color: var(--o700); }

[data-surface="dark"] .csa-action-arrow::after              { color: var(--b600); }
[data-surface="dark"] .csa-action-arrow:hover::after        { color: var(--b500); }
[data-surface="dark"] .csa-action-arrow[aria-selected="true"]::after       { color: var(--o500); }

[data-surface="black"] .csa-action-arrow::after             { color: var(--n700); }
[data-surface="black"] .csa-action-arrow:hover::after       { color: var(--n600); }
[data-surface="black"] .csa-action-arrow[aria-selected="true"]::after       { color: var(--o500); }

/* ── Surface-aware — dark ───────────────────────────────── */
[data-surface="dark"] .csa-action {
  border-color: var(--b600);
  color: #fff;
  backdrop-filter: blur(3px);
}
[data-surface="dark"] .csa-action:hover {
  border-color: var(--b500);
}
[data-surface="dark"] .csa-action[aria-selected="true"] {
  border-color: var(--o500);
  /* o500 (#FF7A00) at 8% opacity — no ramp token at this depth */
  background: rgba(255, 122, 0, .08);
  box-shadow: 0 2px 6px rgba(0, 0, 0, .25), 0 18px 40px -18px rgba(0, 0, 0, .55);
}

/* ── Surface-aware — black ──────────────────────────────── */
[data-surface="black"] .csa-action {
  border-color: var(--n700);
  color: #fff;
}
[data-surface="black"] .csa-action:hover {
  border-color: var(--n600);
}
[data-surface="black"] .csa-action[aria-selected="true"] {
  border-color: var(--o500);
  background: rgba(255, 122, 0, .08);
  box-shadow: 0 2px 6px rgba(0, 0, 0, .25), 0 18px 40px -18px rgba(0, 0, 0, .55);
}
/* =========================================================
   CSA Component Library — Badges
   The original extraction had one glass-pill badge (.csa-badge-hot,
   the spec's "Eyebrow") shared identically across all 4 reference
   pages. It's been retired in favor of the Primary/Secondary/Outlined
   variants below — this file now only carries the fresh-designed set.
   ========================================================= */
.csa-badges { 
  display: flex; 
  gap: 10px; 
  flex-wrap: wrap; 
  margin-bottom: 20px; 
}

/* Sized ~10% up from .csa-tag (12px/6px 13px) on purpose — before this,
   badges and tags were pixel-identical in size, leaving border/italic
   as the only things telling them apart at a glance. */
.csa-badge {
  display: inline-flex; align-items: center; gap: 8px;
  font-size: 13px; font-weight: 400; letter-spacing: .03em;
  padding: 6px 12px; border-radius: 999px;
  border: 1px solid rgba(255,255,255,.24);
  color: #dbe6f2;
  line-height: 1.6;
}

/* =========================================================
   Badge variants — Primary / Secondary / Outlined
   Designed fresh (not extracted): solid/outlined pills with an icon +
   italic label. Two color groups, not four — light/gradient-ish
   backgrounds (white, n25) share one treatment, dark backgrounds
   (dark, black) share another, matching how every other surface-aware
   component in this library only branches on "light-ish" vs
   "dark-ish", never on the individual surface name.

   Icon delivery is a per-instance choice between two techniques, not
   a single fixed rule: a plain <img> when a pre-colored file variant
   already exists for the exact color needed (as with Calendar's
   b500/b100/b300/white files below), or the shared .csa-icon-glyph
   mask primitive (base/icon-glyph.css) when it doesn't — e.g. an
   arbitrary content icon with no color-matched file generated for it.
   The <img> route gives exact, designer-controlled files where they
   already exist; the glyph route tracks an arbitrary text color for
   free without needing a new file generated per color. See the "on
   dark" hero-glass-dark example in the docs for the glyph route in use.

   calendar-b500/b100/b300.svg are new files generated for this spec
   (not part of the original 6-file brand-guide icon export): the brand
   guide only ships colored "chip" icons (a solid background circle +
   white glyph — see calendar-csa-blue.svg) or flat glyph-only icons in
   navy/white (calendar.svg / calendar-white.svg). These badges need a
   flat colored glyph with NO background shape of its own, so each was
   made by copying calendar.svg (glyph-only) and swapping its one
   hardcoded navy fill to the target token's hex — not by recoloring
   the chip icon, which would carry an unwanted background shape along
   with it. */
.csa-badge-primary, .csa-badge-secondary, .csa-badge-outlined, .csa-badge-orange { font-style: italic; }
.csa-badge-primary img, .csa-badge-secondary img, .csa-badge-outlined img, .csa-badge-orange img,
.csa-badge-primary .csa-icon-glyph, .csa-badge-secondary .csa-icon-glyph, .csa-badge-outlined .csa-icon-glyph, .csa-badge-orange .csa-icon-glyph {
  width: 15px; height: 15px; display: block;
}

.csa-badge-primary { background: var(--b500); border-color: var(--b400); color: #fff; }

.csa-badge-secondary { background: var(--b700); border-color: var(--b500); color: #fff; }
/* .csa-hero/.csa-cta listed alongside the [data-surface] pair for the
   same reason buttons.css's Primary glow needs it too: those two are
   always-dark bespoke backgrounds that deliberately never carry
   data-surface (see hero.css), so without this a badge-secondary
   dropped into a hero would fall through to the light-surface b700
   fill — b700 sits close enough to the hero's own navy background to
   nearly disappear into it. */
[data-surface="dark"] .csa-badge-secondary, [data-surface="black"] .csa-badge-secondary,
.csa-hero .csa-badge-secondary, .csa-cta .csa-badge-secondary {
  background: rgba(0,84,159,.5); /* --b500 (#00549F) at 50% */
  border-color: var(--b400);
  color: var(--b100);
}
/* .csa-hero-light is the one hero background that ISN'T dark, so the
   rule above (which keys off .csa-hero itself) would otherwise give its
   badges the translucent-navy dark treatment — wrong on a light bg, and
   low-contrast (light b100 text needs a dark backdrop). Same
   specificity as that rule (two classes), so this only wins by sitting
   later in this file; falls back to the plain light-surface default
   just above (background:b700, border:b500, color:#fff). */
.csa-hero-light .csa-badge-secondary {
  background: var(--b700);
  border-color: var(--b500);
  color: #fff;
}

.csa-badge-outlined { background: transparent; border-color: var(--b400); color: var(--b500); }
/* b100, not b300: b300 text is only ~3.3:1 against the dark b700 surface,
   below the 4.5:1 AA minimum for this size; b100 clears it. Pair with the
   calendar-b100.svg-style (b100/) icon file, not b300, to match. */
[data-surface="dark"] .csa-badge-outlined, [data-surface="black"] .csa-badge-outlined {
  color: var(--b100);
}

/* Orange — a warm accent variant. On light surfaces it's a solid o700 pill
   (o600 border) with white text and a white icon (the existing -white.svg
   files, e.g. calendar-white.svg). On dark/black it flips to the
   translucent treatment Secondary uses: o400 (#FF9100) at 20% fill, o400
   border, and o200 text — with the matching o200/ token-color icon files
   (src/assets/icons/o200/, e.g. o200/calendar-o200.svg). .csa-hero/.csa-cta
   are listed alongside the [data-surface] pair for the same reason as
   Secondary above (always-dark, never carry data-surface), and
   .csa-hero-light restores the light treatment, mirroring Secondary. */
.csa-badge-orange { background: var(--o700); border-color: var(--o600); color: #fff; }
[data-surface="dark"] .csa-badge-orange, [data-surface="black"] .csa-badge-orange,
.csa-hero .csa-badge-orange, .csa-cta .csa-badge-orange {
  background: rgba(255,145,0,.2); /* --o400 (#FF9100) at 20% */
  border-color: var(--o400);
  color: var(--o200);
}
.csa-hero-light .csa-badge-orange {
  background: var(--o700);
  border-color: var(--o600);
  color: #fff;
}
/* =========================================================
   CSA Component Library — Tags
   Designed fresh (not extracted): small pill labels, no border (that's
   what visually separates them from Badges) — color/weight alone carry
   the distinction. Two types:
     .csa-tag         — static, inert, a <span>
     .csa-tag-filter  — clickable/toggleable, a real <button>, selected
                        state driven by aria-pressed. BEHAVIOR SPEC, not
                        implemented here: on click, flip aria-pressed
                        between "true"/"false" — every visual state below
                        is plain CSS keyed off that attribute alone, so
                        that's the entire contract. Pressed state is
                        usually wired to real filtering logic in the
                        app, which is exactly the kind of app-specific
                        behavior this library doesn't own — implement
                        the click handler (or bind aria-pressed to
                        whatever state your app already tracks) on the
                        consuming side. "Filter tag/chip" is the closest universal
                        term for this exact pattern (Material's "Filter
                        chip"; Ant Design's CheckableTag and Carbon's
                        Selectable Tag are the same idea under different
                        names) — picked since these are for filtering
                        result sets, not a general on/off toggle.
   Color is a separate modifier — .csa-tag-neutral / .csa-tag-blue /
   .csa-tag-orange — combined with either type: <span class="csa-tag
   csa-tag-neutral"> or <button class="csa-tag csa-tag-filter
   csa-tag-blue">. A filter tag's resting look is identical to the
   static tag of the same color; only :hover and [aria-pressed="true"]
   diverge.
   Every rule below is written twice on purpose: once for a plain white
   page, once escalated a ramp step for a literal n25 background (see
   data-tone="neutral", the same tone .csa-card already responds to in
   surfaces.css) — without that step, an n25-background tag would be
   invisible sitting on an n25 page. Dark and black share one set of
   rules (identical per spec), diverging only in the pressed-state text
   color (b800 on navy, true black on black).
   .csa-tag-icon (see below) adds an optional leading glyph to either
   type, in any color — see its own comment for why that's a CSS mask
   rather than an <img>. */
.csa-tags { display: flex; flex-wrap: wrap; gap: 8px; }

/* height:31px is explicit — a whole-pixel round-off of what it used to
   render as implicitly (padding 6px×2 + 12px font × reset.css's base
   1.6 line-height ≈ 31.1953125px, an ugly sub-pixel value the font's
   own metrics produced with nothing clean about it). A whole integer
   here also sidesteps the sub-pixel-snapping remainder a fractional
   value like 31.2px still leaves on a 2x-DPI screen (31.2 × 2 = 62.4,
   itself not a whole device pixel) — 31 × 2 = 62 lands exactly.
   Pinning it here at all (rather than deriving it implicitly) gives
   every tag variant (including .csa-tag-removable below) an exact,
   predictable box to size against — box-sizing:border-box (reset.css)
   is what makes this include the padding rather than add to it. */
.csa-tag {
  display: inline-flex; align-items: center;
  font-size: 12px; font-weight: 500; letter-spacing: .02em;
  padding: 6px 13px; border-radius: 999px;
  height: 31px;
}
.csa-tag-filter { appearance: none; border: none; font-family: inherit; cursor: pointer; transition: background-color .15s; }

/* ---- White background (default, unscoped) ---- */
.csa-tag-neutral { background: var(--n25); color: var(--n400); }
.csa-tag-blue    { background: var(--b25); color: var(--b500); }
.csa-tag-orange  { background: var(--o25); color: var(--o700); }

.csa-tag-filter.csa-tag-neutral:hover { background: var(--n50); }
.csa-tag-filter.csa-tag-blue:hover    { background: var(--b50); }
.csa-tag-filter.csa-tag-orange:hover  { background: var(--o50); }

.csa-tag-filter.csa-tag-neutral[aria-pressed="true"] { background: var(--n400); color: #fff; }
.csa-tag-filter.csa-tag-blue[aria-pressed="true"]    { background: var(--b500); color: #fff; }
.csa-tag-filter.csa-tag-orange[aria-pressed="true"]  { background: var(--o700); color: #fff; }

/* ---- Inside a card on a light surface — automatic step-up ----
   A light surface's .csa-card / .csa-card-media is itself n25 (see
   surfaces.css) — whether the card sits inside a data-surface="light"
   section or carries data-surface="light" itself — so a neutral tag
   inside one would vanish into it just like on an n25 band. Same
   one-step escalation as the neutral tone below, applied automatically
   so no hook is needed on the card. Skipped where the card is white
   instead: a gradient surface or the neutral tone, on the card itself
   or around it.
   The whole context is wrapped in :where() so it adds no specificity:
   these rules land at the same weight as the White defaults above and
   win only by coming later, so every [data-surface="dark"/"black"]
   rule below still beats them when a dark section is nested inside a
   light page. Hover/pressed keep White's order (pressed last) for the
   same hover-vs-pressed reason given in the neutral block below. */
:where(:is([data-surface="light"] .csa-card, [data-surface="light"] .csa-card-media, [data-surface="light"].csa-card, [data-surface="light"].csa-card-media):not([data-surface="gradient"], [data-surface="gradient"] *, [data-tone="neutral"], [data-tone="neutral"] *)) .csa-tag-neutral { background: var(--n50); }
:where(:is([data-surface="light"] .csa-card, [data-surface="light"] .csa-card-media, [data-surface="light"].csa-card, [data-surface="light"].csa-card-media):not([data-surface="gradient"], [data-surface="gradient"] *, [data-tone="neutral"], [data-tone="neutral"] *)) .csa-tag-blue    { background: var(--b50); }
:where(:is([data-surface="light"] .csa-card, [data-surface="light"] .csa-card-media, [data-surface="light"].csa-card, [data-surface="light"].csa-card-media):not([data-surface="gradient"], [data-surface="gradient"] *, [data-tone="neutral"], [data-tone="neutral"] *)) .csa-tag-orange  { background: var(--o50); }

:where(:is([data-surface="light"] .csa-card, [data-surface="light"] .csa-card-media, [data-surface="light"].csa-card, [data-surface="light"].csa-card-media):not([data-surface="gradient"], [data-surface="gradient"] *, [data-tone="neutral"], [data-tone="neutral"] *)) .csa-tag-filter.csa-tag-neutral:hover { background: var(--n75); }
:where(:is([data-surface="light"] .csa-card, [data-surface="light"] .csa-card-media, [data-surface="light"].csa-card, [data-surface="light"].csa-card-media):not([data-surface="gradient"], [data-surface="gradient"] *, [data-tone="neutral"], [data-tone="neutral"] *)) .csa-tag-filter.csa-tag-blue:hover    { background: var(--b75); }
:where(:is([data-surface="light"] .csa-card, [data-surface="light"] .csa-card-media, [data-surface="light"].csa-card, [data-surface="light"].csa-card-media):not([data-surface="gradient"], [data-surface="gradient"] *, [data-tone="neutral"], [data-tone="neutral"] *)) .csa-tag-filter.csa-tag-orange:hover  { background: var(--o75); }

:where(:is([data-surface="light"] .csa-card, [data-surface="light"] .csa-card-media, [data-surface="light"].csa-card, [data-surface="light"].csa-card-media):not([data-surface="gradient"], [data-surface="gradient"] *, [data-tone="neutral"], [data-tone="neutral"] *)) .csa-tag-filter.csa-tag-neutral[aria-pressed="true"] { background: var(--n400); color: #fff; }
:where(:is([data-surface="light"] .csa-card, [data-surface="light"] .csa-card-media, [data-surface="light"].csa-card, [data-surface="light"].csa-card-media):not([data-surface="gradient"], [data-surface="gradient"] *, [data-tone="neutral"], [data-tone="neutral"] *)) .csa-tag-filter.csa-tag-blue[aria-pressed="true"]    { background: var(--b500); color: #fff; }
:where(:is([data-surface="light"] .csa-card, [data-surface="light"] .csa-card-media, [data-surface="light"].csa-card, [data-surface="light"].csa-card-media):not([data-surface="gradient"], [data-surface="gradient"] *, [data-tone="neutral"], [data-tone="neutral"] *)) .csa-tag-filter.csa-tag-orange[aria-pressed="true"]  { background: var(--o700); color: #fff; }

/* ---- Neutral (n25) background — one ramp step up so tags stay visible against their own n25 host ----
   Pairing rule, so the wrong variant never gets picked by accident:
   data-tone="neutral" only belongs on an ancestor whose OWN rendered
   background really is n25 (the page, or a plain n25 panel) — never
   combine it with something that then forces the immediate container
   to a DIFFERENT color (e.g. a wrapper carrying the tone purely to turn
   a nested .csa-card white). That mismatch — tags escalated for n25
   sitting on a card that's actually white — is exactly the bug this
   comment exists to prevent; it was shipped once in this project's own
   docs showcase before being caught and fixed. If a card needs to be
   white specifically, style that card directly (background/border
   inline, or its own class) rather than reaching for the tone on an
   ancestor. (This used to key off .csa-bg-n25 too; that name is now a
   plain color-helpers.css utility and no longer escalates anything.) */
[data-tone="neutral"] .csa-tag-neutral { background: var(--n50); }
[data-tone="neutral"] .csa-tag-blue    { background: var(--b50); }
[data-tone="neutral"] .csa-tag-orange  { background: var(--o50); }

[data-tone="neutral"] .csa-tag-filter.csa-tag-neutral:hover { background: var(--n75); }
[data-tone="neutral"] .csa-tag-filter.csa-tag-blue:hover    { background: var(--b75); }
[data-tone="neutral"] .csa-tag-filter.csa-tag-orange:hover  { background: var(--o75); }

/* Pressed state here is identical to White's — but it still needs its
   own tone-scoped copy, not just the shared rule above, purely
   for specificity: the hover rule right above it is 4 selectors deep
   ([data-tone] + 2 classes + :hover) and beats the shared pressed rule's
   3, so hovering a pressed tag was falling back to the light hover
   background while pressed's white text stayed — illegible. Matching
   the hover rule's specificity here (same 4 selectors) is what makes
   pressed reliably win while the mouse is still over the tag. */
[data-tone="neutral"] .csa-tag-filter.csa-tag-neutral[aria-pressed="true"] { background: var(--n400); color: #fff; }
[data-tone="neutral"] .csa-tag-filter.csa-tag-blue[aria-pressed="true"]    { background: var(--b500); color: #fff; }
[data-tone="neutral"] .csa-tag-filter.csa-tag-orange[aria-pressed="true"]  { background: var(--o700); color: #fff; }

/* ---- Dark + black backgrounds (identical except the pressed text color) ---- */
[data-surface="dark"] .csa-tag-neutral, [data-surface="black"] .csa-tag-neutral {
  background: rgba(165,183,198,.2); /* --n100 (#A5B7C6) at 20% */
  color: var(--n100);
}
[data-surface="dark"] .csa-tag-blue, [data-surface="black"] .csa-tag-blue {
  background: rgba(0,147,255,.2); /* --b300 (#0093FF) at 20% */
  color: var(--b100); /* b300 text on this tint is only ~3.3:1 on dark b700; b100 clears 4.5:1 */
}
[data-surface="dark"] .csa-tag-orange, [data-surface="black"] .csa-tag-orange {
  background: rgba(255,166,0,.2); /* --o300 (#FFA600) at 20% */
  color: var(--o300);
}

[data-surface="dark"] .csa-tag-filter.csa-tag-neutral:hover, [data-surface="black"] .csa-tag-filter.csa-tag-neutral:hover { background: rgba(165,183,198,.3); }
[data-surface="dark"] .csa-tag-filter.csa-tag-blue:hover, [data-surface="black"] .csa-tag-filter.csa-tag-blue:hover { background: rgba(0,147,255,.3); }
[data-surface="dark"] .csa-tag-filter.csa-tag-orange:hover, [data-surface="black"] .csa-tag-filter.csa-tag-orange:hover { background: rgba(255,166,0,.3); }

[data-surface="dark"] .csa-tag-filter.csa-tag-neutral[aria-pressed="true"] { background: var(--n100); color: var(--b800); }
[data-surface="dark"] .csa-tag-filter.csa-tag-blue[aria-pressed="true"]   { background: var(--b300); color: var(--b800); }
[data-surface="dark"] .csa-tag-filter.csa-tag-orange[aria-pressed="true"] { background: var(--o300); color: var(--b800); }

[data-surface="black"] .csa-tag-filter.csa-tag-neutral[aria-pressed="true"] { background: var(--n100); color: var(--black); }
[data-surface="black"] .csa-tag-filter.csa-tag-blue[aria-pressed="true"]   { background: var(--b300); color: var(--black); }
[data-surface="black"] .csa-tag-filter.csa-tag-orange[aria-pressed="true"] { background: var(--o300); color: var(--black); }

/* Icon variant — .csa-tag-icon modifier adds a glyph before the label,
   combined with any existing type/color class: <span class="csa-tag
   csa-tag-neutral csa-tag-icon"><span class="csa-icon-glyph"
   style="--icon-glyph:url(/...)"></span>Label</span>. The glyph itself
   (mask + currentColor, so it recolors for free with the tag's own
   text color, including through hover/pressed) is the shared
   .csa-icon-glyph primitive in base/icon-glyph.css — this just sizes
   it for the tag context; its 14px default already matches what tags
   need, so no override is required here. That's also why
   profile-white.svg (mentioned as already existing for the pressed
   state) isn't wired in separately: masking already renders the glyph
   in solid white the instant the pressed rules above set color:#fff,
   so a second file would only reproduce what currentColor already
   does on its own. */
.csa-tag-icon { padding-left: 11px; gap: 4px; }

/* Removable variant — .csa-tag-removable modifier adds a trailing X
   button, combined with any existing color class (like .csa-tag-icon,
   works on top of the base static .csa-tag): <span class="csa-tag
   csa-tag-neutral csa-tag-removable">Label<button type="button"
   class="csa-tag-remove" aria-label="Remove Label"><svg>...</svg>
   </button></span>. The X is a real, separately-focusable <button> even
   though the tag itself is a plain <span> — only the remove action is
   interactive, the tag body isn't a control on its own (unlike
   .csa-tag-filter, where the whole tag IS the control). BEHAVIOR SPEC,
   not implemented here: clicking .csa-tag-remove should remove its
   parent .csa-tag-removable, typically alongside whatever app state
   that tag represents (a selected filter, an attached file, etc.) —
   that data/state coupling is exactly why this library doesn't own the
   removal itself. Every visual state here is plain CSS.
   Reduced right padding since the X button already carries its own
   visual weight — the base .csa-tag's full 13px would read as extra
   dead space between the label and the button.
   6.5px specifically, not a rounder-looking number: the circle's gap
   to the tag's top/bottom edge is padding(6px) + half the leftover
   slack between the tag's 19px content-box height and the circle's own
   18px (0.5px) = 6.5px — matching that exactly here is what makes the
   circle's clearance equal on all four sides instead of visibly
   wider on the right, since the right gap has no equivalent centering
   slack of its own to account for (the button is the flex row's last
   child, so its right edge sits flush against this padding with
   nothing else in between). */
.csa-tag-removable { padding-right: 6.5px; }
/* Sized as a 16px hit target (bigger than the 9px glyph inside it) so
   it's comfortably clickable, not just the icon's own tiny outline —
   same reasoning a native checkbox's click target extends past its
   visible box. color:inherit + currentColor throughout is what makes
   this automatically match every tag color/surface/pressed-state
   combination above for free, with no per-variant override needed. */
.csa-tag-remove {
  position: relative;
  display: inline-flex; align-items: center; justify-content: center;
  flex-shrink: 0;
  width: 18px; height: 18px;
  margin-left: 4px;
  padding: 0; border: none; border-radius: 50%;
  background: transparent; color: inherit;
  cursor: pointer;
}
/* Capped at 18px, not bigger: .csa-tag's height is now pinned explicit
   (31px total, see its own comment above) rather than derived from
   content, so this button can no longer force the tag taller the way
   it could before that change — but .csa-tag still doesn't clip
   overflow, so a button taller than the tag's own 19px content box
   (31px height minus 6px top/bottom padding) would visibly poke out
   past the pill's rounded edge instead. 18px, not the full 19px: at
   exactly 19px the circle sits flush against that ceiling with zero
   margin, one sub-pixel rounding away from visibly overflowing; 18px
   keeps a safe 1px cushion under it. */
.csa-tag-remove svg { position: relative; z-index: 1; width: 13px; height: 13px; }
/* The hover circle: a separate ::before layer (not background-color
   directly on the button) so its opacity fades independently of the X
   icon itself — the icon stays fully opaque, only the circle behind it
   fades in. background:currentColor means this needs zero per-color
   rules: it's always a tinted version of whatever the tag's own text
   color already is, light or dark surface, pressed or not. */
.csa-tag-remove::before {
  content: ''; position: absolute; inset: 0; border-radius: 50%;
  background: currentColor; opacity: 0;
  transition: opacity .15s;
}
.csa-tag-remove:hover::before { opacity: .15; }
/* =========================================================
   CSA Component Library — Nav
   Extracted as-is from the 4 reference pages, tokens renamed.
   Two real variants exist in the wild:
     1. Hub nav   — logo lockup + link row + primary CTA button
                    (csai-foundation-hub.html only)
     2. Subpage nav — logo lockup + single "← back" link
                    (ai-resilience-center / catastrophic-risk-annex /
                     mythos-cyber-capable-ai)
   Both share the same brand lockup and sticky header shell.
   Nav content (links, CTA target, back destination) is a per-page
   placeholder — the real site's own nav will replace this shell
   once these pages are live on cloudsecurityalliance.org.
   ========================================================= */
.csa-nav {
  position: sticky; top: 0; z-index: 60;
  background: rgba(255,255,255,.9);
  backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--n50);
}
.csa-nav-inner { display: flex; align-items: center; justify-content: space-between; height: 74px; }

/* Brand lockup: CSA mark + divider + CSAI mark + "Foundation" tag */
.csa-brand { display: flex; align-items: center; gap: 13px; }
.csa-brand-logo { height: 38px; width: auto; display: block; }
.csa-brand-sublogo { height: 29px; width: auto; display: block; }
.csa-brand-divider { width: 1px; height: 28px; background: var(--n50); }
.csa-brand-lockup { display: flex; align-items: center; gap: 9px; }
.csa-brand-tag {
  font-size: 11px; font-weight: 800; letter-spacing: .15em;
  text-transform: uppercase; color: var(--o700);
}

/* Variant 1: hub nav link row + CTA */
.csa-nav-links { display: flex; gap: 30px; font-size: var(--text-small); font-weight: 500; }
.csa-nav-links a { color: var(--n500); }
.csa-nav-links a:hover { color: var(--b500); }
@media (max-width: 940px) { .csa-nav-links { display: none; } }

/* Variant 2: subpage back-link */
.csa-nav-backlink { font-size: 14px; color: var(--n300); font-weight: 600; }
.csa-nav-backlink:hover { color: var(--b500); }
/* =========================================================
   CSA Component Library — Hero & CTA band
   Built fresh per spec (not in the "extract as-is" list), informed by
   the shared structure across all 4 reference heroes: badges row,
   kicker, h1, sub paragraph, cta button row — always on a dark surface.
   Two real background treatments existed and both are kept as modifiers:
     .csa-hero-navy  — diagonal navy gradient (ai-resilience-center)
     .csa-hero-photo — full-bleed cover photo (the other 3 pages)
   .csa-cta is the closing full-bleed promo band, identical pattern
   across 3 of 4 pages (mythos/catastrophic/ai-resilience); folded in
   here since it's the same "full-bleed dark band with cta buttons" shape.

   .csa-hero and .csa-cta are always-dark, bespoke backgrounds — they
   don't carry a data-surface attribute, so the generic
   [data-surface="dark"] .csa-kicker rule never applies inside them.
   Wire the dark kicker/kicker-sub color directly here instead, and
   split hero from cta since their real precedent differs: all 4 pages
   hardcode the HERO kicker specifically to --b300 (#0093ff); the other
   dark blocks in those same pages (advisory/mission/sage — the same
   shape as .csa-cta) use the spec's regular dark-surface orange. */
.csa-hero .csa-kicker, .csa-hero .csa-kicker-sub { color: var(--b300); }
.csa-cta .csa-kicker, .csa-cta .csa-kicker-sub { color: var(--o500); }

.csa-hero { position: relative; overflow: hidden; color: #fff; }
.csa-hero-navy  { background: linear-gradient(160deg, var(--b800), var(--b700) 48%, var(--b700)); }
.csa-hero-photo { background-position: center; background-size: cover; background-repeat: no-repeat; }

/* .csa-hero-light — same content shape as .csa-hero-navy (badges,
   kicker, h1, sub, cta buttons floating directly on the background, not
   inside a glass panel like .csa-hero-glass), just a light mesh-gradient
   background instead of the navy diagonal. Every one of .csa-hero's own
   defaults above (color:#fff, kicker b300) assumes a dark bg, so this
   needs its own overrides the same way .csa-hero-glass-light overrides
   .csa-card-glass's — see the h1/.csa-sub overrides further below
   (they have to sit after .csa-hero h1/.csa-sub to win that tie), and
   badges.css/buttons.css for the badge-secondary and primary-button-hover
   overrides this same variant needs there.
   Mesh gradient built from 4 sampled mesh points off a reference comp:
   o50 top-left, n25 upper-right (subtle), b50/b75 at the two bottom
   corners — b75 more saturated than b50, so bottom-right reads stronger
   than bottom-left, matching the reference's own asymmetry. */
.csa-hero-light {
  background-color: var(--white);
  background-image:
    radial-gradient(circle at 16% 10%, var(--o50) 0%, rgba(255,231,189,0) 45%),
    radial-gradient(circle at 68% 19%, var(--n25) 0%, rgba(238,243,248,0) 35%),
    radial-gradient(circle at 20% 88%, var(--b50) 0%, rgba(196,230,255,.4) 30%, rgba(196,230,255,0) 48%),
    radial-gradient(circle at 93% 89%, var(--b75) 0%, rgba(153,212,255,.55) 24%, rgba(153,212,255,0) 44%);
  color: var(--n500);
}
.csa-hero-light .csa-kicker { color: var(--b500); }

.csa-hero .csa-wrap { position: relative; z-index: 0; padding: 104px 24px 92px; }

.csa-hero h1 {
  color: #fff; letter-spacing: -.03em; line-height: 1.04;
  font-size: clamp(38px, 5.6vw, 62px);
  margin: 6px 0 20px; max-width: 18ch;
}
.csa-hero .csa-sub {
  font-size: clamp(18px, 2vw, 22px); color: var(--n75);
  max-width: 56ch; line-height: 1.55;
}
/* These two have to sit after .csa-hero h1/.csa-sub above to win the
   specificity tie (both sides are one class + one type selector) —
   b800/n500 match this library's usual "headings b800, body n500"
   light-surface convention (see Surfaces above) rather than inventing
   new values just for this variant. */
.csa-hero-light h1 { color: var(--b800); }
.csa-hero-light .csa-sub { color: var(--n500); }

.csa-hero-cta { display: flex; gap: 14px; margin-top: 34px; flex-wrap: wrap; }
/* Small screens: stack the CTA row and give each button the same
   treatment as .csa-btn-block (display:flex; width:100%; justify-
   content:center), so it spans the full width of whatever it's sitting
   in — the hero's own .csa-wrap on hero-navy, or the glass panel
   (.csa-card-glass) on hero-glass. Applied here automatically rather
   than requiring every hero example to hardcode .csa-btn-block in its
   markup. Same 640px breakpoint .csa-cta already uses below, for the
   same "small screen" cutoff across this file. */
@media (max-width: 640px) {
  .csa-hero-cta { flex-direction: column; }
  .csa-hero-cta .csa-btn { display: flex; width: 100%; justify-content: center; }
}

/* Hero-glass — content sits inside a frosted glass panel over a photo
   background (.csa-hero-photo), instead of floating directly on it
   like .csa-hero-navy does. Reuses .csa-card-glass's exact blur/
   translucency recipe for the panel rather than redefining it: just
   nest a plain .csa-card-glass inside a .csa-hero.csa-hero-photo. Two
   of the 4 reference pages (mythos-cyber-capable-ai.html, csai-
   foundation-hub.html) used this exact shape in an earlier iteration —
   their currently-published versions simplified the hero down to a
   plain gradient with no glass panel. That original panel used the
   values .csa-card-glass's dark variant had at the time; it now uses
   the current frost glass material (cards.css) like every other glass
   panel. width:fit-content on the panel (not full-bleed)
   matches that original — it hugs its own content instead of spanning
   the hero's full width.

   .csa-hero-glass-light / .csa-hero-glass-dark supply the two color
   directions (only one of the two pages actually shipped dark; light
   is a new variant built the same way, swapping to .csa-card-glass's
   light default instead of its dark override). Every override below is
   explicit, not a fallthrough: .csa-hero's own h1/.csa-sub color rules
   (white, for text floating directly on a dark hero) and .csa-card-
   glass's own h1/p color rules (light-panel-appropriate) were never
   written expecting to be nested inside each other, and doing so
   creates a real specificity collision between them — `.csa-hero
   .csa-sub` alone already outranks `.csa-card-glass p` by class count,
   independent of file order — so rather than lean on however that
   cascade happens to resolve, every text color the glass panel needs
   is set here explicitly, scoped to .csa-hero-glass specifically. */
/* Stays left-aligned (no margin-left:auto — panel hugs the wrap's left
   edge like .csa-hero-navy's own content does). width:fit-content is
   meant to shrink the panel to its widest child's actual content, but
   on its own that doesn't work the way it looks like it should: h1/
   .csa-sub/.csa-mission are plain blocks and .csa-hero-cta is a block-
   level flex row, so by default ALL of them have width:auto, which
   means "fill the available width of the containing block" — even
   inside a fit-content parent, capped only by their own max-width
   (18ch/56ch/52ch). That cap, not each element's actual longest
   rendered line, is what fit-content sees as their "preferred" width,
   so the panel sizes itself to the WIDEST cap among them and then
   every other child stretches to match — a short badge row or a
   heading that only needs two much-narrower lines still ends up
   spanning the same bloated width, which is exactly the "glass is
   wider than it needs to be" symptom.
   The fix, and the rule to keep applying here going forward: every
   direct text/content child of a glass panel gets width:fit-content
   too (max-width:100% alongside it, so none can ever overflow the
   panel on a narrow viewport) — that makes each one shrink to ITS OWN
   actual content first, and only THEN does the panel's own
   width:fit-content measure the true widest one among them. Add any
   new child type to this same selector list rather than leaving it on
   the block/flex default. */
.csa-hero-glass .csa-card-glass { padding: 44px; width: fit-content; }
.csa-hero-glass .csa-card-glass h1,
.csa-hero-glass .csa-card-glass .csa-sub,
.csa-hero-glass .csa-card-glass .csa-mission,
.csa-hero-glass .csa-card-glass .csa-hero-cta,
.csa-hero-glass .csa-card-glass .csa-badges {
  width: fit-content; max-width: 100%;
}
/* Matches the plain established h1 (typography.css) exactly, not the
   source pages' own literal per-page values — this doc outlives the
   source HTML it was extracted from, so sizing rules live independent
   of it from here on. (.csa-hero's own h1 override elsewhere in this
   file is its own deliberate exception — ai-resilience-center's heading
   runs longer, so it keeps a smaller clamp — not a pattern to extend.)

   max-width:16ch, not the plain h1's 18ch: this is the one place
   width:fit-content (above) genuinely can't shrink to the actual
   rendered content the way it looks like it should. Once an h1's full
   unwrapped text is wider than its max-width, that max-width becomes
   the box's resolved width outright — CSS has no way to then re-measure
   and shrink to whichever wrapped line ends up longest, so a 2-word
   headline that wraps still gets a box as wide as the full cap allows,
   not as wide as its actual (shorter) two lines. Tightening the cap
   itself is the only lever available; 16ch is chosen to stay just wide
   enough for this pattern's short headlines' longest real line (e.g.
   "Frontier Ready" clears it) without cutting a line short by a word —
   re-check this value if a much longer word ever appears in one. */
.csa-hero-glass .csa-card-glass h1 {
  font-size: clamp(40px, 5.8vw, 66px); letter-spacing: -.03em;
  line-height: 1.05; margin: 14px 0 14px; max-width: 16ch;
}
/* 54ch, not the general 56ch — same underlying limitation h1's own
   gap-tightening comment above describes, just a smaller trim here:
   .csa-sub is allowed to end up wider than h1 (and so become what the
   panel's own width:fit-content actually measures) rather than being
   capped to match it. */
.csa-hero-glass .csa-card-glass .csa-sub { font-size: clamp(18px, 2vw, 22px); max-width: 54ch; line-height: 1.5; }
.csa-hero-glass .csa-card-glass .csa-mission { margin-top: 16px; font-size: 16px; max-width: 52ch; }
.csa-hero-glass .csa-card-glass .csa-hero-cta { margin-top: 26px; }
/* .csa-hero-cta is deliberately width:fit-content above (so the row
   never forces the panel wider than its longest line on desktop) — but
   at the mobile breakpoint (see .csa-hero-cta's own media query near
   the top of this file) its buttons switch to width:100%, which needs
   a real, definite width on this row to resolve against. A percentage
   width against a fit-content ancestor doesn't shrink-then-stretch the
   way it looks like it should (same underlying limitation the h1/
   .csa-sub comments above describe) — the button just falls back to
   its own content size instead, which is exactly why the buttons
   weren't actually reaching the panel's edges. Same selector/
   specificity as the fit-content rule above, winning only because it's
   later in the file — keep this below that rule, not above it. */
@media (max-width: 640px) {
  .csa-hero-glass .csa-card-glass .csa-hero-cta { width: 100%; }
}

/* Kicker: b500 here specifically (not the b300 .csa-hero gives every
   other hero's kicker) — b300 is a bright cyan-blue tuned for reading
   on a dark hero background, and reads washed-out/low-contrast against
   this panel's light, near-white glass instead. Same specificity as
   .csa-hero .csa-kicker (two classes) — wins by sitting later in this
   same file, not by out-ranking it. */
.csa-hero-glass-light .csa-kicker { color: var(--b500); }
.csa-hero-glass-light .csa-card-glass h1 { color: var(--b800); }
.csa-hero-glass-light .csa-card-glass .csa-sub { color: var(--n500); }
.csa-hero-glass-light .csa-card-glass .csa-mission { color: var(--n400); }

/* Glass material: dark is .csa-card-glass's own default (cards.css), so
   .csa-hero-glass-dark needs nothing extra for it; .csa-hero-glass-light
   .csa-card-glass is listed in cards.css's light list alongside
   .csa-card-glass-light. One copy of the values either way. Text colors
   stay here, explicit, for the reasons in the Hero-glass comment above. */
.csa-hero-glass-dark .csa-card-glass h1 { color: #fff; }
.csa-hero-glass-dark .csa-card-glass .csa-sub { color: var(--n75); }
.csa-hero-glass-dark .csa-card-glass .csa-mission { color: var(--n100); }

/* Hero-glass, graphic variant — extracted from the STAR Program
   Overview page's hero: a wide glass panel with a large graphic
   (illustration/badge art) overlapping its right edge, instead of the
   plain fit-content panel .csa-hero-glass uses alone. Composes with
   EITHER .csa-hero-glass-light or .csa-hero-glass-dark — this modifier
   only adds the graphic column and widens the panel to make room for
   it; color is still entirely owned by whichever light/dark modifier
   is also present (the source page only ever shipped the dark
   pairing, but nothing here is dark-specific).

   Markup:
     <div class="csa-hero csa-hero-photo csa-hero-glass csa-hero-glass-dark csa-hero-glass-graphic" style="background-image:url(/...)">
       <div class="csa-wrap csa-hero-graphic-row">
         <div class="csa-card-glass">
           <div class="csa-hero-graphic-content"> ...badges/h1/sub/cta... </div>
         </div>
         <div class="csa-hero-graphic-img"><img src="..." alt=""></div>
       </div>
     </div>

   .csa-hero-graphic-row is a plain flex row, not a grid — the graphic
   is taken out of flow entirely (position:absolute below), so it never
   actually occupies a column track; flex is enough to just place the
   glass panel. The panel is widened (950px cap, up from the plain
   hero-glass fit-content default) so its own right edge reaches
   roughly halfway across the graphic, letting it visually overlap the
   card's edge instead of floating fully apart from it — same
   class-count specificity tie as .csa-hero-glass .csa-card-glass{width:
   fit-content} above, broken by this rule sitting later in the file.
   Because the panel now has a real fixed-ish width instead of fit-
   content, its children don't need the fit-content-chain treatment
   .csa-hero-glass's own comment describes — .csa-hero-graphic-content
   is a plain block, just capped at 640px so the wide panel doesn't
   stretch copy across its full width.

   .csa-hero-graphic-img is pinned with a real 24px inset from the
   wrap's own edge (not right:0, which is flush against the true
   viewport/wrap edge whenever the wrap isn't wider than the viewport),
   taken out of flow so its size/position never depends on the panel's
   width. Its width shrinks continuously with viewport instead of
   snapping to display:none — clamp(300px, 120vw - 960px, 480px) is a
   linear function solved to hit 480px at a 1200px wrap and 300px at a
   1050px wrap, holding at 480px above that and hiding below it.
   z-index:1 puts it above the glass panel's own non-positioned box, so
   it paints over the card's edge rather than being hidden behind it —
   that's the intended "graphic breaking out of the frame" look, not a
   mistake to fix.
   Below 1050px the graphic hides entirely and the panel reverts to
   .csa-hero-glass's own fit-content default (set explicitly here, not
   left unset — the unconditional width rule above is the same
   selector, so it's a source-order tie this query's later rule needs
   to win). */

.csa-hero-graphic-row { display: flex; align-items: center; gap: 0; flex-wrap: wrap; position: relative; }
.csa-hero-glass-graphic .csa-card-glass { width: 80%; max-width: 950px; }
.csa-hero-graphic-content { max-width: 640px; }
.csa-hero-graphic-img { position: absolute; right: 24px; top: 50%; transform: translateY(-50%); width: clamp(300px, calc(120vw - 960px), 480px); z-index: 1; }
.csa-hero-graphic-img img { width: 100%; height: auto; display: block; }
@media (max-width: 1050px) {
  .csa-hero-graphic-img { display: none; }
  .csa-hero-glass-graphic .csa-card-glass { width: fit-content; }
  .csa-hero-graphic-content { max-width: none; }
}

/* Hero-split-graphic — content floats directly on the photo (no glass
   panel, same as .csa-hero-photo alone), split into two columns by
   .csa-wrap itself: text/CTA on the left, a single graphic (a logo/
   wordmark, not an illustration meant to overlap the frame the way
   .csa-hero-glass-graphic's does) vertically centered on the right.
   Promoted from the STAR Program's own "Learn about STAR" hero pattern
   — reused as-is across 4 of its pages (Learn about STAR, Learn about
   STAR for AI, STAR Registry, Get Listed) before landing here, so this
   is a proven shape, not a speculative one.

   Markup:
     <section class="csa-hero csa-hero-photo csa-hero-split-graphic" style="background-image:url(/...)">
       <div class="csa-wrap">
         <div> ...badges/kicker/h1/sub/cta... </div>
         <img class="csa-hero-split-img" src="..." alt="">
       </div>
     </section>

   1.2fr for the text column (not a plain 1fr/1fr split) gives copy the
   larger share — a headline + paragraph + CTA row needs more room to
   breathe than a single static graphic; the graphic column is `auto`,
   sized to .csa-hero-split-img's own width:100%/max-width below (286px
   — the STAR source's real value, not a guess) rather than stretched.
   Below 1050px the graphic drops under the text instead of squeezing
   beside it — the same breakpoint .csa-hero-glass-graphic already uses
   for its own graphic column, kept consistent across both hero
   patterns; the image also shrinks to a smaller 200px cap there,
   matching the source's own mobile behavior. */
.csa-hero-split-graphic .csa-wrap {
  display: grid;
  grid-template-columns: 1.2fr auto;
  gap: 48px;
  align-items: center;
}
.csa-hero-split-img { display: block; justify-self: end; width: 100%; max-width: 286px; }
@media (max-width: 1050px) {
  .csa-hero-split-graphic .csa-wrap { grid-template-columns: 1fr; }
  .csa-hero-split-img { justify-self: center; max-width: 200px; margin-top: 8px; }
}

/* Closing CTA band */
.csa-cta {
  position: relative; overflow: hidden;
  border-radius: 20px; padding: 62px 56px;
  color: #fff; text-align: center;
  background-position: center; background-size: cover; background-repeat: no-repeat;
}
.csa-cta h2 { color: #fff; font-size: 34px; max-width: 20ch; margin: 0 auto; }
.csa-cta p { color: var(--n75); font-size: 17px; margin: 14px auto 28px; max-width: 56ch; }
.csa-cta-row { display: flex; gap: 14px; justify-content: center; flex-wrap: wrap; }
@media (max-width: 640px) { .csa-cta { padding: 40px 26px; } }

/* .csa-cta-photo-bg — an alternative to setting .csa-cta's background
   as a plain `background: url(/...)`, for when only an unprocessed
   photo is on hand instead of a separately pre-blurred/darkened image
   file: drop a plain <img> in as .csa-cta's first child and blur/darken
   it live with filter, right in the browser. scale(1.15) oversizes the
   image so .csa-cta's own overflow:hidden crops away the lighter halo
   a blur leaves at an image's own literal edges (blurring samples past
   the image's real bounds there, which would otherwise show as a soft
   pale border around the CTA). brightness(.4) does the darkening
   instead of a separate rgba() overlay layer — one filter, not two
   stacked layers, and it darkens the busy photo evenly enough for the
   CTA's white text to stay readable, same as a hand-darkened source
   image would.
   Needs .csa-cta-content wrapping the real CTA children (h2/p/
   .csa-cta-row): position:absolute on the img makes it paint above
   plain in-flow siblings by default regardless of source order, so the
   text needs its own explicit stacking (position:relative + z-index)
   to actually land on top of it. */
.csa-cta-photo-bg {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; transform: scale(1.15);
  filter: blur(16px) brightness(.4);
  z-index: 0;
}
.csa-cta-content { position: relative; z-index: 1; }
/* =========================================================
   CSA Component Library — Icons
   Source: CSA Brand Guide 20260421.pdf, "Icon Color Variations" +
   "Approved Icon Color & Background Combinations" (pages 15–16).

   The color/chip treatment is baked directly into each SVG file in
   src/assets/icons/ — there are 6 files per icon, one per approved
   style. Picking the right FILE for a given background is a decision
   the page author makes (see the showcase's Icons section for the
   full approved-combination table) — this file only handles sizing.

     {name}.svg                 Monochrome          navy glyph, no chip
     {name}-white.svg           Inverse Monochrome  white glyph, no chip
     {name}-light-blue.svg      Light Blue          --b50 chip, navy glyph
     {name}-CSA-blue.svg        CSA Blue            --b500 chip, white glyph
     {name}-yellow-orange.svg   Yellow Orange       --o300-ish chip, navy glyph
     {name}-red-orange.svg      Red Orange          --b800 chip, --o700 glyph

   48px is the default per the 4 reference pages. Internal viewBoxes
   vary slightly between an icon's no-chip and chip variants (checked —
   e.g. -15,-15,30,30 vs -15,-13.7,30,27.39), so object-fit:contain
   keeps every icon uniformly sized and undistorted regardless.

   The chip variants (light-blue/CSA-blue/yellow-orange/red-orange) draw
   their glyph at 60% of their own canvas, leaving room for the chip's
   padding — checked across all 101 icons, exactly 0.6 every time, no
   exceptions. Monochrome/white have no chip, so their glyph fills the
   canvas edge to edge — at the same 48px box, that reads noticeably
   bigger than a chip icon's glyph. .csa-icon-mono scales it back down
   to that same 60%, so a monochrome icon's ink matches a chip icon's
   ink at a glance, rather than matching box-for-box.
   ========================================================= */
.csa-icon {
  width: 48px;
  height: 48px;
  display: block;
  object-fit: contain;
}

.csa-icon-small {
  width: 32px;
  height: 32px;
}

.csa-icon-large {
  width: 56px;
  height: 56px;
}


.csa-icon-mono { transform: scale(.6); }

/* Flat light-blue tint backdrop — the "B25–B50" approved background
   named in the brand guide for Monochrome and CSA Blue icons. Doesn't
   correspond to any of the 3 named page surfaces (light/gradient/dark),
   so it's a small standalone utility rather than a new data-surface value. */
.csa-bg-tint { background: var(--b50); }


/* Icon + text — an icon and a run of text side by side on one line,
   vertically centered, with a fixed gap between them. Inline-flex so
   it sits in a line of text or a flex row without forcing a new block.
   Works with any icon: a .csa-icon <img> (any size modifier) or a
   .csa-icon-glyph, or a plain <img>/<svg> (sized 14px by default, see
   below). Override --icon-text-gap to adjust the spacing.

     <span class="csa-icon-text">
       <img class="csa-icon csa-icon-small" src="…" alt="">
       <span>Label text</span>
     </span>

   flex-shrink:0 keeps the icon at its size when the text wraps. */
.csa-icon-text {
  --icon-text-gap: 8px;
  display: inline-flex;
  align-items: center;
  gap: var(--icon-text-gap);
}
.csa-icon-text > .csa-icon { flex-shrink: 0; }
/* A plain inline icon (an <img> or <svg> without .csa-icon's chip
   sizing — e.g. a small monochrome icon in front of a kicker label) is
   14px by default, so markup needn't set its own width. This CSS size
   overrides an HTML width="" attribute; change it per use with
   --icon-text-size. .csa-icon chips and .csa-icon-glyph keep their own
   sizes. */
.csa-icon-text > img:not(.csa-icon), .csa-icon-text > svg:not(.csa-icon) {
  width: var(--icon-text-size, 14px); height: var(--icon-text-size, 14px); flex-shrink: 0;
}
/* =========================================================
   CSA Component Library — Carousel
   Card/arrow/track visuals extracted as-is from the "AI insights &
   events" carousel in csai-foundation-hub.html — tokens renamed.

   BEHAVIOR SPEC — not implemented here. The reference implementation
   was a hand-rolled pointer-drag carousel with momentum, arrow-button
   paging, and a seamless infinite loop (3 copies of the card set, with
   scroll position silently reset on wraparound). That's real app
   behavior, not something this library owns — implement whichever
   subset your app actually needs:
     - Draggable + snap, no loop: the cheapest path is native CSS —
       `overflow-x: auto; scroll-snap-type: x mandatory;` on
       .csa-carousel-viewport and `scroll-snap-align: start;` on
       .csa-carousel-card gets swipe/trackpad drag with real OS momentum
       and zero JS. Arrow buttons then just need
       `viewport.scrollBy({left: cardWidth, behavior: 'smooth'})`.
     - Seamless infinite-loop wraparound has no CSS equivalent — it
       needs JS to detect the scroll-position edge and jump back without
       a visible seam. Only build this if the app genuinely needs it.
   Markup contract per carousel:
     <div class="csa-carousel">
       <div class="csa-carousel-row">
         <button class="csa-carousel-arrow csa-carousel-arrow-prev">...</button>
         <div class="csa-carousel-viewport">
           <div class="csa-carousel-track">
             <!-- .csa-carousel-card items (3x clone sets if implementing
                  the infinite-loop pattern — see the showcase demo) -->
           </div>
         </div>
         <button class="csa-carousel-arrow csa-carousel-arrow-next">...</button>
       </div>
       <div class="csa-carousel-dots">
         <!-- .csa-carousel-dot items, optional — see below -->
       </div>
     </div>
   .csa-carousel is the outer wrapper — a plain flex column stacking
   .csa-carousel-row above .csa-carousel-dots, so copying the one
   .csa-carousel block always brings the dots with it. (An earlier
   version of this file had the dots as a sibling *after* .csa-carousel
   instead of a child — easy to miss when copying just the block you
   can see is "the carousel," which is exactly what happened on a real
   page. Nesting it removes that trap entirely.) .csa-carousel-row is
   the flex row of exactly those three children (arrow, viewport,
   arrow) — the arrows take up their own real space in that row rather
   than sitting absolutely positioned on top of the viewport, so they
   can never overlap the cards no matter how wide the arrows or how
   narrow the cards are. .csa-carousel-viewport is the flex-shrink
   target (flex:1; min-width:0 — the min-width is what lets a flex item
   shrink below its content's natural width, which a viewport that
   needs to clip a wider track always wants).
   .csa-dragging is a hook this CSS already reads (switches the track's
   cursor to grabbing) — toggle it while a drag is in progress, if your
   implementation supports dragging.

   .csa-carousel-dots (optional — omit the element entirely if you
   don't need it, .csa-carousel's own gap only ever applies between
   children that exist) is a row of progress dots below
   .csa-carousel-row — .csa-carousel-dot is the base state,
   .csa-carousel-dot-active the current page. Which dot is active is
   real app state (it tracks scroll/drag position, same as the
   drag/arrow behavior above) — this library only styles the two
   states, your implementation decides how many dots to render and
   which one gets the active class, and updates it on
   scroll/drag/arrow-click.

   No edge fade: cards are narrow enough relative to the viewport that
   the next card visibly peeks in from the side, which reads as "more
   to scroll" on its own — a prior version faded the track's edges to
   its background color instead, which meant re-declaring that
   background as a custom property (--csa-carousel-bg) every time a
   carousel sat on something other than plain white. Peeking cards need
   no such match-the-background bookkeeping.

   (The showcase's carousel demo runs the full drag/momentum/infinite-
   loop version, including live dot updates, as a demo-only script
   purely so the page is interactive to browse — see src/docs/index.html.
   That script is not part of the library and isn't bundled by build.mjs.)
   ========================================================= */
.csa-carousel { display: flex; flex-direction: column; gap: 24px; }
.csa-carousel-row { display: flex; align-items: center; gap: 16px; }
.csa-carousel-viewport { flex: 1; min-width: 0; overflow: hidden; }

.csa-carousel-track { display: flex; gap: 20px; cursor: grab; will-change: transform; touch-action: pan-y; }
.csa-carousel-track.csa-dragging { cursor: grabbing; }

/* 328px is a fixed width, not a percentage of the viewport — matching
   a real reference size exactly (rather than "whatever fraction looks
   similar") is the point here, so it doesn't drift if the viewport's
   own width ever changes for unrelated reasons. */
.csa-carousel-card {
  flex: 0 0 328px;
  background: #fff; border: 1px solid var(--n50); border-radius: 14px;
  overflow: hidden; box-shadow: var(--shadow-sm);
  display: flex; flex-direction: column;
}
.csa-carousel-card img { width: 100%; height: auto; display: block; pointer-events: none; -webkit-user-drag: none; }
.csa-carousel-card .csa-carousel-card-body { padding: 22px; display: flex; flex-direction: column; flex: 1; }
.csa-carousel-card h3 { font-size: 17px; margin-bottom: 8px; }
.csa-carousel-card p { font-size: var(--text-small); color: var(--n400); flex: 1; } /* card copy: small-text size; n300 failed AA */
/* .csa-go is a shared utility now — see cards.css */

/* 44px (down from an earlier 52px) and a stronger two-layer shadow —
   at 52px with only --shadow-sm's single 2px blur, the arrows read as
   flush against the neighboring cards instead of floating above them,
   which at a glance looked like it was cutting into the cards' edges.
   Neither --shadow-sm nor --shadow (tokens/shadows.css) matches this
   exact treatment, so it's written out directly here rather than
   forcing a token that's subtly wrong. */
.csa-carousel-arrow {
  flex-shrink: 0;
  width: 44px; height: 44px; border-radius: 50%;
  background: #fff; border: 1px solid var(--n50);
  box-shadow: 0 4px 12px rgba(16,42,85,.08), 0 1px 2px rgba(16,42,85,.04);
  display: flex; align-items: center; justify-content: center;
  cursor: pointer;
}
.csa-carousel-arrow svg { width: 20px; height: 20px; color: var(--b800); }

/* Prev/next chevrons — buttons need no inner markup, just aria-label */
.csa-carousel-arrow-prev::before,
.csa-carousel-arrow-next::before {
  content: '';
  display: block;
  width: 9px;
  height: 9px;
  border-top: 2px solid var(--b800);
  border-right: 2px solid var(--b800);
}
/* translate(-1.5px, 1.5px) shifts the chevron ~2px toward its tip in
   screen space, compensating for the open end's lack of visual weight.
   The same translate works for both rotations by rotational symmetry. */
.csa-carousel-arrow-next::before { transform: rotate(45deg)   translate(-1.5px, 1.5px); }
.csa-carousel-arrow-prev::before { transform: rotate(-135deg) translate(-1.5px, 1.5px); }

/* Active dot is a wider pill, not just a color swap — reads clearly as
   "current" even for someone not distinguishing the orange from the
   surrounding neutral at a glance. appearance/border are reset
   explicitly since these are real <button>s and base/reset.css only
   resets font/color on form controls, not appearance/border chrome
   (font: inherit below is now redundant with that global reset, kept
   anyway so this rule stays self-contained if it's ever copied out). */
.csa-carousel-dots { display: flex; justify-content: center; align-items: center; gap: 7px; }
.csa-carousel-dot {
  appearance: none; border: none; font: inherit;
  width: 8px; height: 8px; padding: 0; border-radius: 99px;
  background: var(--n75); cursor: pointer; transition: .18s;
}
.csa-carousel-dot-active { width: 22px; background: var(--o700); }

@media (max-width: 900px) { .csa-carousel-card { flex: 0 0 70%; max-width: 328px; } }
@media (max-width: 560px) {
  .csa-carousel-row { gap: 10px; }
  .csa-carousel-arrow { width: 40px; height: 40px; }
  .csa-carousel-arrow svg { width: 18px; height: 18px; }
  .csa-carousel-card { flex: 0 0 88%; }
}
/* =========================================================
   CSA Component Library — Tabs
   Designed fresh: no tabs UI exists in any of the 4 reference pages
   (checked — no tablist/tab-panel pattern anywhere), so this follows
   the system's existing conventions rather than extracting real markup:
     - underline-indicator style, echoing .csa-rule's accent-bar motif
     - active-state color follows the same data-surface-aware pattern
       as .csa-btn-primary: --b500 accent on light/gradient, --o500 on dark
     - inactive tab color matches --n300 (.csa-muted), same as body copy

   BEHAVIOR SPEC — not implemented here. This library defines the look;
   each consuming app wires up the interaction in whatever fits its own
   stack (vanilla JS, React, Rails/Stimulus, etc.). Implement the
   WAI-ARIA "tabs" pattern (https://www.w3.org/WAI/ARIA/apg/patterns/tabs/):
     - Click a tab: set its aria-selected="true" (all siblings "false"),
       add/remove .csa-tab-active to match, set tabindex="0" on it and
       "-1" on every other tab (roving tabindex), and show/hide the
       matching panel via .csa-tabpanel-active + the `hidden` attribute.
     - ArrowRight/ArrowLeft on a focused tab: move focus AND activate
       the next/previous tab (wrapping at the ends).
     - Home/End on a focused tab: jump to the first/last tab.
   Markup contract per tab group:
     <div class="csa-tabs">
       <div class="csa-tablist" role="tablist" aria-label="...">
         <button class="csa-tab csa-tab-active" role="tab" id="tab-a"
                 aria-selected="true" aria-controls="panel-a">Label A</button>
         <button class="csa-tab" role="tab" id="tab-b"
                 aria-selected="false" aria-controls="panel-b" tabindex="-1">Label B</button>
       </div>
       <div class="csa-tabpanel csa-tabpanel-active" role="tabpanel" id="panel-a" aria-labelledby="tab-a">...</div>
       <div class="csa-tabpanel" role="tabpanel" id="panel-b" aria-labelledby="tab-b" hidden>...</div>
     </div>
   (The showcase's tab demos run a small demo-only script purely so the
   page is interactive to browse — see src/docs/index.html. That script
   is not part of the library and isn't bundled by build.mjs.)
   ========================================================= */
.csa-tabs { width: 100%; }

.csa-tablist {
  display: flex; gap: 6px; flex-wrap: wrap;
  border-bottom: 1px solid var(--n50);
  margin-bottom: 28px;
}
[data-surface="dark"] .csa-tablist { border-bottom-color: var(--b600); }
/* Black-surface parity — added after an audit found dark had full
   support here but black had none at all: the light-surface defaults
   (n300 text, n50 underline; b800 active-tab text) were falling
   through unopposed, and b800 in particular is a dark navy that reads
   as nearly invisible against the near-black page background — the
   active tab's own label would all but disappear. Border/text values
   mirror the black-surface choices toggles.css already established for
   this same "row of interactive labels" shape (n700 border, n300
   resting text) rather than inventing new ones. */
[data-surface="black"] .csa-tablist { border-bottom-color: var(--n700); }

.csa-tab {
  position: relative; z-index: 0;
  appearance: none; background: transparent; border: none; cursor: pointer;
  font-family: inherit; font-size: var(--text-small); font-weight: 600;
  color: var(--n300); padding: 12px 16px 14px;
  border-bottom: 2px solid transparent;
  transition: .18s;
}
[data-surface="dark"] .csa-tab { color: var(--n100); }
[data-surface="black"] .csa-tab { color: var(--n300); }

/* Hover fill is a separate ::before, not the tab's own background —
   inset stops short of the tab's bottom edge (where .csa-tablist's
   own border-bottom, or the active tab's indicator, lives) so the
   rounded highlight box floats just above that line instead of
   touching it. position:relative + z-index:0 on .csa-tab (above)
   gives this its own local stacking context, so z-index:-1 here only
   drops it behind the tab's own label text, not behind the page. */
.csa-tab::before {
  content: ''; position: absolute; z-index: -1;
  inset: 4px 2px 8px; border-radius: 8px;
  background: transparent; transition: background-color .18s;
}
/* n50, not n25 — n25 would be invisible against an n25 page (the same
   escalation problem tags.css/toggles.css already document), and n50
   still reads clearly on plain white too. At 50% opacity (not flat)
   so it softens rather than fully overriding whatever's underneath —
   rgba, since a CSS variable can't be given partial opacity directly;
   #DDE6EF (--n50) at 50%. */
.csa-tab:hover { color: var(--b500); }
.csa-tab:hover::before { background: rgba(221,230,239,.5); }
/* One ramp step lighter than each surface's own page background
   (surfaces.css: b700/black) — the same "lift, don't sink" logic
   filters.css's row hover uses; a darker fill here would read as a
   hole in the tab row instead of a highlight. */
[data-surface="dark"] .csa-tab:hover { color: #fff; }
[data-surface="dark"] .csa-tab:hover::before { background: var(--b600); }
[data-surface="black"] .csa-tab:hover { color: #fff; }
[data-surface="black"] .csa-tab:hover::before { background: var(--n700); }

.csa-tab-active,
.csa-tab-active:hover {
  color: var(--b800); border-bottom-color: var(--b500);
}
[data-surface="dark"] .csa-tab-active,
[data-surface="dark"] .csa-tab-active:hover {
  color: #fff; border-bottom-color: var(--o500);
}
[data-surface="black"] .csa-tab-active,
[data-surface="black"] .csa-tab-active:hover {
  color: #fff; border-bottom-color: var(--o500);
}

.csa-tab:focus-visible {
  outline: 2px solid var(--b300); outline-offset: 2px;
}

.csa-tabpanel { display: none; }
.csa-tabpanel-active { display: block; }
/* =========================================================
   CSA Component Library — Toggle Switches
   Two switch patterns extracted from RiskRubric.ai (an existing CSA
   property): an icon toggle (the site's own dark-mode switch, top nav)
   and a segmented toggle (its "10 highest scoring" / "10 lowest
   scoring" filter, Browse Models section). The segmented toggle's
   colors are copied exactly as they render there. The icon toggle's
   resting-state track deliberately isn't: the reference's own value
   (a semi-transparent n300 wash) is now flat tokens instead — n25 on
   white, n50 on an n25 page (data-tone="neutral", same escalation idea as
   .csa-toggle-segmented and tags.css), b800 on the navy dark surface —
   so it stays visually consistent with the rest of this library's
   "one ramp step off the page" convention for a resting control
   background, the same idea .csa-card uses on the dark surface via the
   b800/b600 pairing reused here. The "on" state's orange stays exactly
   as measured on both.

   .csa-toggle-icon-confirm is a second icon-toggle variant, on the
   same button (<button class="csa-toggle-icon csa-toggle-icon-confirm">):
   X / checkmark instead of sun/moon, green (--g200 fill, --g100 border)
   instead of orange for the "on" state — a generic enable/disable or
   confirm/cancel toggle rather than one specifically about dark mode.

   BEHAVIOR SPEC — not implemented here, same minimal contract as
   .csa-tag-filter (tags.css): the entire visual state of both
   components is driven by one attribute the app flips on click, no
   other JS logic needed.
     - .csa-toggle-icon: a <button aria-pressed="true|false">. Flip
       aria-pressed on click; every other visual change (track color,
       which icon shows) is plain CSS keyed off that attribute alone.
     - .csa-toggle-segmented: a <div data-selected="a|b"> wrapping two
       <button class="csa-toggle-segmented-option">. Set data-selected
       to "a" or "b" on click depending on which option was clicked;
       the sliding highlight's position and each option's text color
       are plain CSS keyed off that attribute — first option is "a",
       second is "b".

   Markup contract:
     <button type="button" class="csa-toggle-icon" aria-pressed="false" aria-label="Toggle dark mode">
       <span class="csa-toggle-icon-track">
         <span class="csa-toggle-icon-thumb">
           <svg class="csa-toggle-icon-a">...(shown when aria-pressed="false")...</svg>
           <svg class="csa-toggle-icon-b">...(shown when aria-pressed="true")...</svg>
         </span>
       </span>
     </button>

     <div class="csa-toggle-segmented" data-selected="a">
       <div class="csa-toggle-segmented-highlight"></div>
       <button type="button" class="csa-toggle-segmented-option">Option A</button>
       <button type="button" class="csa-toggle-segmented-option">Option B</button>
     </div>
   .csa-toggle-segmented is a fixed 320px wide, matching the reference
   exactly — the sliding highlight's shift is a literal pixel value
   derived from that width (not a percentage, since translateX()'s own
   percentage basis is the highlight's OWN box, not the track), so
   widening this component means recalculating that one value too.
   ========================================================= */

/* ---- Icon toggle ---- */
.csa-toggle-icon { appearance: none; border: none; background: none; padding: 0; cursor: pointer; width: 58px; height: 30px; flex-shrink: 0; }
.csa-toggle-icon-track {
  display: block; position: relative; width: 100%; height: 100%;
  border-radius: 999px; border: 1px solid rgba(221,230,239,.5); /* --n50 (#DDE6EF) at 50% */
  background: var(--n25);
  transition: background .22s, border-color .22s;
}
/* Same n25→n50 escalation .csa-toggle-segmented uses right below, and
   the same reasoning tags.css established: on a page whose background
   is itself n25, this track needs to step up a ramp level to still
   read as its own distinct shape. Border steps up alongside it (n50→n75,
   still at 50% opacity) for the same reason. */
[data-tone="neutral"] .csa-toggle-icon-track { background: var(--n50); border-color: rgba(195,209,222,.35); /* --n75 (#C3D1DE) at 35% */ }
/* Same step-up inside a card on a light surface (the card is itself n25) —
   see tags.css's "Inside a card on a light surface" block for the
   :where() context and why it carries no specificity. */
:where(:is([data-surface="light"] .csa-card, [data-surface="light"] .csa-card-media, [data-surface="light"].csa-card, [data-surface="light"].csa-card-media):not([data-surface="gradient"], [data-surface="gradient"] *, [data-tone="neutral"], [data-tone="neutral"] *)) .csa-toggle-icon-track { background: var(--n50); border-color: rgba(195,209,222,.35); }
/* Shadow color matches .csa-toggle-segmented-highlight's own per-surface
   shadow below (same rgba values, just this component's own long-
   standing 0 2px 6px offset/blur rather than copying that one's 0 1px
   2px too) — the thumb and the highlight are the same "raised control"
   role in their respective components, so their shadows read as cast
   by the same light source. */
.csa-toggle-icon-thumb {
  position: absolute; top: 2px; left: 2px;
  width: 24px; height: 24px; border-radius: 50%;
  background: #fff; box-shadow: 0 2px 6px rgba(16,42,85,.12);
  display: flex; align-items: center; justify-content: center;
  transition: transform .22s;
}
.csa-toggle-icon-thumb svg { width: 16px; height: 16px; }
/* 28px = track content width (58px outer − 2×1px border = 56px) minus
   the thumb's own 24px minus the same 2px inset the resting position
   uses on the left, so the thumb lands with an equal 2px gap on the
   right instead of overshooting the track. The reference this was
   pulled from doesn't actually slide its thumb — only the track color
   and icon change there — but a toggle whose thumb stays put on click
   reads as broken, so this deviates from the reference on purpose. */
.csa-toggle-icon[aria-pressed="true"] .csa-toggle-icon-thumb { transform: translateX(28px); }
/* "a" (e.g. sun) is the resting default; "b" (e.g. moon) only shows
   once aria-pressed flips true — both are always in the DOM, this is
   a pure visibility swap alongside the thumb's slide above. */
.csa-toggle-icon-b { display: none; }
.csa-toggle-icon[aria-pressed="true"] .csa-toggle-icon-a { display: none; }
.csa-toggle-icon[aria-pressed="true"] .csa-toggle-icon-b { display: block; }
/* "On" stays this same orange regardless of surface — it's the
   universal accent for "active," not a surface-tinted color, so it's
   written unscoped rather than repeated per surface below. Unlike
   every resting-state border above and below, this one stays fully
   opaque — a 50%-faded border on the one state that's meant to read
   as a solid, confident "active" would undercut the point of it. The
   pressed thumb also stays white regardless of surface (see the dark/
   black thumb overrides below, which this outranks on specificity) —
   only the resting-state thumb picks up a surface color. */
.csa-toggle-icon[aria-pressed="true"] .csa-toggle-icon-track { background: var(--o700); border-color: var(--o600); }
.csa-toggle-icon[aria-pressed="true"] .csa-toggle-icon-thumb { background: #fff; }

[data-surface="dark"] .csa-toggle-icon-track { background: var(--b800); border-color: rgba(0,62,119,.5); /* --b600 (#003E77) at 50% */ }
/* Deep tone: band is already b800, so the track steps up to b700 */
[data-surface="dark"][data-tone="deep"] .csa-toggle-icon-track { background: var(--b700); }
[data-surface="black"] .csa-toggle-icon-track { background: var(--n800); border-color: rgba(40,44,56,.5); /* --n700 (#282C38) at 50% */ }
/* Resting-state thumb matches its surface's segmented-toggle highlight
   color instead of staying white — the thumb and the highlight are the
   same "raised control" role in their respective components, so this
   keeps the two toggle types looking like one family on dark/black. */
[data-surface="dark"] .csa-toggle-icon-thumb { background: var(--b600); box-shadow: 0 2px 6px rgba(0,0,0,.5); }
[data-surface="black"] .csa-toggle-icon-thumb { background: var(--black); box-shadow: 0 2px 6px rgba(40,44,56,.75); /* --n700 (#282C38) at 75% */ }
/* The sun glyph's own fill (set inline on the reference's <g>, carried
   over as-is elsewhere) is --n500 — a dark gray that reads fine on a
   white/light thumb but nearly disappears on the navy/black thumb
   these two surfaces now use. A plain descendant selector on the <g>
   beats that inline presentation attribute (CSS always outranks SVG
   presentation attributes, regardless of selector specificity), no
   !important needed. */
[data-surface="dark"] .csa-toggle-icon-a g,
[data-surface="black"] .csa-toggle-icon-a g { fill: #fff; }

/* ---- Icon toggle, confirm variant (X / checkmark) ----
   Same track/thumb mechanics as the sun/moon toggle above — this is
   just a second glyph+accent pairing (green instead of orange) for a
   generic enable/disable or confirm/cancel toggle, rather than one
   specifically about dark mode. Resting-state track/border/thumb colors
   are entirely shared with .csa-toggle-icon above, since nothing here
   needs a different "off" look — only the "on" accent and the resting
   X's own stroke color differ per surface (set inline as --n100 by
   default — white/neutral both use it, so no extra selector needed for
   those two — with dark/black overridden below; a plain path, not the
   sun's <g>-wrapped fill, so it needs its own selector rather than
   reusing that one). */
.csa-toggle-icon.csa-toggle-icon-confirm[aria-pressed="true"] .csa-toggle-icon-track { background: var(--g200); border-color: var(--g100); }
[data-surface="dark"] .csa-toggle-icon-confirm .csa-toggle-icon-a path { stroke: var(--b400); }
[data-surface="black"] .csa-toggle-icon-confirm .csa-toggle-icon-a path { stroke: var(--n400); }

/* ---- Segmented toggle ---- */
.csa-toggle-segmented {
  position: relative; display: flex; width: 320px; gap: 4px; padding: 4px;
  background: var(--n25); border: 1px solid rgba(221,230,239,.5); /* --n50 (#DDE6EF) at 50%, matching .csa-toggle-icon-track's own resting border */
  border-radius: 999px;
}
/* Same escalation idea tags.css already uses for .csa-tag-neutral: on a
   page whose own background is literally n25 (data-tone="neutral" — see that
   file's comment on the convention), this track's default n25 would
   blend straight into the page with no visible edge, so it bumps one
   ramp step to n50 there instead. Border steps up alongside it (n50→n75,
   still at 50% opacity), same pairing .csa-toggle-icon-track uses. */
[data-tone="neutral"] .csa-toggle-segmented { background: var(--n50); border-color: rgba(195,209,222,.35); /* --n75 (#C3D1DE) at 35% */ }
:where(:is([data-surface="light"] .csa-card, [data-surface="light"] .csa-card-media, [data-surface="light"].csa-card, [data-surface="light"].csa-card-media):not([data-surface="gradient"], [data-surface="gradient"] *, [data-tone="neutral"], [data-tone="neutral"] *)) .csa-toggle-segmented { background: var(--n50); border-color: rgba(195,209,222,.35); } /* card step-up, see .csa-toggle-icon-track above */
.csa-toggle-segmented-highlight {
  position: absolute; top: 4px; left: 4px;
  width: calc(50% - 6px); height: calc(100% - 8px);
  border-radius: 999px; background: #fff;
  box-shadow: 0 1px 2px rgba(16,42,85,.12);
  transition: transform .25s, background .25s;
}
.csa-toggle-segmented[data-selected="b"] .csa-toggle-segmented-highlight { transform: translateX(158px); }
.csa-toggle-segmented-option {
  position: relative; z-index: 1; flex: 1;
  appearance: none; border: none; background: none; cursor: pointer;
  font-family: var(--font); font-size: 13px; font-weight: 700;
  padding: 9px 12px; border-radius: 999px;
  color: var(--n300); transition: color .18s;
}
.csa-toggle-segmented[data-selected="a"] .csa-toggle-segmented-option:first-of-type,
.csa-toggle-segmented[data-selected="b"] .csa-toggle-segmented-option:last-of-type { color: var(--b700); }

[data-surface="dark"] .csa-toggle-segmented { background: var(--b800); border-color: rgba(0,62,119,.5); /* --b600 (#003E77) at 50%, matching .csa-toggle-icon-track's dark border */ }
[data-surface="dark"][data-tone="deep"] .csa-toggle-segmented { background: var(--b700); }
[data-surface="dark"] .csa-toggle-segmented-highlight { background: var(--b600); box-shadow: 0 1px 2px rgba(0,0,0,.5); }
[data-surface="dark"] .csa-toggle-segmented-option { color: var(--n100); }
[data-surface="dark"] .csa-toggle-segmented[data-selected="a"] .csa-toggle-segmented-option:first-of-type,
[data-surface="dark"] .csa-toggle-segmented[data-selected="b"] .csa-toggle-segmented-option:last-of-type { color: #fff; }

[data-surface="black"] .csa-toggle-segmented { background: var(--n800); border-color: rgba(40,44,56,.5); /* --n700 (#282C38) at 50%, matching .csa-toggle-icon-track's black border */ }
[data-surface="black"] .csa-toggle-segmented-highlight { background: var(--black); box-shadow: 0 1px 2px rgba(40,44,56,.75); }
[data-surface="black"] .csa-toggle-segmented-option { color: var(--n300); }
[data-surface="black"] .csa-toggle-segmented[data-selected="a"] .csa-toggle-segmented-option:first-of-type,
[data-surface="black"] .csa-toggle-segmented[data-selected="b"] .csa-toggle-segmented-option:last-of-type { color: #fff; }
/* =========================================================
   CSA Component Library — Tables
   A generic data table: n50 row dividers, a b500 header row, and
   alternating white/n25 body rows for scannability across a wide row
   count. Styling technique (wrapper border + row-only dividers, no
   per-cell borders) matches the rounded bordered list RiskRubric.ai
   uses under "How it works" — see the comment on .csa-table-wrap.

   Markup contract:
     <div class="csa-table-wrap">
       <table class="csa-table">
         <thead>
           <tr><th>...</th>...</tr>
         </thead>
         <tbody>
           <tr><td>...</td>...</tr>
           ...
         </tbody>
       </table>
     </div>
   .csa-table-wrap carries the n50 border, border-radius, AND
   overflow:hidden together on purpose — this is what avoids a subtle
   but real bug an earlier version of this file had: giving every cell
   its own full 4-side border (th/td { border: 1px solid ... }) meant
   the outermost cells' own straight corners fought the wrapper's
   rounded clip, since overflow:hidden clips whatever paints past the
   rounded edge, including a cell's own square corner — which reads as
   the corner looking "cropped" rather than cleanly rounded. Cells here
   only ever get border-bottom (a row divider, not a cell border, and
   never on the very last row), and there's no vertical divider between
   columns at all — nothing but the wrapper's own border ever touches
   the rounded edge, so there's nothing left for the clip to cut into.
   ========================================================= */
.csa-table-wrap { border: 1px solid var(--n50); border-radius: 12px; overflow: hidden; }
.csa-table { width: 100%; border-collapse: collapse; }
.csa-table th, .csa-table td {
  padding: 12px 16px;
  text-align: left;
  font-size: 14px;
  border-bottom: 1px solid var(--n50);
}
.csa-table tbody tr:last-child td { border-bottom: none; }
.csa-table thead tr { background: var(--b500); }
.csa-table thead th { color: #fff; font-weight: 700; }
.csa-table tbody td { color: var(--n500); }
.csa-table tbody tr:nth-child(odd) { background: #fff; }
.csa-table tbody tr:nth-child(even) { background: var(--n25); }

/* Dark/black surface support — added after an audit found this was the
   only component in the library with none at all. Without it, td/th
   inherited the page's own dark/black-surface text color (white) with
   nothing inside the table to override it, landing white text on the
   table's own white/n25 row backgrounds — invisible.
   Row alternation mirrors light mode's own logic (odd matches the
   page's own background so it "blends in"; even is one ramp step off
   so it "pops") rather than inventing new tones: dark's page bg is
   b700, its one-step-elevated tone is b800 — the same pair .csa-card
   already uses on dark (surfaces.css); black's page bg is the near-
   black --black token, its elevated tone is n800 — same pair .csa-card
   uses on black. The wrapper border and row dividers reuse those same
   b600/n700 tokens .csa-card's own dark/black borders use, so a table
   reads as the same "elevated surface" identity as every other
   component. The b500 header is left untouched on purpose — a solid
   mid-blue fill with white text already reads fine against light,
   dark, or black alike, so it needs no override. */
[data-surface="dark"] .csa-table-wrap { border-color: var(--b600); }
[data-surface="black"] .csa-table-wrap { border-color: var(--n700); }
[data-surface="dark"] .csa-table th, [data-surface="dark"] .csa-table td { border-bottom-color: var(--b600); }
[data-surface="black"] .csa-table th, [data-surface="black"] .csa-table td { border-bottom-color: var(--n700); }
[data-surface="dark"] .csa-table tbody td, [data-surface="black"] .csa-table tbody td { color: var(--n75); }
[data-surface="dark"] .csa-table tbody tr:nth-child(odd) { background: var(--b700); }
[data-surface="dark"] .csa-table tbody tr:nth-child(even) { background: var(--b800); }
[data-surface="black"] .csa-table tbody tr:nth-child(odd) { background: var(--black); }
[data-surface="black"] .csa-table tbody tr:nth-child(even) { background: var(--n800); }
/* =========================================================
   CSA Component Library — Lists
   Bulleted (or numbered) body-copy lists. Opt-in via a class rather
   than a bare ul/ol selector: reset.css zeroes list padding globally,
   and plenty of structural markup (navs, menus, the host site's own
   widgets) is built on <ul> and must stay unstyled.

   Markup contract:
     <ul class="csa-list">
       <li>Item one</li>
       <li>Item two</li>
     </ul>

   Works on <ol> too — ::marker colors the number the same way.
   Marker color follows the kicker's surface rule (o700 on light/
   gradient, o500 on dark/black) so bullets read as the same orange
   accent; item text inherits the surface's body color like <p>.
   ========================================================= */

.csa-list {
  padding-left: .5em;
  line-height: 1.7;
  margin: 0;
}
.csa-list > li + li { margin-top: 6px; }
.csa-list > li::marker { color: var(--o700); }

[data-surface="light"] .csa-list, [data-surface="gradient"] .csa-list { color: var(--n500); }
[data-surface="dark"] .csa-list, [data-surface="black"] .csa-list { color: var(--n75); }
[data-surface="dark"] .csa-list > li::marker,
[data-surface="black"] .csa-list > li::marker { color: var(--o500); }

/* Nested lists keep the indent and accent; they just tighten up. */
.csa-list .csa-list { margin-top: 6px; }
/* =========================================================
   CSA Component Library — Sep
   A flex row that automatically places a 1 px vertical bar
   between its direct children — no extra markup needed.
   Uses the same border color as .csa-card on every surface.

   Markup contract:
     <div class="csa-sep">
       <span>Item one</span>
       <span>Item two</span>
       <span>Item three</span>
     </div>

   The 12 px padding on each child creates the visual gap on
   either side of the bar. Override --sep-gap to adjust.
   ========================================================= */

.csa-sep {
  --sep-gap: 24px;
  display: flex;
}

.csa-sep > * {
  padding: 0 var(--sep-gap);
}
.csa-sep > *:first-child { padding-left: 0; }
.csa-sep > *:last-child  { padding-right: 0; }

.csa-sep > * + * {
  border-left: 1px solid var(--n75);
}

[data-surface="dark"]  .csa-sep > * + * { border-left-color: var(--b600); }
[data-surface="black"] .csa-sep > * + * { border-left-color: var(--n700); }

@media (max-width: 640px) {
  .csa-sep { flex-direction: column; }
  .csa-sep > * { padding: var(--sep-gap) 0; }
  .csa-sep > *:first-child { padding-top: 0; }
  .csa-sep > *:last-child  { padding-bottom: 0; }
  .csa-sep > * + * { border-left: none; border-top: 1px solid var(--n75); }
  [data-surface="dark"]  .csa-sep > * + * { border-top-color: var(--b600); }
  [data-surface="black"] .csa-sep > * + * { border-top-color: var(--n700); }
}
/* =========================================================
   CSA Component Library — Accordion
   Extracted from the CSA_September_2026_Membership_Drive page's
   "Questions we expect / The honest answers." FAQ section, tokens
   already matching this library's own (n50/n500/b500/b800) with no
   translation needed.

   Built on native <details>/<summary> — unlike every other interactive
   component in this library (toggles, tags, tabs...), there is no
   BEHAVIOR SPEC to hand off here: opening/closing, single-vs-multiple-
   open, and keyboard support all come free from the browser's own
   <details> implementation. Zero JS, ever, from any consuming app.

   Markup contract:
     <div class="csa-accordion">
       <details class="csa-accordion-item">
         <summary class="csa-accordion-trigger">
           Question text
           <span class="csa-accordion-icon"></span>
         </summary>
         <div class="csa-accordion-panel">
           <p>Answer text.</p>
         </div>
       </details>
       <!-- repeat .csa-accordion-item -->
     </div>
   Add the `open` attribute to any .csa-accordion-item that should start
   expanded (the source page's first FAQ item ships this way).

   Smooth open/close, CSS only, via max-height (not grid-template-rows):
   an earlier version of this file animated grid-template-rows 0fr→1fr
   (the same trick .csa-card-reveal-more uses in cards.css), which in
   principle animates to the content's real height automatically,
   whatever that is. In practice it proved unreliable specifically on
   <details> across real testing — confirmed inconsistent even after
   fixing a genuine padding-sync bug along the way, and confirmed NOT
   simply a caching or rapid-click artifact. Replaced with the older,
   far more broadly battle-tested max-height technique instead of
   continuing to chase the exact cause.

   The real trade-off, worth knowing before reusing this on a page with
   unusually long answers: max-height needs a fixed ceiling picked in
   advance (--csa-accordion-max-height, default 900px below) rather than
   sizing to the real content automatically. Two consequences:
     - For any answer shorter than the ceiling (the normal case), the
       animation's timing is slightly front-loaded — CSS keeps easing
       toward the full ceiling value even after the real content stops
       needing space, so the last stretch of the transition's nominal
       duration is visually a no-op. Picking a ceiling reasonably close
       to typical content length (900px comfortably fits several
       paragraphs) keeps this minor.
     - For an answer TALLER than the ceiling, content gets clipped, not
       just mistimed — a real failure mode, not cosmetic. Override the
       custom property per item for an outlier-long answer:
         <details class="csa-accordion-item" style="--csa-accordion-max-height: 1400px">
       rather than bumping the shared default for everyone.
   padding-bottom animates alongside max-height (same duration/easing)
   for the same reason it needed fixing in the old version: an
   unconditional or unsynced padding change reads as a jump/jank at
   either end of the transition otherwise.

   The +/– indicator is drawn as two CSS bars rather than a swapped
   glyph (a plain "+" → "–" content swap can't transition at all —
   `content` isn't animatable): one bar stays horizontal permanently,
   the other starts rotated 90° (vertical, so together they read as a
   plus) and rotates flat to 0° when [open] (landing exactly on top of
   the first bar, reading as a minus) — a smooth, animatable rotation
   doing the same job the instant content-swap used to.
   ========================================================= */
.csa-accordion { border-top: 1px solid var(--n50); }
.csa-accordion-item { border-bottom: 1px solid var(--n50); }

.csa-accordion-trigger {
  display: flex; align-items: center; justify-content: space-between; gap: 20px;
  cursor: pointer; list-style: none;
  padding: 20px 0; font-weight: 700; font-size: 18px; letter-spacing: -.01em;
  color: var(--b800);
}
.csa-accordion-trigger::-webkit-details-marker { display: none; }

.csa-accordion-icon { position: relative; width: 16px; height: 16px; flex-shrink: 0; }
.csa-accordion-icon::before, .csa-accordion-icon::after {
  content: ''; position: absolute; inset: 0; margin: auto;
  width: 16px; height: 3px; border-radius: 1.5px;
  background: var(--b500);
  transition: transform .3s ease;
}
.csa-accordion-icon::after { transform: rotate(90deg); }
.csa-accordion-item[open] .csa-accordion-icon::after { transform: rotate(0deg); }

.csa-accordion-panel {
  overflow: hidden;
  max-height: 0; padding-bottom: 0;
  transition: max-height .4s ease, padding-bottom .4s ease;
}
.csa-accordion-item[open] .csa-accordion-panel {
  max-height: var(--csa-accordion-max-height, 900px);
  padding-bottom: 22px;
}
.csa-accordion-panel p { font-size: 16px; line-height: 1.65; color: var(--n500); max-width: 74ch; }
.csa-accordion-panel p + p { margin-top: 10px; }
/* =========================================================
   CSA Component Library — Filters
   A filter sidebar — extracted from RiskRubric.ai's search page
   (Grade / Confidence / Provider / Platform), with a "Clear all" reset
   above them. A bare layout shell, the same way .csa-actions/.csa-
   badges/.csa-tags are: no background or border of its own, so it
   drops into a plain page column or a .csa-card equally well —
   checkbox/radio styling itself lives in forms.css.

   Two group types:
     1. A checkbox/radio list (Confidence, Provider, Platform) — each
        row is .csa-filter-row, a label wrapping a .csa-checkbox or
        .csa-radio, with its own hover background (RiskRubric's own
        filter rows highlight on hover; a bare .csa-checkbox/.csa-radio
        elsewhere in the library doesn't, since that hover treatment is
        specific to a filter LIST row, not the control itself). Rows
        sit flush against each other (no gap) — only each row's own
        padding separates them, matching the reference exactly.
     2. A Grade quick-filter (Grade) — five single-letter toggle
        buttons in a row, RiskRubric's own compact grade filter, a
        fixed 38x38px per the reference (not a stretch-to-fill flex
        item — a filter control this small reads better at a fixed
        size than one that grows with a wider sidebar). aria-pressed
        marks the selected letter(s); selected state is one exact
        color per letter — confirmed directly against the live site
        (A g200, B b400, C p300, D o300, F o700 — F is a darker step of
        the SAME orange ramp as D, not a red), rendered as that color
        at 20% opacity for the fill plus the solid color for border and
        text. Confirmed identical in both of the reference's own
        light/dark modes, so this one rule applies unscoped by surface
        — no separate dark/black variant needed for the selected state.

   Markup contract:
     <div class="csa-filters">
       <div class="csa-filters-head">
         <span class="csa-filters-title">Filters</span>
         <a href="#" class="csa-filters-clear">Clear all</a>
       </div>

       <div class="csa-filter-group">
         <div class="csa-filter-group-title">Grade</div>
         <div class="csa-filter-grade">
           <button type="button" class="csa-filter-grade-btn" data-grade="a" aria-pressed="false">A</button>
           ...b/c/d/f...
         </div>
       </div>

       <div class="csa-filter-group">
         <div class="csa-filter-group-title">Confidence</div>
         <label class="csa-filter-row csa-checkbox">
           <input type="checkbox">
           <span class="csa-checkbox-box"><svg viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 13l4 4L19 7"/></svg></span>
           <span>1 scanner</span>
         </label>
         ...
       </div>
     </div>

   BEHAVIOR SPEC — not implemented here: .csa-filter-grade-btn's
   aria-pressed is flipped by the app on click, same minimal contract
   as .csa-tag-filter (tags.css) — every visual change is plain CSS
   keyed off that attribute alone.
   ========================================================= */
.csa-filters { display: flex; flex-direction: column; gap: 24px; }
.csa-filters-head {
  display: flex; align-items: center; justify-content: space-between;
  padding-bottom: 16px; border-bottom: 1px solid var(--n50);
}
.csa-filters-title {
  font-size: 22px; font-weight: 800; letter-spacing: -.02em; color: var(--b800);
}
.csa-filters-clear { font-size: var(--text-caption); font-weight: 700; color: var(--n400); text-decoration: underline; transition: color .15s; }
.csa-filters-clear:hover { color: var(--n500); }

/* No gap here — .csa-filter-row supplies its own 5px top/bottom
   padding as the entire visual space between rows, matching the
   reference exactly. A group with no rows (just the Grade buttons)
   still gets a clean 8px under its title from the title's own
   margin-bottom below. */
.csa-filter-group { display: flex; flex-direction: column; }
.csa-filter-group-title { font-size: 12px; font-weight: 700; color: var(--b700); margin-bottom: 8px; }

/* Row hover — the whole row highlights, not just the checkbox/radio
   control, so the entire label reads as the clickable hit target
   RiskRubric's own filter list uses. Negative left/right margin +
   matching padding lets the highlight bleed out to the group's own
   edges rather than only as wide as the label's text. Padding matches
   the reference's own 5px/8px exactly. */
.csa-filter-row {
  padding: 5px 8px; margin: 0 -8px; border-radius: 8px;
  transition: background-color .15s;
}
.csa-filter-row:hover { background: var(--n25); }

/* ---- Grade quick-filter ---- */
.csa-filter-grade { display: flex; gap: 6px; }
.csa-filter-grade-btn {
  width: 38px; height: 38px; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  appearance: none; cursor: pointer;
  border: 1px solid var(--n75); border-radius: 4px; background: #fff;
  font-weight: 700; font-size: 14px; color: var(--n300);
  transition: border-color .15s, background-color .15s, color .15s;
}
.csa-filter-grade-btn:hover { border-color: var(--n200); }
.csa-filter-grade-btn:focus-visible { outline: 2px solid var(--b500); outline-offset: 2px; }
.csa-filter-grade-btn[data-grade="a"][aria-pressed="true"] { background: rgba(0,163,111,.2); border-color: var(--g200); color: var(--g200); }
.csa-filter-grade-btn[data-grade="b"][aria-pressed="true"] { background: rgba(0,114,199,.2); border-color: var(--b400); color: var(--b400); }
.csa-filter-grade-btn[data-grade="c"][aria-pressed="true"] { background: rgba(153,98,223,.2); border-color: var(--p300); color: var(--p300); }
.csa-filter-grade-btn[data-grade="d"][aria-pressed="true"] { background: rgba(255,166,0,.2); border-color: var(--o300); color: var(--o300); }
.csa-filter-grade-btn[data-grade="f"][aria-pressed="true"] { background: rgba(207,67,0,.2); border-color: var(--o700); color: var(--o700); }

/* ---- Surface-aware — dark/black ----
   The Filters title is white, like the group titles (b800 on light);
   Clear all uses this library's standard label-on-dark/black color
   (--n100, the same token .docs-label uses site-wide). Grade's RESTING (unselected) look is
   this library's own surface-aware neutral-outline treatment — its
   SELECTED state above is already surface-independent (confirmed
   identical in both of the reference's own light/dark modes), so
   there's nothing to override here for that part. */
[data-surface="dark"] .csa-filters-head { border-bottom-color: var(--b600); }
[data-surface="dark"] .csa-filters-title { color: #fff; }
[data-surface="dark"] .csa-filter-group-title { color: #fff; }
[data-surface="dark"] .csa-filters-clear { color: var(--n100); }
[data-surface="dark"] .csa-filters-clear:hover { color: #fff; }
/* b600, one step lighter than this surface's own b700 page background
   (surfaces.css) — a hover fill needs to lift off the page, not sink
   into it; b800 (darker than the page) read as a hole, not a highlight. */
[data-surface="dark"] .csa-filter-row:hover { background: var(--b600); }
[data-surface="dark"] .csa-filter-grade-btn { border-color: var(--b600); color: var(--n100); background: transparent; }
[data-surface="dark"] .csa-filter-grade-btn:hover { border-color: var(--b500); }

[data-surface="black"] .csa-filters-head { border-bottom-color: var(--n700); }
[data-surface="black"] .csa-filters-title { color: #fff; }
[data-surface="black"] .csa-filter-group-title { color: #fff; }
[data-surface="black"] .csa-filters-clear { color: var(--n100); }
[data-surface="black"] .csa-filters-clear:hover { color: #fff; }
/* n700, one step lighter than this surface's own black page background
   for the same reason the dark row above steps to b600 — n800 (this
   library's usual "escalated" black-surface tone, .csa-card etc.) sits
   even closer to true black and would read as barely-there here. */
[data-surface="black"] .csa-filter-row:hover { background: var(--n700); }
[data-surface="black"] .csa-filter-grade-btn { border-color: var(--n700); color: var(--n100); background: transparent; }
[data-surface="black"] .csa-filter-grade-btn:hover { border-color: var(--n600); }
/* =========================================================
   CSA Component Library — Forms
   Text input, textarea, select, checkbox, and radio — the controls
   RiskRubric.ai's "Get in Touch" form and its search-page filter
   sidebar both needed, with nothing in this library to build them
   from before now. reset.css already gives button/input/select/
   textarea `font: inherit; color: inherit`, so nothing here needs to
   restate font-family.

   Markup contract:
     <div class="csa-field">
       <label class="csa-label" for="name">Full name</label>
       <input class="csa-input" type="text" id="name" placeholder="Jane Doe">
       <span class="csa-field-hint">Optional helper text.</span>
     </div>

     <div class="csa-field">
       <label class="csa-label" for="msg">Message</label>
       <textarea class="csa-textarea" id="msg" rows="4"></textarea>
     </div>

     <div class="csa-field">
       <label class="csa-label" for="topic">Topic</label>
       <select class="csa-select" id="topic">
         <option>Option A</option>
       </select>
     </div>

     <label class="csa-checkbox">
       <input type="checkbox">
       <span class="csa-checkbox-box">
         <svg viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 13l4 4L19 7"/></svg>
       </span>
       <span>Label text</span>
     </label>

     <label class="csa-radio">
       <input type="radio" name="group">
       <span class="csa-radio-box"></span>
       <span>Label text</span>
     </label>

   Error state is `aria-invalid="true"` on the input/textarea/select
   itself (the same "state lives in one real attribute" contract every
   other interactive component in this library uses — aria-selected,
   aria-pressed, aria-current) plus an optional `.csa-field-error`
   message element in the field stack.

   BEHAVIOR SPEC — not implemented here: a consuming app sets
   aria-invalid="true" on submit-time validation failure (and back to
   "false"/removes it once corrected); nothing here reacts to input
   automatically. Checkbox/radio need no behavior spec at all — the
   native :checked state drives every visual change below, no
   attribute-flipping JS required the way aria-pressed/aria-selected
   controls elsewhere in this library do.
   ========================================================= */

/* ---- Shared field stack ---- */
.csa-field { display: flex; flex-direction: column; gap: 6px; }
.csa-label { font-size: 14px; font-weight: 600; color: var(--n500); }
.csa-field-hint { font-size: var(--text-caption); color: var(--n400); } /* caption size + color, see .csa-caption */
/* r400, not r500 — r500 is calibrated for large-area fills (a filled
   badge/button), too dark and low-contrast for small running text. */
.csa-field-error { font-size: var(--text-caption); color: var(--r400); font-weight: 600; }

/* ---- Text input, textarea, select ----
   One shared base: same border/radius/padding/transition regardless of
   which of the three this is, so a form mixing all three reads as one
   consistent row of controls rather than three subtly different shapes. */
.csa-input, .csa-textarea, .csa-select,
input.csa-input, textarea.csa-textarea, select.csa-select {
  display: block; width: 100%;
  padding: 12px 16px; border-radius: 10px;
  border: 1px solid var(--n75); background: #fff; color: var(--n500);
  font-size: 15px;
  transition: border-color .15s, box-shadow .15s;
}
.csa-input::placeholder, .csa-textarea::placeholder { color: var(--n200); }
.csa-textarea { resize: vertical; min-height: 110px; }
/* Custom chevron: a native <select>'s own arrow can't be restyled
   directly (it's platform chrome, not a paintable box), so appearance:
   none removes it and this SVG background-image draws a replacement —
   n300 (#697A89) baked into the data URI since currentColor doesn't
   reach into a background-image. Right padding clears room for it so
   the arrow never overlaps long option text. */
.csa-select {
  appearance: none;
  padding-right: 40px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23697A89' stroke-width='2.4'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 14px center;
  background-size: 16px;
  cursor: pointer;
}
.csa-input:hover, .csa-textarea:hover, .csa-select:hover { border-color: var(--n100); }
/* Recolored border + a 1px inset box-shadow standing in for the extra
   pixel of thickness, instead of an actual border-width change — a
   real border-width change shrinks the content box by a pixel
   (box-sizing:border-box keeps the control's own outer size fixed,
   but the content area inside it shrinks to compensate), which was
   just enough to visibly shift the text on focus. A box-shadow paints
   without touching the box model at all, so the ring reads as 2px
   thick with nothing inside it moving. outline:none suppresses the
   browser's own default focus ring — without it, that default draws
   its own outline just outside this border, right back into the
   original two-ring problem this is meant to fix. */
.csa-input:focus-visible, .csa-textarea:focus-visible, .csa-select:focus-visible {
  border-color: var(--b500); outline: none;
  box-shadow: inset 0 0 0 1px var(--b500);
}
.csa-input[aria-invalid="true"], .csa-textarea[aria-invalid="true"], .csa-select[aria-invalid="true"] {
  border-color: var(--r400);
}
/* Higher specificity than the plain :focus-visible rule above (an
   extra attribute selector), so an invalid field stays red on focus
   instead of the focus ring's b500 overriding it — the same red as
   its own resting state, not a second, competing color. */
.csa-input[aria-invalid="true"]:focus-visible, .csa-textarea[aria-invalid="true"]:focus-visible, .csa-select[aria-invalid="true"]:focus-visible {
  border-color: var(--r400);
  box-shadow: inset 0 0 0 1px var(--r400);
}
.csa-input:disabled, .csa-textarea:disabled, .csa-select:disabled {
  background: var(--n25); color: var(--n300); border-color: var(--n50); cursor: not-allowed;
}

/* ---- Checkbox ----
   A real <input type="checkbox"> stays in the DOM (keyboard/screen-
   reader/form-submission all keep working unmodified) but is visually
   hidden via opacity:0 rather than display:none — display:none would
   pull it out of tab order in some browsers, opacity:0 keeps it
   present and focusable, just invisible, with .csa-checkbox-box (a
   plain sibling span) painted in its place and keyed off the real
   input's :checked/:focus-visible state via the ~ sibling combinator. */
.csa-checkbox, .csa-radio {
  display: inline-flex; align-items: center; gap: 10px;
  cursor: pointer; font-size: var(--text-small); color: var(--n500);
}
.csa-checkbox input, .csa-radio input {
  position: absolute; opacity: 0; width: 18px; height: 18px; margin: 0;
}
.csa-checkbox-box, .csa-radio-box {
  position: relative; flex-shrink: 0;
  width: 18px; height: 18px; border: 1.5px solid var(--n100); background: #fff;
  transition: background .15s, border-color .15s;
}
.csa-checkbox-box { border-radius: 5px; display: flex; align-items: center; justify-content: center; }
.csa-radio-box { border-radius: 50%; }
.csa-checkbox:hover .csa-checkbox-box, .csa-radio:hover .csa-radio-box { border-color: var(--n200); }
.csa-checkbox input:focus-visible ~ .csa-checkbox-box,
.csa-radio input:focus-visible ~ .csa-radio-box {
  outline: 2px solid var(--b500); outline-offset: 2px;
}
.csa-checkbox input:checked ~ .csa-checkbox-box,
.csa-radio input:checked ~ .csa-radio-box { background: var(--b500); border-color: var(--b500); }
/* Checkmark is a real SVG child (the same path .csa-toggle-icon-
   confirm's own checkmark glyph uses — toggles.css — reused here for
   a consistent checkmark shape across the library), not the border-
   rotation trick this used before. flex-centering the box (above)
   centers a real element exactly, rather than a hand-tuned pixel
   offset on a ::after that read as slightly off-center. */
.csa-checkbox-box svg { width: 11px; height: 11px; opacity: 0; transition: opacity .1s; }
.csa-checkbox input:checked ~ .csa-checkbox-box svg { opacity: 1; }
.csa-radio-box::after {
  content: ''; position: absolute; inset: 4px; border-radius: 50%;
  background: #fff; opacity: 0; transition: opacity .1s;
}
.csa-radio input:checked ~ .csa-radio-box::after { opacity: 1; }
.csa-checkbox input:disabled ~ .csa-checkbox-box,
.csa-radio input:disabled ~ .csa-radio-box { background: var(--n25); border-color: var(--n50); }
.csa-checkbox:has(input:disabled), .csa-radio:has(input:disabled) { color: var(--n300); cursor: not-allowed; }

/* ---- Surface-aware — dark ----
   Same b-scale pairing surfaces.css's own [data-surface="dark"] .csa-
   card uses (b800 fill / b600 border) for the fields themselves; the
   checkbox/radio box instead stays a step lighter (transparent + b600
   border) since a small control reads better as an outline against a
   filled field than as another filled box next to one. */
[data-surface="dark"] .csa-input, [data-surface="dark"] .csa-textarea, [data-surface="dark"] .csa-select {
  background: var(--b800); border-color: var(--b600); color: #fff;
}
[data-surface="dark"] .csa-input::placeholder, [data-surface="dark"] .csa-textarea::placeholder { color: var(--b400); }
[data-surface="dark"] .csa-select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23A5B7C6' stroke-width='2.4'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
}
[data-surface="dark"] .csa-input:hover, [data-surface="dark"] .csa-textarea:hover, [data-surface="dark"] .csa-select:hover { border-color: var(--b500); }
[data-surface="dark"] .csa-label { color: var(--n100); }
[data-surface="dark"] .csa-field-hint { color: var(--n200); }
[data-surface="dark"] .csa-field-error { color: var(--r100); } /* r400 is 2.4:1 here and r200 4.3:1; r100 is 5.3:1+ */
[data-surface="dark"] .csa-checkbox, [data-surface="dark"] .csa-radio { color: var(--n75); }
[data-surface="dark"] .csa-checkbox-box, [data-surface="dark"] .csa-radio-box { background: transparent; border-color: var(--b500); }
[data-surface="dark"] .csa-checkbox:hover .csa-checkbox-box, [data-surface="dark"] .csa-radio:hover .csa-radio-box { border-color: var(--b400); }
/* b300, not b500 (the light-surface checked fill) — b500 sits close
   to this surface's own b600 border/b700 page tones and reads as
   muddy against navy; b300 is bright enough to read clearly as
   "checked" here. */
[data-surface="dark"] .csa-checkbox input:checked ~ .csa-checkbox-box,
[data-surface="dark"] .csa-radio input:checked ~ .csa-radio-box { background: var(--b300); border-color: var(--b300); }

/* ---- Surface-aware — black ---- */
[data-surface="black"] .csa-input, [data-surface="black"] .csa-textarea, [data-surface="black"] .csa-select {
  background: var(--n800); border-color: var(--n700); color: #fff;
}
[data-surface="black"] .csa-input::placeholder, [data-surface="black"] .csa-textarea::placeholder { color: var(--n500); }
[data-surface="black"] .csa-select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23A5B7C6' stroke-width='2.4'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
}
[data-surface="black"] .csa-input:hover, [data-surface="black"] .csa-textarea:hover, [data-surface="black"] .csa-select:hover { border-color: var(--n600); }
[data-surface="black"] .csa-label { color: var(--n100); }
[data-surface="black"] .csa-field-hint { color: var(--n200); } /* n300 was 4.3:1 on black, under AA */
[data-surface="black"] .csa-field-error { color: var(--r100); }
[data-surface="black"] .csa-checkbox, [data-surface="black"] .csa-radio { color: var(--n75); }
[data-surface="black"] .csa-checkbox-box, [data-surface="black"] .csa-radio-box { background: transparent; border-color: var(--n600); }
[data-surface="black"] .csa-checkbox:hover .csa-checkbox-box, [data-surface="black"] .csa-radio:hover .csa-radio-box { border-color: var(--n400); }
/* b300 here too, not an n-scale tone — checked state reads as a form
   input's own "selected" semantic rather than a surface decoration,
   so it stays the same blue as the dark-surface checked fill above
   rather than following black's usual n-scale convention. */
[data-surface="black"] .csa-checkbox input:checked ~ .csa-checkbox-box,
[data-surface="black"] .csa-radio input:checked ~ .csa-radio-box { background: var(--b300); border-color: var(--b300); }
/* =========================================================
   CSA Component Library — Pagination
   Numbered page buttons (the CSA STAR Registry's own pattern) framed
   by a Prev/Next pair (RiskRubric.ai's own wording, shortened from
   "Previous" to match "Next" as a one-word pair) — same rounded-card
   button shape as .csa-action, one row.

   Markup contract:
     <nav class="csa-pagination" aria-label="Pagination">
       <button type="button" class="csa-pagination-btn csa-pagination-prev" disabled>← Prev</button>
       <button type="button" class="csa-pagination-btn" aria-current="page">1</button>
       <button type="button" class="csa-pagination-btn">2</button>
       <button type="button" class="csa-pagination-btn">3</button>
       <button type="button" class="csa-pagination-btn csa-pagination-next">Next →</button>
     </nav>
   Every button is a real <button>, not a link — pagination changes
   what's shown on the same view rather than navigating to a new
   document. aria-current="page" marks the active page, the same
   "state lives in one real attribute" contract every other
   interactive component in this library uses.

   BEHAVIOR SPEC — not implemented here: clicking a page button moves
   aria-current="page" to it (removing it from whichever button had
   it); clicking Prev/Next moves aria-current to the previous/next
   page button in the same way. The app disables Prev when the first
   page is current and Next when the last page is current — a plain
   disabled attribute, not a class, so :disabled below is all the
   styling either state needs.
   ========================================================= */
.csa-pagination { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.csa-pagination-btn {
  appearance: none; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  min-width: 40px; height: 40px; padding: 0 14px;
  border-radius: 10px; border: 1px solid var(--n50); background: #fff;
  font-weight: 400; font-size: 14px; color: var(--n500);
  transition: border-color .15s, background-color .15s, color .15s;
}
.csa-pagination-btn:hover { border-color: var(--n100); }
.csa-pagination-btn:focus-visible { outline: 2px solid var(--b500); outline-offset: 2px; }
/* Current page is a solid b500 fill with white text — the one filled
   control in the row, so it reads as "you are here" at a glance rather
   than needing a second look to spot a color-only difference. Every
   other button (plain numbers, Prev, and Next — Next has no color
   accent of its own, its arrow glyph is what marks it, not a text
   color) keeps the base n500. */
.csa-pagination-btn[aria-current="page"] { background: var(--b500); border-color: var(--b500); color: #fff; }
.csa-pagination-btn:disabled { color: var(--n200); border-color: var(--n50); cursor: not-allowed; }
/* Prev/Next's disabled state (first/last page) borrows .csa-btn-
   disabled's exact colors (buttons.css) rather than this component's
   own plain-disabled treatment above — same fill-not-just-faded-text
   look as every other disabled control in this library, and higher
   specificity (two classes) than the plain :disabled rule above wins
   it automatically. */
.csa-pagination-prev:disabled, .csa-pagination-next:disabled { background: var(--n50); color: var(--n300); border-color: transparent; }

/* ---- Surface-aware — dark ---- */
[data-surface="dark"] .csa-pagination-btn { border-color: var(--b600); color: var(--n75); background: transparent; }
[data-surface="dark"] .csa-pagination-btn:hover { border-color: var(--b500); }
[data-surface="dark"] .csa-pagination-btn[aria-current="page"] { background: var(--b300); border-color: var(--b300); color: #fff; }
[data-surface="dark"] .csa-pagination-btn:disabled { color: var(--b600); border-color: var(--b700); }
[data-surface="dark"] .csa-pagination-prev:disabled, [data-surface="dark"] .csa-pagination-next:disabled { background: var(--b800); color: var(--b500); border-color: transparent; }
/* Deep tone: band is already b800, so the disabled fill steps up to b700 */
[data-surface="dark"][data-tone="deep"] .csa-pagination-prev:disabled, [data-surface="dark"][data-tone="deep"] .csa-pagination-next:disabled { background: var(--b700); }

/* ---- Surface-aware — black ---- */
[data-surface="black"] .csa-pagination-btn { border-color: var(--n700); color: var(--n75); background: transparent; }
[data-surface="black"] .csa-pagination-btn:hover { border-color: var(--n600); }
[data-surface="black"] .csa-pagination-btn[aria-current="page"] { background: var(--b300); border-color: var(--b300); color: #fff; }
[data-surface="black"] .csa-pagination-btn:disabled { color: var(--n600); border-color: var(--n800); }
[data-surface="black"] .csa-pagination-prev:disabled, [data-surface="black"] .csa-pagination-next:disabled { background: var(--n800); color: var(--n500); border-color: transparent; }
/* =========================================================
   CSA Component Library — Progress
   A determinate meter bar — extracted from RiskRubric.ai's per-pillar
   score bars (Transparency/Reliability/Security/etc., each a value out
   of 1000). The grade-lettered badge that sits next to those bars on
   the real site is its own, separate concern (out of scope here) —
   this is just the bar: a track plus a fill sized by inline width.

   Markup contract:
     <div class="csa-progress" role="progressbar" aria-valuenow="70" aria-valuemin="0" aria-valuemax="100">
       <div class="csa-progress-fill" style="width:70%; --csa-progress-value:70"></div>
     </div>
   The percentage is a plain inline style on the fill — this component
   only draws the bar, it doesn't compute a value/max into a width.
   role="progressbar" + aria-value* carry the real number to assistive
   tech independent of the fill's visual width.

   --csa-progress-value (a bare number, same as aria-valuenow when max
   is 100) is what makes the fill's gradient read as a slice of one
   continuous gradient spanning the FULL track, not the full gradient
   compressed into however wide the fill happens to be — a 40%-full bar
   shows the first 40% of the b300→b500 range, not the complete range
   squeezed into a shorter box. See the .csa-progress-fill comment
   below for the mechanism. Omitting it just falls back to treating the
   fill's own width as the full gradient range (this library's plain
   default before this existed).

   Color is a modifier on the outer .csa-progress, not the fill, so a
   consuming app only ever toggles one class to recolor the whole bar:
     .csa-progress               → b300→b500, the default/neutral reading
     .csa-progress-success       → g100→g300
     .csa-progress-warning       → o300→o600
     .csa-progress-danger        → r300→r600
   ========================================================= */
.csa-progress { width: 100%; height: 8px; border-radius: 999px; background: var(--n50); overflow: hidden; }
/* The gradient image is drawn at the TRACK's full width, not the
   fill's own — background-size in a percentage is relative to the
   element's own box, so sizing it to 100% here would just repaint the
   same b300→b500 range no matter how narrow the fill is. Scaling it up
   to (10000 / value)% instead means 100% of that oversized image = the
   full track width; background-position:left then anchors the image's
   0% (b300) to the fill's own left edge, which is also the track's
   left edge, so the visible slice — 0% through the fill's own width —
   is always the correct corresponding segment of the one virtual
   track-wide gradient, never the whole range rescaled. */
.csa-progress-fill {
  height: 100%; border-radius: 999px;
  background-image: linear-gradient(to right, var(--b300), var(--b500));
  background-repeat: no-repeat;
  background-position: left center;
  background-size: calc(10000% / var(--csa-progress-value, 100)) 100%;
  transition: width .3s ease;
}

.csa-progress-success .csa-progress-fill { background-image: linear-gradient(to right, var(--g100), var(--g300)); }
.csa-progress-warning .csa-progress-fill { background-image: linear-gradient(to right, var(--o300), var(--o600)); }
.csa-progress-danger .csa-progress-fill { background-image: linear-gradient(to right, var(--r300), var(--r600)); }

/* Track steps up a ramp — same "one level off the page" convention
   toggles.css's own track colors use on these two surfaces (b800/n700,
   see .csa-toggle-icon-track) — the fill colors themselves stay as-is,
   already bright enough to read on either. */
[data-surface="dark"] .csa-progress { background: var(--b800); }
/* Deep tone: the band is already b800, so the track steps up to b700 */
[data-surface="dark"][data-tone="deep"] .csa-progress { background: var(--b700); }
[data-surface="black"] .csa-progress { background: var(--n700); }
/*
 * CSA Component Library
 * Source: https://github.com/CloudSecurityAlliance/CSA-Component-Library
 * Update: yarn upgrade csa-component-library (once package.json PR is merged upstream)
 * Local dev: yarn link csa-component-library (from CSA-Component-Library repo)
 *
 * Load order mirrors build.mjs: tokens → base → components.
 * reset.css is intentionally excluded — this app has its own reset.
 *






























 */
