/* ============================================================================
   isConnect — design tokens + global chrome (nav + footer).
   Tokens mirror the Figma "ISV isConnect UI Design" variables.
   Fonts (@font-face + the Barlow import) live in wwwroot/css/fonts.css, which
   _Layout loads BEFORE this file.

   COLOUR IS TWO LAYERS, and the split is load-bearing:

     LAYER 1  the raw brand palette — the ONLY place a brand hex may appear.
     LAYER 2  semantic roles — what every rule in this file actually references.

   Why: the rebrand collapses six brand colours onto three, and the retired
   --teal-1 alone meant link colour AND icon colour AND border colour AND
   "background under white text" — four things that must now diverge, because
   white on Royal Blue is 3.80:1 while white on Deep Blue is 7.10:1. A flat
   palette cannot express that; renaming or aliasing the old tokens would have
   preserved the conflation. It also means the open accessibility question
   (Royal Blue text on white fails AA at small sizes) is a ONE-LINE flip of
   --brand-primary rather than a second sweep of ~56 call sites.

   Rule: never reference a LAYER 1 *brand* token outside LAYER 2, and never
   write a brand hex anywhere else in this file. The bare neutrals (--white,
   --black, --grey, --body-text) are exempt — they are identical in both brands
   and reading `var(--white)` on a fill is clearer than `var(--surface)`.
   ========================================================================== */
:root {
    /* ---- LAYER 1 — raw palette (Figma "Style → Colours", node 279:7442) ---- */
    --deep-blue: #2756a0;
    --royal-blue: #2985e0;
    --ocean-blue: #a0dbec;
    --sunshine: #fce17c;
    --cream: #fef9e5;
    /* Royal Blue at ~10% over white — the hairline on chips, search fields and
       bookmark boxes (Figma card component 67:4949). */
    --royal-blue-tint: #d4e7f9;
    --blue-doc: #0076DF;   /* document file-type icon (Figma node 102:7099) — a
                              file-type convention, deliberately NOT brand. */

    /* Neutrals */
    --white: #ffffff;
    --black: #000000;
    --grey: #888888;
    --body-text: #666666;
    --light-grey: #d8d8d8;          /* hairlines: nav underline, footer overline */
    --light-grey-neutral: #f4f4f4;  /* nav / footer / category-card surface */
    --light-grey-cool: #e2f4f9;     /* the Resources / Recently-explored band */

    /* ---- LAYER 2 — semantic roles (reference these, not the above) -------- */
    --brand-primary: var(--royal-blue);        /* links, icon glyphs, borders on light */
    --brand-primary-strong: var(--deep-blue);  /* fills that carry WHITE text (7.10:1) */
    --brand-primary-hover: var(--deep-blue);   /* hover / active / selected */
    --brand-soft: var(--ocean-blue);           /* links on dark, decorative rules */
    --brand-deep: var(--deep-blue);            /* wordmark, on-light emphasis */
    --accent-warm: var(--sunshine);
    --accent-warm-bg: var(--cream);

    --text-strong: var(--black);       /* headings, card titles */
    --text-body: var(--body-text);     /* body copy, footer links */
    --text-muted: var(--grey);         /* dates, meta, disabled */

    --surface: var(--white);
    --surface-muted: var(--light-grey-neutral);
    --surface-cool: var(--light-grey-cool);
    --surface-brand: var(--royal-blue);   /* the hero wash */

    --border-hairline: var(--light-grey);
    --border-accent: var(--royal-blue-tint);
    --focus-ring: var(--brand-primary-hover);
    --icon-accent: var(--brand-primary);

    /* Shadows and scrims are neutral black, NOT tinted. They previously encoded
       rgba(41,64,83,…) — which is the retired #294053 written in decimal. */
    --shadow-card: 0 6px 18px rgba(0, 0, 0, .12);
    --shadow-pop: 0 10px 30px rgba(0, 0, 0, .18);
    --scrim: rgba(0, 0, 0, .45);

    /* Type scale (Stevie Sans) — Figma "Typography" (node 279:7373).
       Canonical roles + responsive sizes live with the .t-* classes below.
       See /isv-typography-skill before authoring block headings.
       @font-face for both families lives in ONE place: wwwroot/css/fonts.css. */
    /* NO FALLBACK STACK — deliberate. Segoe UI is close enough to Stevie Sans that a failed
       webfont load was invisible; with the stack gone a failure drops to the browser's default
       serif and is unmistakable. Keep in step with the identical token in editor.css.
       Trade-off: if the woff2 ever 404s, visitors get Times, not a tidy system sans. Restore
       the stack ("Segoe UI", system-ui, -apple-system, Roboto, Helvetica, Arial, sans-serif)
       once the typography has been signed off. See the font check in _Layout.cshtml. */
    --font-body: "Stevie Sans";
    /* Uppercase eyebrow labels only ("LATEST UPDATES", "QUICK LINKS", the rail heading).
       Stevie Sans has no matching cut, so Barlow Bold stays from the previous brand. */
    --font-eyebrow: "Barlow", var(--font-body);

    /* Role sizes — desktop (≥1025px). Tablet/mobile overrides in media queries. */
    --fs-h1-xl: 60px;
    --fs-h1-lg: 50px;
    --fs-h1: 40px;
    --fs-h2: 24px;
    --fs-h3: 20px;
    --fs-h4: 18px;
    --fs-h5: 16px;
    --fs-body: 16px;
    --fs-body-sm: 14px;
    /* Figma "Body extra small" — dates and card meta lines ("Last updated: DD/MM/YYYY",
       Home node 102:2747). Book 400 / 12px / lh 1.4. Was hardcoded as a bare 12px at
       several call sites before it was tokenised. */
    --fs-body-xs: 12px;

    /* Role line-heights */
    --lh-display: 1.2;  /* h1-xl, h1-lg, h1 */
    --lh-heading: 1.3;  /* h2, h3 */
    --lh-subhead: 1.4;  /* h4, h5, body-xs */
    --lh-body: 1.5;     /* body, body-sm */

    /* Role weights — Stevie Sans cuts, declared in fonts.css. The names are the FOUNDRY's
       style names, not CSS-weight nicknames, so "regular" is 550 and "medium" is 600.
       The old Quicksand set (medium 500 / semibold 600 / bold 700) is retired: every call
       site was re-pointed explicitly rather than redefining --fw-medium in place, which
       would have silently bolded all body copy from 500 to 600. */
    --fw-light: 300;     /* Light   — h1-xl, h1-lg, h1 */
    --fw-book: 400;      /* Book    — body, body-sm */
    --fw-regular: 550;   /* Regular — buttons, pill links, search chips */
    --fw-medium: 600;    /* Medium  — h2, h3, h4, h5, nav links */
    --fw-eyebrow: 700;   /* Barlow Bold — uppercase eyebrow labels only */

    /* Spacing */
    --sp-grid: 30px;
    --sp-v-medium: 40px;
    --sp-v-large: 50px;
    --sp-h-large: 60px;

    /* Layout */
    --container: 1440px;
    --container-pad: 55px;
    /* Auto-hiding sticky nav height — SINGLE SOURCE OF TRUTH for header clearance.
       --header-h            → where jumped-to anchors land (scroll-margin-top).
       --sticky-top          → where a sticky rail pins while the nav is VISIBLE (below it).
       --sticky-top-collapsed → where a sticky rail pins while the nav is HIDDEN (scrolled
                                down) — near the very top. site.js toggles .nav-hidden on
                                <html>; rails transition between the two, so a rail rides up
                                to the top when the nav slides away and drops back to clear it
                                on scroll-up. Edit these once and every rail + anchor follows. */
    --header-h: 90px;
    --sticky-top: calc(var(--header-h) + var(--sp-grid));
    --sticky-top-collapsed: 12px;
    /* Height of the article's sticky breadcrumb/share topbar (its tallest child is the 34px share
       button). Anything else that pins inside .article__main has to clear it — see the jump bar. */
    --article-topbar-h: 34px;
    /* The LIVE pin offset every sticky rail reads (`top: var(--rail-top)`): --sticky-top while the
       nav is visible, --sticky-top-collapsed once site.js adds .nav-hidden to <html> on scroll-down.
       This is the ONE switch — flipping it here moves the article topbar/contents rail, the hub rail,
       the Side Navigation pane, and any future rail together, so they always stay aligned. */
    --rail-top: var(--sticky-top);

    /* 1 once the viewport is too narrow (or too short, turned sideways) for the listing's 250px
       filter rail, so it collapses into the sticky FILTERS dropdown. CSS owns this breakpoint —
       site.js only READS the flag (search "--filters-collapsed" for the query that sets it). */
    --filters-collapsed: 0;

    /* Semantic */
    --isv-bg: var(--white);
    --isv-fg: var(--text-strong);
}

/* Nav hidden (scrolled down) → every rail rides up to the near-top in lockstep (transition on each). */
:root.nav-hidden {
    --rail-top: var(--sticky-top-collapsed);
}

* {
    box-sizing: border-box;
}

/* Replaced elements never exceed their column. TinyMCE writes intrinsic width/height
   attributes onto editor-inserted images, and without this cap that intrinsic width
   becomes the subtree's MIN-content width — which propagates up the flex shell and lays
   the whole content column (text included) out wider than the viewport on mobile.
   Safe as a bare-element rule: every other img rule in this file is class-scoped, so it
   out-specifies this one (e.g. .media-block__img keeps its width:100%). */
img, svg, video, iframe, canvas {
    max-width: 100%;
}

img {
    height: auto;   /* keep the aspect ratio once the width is clamped */
}

body {
    margin: 0;
    font-family: var(--font-body);
    font-size: var(--fs-body);
    font-weight: var(--fw-book);
    line-height: 1.5;
    color: var(--isv-fg);
    background: var(--isv-bg);
}

a {
    color: inherit;
}

.container {
    max-width: var(--container);
    margin: 0 auto;
    padding-inline: var(--container-pad);
}

/* ---- Typography ----------------------------------------------------------
   Canonical type roles from Figma node 279:7373. Two ways to apply:

   1. .t-* utility classes  — put the visual ROLE on any element, regardless of
      its HTML tag (e.g. a section title in an <h2> styled as h1-large):
          <h2 class="t-h1-lg">News &amp; Articles</h2>
   2. Bare elements          — h1–h5 / p get role defaults so rich-text
      (XhtmlString via @Html.PropertyFor) renders on-scale with no classes.

   Roles set family / weight / size / line-height only — colour is INHERITED so
   blocks stay in control (Figma's spec sheet shows them black for demo only).
   All sizes are responsive; tablet/mobile overrides live in the media queries
   at the bottom of this file. Author headings via /isv-typography-skill. */
.t-h1-xl,
.t-h1-lg,
.t-h1,
.t-h2,
.t-h3,
.t-h4,
.t-h5 {
    font-family: var(--font-body);
    margin: 0;
}

/* Display roles are Stevie Sans LIGHT (300) — Figma H1-extra-large / H1-large / H1.
   This is the most visible change in the rebrand: they were Quicksand Bold 700. */
.t-h1-xl { font-weight: var(--fw-light);  font-size: var(--fs-h1-xl); line-height: var(--lh-display); }
.t-h1-lg { font-weight: var(--fw-light);  font-size: var(--fs-h1-lg); line-height: var(--lh-display); }
.t-h1    { font-weight: var(--fw-light);  font-size: var(--fs-h1);    line-height: var(--lh-display); }
.t-h2    { font-weight: var(--fw-medium); font-size: var(--fs-h2);    line-height: var(--lh-heading); }
.t-h3    { font-weight: var(--fw-medium); font-size: var(--fs-h3);    line-height: var(--lh-heading); }
.t-h4    { font-weight: var(--fw-medium); font-size: var(--fs-h4);    line-height: var(--lh-subhead); }
.t-h5    { font-weight: var(--fw-medium); font-size: var(--fs-h5);    line-height: var(--lh-subhead); }

.t-body    { font-family: var(--font-body); font-weight: var(--fw-book); font-size: var(--fs-body);    line-height: var(--lh-body); }
.t-body-sm { font-family: var(--font-body); font-weight: var(--fw-book); font-size: var(--fs-body-sm); line-height: var(--lh-body); }

/* Figma "Body extra small" (Stevie Sans Book 400, 12/16.8) — dates and card meta rows.
   NOTE the line-height breaks the pattern: it is 1.4, not the 1.5 the other body roles use. */
.t-body-xs { font-family: var(--font-body); font-weight: var(--fw-book); font-size: var(--fs-body-xs); line-height: var(--lh-subhead); }

/* Interactive labels — Figma "Body small regular" (Stevie Sans Regular 550):
   buttons, pill links, search chips, "Subscribe to newsletter". */
.t-label { font-family: var(--font-body); font-weight: var(--fw-regular); font-size: var(--fs-body-sm); line-height: var(--lh-body); }

/* Bare-element defaults so rich text renders on-scale (tag → nearest role). */
h1 { font: var(--fw-light)  var(--fs-h1)/var(--lh-display) var(--font-body); margin: 0 0 .5em; }
h2 { font: var(--fw-medium) var(--fs-h2)/var(--lh-heading) var(--font-body); margin: 0 0 .5em; }
h3 { font: var(--fw-medium) var(--fs-h3)/var(--lh-heading) var(--font-body); margin: 0 0 .5em; }
h4 { font: var(--fw-medium) var(--fs-h4)/var(--lh-subhead) var(--font-body); margin: 0 0 .5em; }
h5 { font: var(--fw-medium) var(--fs-h5)/var(--lh-subhead) var(--font-body); margin: 0 0 .5em; }

/* ---- Buttons -------------------------------------------------------------- */
.icon-btn {
    /* Figma: white rounded square, NO border, Royal Blue icon. */
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    border: 0;
    border-radius: 6px;
    background: var(--white);
    color: var(--brand-primary);
    font-size: 16px;
    cursor: pointer;
    transition: background-color .15s ease;
}

.icon-btn:hover {
    background: var(--surface-cool);
}

a.icon-btn {
    text-decoration: none;
}

.icon-btn img {
    width: 18px;
    height: 18px;
    object-fit: contain;
}

.btn {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 9px 16px;
    border-radius: 6px;
    font-size: var(--fs-body-sm);
    font-weight: var(--fw-regular);
    text-decoration: none;
    cursor: pointer;
}

.btn--outline {
    border: 1px solid var(--brand-primary);
    color: var(--brand-primary);
    background: var(--white);
}

.btn--outline:hover {
    background: var(--brand-primary-strong);
    color: var(--white);
}

/* ---- Brand mark ----------------------------------------------------------- */
.brand {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    text-decoration: none;
}

.brand__mark {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 30px;
    background: var(--brand-deep);
    color: var(--white);
    border-radius: 3px;
    font-weight: var(--fw-medium);
    font-size: 15px;
    letter-spacing: .5px;
    clip-path: polygon(0 0, 100% 0, 100% 70%, 70% 100%, 0 100%);
}

.brand__logo {
    height: 40px;
    width: auto;
    max-width: 220px;
    object-fit: contain;
    display: block;
}

.brand__name {
    font-weight: var(--fw-medium);
    font-size: 22px;
    color: var(--brand-deep);
}

/* ---- Hero band (HeroSection + HeroBlock) ---------------------------------- */
.hero-band {
    /* The coloured band, contained to the 1330px frame (matching the nav/footer)
       rather than bleeding edge to edge — the page background shows in the side
       margins. background-color is set inline from the section's BackgroundColor.
       Figma node 102:2733 keeps this 1330 frame; it is NOT full-bleed. */
    position: relative;      /* containing block for the optional photo layer */
    isolation: isolate;      /* keep the photo's stacking context out of the page's */
    overflow: hidden;        /* the photo is cropped to fill, per Figma */
    max-width: 1330px;
    margin: 0 auto;
    border: 1px solid var(--border-hairline);
    border-bottom: 0;        /* the search band below draws the shared seam */
    /* Dark band → white text by default, so colour-inheriting elements (e.g. the generic
       Section Header) read correctly here; light sections leave text dark. */
    color: var(--white);
    /* ...and the Section Header's action link ("View all") goes Ocean Blue on the wash —
       Figma node 102:4698 — because brand blue on brand blue disappears. */
    --section-header-action: var(--brand-soft);
    --section-header-action-hover: var(--white);
}

/* Optional background photo — Figma node 102:2735 lays the image over the colour wash at
   50%. A real <img> rather than a CSS background-image, so the ContentReference resolves
   through IUrlResolver like every other image here and object-fit owns the crop.
   --hero-overlay comes from the section's "Photo strength" property, inline per band. */
.hero-band__media {
    position: absolute;
    inset: 0;
    opacity: var(--hero-overlay, .5);
    pointer-events: none;
}

.hero-band__media img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    /* Figma crops the photo well above centre so faces clear the headline. */
    object-position: center 30%;
}

/* Everything authored in the band sits above the photo. */
.hero-band > .hero-band__inner {
    position: relative;
    z-index: 1;
}

.hero-band__inner {
    /* Content frame. Figma node 102:2736: px 60 / py 40. */
    padding: var(--sp-v-medium) var(--sp-h-large);
}

/* Lay out the section's composition (a row with a Hero column + an Updates column)
   into the hero-left / cards-right design. The VB grid renders CLASS-LESS divs
   (.hero-band__inner > grid > row > columns), so we target row/columns by depth and
   tell them apart with :has(). The Hero column is a fixed 590 on the left; the Updates
   column fills the rest and lays its cards out side by side (wrapping if there are
   more than two). */

/* The row → columns side by side. */
.hero-band__inner > div > div {
    display: flex;
    flex-wrap: wrap;
    align-items: stretch;
    gap: var(--sp-grid);
}

/* Hero column → fixed left, content vertically centred against the cards. */
.hero-band__inner > div > div > div:has(.hero-block) {
    flex: 0 0 590px;
    max-width: 590px;
    display: flex;
    flex-direction: column;
    justify-content: center;
}

/* Updates column → fills the right; its cards sit in a row.
   TWO different gaps, and the split is the point: Figma node 102:2740 puts 15px between the
   "LATEST UPDATES" header row and the cards, while the card row itself (102:2742) is 30px.
   One wrapping flex serves both, so row-gap and column-gap are set separately rather than with
   a single --sp-grid — which was making the header sit 15px too low and the band 15px too tall
   (Figma's banner is 403px). Note this also makes a SECOND row of cards sit 15px below the
   first; the design only ever shows two, so there is no Figma value to honour there. */
.hero-band__inner > div > div > div:has(.update-card) {
    flex: 1 1 0;
    min-width: 0;
    display: flex;
    flex-wrap: wrap;
    align-content: flex-start;
    gap: 15px var(--sp-grid);
}

/* Each card (its class-less wrapper) → equal width, side by side. */
.hero-band__inner > div > div > div:has(.update-card) > div {
    flex: 1 1 220px;
    min-width: 0;
}

/* The header element (Heading + Text, Section Header variant) → full width, on its own line
   above the cards. */
.hero-band__inner > div > div > div:has(.update-card) > div:has(.section-header) {
    flex: 0 0 100%;
}

.updates-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-grid);
}

/* Figma node 102:2741 — Barlow Bold 700, like every other uppercase eyebrow on the site
   (.contents-rail__top, .site-footer__heading). Stevie Sans has no matching cut, so the
   family MUST be restated here: without it this inherits --font-body and renders as
   Stevie Sans Medium, which is what the rebrand left behind. */
.updates-header__eyebrow {
    margin: 0;
    font-family: var(--font-eyebrow);
    font-size: 12px;
    font-weight: var(--fw-eyebrow);
    letter-spacing: 1.2px;
    text-transform: uppercase;
    color: var(--white);
}

.updates-header__viewall {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    flex: 0 0 auto;
    /* Figma 102:4698 — Ocean Blue, because this link sits ON the blue hero band. */
    color: var(--brand-soft);
    font-size: var(--fs-body-sm);
    /* Figma "Body small regular" (Stevie Sans Regular 550) — the shared type of every
       "Button icon text" in the design ("View all resources", "View all recent"). The hero's
       own node still reads Quicksand SemiBold, a leftover in the Figma file: Quicksand is
       retired brand-wide, so follow the design system's 550, not the stale layer. */
    font-weight: var(--fw-regular);
    text-decoration: none;
}

.updates-header__viewall:hover {
    color: var(--brand-primary-hover);
}

/* ---- Update card ---------------------------------------------------------- */
.update-card {
    /* Fill the equal-height wrapper so the bottom-pinned date lines up across cards
       even when one title wraps to more lines than another. */
    height: 100%;
    display: flex;
    flex-direction: column;
    gap: 15px;
}

.update-card__link {
    /* Grow so the "Last updated" meta pins to the card bottom (Figma justify-between):
       keeps every card's image top-aligned and date bottom-aligned regardless of how
       many lines the title wraps to. */
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    gap: 15px;
    text-decoration: none;
    color: inherit;
}

.update-card__media {
    /* Fixed-ratio box: the image is absolutely positioned inside so its intrinsic
       size can't feed back and override the ratio — every card's image is identical
       regardless of the source photo's dimensions. */
    position: relative;
    display: block;
    width: 100%;
    aspect-ratio: 1445 / 850;
    flex: none;
    /* Square corners — Figma node 102:2744 puts no radius on the hero update thumbnails.
       (The Grid card look's 8px media radius IS in the design; don't confuse the two.) */
    overflow: hidden;
    /* Placeholder tint when no thumbnail is set. */
    background: rgba(255, 255, 255, .1);
}

.update-card__media img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.update-card__title {
    margin: 0;
    color: var(--white);
}

.update-card__meta {
    margin: 0;
    /* Figma "Body extra small" (node 102:2747), white at 50% on the hero band. */
    font-size: var(--fs-body-xs);
    font-weight: var(--fw-book);
    line-height: var(--lh-subhead);
    color: rgba(255, 255, 255, .5);
}

/* ---- Section Header (generic: heading + optional subtext + action link) ----
   Colours INHERIT from the section context (white on the hero band, dark on light
   sections). The header itself has no variant — only the cards do. */
.section-header {
    display: flex;
    flex-direction: column;
    gap: 10px;
    color: inherit;
}

.section-header__row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-grid);
}

/* Figma node 102:2741 ("LATEST UPDATES") — Barlow Bold 700. Restating the family is
   load-bearing: Stevie Sans has no eyebrow cut, so inheriting --font-body silently renders
   this as Stevie Sans Medium. Colour inherits so the eyebrow reads white on the hero band
   and dark on a light section. */
.section-header__eyebrow {
    margin: 0;
    font-family: var(--font-eyebrow);
    font-size: 12px;
    font-weight: var(--fw-eyebrow);
    letter-spacing: 1.2px;
    text-transform: uppercase;
    color: inherit;
}

.section-header__action {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    flex: 0 0 auto;
    /* Brand blue on a light section; a coloured band (e.g. .hero-band) re-points
       --section-header-action to Ocean Blue. Figma "Button icon text" = Body small regular
       (Stevie Sans Regular 550 / 14 / 150%) — the same role .pill-link takes via .t-label. */
    color: var(--section-header-action, var(--brand-primary));
    font-size: var(--fs-body-sm);
    font-weight: var(--fw-regular);
    line-height: var(--lh-body);
    text-decoration: none;
}

.section-header__action:hover { color: var(--section-header-action-hover, var(--brand-primary-hover)); }

.section-header__subtext { color: inherit; }
.section-header__subtext:empty { display: none; }   /* no space when empty (view mode) */
.section-header__subtext > :first-child { margin-top: 0; }
.section-header__subtext > :last-child { margin-bottom: 0; }

/* ============================================================================
   Unified card system (.isv-card) — ONE markup + ONE set of styles for every
   card "look". Used by:
     • home Category Group cards  (Views/Shared/_IsvCard.cshtml)
     • the query-driven listing/article grid (ListingResultCardGrid/Default.cshtml)

   A "look" is a CSS modifier — NOT a separate stylesheet — so it can be set in
   two places that CASCADE (this is what makes a section-level style possible):
     1. CONTAINER (group default): a wrapper look class restyles every inheriting
        card inside it — a card with NO explicit look (no .isv-card--styled). Two
        container hooks share the SAME look definitions below:
          • .cat-group--{look}   — home Category Group section (also drives its layout)
          • .isv-cards--{look}   — any card container; the Listing Result Card Grid stamps it on
            its grid root per the element's Style-panel variant so the query-driven grid gets the
            same looks (via ListingResultCardGridViewComponent).
     2. CARD (override):          .isv-card--styled.isv-card--{look} on a single
        card wins (the container rule excludes .isv-card--styled), so you can
        cherry-pick one card to differ while the rest follow the group.

   Each look is expressed ONCE as a small block of CSS custom properties (below);
   the structural rules read those vars, so changing a look in one place updates
   it everywhere. Base defaults = the Category (icon panel) look, which is also
   the safety net for a bare inherit card outside any styled container.
   (Hero keeps its own .update-card view — white-on-dark, homepage hero band only.)
   ========================================================================== */
.isv-card {
    /* Tunable look vars — defaults are the CATEGORY look. */
    --card-link-gap: 10px;
    --card-body-gap: 10px;
    --card-bg: var(--surface-muted);
    --card-pad: 25px;
    --card-radius: 12px;
    --card-fg: inherit;
    --card-hover-shadow: var(--shadow-card);
    --card-hover-transform: translateY(-2px);
    --card-top-display: flex;          /* icon row — category only */
    --card-media-display: none;
    --card-media-radius: 8px;
    --card-media-bg: rgba(0, 0, 0, .06);
    --card-meta-display: none;
    --card-tags-display: inline-flex;
    --card-date-display: inline;       /* meta date — a look can hide it (Resources look) */
    --card-bookmark-display: none;     /* meta-row bookmark — the Resources look opts in */
    --card-date-color: var(--text-muted);
    --card-title-fs: var(--fs-h4);
    --card-title-fw: var(--fw-medium);
    --card-title-color: var(--black);
    --card-title-hover-color: var(--card-title-color);   /* per-look opt-in; default = no change */
    --card-summary-color: var(--body-text);

    display: flex;
    flex-direction: column;
    height: 100%;                      /* equal heights across a grid row */
    background: var(--card-bg);
    padding: var(--card-pad);
    border-radius: var(--card-radius);
    color: var(--card-fg);
    transition: box-shadow .15s ease, transform .15s ease;
}

.isv-card:hover {
    box-shadow: var(--card-hover-shadow);
    transform: var(--card-hover-transform);
}

/* Title recolours on hover for looks that opt in via --card-title-hover-color
   (Grid look = Deep Blue). Looks that don't set it keep their title colour. */
.isv-card:hover .isv-card__title {
    color: var(--card-title-hover-color);
}

.isv-card__link {
    display: flex;
    flex-direction: column;
    gap: var(--card-link-gap);
    height: 100%;
    text-decoration: none;
    color: inherit;
}

/* Icon row (category look) */
.isv-card__top {
    display: var(--card-top-display);
    align-items: center;
    justify-content: space-between;
}

.isv-card__icon {
    /* Figma card component 67:4949 draws these in Font Awesome Pro LIGHT at 30px.
       --fa-style is FA7's weight switch, so this one declaration thins whatever cut
       the author picked (fa-solid / fa-regular) down to Light — no need to restate
       the weight on every stored icon class in the CMS. It out-ranks .fa-solid's own
       --fa-style only because site.css loads after all.min.css; keep that order. */
    --fa-style: 300;
    font-size: 30px;
    line-height: 1;
    color: var(--icon-accent);
}

.isv-card__bookmark {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    border: 1px solid var(--border-accent);
    border-radius: 6px;
    background: var(--white);
    color: var(--brand-primary);
    font-size: 16px;
}

/* Bookmark rendered inside the meta row (Resources look) — reuses the box look above but
   its visibility is look-driven: hidden by default, the Resources look sets the var. This
   overrides the base .isv-card__bookmark inline-flex used by the icon-row bookmark. */
.isv-card__bookmark--in-meta { display: var(--card-bookmark-display); }

/* Media (image looks) — fixed-ratio box, image absolutely placed so the source
   photo's intrinsic size can't override the ratio. */
.isv-card__media {
    display: var(--card-media-display);
    position: relative;
    width: 100%;
    aspect-ratio: 1445 / 850;
    overflow: hidden;
    border-radius: var(--card-media-radius);
    background: var(--card-media-bg);
}

.isv-card__media img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.isv-card__body {
    display: flex;
    flex-direction: column;
    gap: var(--card-body-gap);
}

/* Tag pills (left) opposite the date (right). */
.isv-card__meta {
    display: var(--card-meta-display);
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    width: 100%;
}

.isv-card__tags {
    display: var(--card-tags-display);
    flex-wrap: wrap;
    gap: 8px;
    min-width: 0;   /* lets the pills below shrink instead of stretching the card's meta row */
}

.isv-card__tag {
    background: var(--white);
    border: 1px solid var(--border-accent);
    border-radius: 100px;
    padding: 6px 12px;
    /* Figma's Tag component (I102:5378;111:7815) is Stevie Sans REGULAR (550), not Medium —
       the same interactive-label cut the breadcrumb and sort pills use. Size reads the
       body-xs token so the pill follows the mobile type step; line-height stays `normal`
       because the pill's height comes from its padding, as in the component. */
    font-size: var(--fs-body-xs);
    font-weight: var(--fw-regular);
    line-height: normal;
    color: var(--brand-primary);
    white-space: nowrap;
    /* A long category name stays on one line (the pill shape depends on it), so cap it at the
       card's width and ellipsis the overflow — otherwise it juts out of a narrow card. */
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Figma "Body extra small" — the card's "Last updated: …" line (Home node 102:2747). */
.isv-card__date {
    display: var(--card-date-display, inline);
    font-size: var(--fs-body-xs);
    font-weight: var(--fw-book);
    line-height: var(--lh-subhead);
    color: var(--card-date-color);
    white-space: nowrap;
}

.isv-card__title {
    margin: 0;
    font-family: var(--font-body);
    font-size: var(--card-title-fs);
    font-weight: var(--card-title-fw);
    line-height: var(--lh-subhead);
    color: var(--card-title-color);
    transition: color .15s ease;
}

.isv-card__summary {
    color: var(--card-summary-color);
    font-family: var(--font-body);
    font-weight: var(--fw-book);
    font-size: var(--fs-body-sm);
    line-height: var(--lh-body);
}

.isv-card__summary:empty { display: none; }        /* no gap when description is blank */
.isv-card__summary > :first-child { margin-top: 0; }
.isv-card__summary > :last-child { margin-bottom: 0; }

/* ---- Looks (var overrides). Each look is written ONCE, applied at BOTH the
   per-card level (.isv-card--X) and the group-cascade level
   (.cat-group--X .isv-card:not(.isv-card--styled)). ----------------------- */

/* Category = base defaults; re-asserted so an explicit Category card inside a
   non-category group still renders the panel look. */
.isv-card--category { /* uses base vars (category) */ }

/* Article card — borderless image + tag/date meta + title + clamped summary. */
.isv-card--article,
.cat-group--article .isv-card:not(.isv-card--styled),
.isv-cards--article .isv-card:not(.isv-card--styled) {
    --card-link-gap: 15px;
    --card-body-gap: 10px;
    --card-bg: transparent;
    --card-pad: 0;
    --card-radius: 0;
    --card-hover-shadow: none;
    --card-hover-transform: none;
    --card-top-display: none;
    --card-media-display: block;
    --card-media-radius: 0;
    --card-media-bg: var(--surface-muted);
    --card-meta-display: flex;
    --card-bookmark-display: inline-flex;   /* Figma 102:5378 puts the bookmark box opposite the tags */
    /* Lighter than --text-muted (#888): Figma 102:5378 draws the card date at #a3a3a3.
       Design-fidelity call — it is 2.53:1 on white, so it sits with the rebrand's
       known AA debt rather than passing it. Article look only; Grid keeps its own. */
    --card-date-color: #a3a3a3;
    --card-title-fs: var(--fs-h5);
    --card-title-fw: var(--fw-medium);
    --card-title-color: #000;
}

/* Grid — larger 3-up article card, rounded media, tag pills + date. */
.isv-card--grid,
.cat-group--grid .isv-card:not(.isv-card--styled),
.isv-cards--grid .isv-card:not(.isv-card--styled) {
    --card-link-gap: 12px;
    --card-body-gap: 12px;
    --card-bg: transparent;
    --card-pad: 0;
    --card-radius: 0;
    --card-fg: var(--text-strong);
    --card-hover-shadow: none;
    --card-hover-transform: none;
    --card-top-display: none;
    --card-media-display: block;
    --card-media-radius: 8px;
    --card-meta-display: flex;
    --card-tags-display: inline-flex;
    --card-date-color: rgba(0, 0, 0, .5);
    --card-title-fs: var(--fs-h4);
    --card-title-fw: var(--fw-medium);
    --card-title-color: var(--text-strong);
    --card-title-hover-color: var(--brand-primary-hover);   /* Grid card title turns Deep Blue on hover */
    --card-summary-color: rgba(0, 0, 0, .7);
}

/* Text — no media, left teal accent (listing "All Resources"). */
.isv-card--text,
.cat-group--text .isv-card:not(.isv-card--styled),
.isv-cards--text .isv-card:not(.isv-card--styled) {
    --card-link-gap: 15px;
    --card-body-gap: 10px;
    --card-bg: transparent;
    --card-pad: 0;
    --card-radius: 0;
    --card-hover-shadow: none;
    --card-hover-transform: none;
    --card-top-display: none;
    --card-media-display: none;
    --card-meta-display: flex;
    --card-title-fs: var(--fs-h5);
    --card-title-fw: var(--fw-medium);
    --card-title-color: #000;
}

.isv-card--text,
.cat-group--text .isv-card:not(.isv-card--styled),
.isv-cards--text .isv-card:not(.isv-card--styled) {
    border-left: 4px solid var(--brand-soft);
    padding-left: 18px;
}

/* Resources — filled PANEL (same panel as the Category look: base bg/pad/radius/hover) but
   leading with the page's category tag pill + a bookmark instead of an icon, then the
   title and a clamped summary. Wired to the Listing Result Card Grid "Resources cards" Style
   variant, which stamps .isv-cards--resources on the grid so its bare cards inherit this look. */
.isv-card--resources,
.cat-group--resources .isv-card:not(.isv-card--styled),
.isv-cards--resources .isv-card:not(.isv-card--styled) {
    --card-body-gap: 12px;
    --card-top-display: none;               /* no icon row */
    --card-media-display: none;             /* panel — no image */
    --card-meta-display: flex;              /* the tag pill row */
    --card-bookmark-display: inline-flex;   /* bookmark, opposite the tags */
    --card-date-display: none;              /* no date in this look */
    --card-title-fs: var(--fs-h4);
    --card-title-fw: var(--fw-medium);
    --card-title-color: var(--text-strong);
    --card-title-hover-color: var(--brand-primary-hover); /* title turns Deep Blue on hover */
}

/* Search result rows — the full-width row from Figma 237:7081: a soft filled panel laid out as
   [icon] [title + snippet] [category pill], one per line. Wired to the Search Results element's
   default "Search result rows" Style variant, which stamps .isv-cards--searchrow.

   The ONE look that changes the card's LAYOUT rather than just its colours, because the tag pill has
   to move from above the title (where every other look puts it) to a right-hand column. Rather than
   fork _IsvCard.cshtml's markup, .isv-card__body is set to display:contents so its children become
   grid items of the link and can be placed directly — the markup stays shared, the layout differs. */
.isv-card--searchrow,
.isv-cards--searchrow .isv-card:not(.isv-card--styled) {
    --card-bg: var(--surface-muted);
    --card-pad: 16px 20px;
    --card-radius: 8px;
    --card-link-gap: 0;
    --card-hover-shadow: none;
    --card-hover-transform: none;
    --card-top-display: flex;               /* the leading type icon */
    --card-media-display: none;             /* a row, never an image */
    --card-meta-display: inline-flex;       /* the category pill, moved right below */
    --card-date-display: none;              /* the design shows no date on a result row */
    --card-bookmark-display: none;
    --card-title-fs: var(--fs-h5);
    --card-title-fw: var(--fw-medium);
    --card-title-color: var(--text-strong);
    --card-title-hover-color: var(--brand-primary-hover);
}

/* Rows sit in a 1-up grid, so kill the equal-height stretch the card grid gives tiles. */
.isv-cards--searchrow .isv-card:not(.isv-card--styled),
.isv-card--searchrow {
    height: auto;
}

.isv-cards--searchrow .isv-card:not(.isv-card--styled) .isv-card__link,
.isv-card--searchrow .isv-card__link {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    grid-template-rows: auto auto;
    align-items: center;
    column-gap: 16px;
    row-gap: 2px;
    height: auto;
}

/* Lift the body's children into the link's grid so the meta row can leave the text column. */
.isv-cards--searchrow .isv-card:not(.isv-card--styled) .isv-card__body,
.isv-card--searchrow .isv-card__body {
    display: contents;
}

.isv-cards--searchrow .isv-card:not(.isv-card--styled) .isv-card__top,
.isv-card--searchrow .isv-card__top {
    grid-column: 1;
    grid-row: 1 / span 2;
}

.isv-cards--searchrow .isv-card:not(.isv-card--styled) .isv-card__icon,
.isv-card--searchrow .isv-card__icon {
    font-size: 22px;
    color: var(--blue-doc);
}

/* A listing/hub result is a "topic" — flagged maroon, as in the design's TOPICS row. */
.isv-cards--searchrow .isv-card:not(.isv-card--styled) .isv-card__icon.fa-clipboard-check,
.isv-card--searchrow .isv-card__icon.fa-clipboard-check {
    /* Deep, not Royal: this sits beside the --blue-doc file icon, which is 3 degrees of hue away. */
    color: var(--brand-primary-strong);
}

.isv-cards--searchrow .isv-card:not(.isv-card--styled) .isv-card__title,
.isv-card--searchrow .isv-card__title {
    grid-column: 2;
    grid-row: 1;
}

.isv-cards--searchrow .isv-card:not(.isv-card--styled) .isv-card__summary,
.isv-card--searchrow .isv-card__summary {
    grid-column: 2;
    grid-row: 2;
    font-size: var(--fs-body-sm);
    /* One line, ellipsised — the design's rows are a fixed height. */
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.isv-cards--searchrow .isv-card:not(.isv-card--styled) .isv-card__meta,
.isv-card--searchrow .isv-card__meta {
    grid-column: 3;
    grid-row: 1 / span 2;
    justify-self: end;
}

/* The matched-term highlight. Graph tells us which words matched (including stems), and
   GraphSearchService wraps them in <mark> AFTER encoding — see its remarks. Restyled from the browser
   default yellow block to the design's teal emphasis. */
.isv-card__title mark,
.isv-card__summary mark {
    background: none;
    color: var(--brand-primary);
    font-weight: inherit;
}

/* Clamp the summary teaser to 3 lines for the image/text article + resources looks. */
.isv-card--article .isv-card__summary,
.cat-group--article .isv-card:not(.isv-card--styled) .isv-card__summary,
.isv-cards--article .isv-card:not(.isv-card--styled) .isv-card__summary,
.cat-group--text .isv-card:not(.isv-card--styled) .isv-card__summary,
.isv-cards--text .isv-card:not(.isv-card--styled) .isv-card__summary,
.isv-card--text .isv-card__summary,
.cat-group--resources .isv-card:not(.isv-card--styled) .isv-card__summary,
.isv-cards--resources .isv-card:not(.isv-card--styled) .isv-card__summary,
.isv-card--resources .isv-card__summary {
    display: -webkit-box;
    -webkit-line-clamp: 3;
    line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.hero-block {
    display: flex;
    flex-direction: column;
    gap: 10px;
    color: var(--white);
}

.hero-block__heading {
    margin: 0;
}

/* Strip the rich-text wrapper's default margins so the subtext sits tight under
   the heading (gap is owned by .hero-block). */
.hero-block__body > :first-child {
    margin-top: 0;
}

.hero-block__body > :last-child {
    margin-bottom: 0;
}

/* ---- Heading + Text (generic element, default variant) -------------------- */
.heading-text {
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.heading-text__heading {
    margin: 0;
}

/* Strip the rich-text wrapper's default outer margins so spacing is owned by .heading-text. */
.heading-text__body > :first-child {
    margin-top: 0;
}

.heading-text__body > :last-child {
    margin-bottom: 0;
}

/* ---- Generic Heading + Text element — self-contained framing --------------
   The element is droppable into ANY section (the VB add-element picker can't be
   scoped per section), so it can land in a FRAMELESS full-width section — e.g.
   inside the Listing Section above the grid — where nothing constrains it and it
   bled to the viewport edge. So the (dark-text) variants frame THEMSELVES to the
   shared 1330 content box with the standard gutters + vertical breathing room,
   matching how the listing element (.listing) self-frames. Where an ancestor
   ALREADY owns the frame — the Header / Hero bands, or the narrow article column —
   the self-frame is neutralised so widths/padding don't double up. The Hero
   variant (.hero-block) is intentionally excluded: it's white-on-dark and only
   reads inside its dark band. */
.heading-text,
.header-content,
.section-header,
.article-header {
    max-width: 1330px;
    margin-inline: auto;
    padding: var(--sp-v-medium) var(--sp-h-large);
}

.header-section__body :is(.heading-text, .header-content, .section-header, .article-header),
.hero-band__inner :is(.heading-text, .header-content, .section-header, .article-header),
.article__main :is(.heading-text, .header-content, .section-header, .article-header) {
    max-width: none;
    margin-inline: 0;
    padding: 0;
}

/* ---- Filled (primary) button ----------------------------------------------
   Deep Blue, not Royal: this fill carries WHITE text (7.10:1 vs 3.80:1). */
.btn--primary {
    background: var(--brand-primary-strong);
    color: var(--white);
    border: 1px solid var(--brand-primary-strong);
}

.btn--primary:hover {
    background: var(--brand-primary-hover);
    border-color: var(--brand-primary-hover);
    color: var(--white);
}

/* ---- Header Section (HeaderSection + its elements) ------------------------
   Figma node 212:6393: light-grey-cool banner, light-grey border, pt 30 / pb 40 /
   px 60. Contained to the same 1330 frame as nav/footer. Breadcrumbs auto on top;
   editable content (Header Content + CTA Buttons) are VB elements composed below. */
.header-section {
    max-width: 1330px;
    margin: 0 auto;
}

.header-section__inner {
    display: flex;
    flex-direction: column;
    gap: 10px;   /* Figma 212:6393 gap-10 — 30 + 34 + 10 + 94 + 40 closes its 208 height exactly */
    background: var(--surface-cool);
    border: 1px solid var(--border-hairline);
    padding: 30px var(--sp-h-large) var(--sp-v-medium);
}

/* Article: flat header — no tinted band, no inner padding, so it sits on white and fills the
   content column (the breadcrumb/heading align with the column edge, no empty box). */
.header-section--flat .header-section__inner {
    background: transparent;
    border: 0;
    padding: 0;
}

/* Top row: breadcrumb (left) opposite the share action (right). */
.header-section__top {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
}

/* The 34px square icon button (Figma 191:14917). ONE definition for every icon in the article
   topbar's action row — share and bookmark from the markup, plus the back-to-top and jump-to
   controls site.js appends there, which arrive with the rail-bar's own class names. */
.header-section__share,
.article__icon-btn,
.contents-rail-bar--inline .contents-rail-bar__btn {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    padding: 0;
    border: 1px solid var(--border-accent);
    border-radius: 6px;
    background: var(--white);
    color: var(--brand-primary);
    font-size: 16px;
    text-decoration: none;   /* back-to-the-top is an <a> */
    cursor: pointer;
}

.header-section__share:hover,
.article__icon-btn:hover,
.contents-rail-bar--inline .contents-rail-bar__btn:hover {
    background: var(--surface-cool);
}

/* The ≡ section-navigation toggle (Figma 191:14915) — the same 34px square, in the maroon of the
   side-nav eyebrow it opens, so it reads as that rail's control and not another page action. */
.article__icon-btn--nav {
    color: var(--icon-accent);
}

/* The action group itself — right-aligned in the topbar, 12px between icons (Figma 1739:8585,
   which binds the "Spacing/Buttons" variable). .article__actions holds share + bookmark; the
   appended .contents-rail-bar--inline contributes the other two on the touch band, so the row
   reads as one continuous set of four at the same rhythm. */
.article__actions {
    flex: 0 0 auto;   /* the icons never shrink — the breadcrumb beside them absorbs the squeeze */
    margin-left: auto;   /* hard right, whatever sits to the left (see .article__topbar) */
    display: flex;
    align-items: center;
    gap: 12px;
}

/* The element content renders through the epi-grid composition (grid → row → column →
   element wrappers, all class-less divs). Lay rows in a column, columns in a row, and
   stack the elements within a column. */
.header-section__body > div { display: flex; flex-direction: column; gap: 16px; }      /* grid → rows */
.header-section__body > div > div { display: flex; flex-wrap: wrap; gap: 16px; }       /* row → columns */
.header-section__body > div > div > div {                                              /* column → elements */
    display: flex;
    flex-direction: column;
    gap: 12px;
    align-items: flex-start;
    flex: 1 1 auto;
    min-width: 0;
}
/* CTA buttons sit in a wrapping row even when stacked in one column. */
.header-section__body div:has(> a.btn) { display: inline-flex; }

/* Header Content element (heading + description). */
.header-content {
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.header-content__heading {
    margin: 0;
    /* Deep Blue, NOT --text-strong: Figma 212:6397 is text-[color:var(--deep-blue,#2756a0)].
       Only the SUBTEXT below is black. --brand-deep is the layer-2 role for on-light emphasis
       (7.10:1 on the light-grey-cool band), so no layer-1 hex is referenced here. */
    color: var(--brand-deep);
}

.header-content__description { color: var(--black); }
.header-content__description > :first-child { margin-top: 0; }
.header-content__description > :last-child { margin-bottom: 0; }

/* ---- Breadcrumbs ----------------------------------------------------------
   Figma "Button icon text" chips: white, --border-accent hairline, Royal Blue text, rounded. */
.breadcrumbs__list {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    list-style: none;
    margin: 0;
    padding: 0;
}

.breadcrumbs__item {
    display: inline-flex;
    align-items: center;
    gap: 8px;
}

/* Chevron separator between crumbs. The family name must match one the self-hosted Font Awesome
   Pro 7 stylesheet actually declares — it defines "Font Awesome 7 Pro" (plus 7 Free/Brands/Duotone
   and a 5 Pro shim) and NOT the "Font Awesome 6 Free" this used to name, which rendered nothing. */
.breadcrumbs__item + .breadcrumbs__item::before {
    content: "\f054"; /* fa chevron-right */
    font-family: "Font Awesome 7 Pro";
    font-weight: 900;
    font-size: 10px;
    color: var(--grey);
}

.breadcrumbs__crumb {
    display: inline-flex;
    align-items: center;
    /* Figma "Button icon text" component (I212:6395) — gap 10, pt 6 / pb 7 / px 10, and
       Stevie Sans REGULAR 550, i.e. the .t-label interactive role, not Medium 600. */
    gap: 10px;
    padding: 6px 10px 7px;
    border: 1px solid var(--border-accent);
    border-radius: 6px;
    background: var(--white);
    color: var(--brand-primary);
    font-weight: var(--fw-regular);
    font-size: var(--fs-body-sm);
    text-decoration: none;
}

a.breadcrumbs__crumb:hover {
    background: var(--surface-cool);
}

.breadcrumbs__crumb--current {
    background: transparent;
    border-color: transparent;
    padding-left: 0;
    padding-right: 0;
    color: var(--body-text);
}

/* ---- "Back" crumb (article/Content topbar only) ---------------------------
   Figma 191:14913 reads the topbar crumb as a BACK control — "← Strategic Planning" where the row
   has room, "← Back" where it does not. The arrow is injected by site.js (initBackCrumb), which
   also does the label swap; here it just gets the icon's size/colour. Scoped to .article__topbar
   so the same Breadcrumb component keeps its plain trail inside header sections. */
.article__topbar .breadcrumbs__arrow {
    flex: 0 0 auto;
    font-size: 13px;
    color: var(--brand-primary);
}

/* Collapsed to "Back": the pill is now short enough that shrinking it further only makes it
   unreadable, so let it hold its natural width instead of absorbing the row's squeeze. */
.article__topbar .breadcrumbs__crumb--back {
    flex: 0 0 auto;
    white-space: nowrap;
}

/* ---- Search band (SearchSection + ResourcesBlock + RecentlyExploredBlock) --
   Figma node 102:2754: light band, two equal columns split by a vertical divider,
   px 60 / py 40, 40px gap. Contained to the same 1330 frame as nav/footer, with the
   matching light-grey side/bottom border. Background colour is set inline from the
   section's BackgroundColor. */
.search-band {
    max-width: 1330px;
    margin: 0 auto;
    background: var(--surface-cool);
    border-bottom: 1px solid var(--border-hairline);
    border-left: 1px solid var(--border-hairline);
    border-right: 1px solid var(--border-hairline);
}

.search-band__inner {
    padding: var(--sp-v-medium) var(--sp-h-large);
    display: flex;
    align-items: stretch;
    gap: var(--sp-v-medium);
}

.search-band__col {
    flex: 1 1 0;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 20px;
}

.search-band__divider {
    flex: 0 0 1px;
    align-self: stretch;
    background: var(--border-hairline);
}

/* Shared panel layout (both columns) ---------------------------------------- */
.search-panel {
    display: flex;
    flex-direction: column;
    gap: 20px;
    height: 100%;
}

.search-panel__head {
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.search-panel__topline {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--sp-grid);
}

.search-panel__icon {
    /* Figma nodes 102:2758 / 102:2772 draw these in Font Awesome Pro LIGHT at 32px. The glyph
       class is CMS-authored (an editor picks "fa-solid fa-clipboard-list"), so the cut has to be
       forced here rather than in the markup — --fa-style is FA7's weight switch and thins whatever
       the author chose. Same device as .isv-card__icon; it out-ranks .fa-solid's own --fa-style
       only because site.css loads after all.min.css, so keep that order. */
    --fa-style: 300;
    font-size: 32px;
    line-height: 1;
    color: var(--icon-accent);
}

.search-panel__title {
    margin: 0;
    color: var(--isv-fg);
}

.search-panel__desc {
    margin: 0;
    color: var(--body-text);
}

/* The white rounded "view all …" link button (Figma "Button icon text").
   Type comes from the .t-label role in the markup (Figma "Body small regular" —
   Stevie Sans 550 / 14px / 150%); this rule owns only box + colour. */
.pill-link {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    flex: 0 0 auto;
    background: var(--white);
    color: var(--brand-primary);
    text-decoration: none;
    padding: 6px 10px;
    border-radius: 6px;
}

.pill-link:hover {
    color: var(--brand-primary-hover);
}

/* Resources: search field + popular-search pills (static mock) -------------- */
.search-panel__search {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.search-field {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    max-width: 565px;
    background: var(--white);
    /* Royal Blue tint hairline — Figma card component 67:4949. */
    border: 1px solid var(--border-accent);
    border-radius: 10px;
    padding: 7px 7px 7px 15px;
}

.search-field__input {
    flex: 1 1 auto;
    min-width: 0;
    border: 0;
    background: transparent;
    outline: none;
    color: var(--isv-fg);
    padding: 0;
}

.search-field__input::placeholder {
    color: var(--grey);
}

.search-field__btn {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    background: var(--white);
    border: 1px solid var(--border-accent);
    border-radius: 6px;
    color: var(--brand-primary);
    cursor: pointer;
}

/* Rendered only when no search page is nominated on the start page, so the field has nowhere to
   submit (see IHasSiteSearch / SiteSearchViewComponent, which also logs a warning). It must LOOK
   unavailable — a live-looking magnifier that silently does nothing is the whole reason this state
   was hard to diagnose. */
.search-field__btn:disabled {
    color: var(--grey);
    border-color: var(--border-hairline);
    cursor: not-allowed;
}

.search-tags {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
}

/* Same author-vs-UA problem as .explore-tags[hidden] below: the home panel's recent-search shell
   (data-home-search-recent) ships `hidden`, and without this a visitor with no search history got
   an unexplained gap in the Resources column. */
.search-tags[hidden] {
    display: none;
}

.search-tag {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    background: var(--white);
    color: var(--brand-primary);
    /* Figma "Button icon text" — Body small regular (Stevie Sans Regular 550), matching
       .pill-link / .t-label. Was --fw-medium 600, half a cut too heavy. */
    font-weight: var(--fw-regular);
    text-decoration: none;
    padding: 6px 10px;
    border-radius: 6px;
}

.search-tag:hover {
    color: var(--brand-primary-hover);
}

/* Recently explored: item pills -------------------------------------------- */
/* Built by renderRecentExplored() from this visitor's own localStorage history — the server
   never sees it, so the shell below ships empty. */
.explore-tags {
    display: flex;
    flex-wrap: wrap;
    align-content: flex-start;
    gap: 12px;
}

/* Load-bearing: the rule above is an AUTHOR declaration, which outranks the UA's
   [hidden] { display: none } no matter the specificity. Without this the empty shell stays
   display:flex and still eats its slot in .search-panel's 20px column gap — an unexplained hole
   above the empty-state line. Same fix as .site-search-modal__recent[hidden] further down. */
.explore-tags[hidden] {
    display: none;
}

.explore-tag {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    background: var(--white);
    color: var(--isv-fg);
    text-decoration: none;
    padding: 10px 15px;
    border-radius: 12px;
    /* A long card title is a real possibility now these are real pages, not three curated mocks. */
    max-width: 100%;
}

/* These are real links now, so they need a hover; as mocks pointing at "#" they had none. */
.explore-tag:hover {
    color: var(--brand-primary);
}

/* Shown until this visitor has any history — see RecentlyExploredBlock.EmptyStateText. */
.explore-empty {
    margin: 0;
    color: var(--text-muted);
}

.explore-tag__icon {
    /* Figma node 102:2783 — Font Awesome Pro REGULAR at 16px. The markup hardcodes fa-solid,
       so thin it here (see .search-panel__icon for why the cut is forced in CSS). */
    --fa-style: 400;
    flex: 0 0 auto;
    width: 16px;
    text-align: center;
    color: var(--icon-accent);
}

/* ---- Top navigation ------------------------------------------------------- */
.site-header {
    /* Figma: full-width --surface-muted bar with a bottom border. The 55px side
       gutters are this grey showing beside the centred, bordered content box. */
    background: var(--surface-muted);
    border-bottom: 1px solid var(--border-hairline);
    /* Auto-hiding sticky header (Amazon-style): sticks to the top, slides up when
       scrolling down, slides back in when scrolling up — toggled by site.js. */
    position: sticky;
    top: 0;
    z-index: 100;
    transition: transform 0.25s ease;
}

.site-header--hidden {
    transform: translateY(-100%);
}

.site-nav__inner {
    /* The 1330px content box, centred (→ 55px gutters at 1440). No side borders:
       the header/footer read as clean full-width bands.
       VERTICAL padding only — Figma's nav inner (102:2732) is w-1330 / py-24 with no side pad,
       which is what puts the logo exactly on the hero band's left edge. A horizontal 24px here
       pushed the logo and the action icons 24px inside the frame, out of line with every band
       below. The ≤720px rule sets its own padding-inline and still wins. */
    max-width: 1330px;
    margin: 0 auto;
    padding: 24px 0;
    display: flex;
    align-items: center;
    gap: var(--sp-grid);
}

.site-nav__links {
    display: flex;
    align-items: center;
    gap: var(--sp-grid);   /* Figma 47:1701 — 30px between primary links */
    margin: 0;
    padding: 0;
    list-style: none;
}

.site-nav__links a {
    text-decoration: none;
    font-weight: var(--fw-medium);
    color: var(--text-body);
}

.site-nav__links a:hover {
    color: var(--brand-primary);
}

.site-nav__actions {
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: 12px;   /* Figma "Spacing/Buttons" */
}

/* Figma 47:1706-1709 — all four nav actions are Font Awesome Pro REGULAR at 16px. Their glyph
   classes are CMS-authored (NavActionBlock.Icon, no code default), so an author typing
   "fa-solid …" for one of them silently breaks the row's consistency — which is what happened to
   the magnifier. Force the cut for the slot instead of chasing the content. Scoped to a.icon-btn
   so the burger <button> (mobile-only, not in this Figma row) is left alone. */
.site-nav__actions a.icon-btn {
    --fa-style: 400;
}

/* Burger + its panel are the mobile face of .site-nav__links — both off above the
   720px band (see the mobile media query, which swaps links out for these). */
.site-nav__burger,
.site-nav__menu {
    display: none;
}

.site-nav__menu-links {
    margin: 0;
    padding: 0;
    list-style: none;
}

.site-nav__menu-links a {
    display: block;
    padding: 14px 20px;
    text-decoration: none;
    font-weight: var(--fw-medium);
    color: var(--text-body);
    border-bottom: 1px solid var(--border-hairline);
}

.site-nav__menu-links li:last-child a {
    border-bottom: 0;
}

.site-nav__menu-links a:hover {
    color: var(--brand-primary);
    background: var(--surface-cool);
}

/* ---- Footer --------------------------------------------------------------- */
.site-footer {
    background: var(--surface-muted);
    color: var(--text-body);
    border-top: 1px solid var(--border-hairline);
}

.site-footer__inner {
    /* Mirror the header frame: a centred 1330px box, no vertical side borders —
       only the footer's full-width border-top separates it from the content.
       VERTICAL padding only, like .site-nav__inner: Figma's footer row (87:1364) is py-50 with
       no side pad, so the footer logo lands on the same line as the nav logo and the hero band's
       left edge. Column gap is the 30px grid unit (--sp-grid), not --sp-h-large. */
    max-width: 1330px;
    margin: 0 auto;
    padding: var(--sp-v-large) 0;
    display: grid;
    grid-template-columns: 1.2fr 2fr 1.2fr;
    gap: var(--sp-grid);
}

/* Side gutter for the chrome BELOW the design width. The nav and footer sit flush to the 1330
   frame edge (Figma), which is only safe once the viewport is wide enough for that edge to be a
   real margin — the frame is `max-width: 1330px; margin: 0 auto`, so its gutters are 0 at 1330px
   and only reach Figma's 55px at 1440. Without this, every laptop width between (1280, 1366 …)
   renders the logo and the footer hard against the browser edge. Below the design width, fall
   back to the same --sp-h-large the content bands use, so the chrome lines up with the hero and
   section text instead. The ≤720px rule further down sets 20px and still wins (later, same
   specificity) — keep this block above it. */
@media (max-width: 1439px) {
    .site-nav__inner,
    .site-footer__inner {
        padding-inline: var(--sp-h-large);
    }
}

/* Grid items default to min-width:auto, so a long unbroken link or address can
   push a column past the frame and clip the rest. */
.site-footer__brand,
.site-footer__col {
    min-width: 0;
}

.footer-logo {
    display: flex;
    align-items: center;
    gap: 12px;
}

.footer-logo__mark {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 40px;
    background: var(--brand-deep);
    color: var(--white);
    border-radius: 3px;
    font-weight: var(--fw-medium);
    clip-path: polygon(0 0, 100% 0, 100% 70%, 70% 100%, 0 100%);
}

.footer-logo__name {
    font-size: 15px;
    line-height: 1.2;
}

.footer-logo__name strong {
    color: var(--brand-deep);
}

.footer-logo__name span {
    color: var(--body-text);
}

.site-footer__social {
    display: flex;
    /* Figma "Spacing/Buttons" (12px) between the marks; 30px below the logo (87:1365). */
    gap: 12px;
    margin: var(--sp-grid) 0;
    padding: 0;
    list-style: none;
}

.site-footer__social a {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    border-radius: 6px;
    background: var(--white);
    /* NO border — Figma 102:2886 draws these as plain white 6px squares, like .icon-btn.
       The bordered 34px box (--border-accent hairline) is a DIFFERENT component: the card
       bookmark and search-field button. Don't merge the two. */
    border: 0;
    /* Figma 102:2886 — the footer social buttons carry a Royal Blue glyph. */
    color: var(--icon-accent);
    text-decoration: none;
    transition: background-color .15s ease;
}

.site-footer__social a:hover {
    color: var(--brand-primary-hover);
    background: var(--surface-cool);
}

.site-footer__heading {
    font-family: var(--font-eyebrow);
    font-size: 12px;
    font-weight: var(--fw-eyebrow);
    line-height: 1.5;
    letter-spacing: 1.2px;
    text-transform: uppercase;
    color: var(--brand-primary);
    margin: 0 0 18px;
}

.site-footer__links {
    display: flex;
    flex-wrap: wrap;
    /* Row gap matches the 12px between links, so when the chunks stack (narrow
       viewports) the seam between two lists reads as one continuous list. */
    gap: 12px var(--sp-grid);
}

/* Each chunk of (up to) five links is one equal-width column. */
.site-footer__links > .footer-links {
    flex: 1 1 0;
    min-width: 150px;
}

.footer-links {
    margin: 0;
    padding: 0;
    list-style: none;
}

.footer-links li {
    /* Figma: 12px gap between links (node 87:1364). */
    margin-bottom: 12px;
    line-height: var(--lh-body);
}

.footer-links li:last-child {
    margin-bottom: 0;
}

.footer-links a {
    text-decoration: none;
    /* Figma node 87:1364 — Stevie Sans Book 14 / 150%, body-text grey. */
    color: var(--text-body);
    font-size: var(--fs-body-sm);
    font-weight: var(--fw-book);
}

.footer-links a:hover {
    color: var(--brand-primary);
}

.site-footer__contact {
    margin: 0;
    padding: 0;
    list-style: none;
}

.site-footer__contact li {
    display: flex;
    gap: 12px;
    margin-bottom: 14px;
    font-size: var(--fs-body-sm);
    line-height: 1.5;
}

.site-footer__contact i {
    /* Figma 102:2886 — the footer contact glyphs are body grey, not brand. */
    color: var(--text-body);
    margin-top: 3px;
}

.site-footer__contact a {
    text-decoration: none;
    color: var(--text-body);
}

.site-footer__contact a:hover {
    color: var(--brand-primary);
}

/* Footer media (uploaded logo / social images) */
.footer-logo img {
    height: 48px;
    width: auto;
    max-width: 240px;
    object-fit: contain;
    display: block;
}

.site-footer__social a img {
    width: 18px;
    height: 18px;
    object-fit: contain;
}

/* ---- Contact Us block (footer + reusable) --------------------------------- */
.contact-us__heading {
    font-family: var(--font-eyebrow);
    font-size: 12px;
    font-weight: var(--fw-eyebrow);
    line-height: 1.5;
    letter-spacing: 1.2px;
    text-transform: uppercase;
    color: var(--brand-primary);
    margin: 0 0 18px;
}

.contact-us__items {
    margin: 0;
    padding: 0;
    list-style: none;
}

.contact-us__item {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    margin-bottom: 8px;
    /* Figma node 87:1364 — Stevie Sans Book 14 / 150%, body-text grey. */
    color: var(--text-body);
    font-size: var(--fs-body-sm);
    font-weight: var(--fw-book);
    line-height: var(--lh-body);
}

.contact-us__icon {
    flex: 0 0 auto;
    color: var(--text-body);
    /* nudge so the glyph centres on the first line of text */
    margin-top: 1px;
}

.contact-us__icon img {
    width: 16px;
    height: 16px;
    object-fit: contain;
}

/* XhtmlString renders the text inside <p>; strip its default margins so the
   text aligns with the icon and items don't gain extra vertical space. */
.contact-us__text > :first-child {
    margin-top: 0;
}

.contact-us__text > :last-child {
    margin-bottom: 0;
}

.contact-us__text a {
    text-decoration: none;
    color: var(--text-body);
}

.contact-us__text a:hover {
    color: var(--brand-primary);
}

/* ---- Category groups (CategoryGroupSection + CategoryCardBlock) ----------
   Figma node 102:2807: each titled group is its OWN full-width section (white, 1330
   frame) with a heading + description and a 3-up card grid. A single page-level rail
   (.contents-rail--fixed) is fixed to the right of the frame and aggregates every group. */
.cat-group {
    max-width: 1330px;
    margin: 0 auto;
    background: var(--white);
    border-left: 1px solid var(--border-hairline);     /* frame the region (matches nav/footer/search) */
    border-right: 1px solid var(--border-hairline);
    scroll-margin-top: var(--header-h);   /* clears the sticky header when jumped to via anchor */
}

/* The first group sits flush under the Search band (its border-bottom is the divider) —
   no top border, no gap. The last group closes the frame bottom. */
.cat-group--last {
    border-bottom: 1px solid var(--border-hairline);
}

/* Separator between groups — a content-width line above the heading (Figma node 102:2820:
   border-top + 30px). Drawn on the head so it insets to the card column, not the frame edge. */
.cat-group--sep .cat-group__head {
    border-top: 1px solid var(--border-hairline);
    padding-top: var(--sp-grid);
}

/* Separated groups start flush at the band top, so the separator sits ~30px below the
   previous group's cards (the previous group's 30px bottom padding) instead of ~80px. */
.cat-group--sep .cat-group__inner {
    padding-top: 0;
}

.cat-group__inner {
    /* top 40 (gap below the Search band) · right = rail gutter · bottom 30 · left 60.
       --cat-rail-gutter reserves the fixed contents rail's 250px (Figma node 102:3270) plus the
       30px --sp-grid that separates it from the card column. On the 1330 frame that leaves a
       990px content column — exactly Figma's Content area (102:2807) — so the 3-up cards land on
       310px, the literal width of every Category card instance in the file. Keep this in step with
       .contents-rail--fixed's width: the two numbers are one piece of geometry.
       It collapses to the normal side pad when the rail isn't actually shown (see the reclaim rule
       below) so the cards fill the frame instead of leaving a dead column on the right. */
    --cat-rail-gutter: 280px;
    padding: var(--sp-v-medium) var(--cat-rail-gutter) var(--sp-grid) var(--sp-h-large);
}

/* Reclaim the rail gutter whenever the fixed contents rail isn't actually on screen — either the
   page turned it Off (no .contents-rail--fixed in the DOM) or it has no opted-in sections (site.js
   never adds .contents-rail--on). Content then flexes to fill the space the rail would have taken.
   Full-width looks (--article/--grid) and article-embedded groups set padding-right / padding-inline
   directly (higher specificity than the shorthand), so they're unaffected in either state. */
body:not(:has(.contents-rail--fixed.contents-rail--on)) .cat-group__inner {
    --cat-rail-gutter: var(--sp-h-large);
}

/* Image-card group looks (Article / Grid) are full-width feature strips — they don't sit
   beside the contents rail, so reclaim its 300px right gutter. Effect: the 3-up cards use
   the full content width (bigger, matching the listing grid) AND the group heading +
   separator line span the full width (a top divider, not a short line inset on the left).
   Category groups keep the gutter for the rail. If a full-width group shares a page with
   the rail, turn OFF its "Show in contents rail" so the rail doesn't overlap the cards. */
.cat-group--article .cat-group__inner,
.cat-group--grid .cat-group__inner {
    padding-right: var(--sp-h-large);
}

/* Inside an article body the content column (.article__main) already insets by 60px and supplies
   its OWN right rail (.article__aside) — so a category group here needs neither the 300px homepage
   contents-rail gutter nor its own side padding, or the cards get crushed into a sliver of the
   already-narrow column (worse still when a side-nav pane is also present). Zero the side padding
   for EVERY look so the cards fill the content column, aligned to the rich-text edge. (Homepage
   groups keep the gutter for their fixed contents rail — they have no .article__main ancestor.) */
.article__main .cat-group__inner {
    padding-inline: 0;
    /* Figma 224:7218 puts 40px between the body and the section's top rule, then closes flush at
       the bottom (the article's own 50px tail follows). The outline wrapper already contributes
       30px of that, so top-up by the difference rather than restating 40 and double-spacing. */
    padding-block: calc(var(--sp-v-medium) - var(--sp-grid)) 0;
}

/* ...and they're standalone strips (e.g. inside an article body), NOT part of the homepage's
   framed "explore by category" column — so drop the frame borders entirely and use a separator line
   ON TOP (above the heading) instead of lines boxing the strip in.
   border-bottom matters as much as the sides: site.js stamps --last on the LAST [data-cat-group] on
   any page, so a lone strip in an article body is both --first and --last and closed the frame
   bottom it never opened — a second rule under the cards. Later than .cat-group--last (same
   specificity), so it wins; keep it after. */
.cat-group--article,
.cat-group--grid {
    border-left: 0;
    border-right: 0;
    border-bottom: 0;
}

.cat-group--article .cat-group__head,
.cat-group--grid .cat-group__head {
    border-top: 1px solid var(--border-hairline);
    padding-top: var(--sp-grid);
}

/* "Hide separator line" ticked in the group's Style panel (the hideSeparator display setting —
   see ISV.Web.Rendering.DisplaySettings). Drops the rule from BOTH routes above: the between-groups
   line site.js adds as --sep, and the top rule the Article/Grid looks always draw. Same specificity
   as those two, so it wins on source order — keep it after them.
   The border ONLY: every padding stays, so turning the line off never re-flows the group. */
.cat-group--nosep .cat-group__head {
    border-top: 0;
}

.cat-group__head {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin-bottom: var(--sp-grid);
}

/* Inside an article the trailing "Relevant Learning and Events" strip is tighter than the
   homepage's: Figma 224:7218/224:7219 give 40px below the rule and 20px from title to cards. */
.article__main :is(.cat-group--article, .cat-group--grid) .cat-group__head {
    padding-top: var(--sp-v-medium);
    margin-bottom: 20px;
}

/* ...and its title is H3 (node 224:7221), a step below the homepage groups' H2. Overriding the
   markup's .t-h2 from CSS bends the "type role lives in the markup class" rule, but the section
   view is shared with the homepage and a whole extra look for one font-size is worse. */
.article__main .cat-group__title {
    font-size: var(--fs-h3);
    line-height: var(--lh-heading);
}

.cat-group__title {
    margin: 0;
    color: var(--text-strong);
}

.cat-group__desc {
    margin: 0;
    color: var(--body-text);
}

/* The VB grid renders CLASS-LESS divs (.cat-group__grid > grid > row > column >
   [card wrappers]). Make the column a 3-up grid; cards fill the cells and stay
   individually selectable. */
.cat-group__grid > div > div > div {
    display: grid;
    /* minmax(0, …), not a bare 1fr — see the .listing-grid note: a bare 1fr lets a card's
       min-content (a long nowrap tag pill) widen its own column and break the row's evenness. */
    grid-template-columns: repeat(var(--cards, 3), minmax(0, 1fr));
    grid-auto-rows: 1fr;        /* every row equal height → uniform cards (Figma: all 217px) */
    gap: var(--sp-grid);
    align-items: stretch;       /* each card fills its cell (card has height:100%) */
}

/* Cards per row (editor "size" control): --cards is set on the grid wrapper and inherited by the
   class-less VB column above. Fewer columns = larger cards. The view always emits data-cols (default
   3), so existing content keeps the 3-up look. 3 uses the base default; the others override. */
.cat-group__grid[data-cols="1"] { --cards: 1; }
.cat-group__grid[data-cols="2"] { --cards: 2; }
.cat-group__grid[data-cols="4"] { --cards: 4; }

/* Category Group cards now use the unified .isv-card system (see top of file).
   The group's per-look default is set on the .cat-group--{look} wrapper. */

/* Rich-text (XhtmlString) descriptions render their own <p>/list markup — strip the
   first/last child margins so the copy sits flush inside its styled container. */
.search-panel__desc > :first-child,
.cat-group__desc > :first-child {
    margin-top: 0;
}

.search-panel__desc > :last-child,
.cat-group__desc > :last-child {
    margin-bottom: 0;
}

/* Page-level contents rail: the fixed-right variant of the unified rail now lives with the shared
   .contents-rail rules (search ".contents-rail--fixed"). The link vocabulary (.contents-rail__top /
   __list / __link) is shared by both variants — the former .cat-rail* rules were folded into it. */

/* ---- Responsive ----------------------------------------------------------- */
/* Tablet band = 721–1024px, from the Figma "Tablet" home frame (node 124:2851, 834 wide).
   The design does NOT collapse to a single column here: the hero keeps its two columns, the
   search band keeps its two columns + divider, the footer keeps its three, the nav keeps its
   links, and the category grids stay 3-up. Only two things actually change — the content frame
   insets 30px instead of 60, and the fixed right contents rail is swapped for a "Back to the
   top / Jump to" bar at the top of the content area. Everything genuinely stacks at ≤720
   (the mobile block below), which is also where the nav links drop out. */
@media (max-width: 1024px) {
    /* Tablet type scale (Figma "Tablet" column). */
    :root {
        --fs-h1-xl: 40px;
        --fs-h1-lg: 40px;
        --fs-h1: 30px;
        --fs-h4: 16px;

        /* Figma tablet: the content frame insets 30px, not the desktop 60. This one token
           drives the hero band, search band and category-group gutters together. */
        --sp-h-large: 30px;
    }

    /* Hero band KEEPS its two columns. The intro column is proportional here rather than the
       desktop 590px fixed (Figma tablet: 245 of a 774 content width ≈ a third). */
    .hero-band__inner > div > div > div:has(.hero-block) {
        flex: 1 1 32%;
        max-width: 34%;
    }

    /* ...and the two update cards stay side by side: drop their 220px basis, which would wrap
       them at the narrow end of the band. flex-grow still fills the column. */
    .hero-band__inner > div > div > div:has(.update-card) > div {
        flex: 1 1 160px;
    }

    /* Category groups reclaim the 300px gutter the fixed rail would have taken (the rail↔bar
       swap itself lives with the rail rules — search ".contents-rail-bar", it HAS to come
       after them in the file to win the cascade). */
    .cat-group__inner {
        padding-right: var(--sp-h-large);
    }

    /* Tablet keeps the 3-up grid (Figma); only the 4-up density steps down. */
    .cat-group__grid[data-cols="4"] { --cards: 3; }
}

/* Footer in the tablet band only (721–1024). It keeps the three tracks —
   brand | Quick links | Contact — per the Figma tablet frame. The problem was
   only that the desktop 1.2fr/2fr/1.2fr left the links track wide enough for the
   chunk-of-five lists to sit side by side, so the band read as FOUR columns.
   Equal thirds narrow that track to ~220px (Figma) and the chunks stack into one
   continuous list. Scoped with min-width so the mobile block below, which stacks
   to a single column, inherits nothing from it. */
@media (min-width: 721px) and (max-width: 1024px) {
    .site-footer__inner {
        grid-template-columns: 1fr 1fr 1fr;
    }

    .site-footer__links {
        flex-direction: column;
    }
}

@media (max-width: 720px) {
    /* Mobile type scale (Figma "Mobile" column). */
    :root {
        --fs-h1-xl: 30px;
        --fs-h1-lg: 30px;
        --fs-h1: 28px;
        --fs-h2: 22px;
        --fs-h3: 18px;
        --fs-body: 14px;

        /* Second step down from the tablet 30 (the desktop 60 halves at 1024). The nav and
           footer inset 20px on mobile (see .site-nav__inner below), so this pulls the content
           frames — article column, header section, hero/search bands — onto the same line as
           the chrome above and below them instead of sitting 10px further in. */
        --sp-h-large: 20px;
    }

    /* Primary links collapse into the burger + its stacked panel. */
    .site-nav__links {
        display: none;
    }

    .site-nav__burger {
        display: inline-flex;
    }

    /* The burger adds a 5th button to the icon row — tighten it and the logo so the
       whole bar still fits a 375px viewport without wrapping. */
    .site-nav__actions {
        flex: 0 0 auto;
        gap: 4px;
    }

    .site-nav__actions .icon-btn {
        width: 32px;
        height: 32px;
    }

    .brand__logo {
        height: 32px;
        max-width: 150px;
    }

    .site-nav__menu {
        display: block;
        background: var(--white);
        border-top: 1px solid var(--border-hairline);
    }

    /* Closed state — must out-specify the display:block above. */
    .site-nav__menu[hidden] {
        display: none;
    }

    .site-nav__inner,
    .site-footer__inner {
        padding-inline: 20px;
        /* The 30px desktop gap is for logo↔links; with only the logo and the icon
           row left, keep them from crowding without pushing the row off-screen. */
        gap: 12px;
    }

    /* Footer stacks to one column: brand (logo → socials → newsletter button) →
       Quick links → Contact, matching the Figma mobile frame (node 28:518).
       The tablet band above keeps two rows. */
    .site-footer__inner {
        grid-template-columns: 1fr;
        gap: var(--sp-v-medium);
    }

    /* One continuous list — the chunk-of-five columns stack, and the 12px row gap
       (set on the base rule) keeps the seam between them even with the links. */
    .site-footer__links {
        flex-direction: column;
    }

    /* Search band stacks: Resources above Recently explored; drop the vertical rule.
       (Both columns sit side by side across the tablet band — Figma node 124:2910.) */
    .search-band__inner {
        flex-direction: column;
        align-items: stretch;
        gap: var(--sp-v-medium);
    }

    .search-band__divider {
        display: none;
    }

    /* Hero band stacks on mobile: hero above the updates, each card full width. */
    .hero-band__inner > div > div {
        flex-direction: column;
    }

    .hero-band__inner > div > div > div:has(.hero-block) {
        flex: 1 1 auto;
        max-width: none;
    }

    .hero-band__inner > div > div > div:has(.update-card) > div {
        flex: 1 1 100%;
    }

    /* Category cards go single-column on mobile (any density). */
    .cat-group__grid[data-cols] { --cards: 1; }

    /* The listing column's inset is hardcoded (it predates --sp-h-large), so the token step
       above can't reach it — bring it down by hand to match. .calendar__main gets the same
       treatment in its own block further down. */
    .listing__main {
        padding: 20px;
    }
}

/* ============================================================================
   Separator element — a horizontal dividing rule authors drop into any section
   (SeparatorBlock). Replaces the rule that .content-section--cards used to draw
   automatically: drawing it there AND offering this element produced two lines
   with no way to remove the built-in one.

   Droppable into ANY section (the VB add-element picker can't be scoped), so it
   has to behave wherever it lands — hence the width/margin resets below, which
   defend against the <hr> UA defaults rather than against any section.
   ========================================================================== */
.separator {
    /* <hr>'s UA style is an inset 3D groove with `margin-inline: auto` — all three
       have to go or the rule renders shrink-wrapped and double-toned. */
    border: 0;
    border-top: 1px solid var(--border-hairline);
    width: 100%;
    margin: 0;
    /* 40px of air above and below, the step the Card grid variant used to draw. Inside a
       Content Section's grid the surrounding gap is supplied by the grid itself, so this is
       the standalone value; the canvas-column case is netted separately below. */
    margin-block: var(--sp-v-medium);
}

/* At the article canvas's top level the wrapper is already a 30px-gap flex column
   (.article__main > div:not([class])), so that gap supplies 30 of the 40 and only the
   remainder is added here — the same bookkeeping .content-section--cards does. */
.article__main > div:not([class]) > .separator {
    margin-block: calc(var(--sp-v-medium) - var(--sp-grid));
}

/* ============================================================================
   Listing experience — page header + query-driven grid (filter rail, cards,
   pagination). Used by "News & Articles", "All Resources" and any future listing.
   ========================================================================== */
/* (The listing page header is now a reusable HeaderSectionBlock — see .header-section.) */

/* No vertical padding: the listing frame sits flush under the header band (its bottom
   border is the divider) and flush above the footer, so the side borders run as one
   continuous frame — header → content → footer (per Figma). */
.listing-section {
    padding-block: 0;
}

/* The 1330px content box, centred (→ 55px gutters at 1440), framed left/right — matches
   .site-nav__inner / .site-footer__inner. No bottom border: the footer's full-width
   border-top closes the frame, giving the same intersecting corner lines as nav→footer. */
.listing {
    max-width: 1330px;
    margin: 0 auto;
    border-left: 1px solid var(--border-hairline);
    border-right: 1px solid var(--border-hairline);
}

/* Two columns, no gap: the sidebar's left padding and the main column's padding give the
   inset; the filter rail sits flush against the frame's left border (per Figma 102:4969). */
.listing__body {
    display: grid;
    grid-template-columns: 250px 1fr;
    align-items: start;
}

/* Filter rail hidden (ShowFilterRail off) — content spans the full width. */
.listing__body--no-rail {
    grid-template-columns: 1fr;
}

/* Content column: toolbar → grid → pager. */
.listing__main {
    padding: 30px;
    min-width: 0;
}

/* Toolbar: "Showing N" left, sort control right. No separator line (Figma 102:6228). */
.listing__bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-grid);
    margin-bottom: var(--sp-grid);
}

.listing__count {
    color: var(--black);
}

/* ---- Sort control (Figma 102:6221: white pill, teal text, chevron) -------- */
.listing-sort {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    flex: 0 0 auto;
}

/* Visually-hidden but screen-reader-accessible (used for the sort control's label). */
.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;
}

.listing-sort__control {
    position: relative;
    display: inline-flex;
    align-items: center;
}

.listing-sort__select {
    appearance: none;
    -webkit-appearance: none;
    background: var(--white);
    border: 1px solid var(--border-accent);
    border-radius: 6px;
    /* Figma 102:6221 is px 10 / pt 6 / pb 7; the right side keeps a 34px gutter for the
       absolutely-positioned chevron, which the component draws as a separate flex child. */
    padding: 6px 34px 7px 10px;
    color: var(--brand-primary);
    font-family: var(--font-body);
    /* Regular (550), not Medium — the .t-label interactive role, same as the breadcrumb pill. */
    font-size: var(--fs-body-sm);
    font-weight: var(--fw-regular);
    line-height: var(--lh-body);
    cursor: pointer;
}

.listing-sort__select:focus-visible {
    outline: 2px solid var(--focus-ring);
    outline-offset: 1px;
}

.listing-sort__chevron {
    position: absolute;
    right: 12px;
    font-size: 12px;
    color: var(--brand-primary);
    pointer-events: none;
}

/* ---- Toolbar controls cluster (keyword search + sort) --------------------
   Groups the toolbar controls so the whole cluster can be repositioned later
   as one unit (the planned "filters position" style). Static/right-aligned now. */
.listing__tools {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    flex-wrap: wrap;
    gap: 12px;
    flex: 0 1 auto;
}

/* ---- Keyword search (white pill + magnifier, matches the sort control) ---- */
.listing-search {
    display: inline-flex;
    align-items: center;
    background: var(--white);
    border: 1px solid var(--border-accent);
    border-radius: 6px;
    padding: 0 4px 0 12px;
    flex: 0 1 auto;
    min-width: 0;
}

.listing-search:focus-within {
    outline: 2px solid var(--focus-ring);
    outline-offset: 1px;
}

.listing-search__input {
    appearance: none;
    -webkit-appearance: none;
    border: 0;
    background: transparent;
    padding: 7px 0;
    width: 210px;
    max-width: 100%;
    min-width: 0;
    color: var(--black);
    font-family: var(--font-body);
    font-size: 14px;
    line-height: 1.5;
}

.listing-search__input::placeholder {
    color: var(--text-muted);
    opacity: 1;
}

.listing-search__input:focus-visible {
    outline: none;
}

.listing-search__submit {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 0;
    background: transparent;
    padding: 6px 8px;
    color: var(--brand-primary);
    font-size: 14px;
    cursor: pointer;
}

.listing-search__submit:focus-visible {
    outline: 2px solid var(--focus-ring);
    outline-offset: 1px;
    border-radius: 4px;
}

/* ---- Filter rail (container styling; faceted accordion comes later) ------- */
.listing-filters {
    padding: 30px 0 30px 20px;
    position: sticky;
    top: var(--sticky-top);   /* clears the auto-hiding header (see :root --sticky-top) */
}

/* "FILTERS" eyebrow — mirrors .contents-rail__top (Barlow, uppercase, teal). */
.listing-filters__eyebrow {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    margin: 0 0 15px;
    font-family: var(--font-eyebrow);
    font-size: 12px;
    font-weight: var(--fw-eyebrow);
    line-height: 1.5;
    letter-spacing: 1.2px;
    text-transform: uppercase;
    color: var(--brand-primary);
}

/* Accordion groups — native <details>; header = parent category, options = its children. */
.listing-filters__group {
    border-bottom: 1px solid var(--border-hairline);
    padding-bottom: 9px;
    margin-bottom: 5px;
}

.listing-filters__summary {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 9px 0;
    cursor: pointer;
    list-style: none;
    font-family: var(--font-body);
    /* Figma's Sidebar section component (I102:4974;56:2959) is Stevie Sans REGULAR (550)
       at 14/1.5 — a shade lighter and looser than the Medium 600 / 1.3 used here before. */
    font-size: var(--fs-body-sm);
    font-weight: var(--fw-regular);
    line-height: var(--lh-body);
    color: #000;
}

/* Hide the native disclosure triangle (we use our own chevron). */
.listing-filters__summary::-webkit-details-marker { display: none; }
.listing-filters__summary::marker { content: ""; }

.listing-filters__chevron {
    font-size: 12px;
    color: var(--grey);
    transition: transform .15s ease;
}

.listing-filters__group[open] > .listing-filters__summary .listing-filters__chevron,
.listing-filters__subgroup[open] > .listing-filters__summary .listing-filters__chevron {
    transform: rotate(180deg);
}

/* Date-range facet (lives inside an accordion group). */
.listing-filters__date {
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 4px 0 2px;
}

.listing-filters__date-field {
    display: flex;
    flex-direction: column;
    gap: 4px;
    font-family: var(--font-body);
    font-size: 12px;
    font-weight: var(--fw-medium);
    color: var(--brand-primary);
}

.listing-filters__date-field input[type="date"] {
    appearance: none;
    -webkit-appearance: none;
    background: var(--white);
    border: 1px solid var(--border-accent);
    border-radius: 6px;
    padding: 7px 10px;
    color: var(--body-text);
    font-family: var(--font-body);
    font-size: 14px;
    font-weight: var(--fw-book);
    line-height: 1.4;
}

.listing-filters__date-field input[type="date"]:focus-visible {
    outline: 2px solid var(--focus-ring);
    outline-offset: 1px;
}

.listing-filters__date-actions {
    display: flex;
    align-items: center;
    gap: 14px;
    margin-top: 2px;
}

.listing-filters__apply {
    border: 0;
    cursor: pointer;
    background: var(--brand-primary-strong);
    color: var(--white);
    font-family: var(--font-body);
    font-size: 14px;
    font-weight: var(--fw-medium);
    line-height: 1.3;
    padding: 8px 18px;
    border-radius: 6px;
    transition: background-color .15s ease;
}

.listing-filters__apply:hover {
    background: var(--brand-primary-hover);
}

.listing-filters__clear {
    font-family: var(--font-body);
    font-size: 14px;
    font-weight: var(--fw-book);
    color: var(--body-text);
    text-decoration: underline;
}

.listing-filters__clear:hover {
    color: var(--brand-primary);
}

.listing-filters__list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.listing-filters__link {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 6px 10px;
    border-radius: 6px;
    text-decoration: none;
    color: var(--body-text);
    font-family: var(--font-body);
    font-size: var(--fs-body-sm);
    font-weight: var(--fw-book);
    line-height: var(--lh-body);   /* Figma 102:5423 — Book 14/1.5, matching the body-sm role */
    transition: background-color .15s ease, color .15s ease;
}

.listing-filters__link:hover {
    background: var(--surface-cool);
    color: var(--brand-primary);
}

.listing-filters__link.is-active {
    /* Figma 102:5407 fills the active item with white at 90% over Royal Blue — i.e. the
       brand blue at 10%. This was rgba(0,192,207,.1), the RETIRED Teal 2, and was the last
       hard-coded pre-rebrand brand colour left in the listing block. */
    background: rgba(41, 133, 224, .1);
    color: var(--brand-primary);
}

.listing-filters__count {
    font-size: 12px;
    opacity: .8;
}

/* A top-level category with no configured children: a single filter, rendered like a group row. */
.listing-filters__group--leaf {
    padding: 3px 0 9px;
}

/* Non-clickable node (a structural ancestor the editor didn't enable) — reads as a plain label. */
.listing-filters__link--static {
    color: var(--body-text);
    cursor: default;
}
.listing-filters__link--static:hover {
    background: none;
    color: var(--body-text);
}

/* The clickable LABEL of a parent node (lives in the accordion summary, beside the chevron). It
   filters (matches the node's whole enabled subtree); the chevron toggles the accordion. */
.listing-filters__branch-link {
    text-decoration: none;
    color: inherit;
    transition: color .15s ease;
}
.listing-filters__branch-link:hover {
    color: var(--brand-primary);
}
.listing-filters__branch-link.is-active {
    color: var(--brand-primary);
}

/* Nested accordion (a subcategory that itself has configured children). No divider — the parent
   group's border already frames it; the indented list gives the tree its shape. */
.listing-filters__subgroup {
    border: 0;
}

/* Subcategory summaries read like a regular option link — same weight AND colour as the
   option links; only the top-level parent header keeps the bold/heavier black. */
.listing-filters__summary--nested {
    padding: 6px 10px;
    font-size: 14px;
    font-weight: var(--fw-book);
    color: var(--body-text);
    border-radius: 6px;
}
.listing-filters__summary--nested:hover {
    background: var(--surface-cool);
}

/* Indent each nested level and drop a subtle guide line so the hierarchy reads at a glance. */
.listing-filters__list--nested {
    margin: 2px 0 2px 12px;
    padding-left: 10px;
    border-left: 1px solid var(--border-hairline);
}

/* ---- Card grid ------------------------------------------------------------ */
/* minmax(0, 1fr), never a bare 1fr: `1fr` is minmax(AUTO, 1fr), so each track is floored at its
   card's min-content width — a card holding a long nowrap tag pill ("News and Annnouncements")
   then bullies its column wider than its share and the row comes out with visibly DIFFERENT card
   widths plus horizontal overflow (measured 187/309/278px in a 728px column). A 0 floor keeps
   every column exactly equal and lets the cards shrink instead. */
.listing-grid {
    display: grid;
    grid-template-columns: repeat(var(--cards, 3), minmax(0, 1fr));
    gap: var(--sp-grid);
}

/* Cards per row (editor "size" control). The view always emits data-cols (default 3); 3 uses the
   base default, the others override. Fewer columns = larger cards. Ignored by the Grouped (hub) look
   (a different grid). */
.listing-grid[data-cols="1"] { --cards: 1; }
.listing-grid[data-cols="2"] { --cards: 2; }
.listing-grid[data-cols="4"] { --cards: 4; }

.listing-grid__empty {
    grid-column: 1 / -1;
    color: var(--body-text);
}

/* Edit/preview only (never rendered on the live site): explains WHY an unfiltered grid
   came back empty, right where the editor sees the emptiness. Deliberately looks like a
   CMS hint rather than page content, so it can't be mistaken for a design element. */
.listing-grid__editor-note {
    grid-column: 1 / -1;
    display: flex;
    gap: 0.5rem;
    align-items: flex-start;
    max-width: 60ch;
    margin: 0;
    padding: 0.75rem 1rem;
    border-left: 3px solid var(--blue-doc, #0076df);
    border-radius: 4px;
    background: rgb(0 118 223 / 6%);
    color: var(--body-text);
}

.listing-grid__editor-note i {
    margin-top: 0.15em;
    color: var(--blue-doc, #0076df);
}

/* Listing cards now use the unified .isv-card system (see top of file):
   image style → .isv-card--article, text style → .isv-card--text.
   Opt these cards into the hover title recolour (Deep Blue) without touching the
   shared --article / --text looks used by homepage category groups — scoped to
   the listing grid container. Uses the base .isv-card:hover .isv-card__title rule. */
.listing-grid .isv-card {
    --card-title-hover-color: var(--brand-primary-hover);
}

/* ---- Pagination -----------------------------------------------------------
   Figma 102:5426: a centred row of 30px white cells with the --border-accent
   hairline, 8px apart — the same chip vocabulary as the breadcrumb, sort and
   bookmark boxes, NOT the filled grey/blue buttons this used to draw. The
   current page differs only by a full-strength Royal Blue border plus a 10%
   blue wash; its text stays Royal Blue rather than inverting to white. */
.listing-pager {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: var(--sp-v-large);
}

.listing-pager__page,
.listing-pager__step {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 30px;
    height: 30px;
    padding: 0 10px;
    background: var(--white);
    border: 1px solid var(--border-accent);
    border-radius: 6px;
    text-decoration: none;
    color: var(--brand-primary);
    font-size: var(--fs-body-sm);
    font-weight: var(--fw-book);
    line-height: var(--lh-body);
    transition: background-color .15s ease, border-color .15s ease, color .15s ease;
}

.listing-pager__page:hover,
.listing-pager__step:hover {
    border-color: var(--brand-primary-hover);
    color: var(--brand-primary-hover);
}

/* Current page — same 10% blue wash as the active filter item (Figma 102:5427), with the
   hairline promoted to full-strength Royal Blue. Deliberately not a solid fill: the design
   keeps every cell in the same white-chip family. */
.listing-pager__page.is-active {
    background: rgba(41, 133, 224, .1);
    border-color: var(--brand-primary);
    color: var(--brand-primary);
}

/* The prev/next glyphs are Font Awesome chevrons (Figma \f053 / \f054), so they need the
   icon font's own metrics rather than the 14px body step the numbers use. */
.listing-pager__step i {
    font-size: 14px;
    line-height: 1;
}

/* ---- Tablet/mobile filter dropdown (Figma node 186:3714) -------------------
   Below 1025px the 250px rail collapses into a "FILTERS" pill in the toolbar, closed until
   tapped — the same swap the contents rail makes into its tablet jump bar (search
   ".contents-rail-bar"), and deliberately the same pill vocabulary. site.js
   (initFilterDrop → syncFilterDrop) MOVES the one <aside class="listing-filters"> in and out
   of this wrapper on the 1024 boundary, so these rules only ever style the collapsed state;
   with JS off the wrapper is never built and the rail keeps its stacked no-JS fallback below. */
.listing-filters-drop {
    position: relative;
    display: inline-flex;
    flex: 0 0 auto;
    order: 1;   /* first in the toolbar row: pill left, sort right (see .listing__bar below) */
}

/* Same button as .contents-rail-bar__btn — white, --border-accent hairline, Barlow all-caps. */
.listing-filters-drop__btn {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 8px 10px;
    background: var(--white);
    border: 1px solid var(--border-accent);
    border-radius: 6px;
    color: var(--brand-primary);
    font-family: var(--font-eyebrow);
    font-size: 12px;
    font-weight: var(--fw-eyebrow);
    line-height: 1.5;
    letter-spacing: 1.2px;
    text-transform: uppercase;
    white-space: nowrap;
    cursor: pointer;
}

.listing-filters-drop__btn:hover {
    color: var(--brand-primary);
    border-color: var(--brand-primary-hover);
}

.listing-filters-drop.is-open .listing-filters-drop__btn {
    color: var(--brand-primary);
    border-color: var(--brand-primary-hover);
}

/* The panel: an OPAQUE sheet anchored under the pill (never a transparent overlay — the cards
   behind it have to be fully covered), riding along with the sticky toolbar because it is the
   pill's own positioned sibling. Wins over the base .listing-filters sticky/padding rules on
   specificity, so the rail needs no state class of its own. */
.listing-filters-drop .listing-filters {
    display: none;
    position: absolute;
    top: calc(100% + 8px);
    left: 0;
    z-index: 60;
    width: min(340px, calc(100vw - 48px));
    max-height: 70vh;
    overflow-y: auto;
    padding: 16px 18px 18px;
    background: var(--white);
    border: 1px solid var(--border-hairline);
    border-radius: 6px;
    box-shadow: var(--shadow-pop);
}

.listing-filters-drop.is-open .listing-filters {
    display: block;
}

/* The pill already reads "FILTERS" — don't say it twice inside the panel. */
.listing-filters-drop .listing-filters__eyebrow {
    display: none;
}

/* The rail's group separators are between-groups rules; the last one would hang as a stray
   hairline above the panel's bottom edge. */
.listing-filters-drop .listing-filters > :last-child {
    border-bottom: 0;
    margin-bottom: 0;
    padding-bottom: 0;
}

/* ---- Responsive -----------------------------------------------------------
   TWO conditions collapse the listing to its narrow layout:
     • max-width: 1024px — phones and tablet portrait.
     • a SHORT LANDSCAPE viewport (≤1366 wide AND ≤620 tall) — a phone or small tablet turned
       sideways measures WIDER than 1024 (Pixel 7 Pro landscape = 1040, iPad mini = 1133) yet has
       even less room for a 250px rail than it did in portrait. The height bound is what tells it
       apart from a real laptop, which is just as wide but ~768+ tall.
   site.js reads --filters-collapsed (set below) rather than re-implementing this in JS, so the
   breakpoint has exactly one definition — this query. */
@media (max-width: 1024px), (max-width: 1366px) and (max-height: 620px) {
    /* The flag site.js's syncFilterDrop() polls to decide collapsed vs. expanded. */
    :root {
        --filters-collapsed: 1;
    }

    .listing__body {
        grid-template-columns: 1fr;
    }

    /* No-JS fallback only: with initFilterDrop running, the rail is inside the dropdown
       instead and the rules above take over. */
    .listing-filters {
        position: static;
        padding: 30px 30px 0;
    }

    /* The toolbar now carries the filters trigger, so it PINS below the nav (same --rail-top
       every other rail reads, so it rises and drops with the auto-hiding header) — the pill and
       the sort stay in reach the whole way down the results. Opaque white + negative gutters so
       the cards scrolling underneath can't show through the pinned strip. */
    .listing__bar {
        position: sticky;
        top: var(--rail-top);
        z-index: 40;
        flex-wrap: wrap;
        /* Tighter than the desktop --sp-grid gap: this is now a multi-ROW strip, and a 30px
           row gap would push the count a whole line away from the controls. */
        gap: 12px 16px;
        background: var(--white);
        margin-inline: -30px;
        /* The white strip has to carry its OWN bottom breathing room: the bar's margin sits
           outside the painted box, so without this the first card scrolls flush against the
           count text. Padding + margin still add up to the --sp-grid rhythm. */
        padding: 10px 30px 14px;
        margin-bottom: 16px;
        transition: top 0.25s ease;   /* slide with the nav (matches .site-header) */
    }

    /* Figma 186:3714: pill left, sort right, "Showing N" on its own line beneath them. */
    .listing__tools {
        order: 2;
        flex: 1 1 auto;
    }

    .listing__count {
        order: 3;
        flex-basis: 100%;
    }

    /* Search page: no toolbar to pin, so the pill carries the pin itself — as its own opaque
       full-width strip (same white band + negative gutters as .listing__bar), or the result
       rows would scroll along right beside it. */
    .listing-filters-drop--standalone {
        position: sticky;
        top: var(--rail-top);
        z-index: 40;
        display: flex;
        justify-content: flex-start;
        background: var(--white);
        margin-inline: -30px;
        margin-bottom: 16px;
        padding: 8px 30px 14px;
        transition: top 0.25s ease;
    }

    /* The strip's own 30px gutter is not the pill's position, so re-align the panel to it. */
    .listing-filters-drop--standalone .listing-filters {
        left: 30px;
    }

}

/* Column density is a WIDTH question only — deliberately NOT on the collapse query above, which
   also fires on short landscape viewports. Once the rail is out of the way a sideways phone has
   ~978px of content width, so the author's 3-up gives ~306px cards — wider than a 3-up iPad
   landscape (269px) and level with a 1440 desktop (319px). Capping those to 2 would override a
   setting the editor made, for no gain. Below 1025 the column really is too narrow for 3. */
@media (max-width: 1024px) {
    /* Cap dense grids at 2-up (1 and 2 keep their setting). */
    .listing-grid[data-cols="3"] { --cards: 2; }
    .listing-grid[data-cols="4"] { --cards: 2; }
}

@media (max-width: 640px) {
    /* Mobile: single column at any density. */
    .listing-grid[data-cols] { --cards: 1; }

    /* Phone toolbar: three stacked controls is too much pinned chrome, so flatten the tools
       cluster (display:contents lifts the search + sort out of it and into the bar's own flex
       flow) and lay the strip out as: [FILTERS] … [sort] / [keyword, full width] / count. */
    .listing__tools {
        display: contents;
    }

    .listing-sort {
        order: 2;
        margin-left: auto;   /* the cluster's justify-content is gone with display:contents */
    }

    .listing-search {
        order: 3;
        flex: 1 1 100%;
    }

    /* Keep the count last, under the whole control set (it is not itself a control). */
    .listing__count {
        order: 4;
    }

    .listing-search__input {
        flex: 1 1 auto;
        width: auto;
    }
}

/* ============================================================================
   Article experience — main content column (VB canvas) + sticky right rail
   (auto "on this page" anchors + key contacts). Figma node 224:7192.
   ========================================================================== */
.article__frame {
    max-width: 1330px;
    margin: 0 auto;
    display: flex;
    align-items: flex-start;
}

.article__main {
    flex: 1 1 auto;
    min-width: 0;
    border-left: 1px solid var(--border-hairline);
    padding: var(--sp-grid) var(--sp-h-large) var(--sp-v-large);
    display: flex;
    flex-direction: column;
    gap: var(--sp-grid);   /* topbar → body: 30px, same step as between body blocks (below) */
}

/* Figma's article body (Frame 120) is ONE auto-layout column on a flat 30px itemSpacing — every
   block sits the same distance from its neighbour. The <epi-outline> tag helper renders a
   CLASS-LESS <div> as the only other child of .article__main, with the sections as its direct
   siblings, so making that wrapper the column puts all of the vertical rhythm in one declaration.
   (Same class-less-VB-wrapper convention as .cat-group__grid > div > div > div.) Sections
   therefore carry NO vertical padding of their own — see .content-section. */
.article__main > div:not([class]) {
    display: flex;
    flex-direction: column;
    gap: var(--sp-grid);
}

/* Sections that double as top-level page bands (.header-section, .cat-group, .search-band) centre
   themselves with `max-width: 1330px; margin: 0 auto`. That was inert while this wrapper was a
   plain block, but an auto inline margin on a FLEX ITEM beats stretch — it shrink-wraps the band
   and centres its copy. Inside the article the 1330 frame and the 60px inset are already supplied
   by .article__frame / .article__main, so drop both here and let every section fill the column. */
.article__main > div:not([class]) > * {
    max-width: none;
    margin-inline: 0;
    /* Flex items default to min-width:auto and refuse to shrink below their min-content width —
       a wide table, a long URL or a card grid would then push the whole column past the viewport
       on mobile. Same guard .article__main itself carries. */
    min-width: 0;
}

/* Sticky back-nav bar (breadcrumb + share) — pins at the SAME offset as the right rail
   (--sticky-top) so the two align and follow the article scroll. White background masks the
   article content scrolling beneath it. */
.article__topbar {
    position: sticky;
    top: var(--rail-top);
    z-index: 5;
    display: flex;
    align-items: center;
    /* Left-packed, NOT space-between: site.js injects a third child (the ≡ section-nav dropdown)
       ahead of the breadcrumb on the touch band, and space-between would spread the three across
       the row. The action group pushes itself right with margin-left:auto instead, so any number
       of left-hand controls stays grouped at the left edge. */
    justify-content: flex-start;
    gap: 16px;
    transition: top 0.25s ease;   /* slide with the nav (matches .site-header) */
    /* Transparent bar: only the breadcrumb pill + share button carry their own (white)
       backgrounds, so article content scrolls UNDER them like a floating navbar — no
       full-width white blockade across the row. */
}

/* Right column — a real sticky panel (not the homepage's fixed overlay rail). */
.article__aside {
    flex: 0 0 250px;
    align-self: stretch;
    border-right: 1px solid var(--border-hairline);
    padding: var(--sp-grid) 20px var(--sp-grid) 0;
}

.article__aside-inner {
    position: sticky;
    top: var(--rail-top);   /* follows the auto-hiding nav via :root --rail-top */
    display: flex;
    flex-direction: column;
    gap: 20px;
    transition: top 0.25s ease;   /* slide with the nav (matches .site-header) */
}

/* Contents rail (shared _ContentsRail partial). Base = the sticky-column variant (article aside;
   positioning comes from .article__aside-inner). The --fixed modifier is the global fixed-right
   variant (formerly .cat-rail). Link vocabulary (.contents-rail__*) below is shared by both. */
.contents-rail {
    display: flex;
    flex-direction: column;
    gap: 15px;
}

/* Fixed-right variant (global rail via ContentsRailViewComponent → _Layout). Hidden until the engine
   adds .contents-rail--on (i.e. the page has rail entries), so it never floats over the hero/footer.
   Placed after the base rule so its display:none wins for the initial hidden state. */
/* Geometry is Figma's Sections column (node 102:3270): 250px wide, RIGHT-ALIGNED to the 1330
   frame edge — not inset from it — with 20px of inner right padding. Paired with
   .cat-group__inner's 280px --cat-rail-gutter, that puts 30px (--sp-grid) between the card
   column and the rail text. The frame's right border is already drawn by .cat-group, so the
   rail draws none of its own. Change either number and you must change the other. */
.contents-rail--fixed {
    display: none;
    position: fixed;
    top: var(--sticky-top);   /* resting offset; JS overrides on scroll but pins to the same value */
    /* 50% of the fixed element's containing block (the layout viewport) minus half the frame —
       NOT (100vw - 1330px) / 2. 100vw INCLUDES the scrollbar while the frame's `margin: 0 auto`
       centres on the layout width that excludes it, so the vw form lands half a scrollbar (~7.5px)
       inside the frame edge. Invisible while the rail was inset 60px; obvious now it's flush. */
    right: max(0px, calc(50% - 665px));   /* 665 = 1330 / 2 */
    width: 250px;
    max-height: calc(100vh - var(--sticky-top) - var(--sp-v-medium));
    overflow-y: auto;
    padding-right: 20px;
    z-index: 5;
}

.contents-rail--fixed.contents-rail--on {
    display: flex;
}

/* Figma: Deep Blue (#2756a0) — NOT --brand-primary (Royal Blue). Barlow 700, 12/18, +1.2px caps. */
.contents-rail__top {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    text-decoration: none;
    color: var(--brand-deep);
    font-family: var(--font-eyebrow);
    font-size: 12px;
    font-weight: var(--fw-eyebrow);
    line-height: 1.5;
    letter-spacing: 1.2px;
    text-transform: uppercase;
}

.contents-rail__list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 12px;
}

/* Figma Home node 102:3280 — Body small (Stevie Sans Book 400, 14/21) in Grey #888,
   NOT --body-text #666. The darker grey was reading as a heavier weight at 14px. */
.contents-rail__link {
    position: relative;
    display: block;
    /* 21px = the 11px marker box + its 10px gap (Figma 224:7233), so labels line up whether or
       not the row is the active one. */
    padding-left: 21px;
    text-decoration: none;
    color: var(--grey);
    font-size: var(--fs-body-sm);
    line-height: var(--lh-body);
}

.contents-rail__link:hover {
    color: var(--brand-primary);
}

/* Active is a COLOUR-ONLY change in the design (node 102:3278): the weight stays Book 400
   and only the fill goes to Black. Don't reintroduce a font-weight bump here — the chevron
   marker below is what carries the extra emphasis. */
.contents-rail__link.is-active {
    color: var(--text-strong);
}

/* A real Font Awesome chevron-right in REGULAR, not the typographic › it used to be — Figma
   224:7233 draws the FA glyph at 12px inside an 11px box. Same idiom as .breadcrumbs__item::before
   (which uses weight 900 for Solid); 400 is the Regular cut. */
.contents-rail__link.is-active::before {
    content: "\f054";
    position: absolute;
    left: 0;
    width: 11px;
    text-align: center;
    font-family: "Font Awesome 7 Pro";
    font-weight: 400;
    font-size: 12px;
}

/* Nested in-body anchor entries (under a rail-tagged section). Flat anchors from
   untagged sections reuse the top-level .contents-rail__link styling. */
.contents-rail__sublist {
    list-style: none;
    margin: 12px 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.contents-rail__sublink {
    padding-left: 33px;   /* the 21px top-level indent + a 12px nesting step */
    font-size: calc(var(--fs-body-sm) - 1px);
}

.contents-rail__sublink.is-active::before {
    left: 22px;   /* keeps the 11px marker box butted against the label */
}

/* ---- Tablet jump bar (Figma node 145:3563) --------------------------------
   The tablet frame has no room for the fixed right rail, so the design replaces it with a
   right-aligned pair of pill buttons at the top of the content area: "Back to the top" and
   "Jump to" (a disclosure holding the very same rail list). It is BUILT AND MOUNTED by
   site.js (initContentsRail → buildRailBar) from the list the rail engine just produced, so
   there is still exactly one source of entries — CSS only decides which of the two
   presentations is on screen (rail ≥1025, bar ≤1024). */
.contents-rail-bar {
    display: none;   /* the fixed rail is shown instead on desktop */
    align-items: center;
    justify-content: flex-end;
    gap: 12px;
    /* Sits OUTSIDE the .cat-group frames (site.js mounts it as their sibling so it can stick
       across the whole band), so it carries the 1330 frame geometry itself — same centred box
       and side gutters as the sections it sits above. */
    max-width: 1330px;
    margin: 0 auto var(--sp-grid);
    padding-inline: var(--sp-h-large);
    /* Sticky, like the desktop rail: it rides down the page pinned below the nav instead of
       scrolling away with the first group. --rail-top is the shared pin offset every rail
       reads, so the bar rises and drops in lockstep with the auto-hiding header. */
    position: sticky;
    top: var(--rail-top);
    z-index: 50;
    transition: top 0.25s ease;
}

/* The bar now supplies the gap above the first group's heading, so drop that group's own
   top padding rather than stacking the two — but ONLY in the band where the bar is actually
   on screen. site.js mounts the bar at EVERY width (CSS decides which presentation shows), so
   an unscoped rule here also stripped the first group's 40px gap below the Search band on
   desktop — and with it the fixed rail's rest position, which anchors to .cat-group__head.
   The query must match the rail ⇄ bar swap at the bottom of this file exactly. */
@media (max-width: 1024px), (max-width: 1366px) and (pointer: coarse) {   /* the touch band */
    .contents-rail-bar + .cat-group .cat-group__inner {
        padding-top: 0;
    }
}

/* ---- Inline (icon-only) variant — the article/Content topbar action row -------------------
   Figma 191:14913: on narrow viewports the two rail controls are the last two icons of the
   topbar's four-icon group (share · bookmark · back-to-top · jump-to), not a standalone bar.
   site.js appends the bar into .article__actions and tags it --inline; here it sheds every
   bit of standalone-bar geometry (its own frame, gutters, sticky pin) and becomes two plain
   icon buttons, because the topbar it now lives in is already sticky and already inset. */
.contents-rail-bar--inline {
    position: static;
    max-width: none;
    margin: 0;
    padding-inline: 0;
    gap: 12px;   /* matches .article__actions — Figma spaces the icons 12px apart */
}

/* Icon-only: the label rides along for the standalone bar, but here the 34px square has no room
   for it. Safe to remove from the accessibility tree too — site.js puts the same wording on the
   button's aria-label, so the name survives. */
.contents-rail-bar--inline .contents-rail-bar__btn span {
    display: none;
}

/* Fallback only: a column-variant page with no .article__actions gets the standalone bar mounted
   in the body instead. It then has to shed the gutters it carries for the homepage (where it sits
   OUTSIDE the section frames — .article__main already insets) and clear the sticky topbar, which
   pins at the same --rail-top and would otherwise sit on top of it. */
.article__main > .contents-rail-bar:not(.contents-rail-bar--inline) {
    max-width: none;
    margin-inline: 0;
    margin-bottom: 0;
    padding-inline: 0;
    top: calc(var(--rail-top) + var(--article-topbar-h) + 12px);
}

/* Share + Bookmark both show at EVERY width — Figma 1739:8585 puts the pair in the desktop
   topbar. (This used to be share-only on desktop, with bookmark introduced alongside the mobile
   toolbar; the design has since moved on.) The ≡ section-nav toggle is still touch-band only:
   on desktop the side-nav pane sits beside the content, so the toggle has nothing to open. */
.article__topbar .topbar-drop--left {
    display: none;
}

/* ---- THE TOUCH BAND -------------------------------------------------------------------------
   `(max-width: 1024px), (max-width: 1366px) and (pointer: coarse)`

   Everything that swaps the article/Content page to its mobile presentation — the four-icon
   toolbar, the rail→jump-bar swap, the stacked aside, the stacked side-nav pane — shares this
   ONE condition. Keep them identical; splitting them strands a page half-converted.

   Why the second clause. Plain `max-width: 1024px` covers phones in both orientations and tablets
   in PORTRAIT, but every tablet in LANDSCAPE is wider than that (iPad mini 1133, iPad 10.9 1180,
   iPad Pro 11 1194, iPad Pro 12.9 1366) and was still getting the desktop 250px rail on a screen
   with no room for it.

   Why `pointer: coarse` and not a bigger width. No width/height rule can separate an iPad Pro 12.9
   in landscape (1366x1024) from a small laptop (1280x800) — the tablet is the WIDER of the two, so
   raising the cap to 1366 would drag laptops into the mobile layout as well. The input device is
   the only signal that actually distinguishes them: a tablet reports a coarse pointer, a laptop a
   fine one. (The existing listing-filter query one screen up uses a max-height clause instead —
   that one is targeting a phone held sideways, which really is short; a landscape tablet is not.)

   Caveat when testing: DevTools device mode emulates `pointer: coarse`, but some third-party
   responsive-preview extensions do not — in those, resize a real window instead. */
@media (max-width: 1024px), (max-width: 1366px) and (pointer: coarse) {
    /* Keep this query IDENTICAL to the one site.js matchMedia's on (initTopbarNav) — the toggle
       showing without the nav having been relocated into it would open an empty panel. */
    .article__topbar .topbar-drop--left {
        display: block;
    }
}

/* Figma "All caps title / Button": white, --border-accent hairline (same as the
   breadcrumb chips), Barlow bold all-caps in Teal 2. */
.contents-rail-bar__btn {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 8px 10px;
    background: var(--white);
    border: 1px solid var(--border-accent);
    border-radius: 6px;
    color: var(--brand-primary);
    font-family: var(--font-eyebrow);
    font-size: 12px;
    font-weight: var(--fw-eyebrow);
    line-height: 1.5;
    letter-spacing: 1.2px;
    text-transform: uppercase;
    text-decoration: none;
    white-space: nowrap;
    cursor: pointer;
}

.contents-rail-bar__btn:hover {
    color: var(--brand-primary);
    border-color: var(--brand-primary-hover);
}

/* ---- Topbar dropdowns (.topbar-drop) ---------------------------------------
   ONE dropdown object, used TWICE in the article/Content topbar on the touch band, so the two
   rails read as a matched pair instead of two unrelated menus (Figma 191:14913):
     • --left  → the ≡ section-navigation dropdown: the Side Navigation pane's own .side-nav,
                 RELOCATED into the panel by site.js → initTopbarNav.
     • --right → the "on this page" jump-to panel buildRailBar builds from the contents rail.
   Both toggles are plain .article__icon-btn squares; only the panel anchoring differs. The
   homepage/listing STANDALONE jump bar reuses the same --right panel, so there is one sheet. */
.topbar-drop {
    position: relative;
}

/* The open panel is an OPAQUE sheet, not a transparent overlay — the content behind it must be
   fully covered (the fixed rail's see-through-over-content look is a desktop-only affordance,
   where it has its own gutter). Solid white + border + shadow, and a z-index above the section
   frames so nothing bleeds through. */
.topbar-drop__panel {
    display: none;
    position: absolute;
    top: calc(100% + 8px);
    z-index: 60;
    min-width: 280px;
    max-width: min(380px, 85vw);
    max-height: 60vh;
    overflow-y: auto;
    padding: var(--sp-grid);
    background: var(--white);
    border: 1px solid var(--border-hairline);
    border-radius: 6px;
    box-shadow: var(--shadow-pop);
}

/* Anchored to its own toggle's edge — the ≡ opens rightwards from the left of the row, the
   jump-to opens leftwards from the right of it, so neither can run off the viewport. */
.topbar-drop--left .topbar-drop__panel { left: 0; }
.topbar-drop--right .topbar-drop__panel { right: 0; }

.topbar-drop.is-open > .topbar-drop__panel {
    display: block;
}

/* The relocated section nav sheds any pane geometry — the panel owns the scrolling now. */
.topbar-drop__panel .side-nav {
    position: static;
    max-height: none;
    overflow: visible;
    padding: 0;
}

/* The cloned rail list, re-dressed in the side-nav's row language (hairline-separated rows, the
   current entry as a tinted pill) so both panels look like the same menu. Scoped to the panel —
   the desktop rail and the hub rail keep their airy "›" marker look. */
.topbar-drop__panel .contents-rail__list {
    gap: 0;
}

.topbar-drop__panel .contents-rail__list > li + li {
    border-top: 1px solid var(--border-hairline);
}

.topbar-drop__panel .contents-rail__link {
    padding: 12px 10px;
    margin: 0;
    border-radius: 6px;
    color: var(--text-strong);
    font-weight: var(--fw-medium);
}

.topbar-drop__panel .contents-rail__sublist {
    margin: 0 0 8px;
    padding-left: 14px;
    gap: 2px;
}

.topbar-drop__panel .contents-rail__sublink {
    padding: 6px 10px;
    font-size: var(--fs-body-sm);
    font-weight: normal;
}

.topbar-drop__panel .contents-rail__link.is-active {
    background: var(--surface-cool);
    color: var(--brand-primary);
}

/* The rail's "›" marker is replaced by the pill above. */
.topbar-drop__panel .contents-rail__link.is-active::before {
    content: none;
}

/* ---- Rail ⇄ bar swap ------------------------------------------------------
   The ONE place that decides which presentation is on screen. It must sit AFTER both
   `.contents-rail--fixed.contents-rail--on { display: flex }` and `.contents-rail-bar
   { display: none }` above: a media query adds no specificity, so an earlier block
   (e.g. the main tablet layer up at "max-width: 1024px") silently loses to them. */
@media (max-width: 1024px), (max-width: 1366px) and (pointer: coarse) {   /* the touch band */
    /* No room for the fixed right rail on tablet — collapse it away entirely. */
    .contents-rail--fixed,
    .contents-rail--fixed.contents-rail--on {
        display: none;
    }

    /* ...and the jump bar takes over (Figma node 145:3563). */
    .contents-rail-bar {
        display: flex;
    }
}

/* Article header (HeadingTextBlock "Article" variant) ---------------------- */
.article-header {
    display: flex;
    flex-direction: column;
    gap: 16px;
}

.article-header__heading {
    margin: 0;
    color: var(--black);
}

.article-header__intro {
    color: var(--body-text);
}

.article-meta {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 12px;
}

.article-meta__tags {
    display: inline-flex;
    flex-wrap: wrap;
    gap: 8px;
}

.article-meta__tag {
    background: var(--white);
    border: 1px solid var(--border-accent);
    border-radius: 100px;
    padding: 6px 12px;
    font-size: var(--fs-body-xs);
    /* 550 (Stevie Sans Regular), NOT 600 — Figma 224:7204. Same weight as the card pill
       (.isv-card__tag), which the listing sweep already corrected. */
    font-weight: var(--fw-regular);
    line-height: normal;
    color: var(--brand-primary);
    white-space: nowrap;
}

.article-meta__date {
    font-size: var(--fs-body-xs);
    font-weight: var(--fw-book);
    line-height: var(--lh-subhead);
    color: var(--grey);
    white-space: nowrap;
}

/* Content sections (ContentSection) --------------------------------------- */
/* No vertical padding: spacing between body blocks is the 30px gap on the outline wrapper
   (.article__main > div:not([class])) so one number governs the whole column. scroll-margin
   is unaffected — it offsets the jump target, not the layout. */
.content-section {
    scroll-margin-top: var(--header-h);   /* clear the sticky bar when jumped to via anchor */
}

/* In-body bookmark anchors (editor-inserted <a id>/<a name> with no href) are the
   contents-rail's other jump targets. Mirror the JS selector in site.js (initContentsRail) so
   they land below the auto-hiding header — same --header-h source as section targets,
   and this also covers native hash navigation (reload / shared links), which the rail's
   JS click handler doesn't. Centralized: one rule, every page that uses the rail follows. */
a[id]:not([href]),
a[name]:not([href]) {
    scroll-margin-top: var(--header-h);
}

.content-section__inner {
    display: flex;
    flex-direction: column;
    gap: 24px;   /* section heading → body (Figma 224:7211: the H2 carries mb 24) */
}

.content-section__title {
    margin: 0;
    color: var(--black);
}

/* Callout — the tinted "Overview" panel (heading + body inside the box).
   CREAM, not the cool blue: Figma node 224:7206 fills it with 80% white over Sunshine
   (#fce17c), which composites to exactly #fef9e5 = --cream / --accent-warm-bg. This is the
   article body's one warm surface — don't "correct" it back to --surface-cool. */
.content-section--callout .content-section__inner {
    background: var(--accent-warm-bg);
    border-radius: 12px;
    padding: 25px;
    gap: 10px;
}

/* Title stays Royal Blue (node 224:7208). At 3.60:1 on cream it misses AA by a hair — 18px/600
   sits just under the 18.66px large-text threshold — so it rides with the brand refresh's known
   AA debt rather than deviating from the design. */
.content-section--callout .content-section__title {
    color: var(--brand-primary);
}

/* Copy inside the callout is Deep Blue (node 224:7209), NOT the --body-text grey the rest of
   the article uses — the warm panel needs the darker ink to hold. 6.81:1 on cream, passes AA.
   .rich-text is named explicitly because it declares its own default ink (see .rich-text below),
   and a direct declaration beats an inherited one no matter how specific the ancestor rule is. */
.content-section--callout .content-section__body,
.content-section--callout .rich-text {
    color: var(--brand-deep);
}

/* Card grid — "Relevant Learning & Events": a separated 3-up grid of cards. Kept numerically
   identical to the CategoryGroupSection route above (.article__main .cat-group__*) so it makes
   no visual difference which of the two an editor reaches for. Figma node 224:7218. */
.content-section--cards {
    /* NO border-top: the dividing rule is not automatic any more — add a Separator element as
       the section's first item where one is wanted (and leave it out between two adjacent card
       rows). Drawing it here as well gave two lines, with the built-in one unremovable.
       The spacing stays: a card row needs its breathing room either way. */
    /* 40px above; the outline wrapper's gap already supplies 30 of it. */
    margin-top: calc(var(--sp-v-medium) - var(--sp-grid));
    padding-top: var(--sp-v-medium);
}

.content-section--cards .content-section__inner {
    gap: 20px;   /* title → cards (node 224:7219) */
}

.content-section--cards .content-section__body > div > div > div {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    grid-auto-rows: 1fr;
    gap: var(--sp-grid);
    align-items: stretch;
}

/* Rich text — the editor writes raw HTML here, so the column has to defend itself against
   whatever an author pastes in: oversized images, unbroken URLs, and wide tables are the three
   things that otherwise push the body past the viewport on mobile. The img rule also beats
   TinyMCE's intrinsic width/height attributes; tables and pre scroll inside their own box
   rather than stretching the column. */
.rich-text {
    overflow-wrap: anywhere;
    /* DEFAULT ink for authored PROSE: Body text #666, not the black inherited from <body>.
       Every paragraph, list and list item in Figma's article body (224:7213) is --body-text, and
       the block carries .t-body for the matching 16/400/1.5. Headings are NOT body-coloured —
       see the rule below. */
    color: var(--text-body);
}

/* Headings inside authored copy are BLACK, not the prose grey. Figma's article body (224:7213)
   resolves to exactly two inks — Black for the section headings, Body text for the copy — and the
   whole article frame (224:7192) carries no other heading colour. This matches the page title
   (.article-header__heading) so an authored h2 and the H1 above it read as one hierarchy.
   Declared on the heading itself, so an author's own colour still wins; the two surfaces that own
   their ink hand it back immediately below. */
.rich-text :is(h1, h2, h3, h4, h5, h6) {
    color: var(--text-strong);
}

/* Surfaces that set their own ink have to re-assert it AGAINST .rich-text, not just on an
   ancestor: a colour declared directly on the element always beats an inherited one, whatever
   the ancestor's specificity. Miss one of these and the copy silently goes grey. */
.hero-band .rich-text {
    color: inherit;   /* white on the blue hero band (.hero-block) */
}

/* Same trap, one level down: the heading rule above is a DIRECT declaration, so it would out-rank
   the ink these two surfaces set on .rich-text and drop a black heading onto the blue hero wash or
   the cream callout. Hand it back. Callout headings are Deep Blue with the rest of the panel
   (node 224:7209); hero headings stay white. */
.content-section--callout .rich-text :is(h1, h2, h3, h4, h5, h6),
.hero-band .rich-text :is(h1, h2, h3, h4, h5, h6) {
    color: inherit;
}

.rich-text img {
    max-width: 100%;
    height: auto;
}

.rich-text :is(table, pre) {
    display: block;
    max-width: 100%;
    overflow-x: auto;
}

/* An article's page title is the ONLY h1 on the page, so a rich-text h1 an author typed into
   the body is always really a section heading — render it as one (Figma 224:7211 = H2 24/600/1.3)
   instead of letting it outrank the title at 40px Light. Guard only; the markup should still be
   authored as h2, since two h1s is also an a11y/SEO defect this rule can't fix. */
.article__main .rich-text h1 {
    font-size: var(--fs-h2);
    font-weight: var(--fw-medium);
    line-height: var(--lh-heading);
}

/* Figma splits the article body into one frame per heading — 30px between frames, 24px from a
   heading to its own copy. Authors instead write one long rich-text blob, so reproduce that
   rhythm on the headings themselves: bound to its body, separated from what came before. Without
   this a heading sits 16px below the previous paragraph and 16px above its own (margins collapse
   against the p's 1em), reading as though it belongs to the section it just ended.
   Empty <p>&nbsp;</p> spacer paragraphs are no longer needed and should be removed from copy. */
.article__main .rich-text :is(h1, h2) {
    margin-top: var(--sp-grid);
    margin-bottom: 1em;
}

/* ...but not the block's own opening heading — the outline wrapper's gap already spaced it off
   whatever came before. Needed explicitly because the rule above out-ranks the generic
   `.rich-text > :first-child { margin-top: 0 }` reset further down. */
.article__main .rich-text > :is(h1, h2):first-child {
    margin-top: 0;
}

/* Rich text — strip first/last child margins so copy sits flush in its box. */
.rich-text > :first-child {
    margin-top: 0;
}

.rich-text > :last-child {
    margin-bottom: 0;
}

/* Blockquote — Figma node 121:2813: 3px maroon left bar + 24px inset, body-text colour.
   Mirrored in editor.css so the TinyMCE surface matches the published look. */
.rich-text blockquote {
    margin: 0 0 1em;
    padding-inline-start: 24px;
    border-inline-start: 3px solid var(--brand-primary);
}

/* ---- Media block (MediaBlock, figures) -------------------------------------
   Two Style-panel looks share one element:
     • .media-block--image  = a captioned image                                  — default
     • .media-block--video  = a captioned video, EITHER an uploaded file or a
                              YouTube/Vimeo embed (the source is data, not a look)

   The video look renders a click-to-play FAÇADE (.isv-video): poster + play button
   + optional duration badge, with the real player only fetched on intent. A silent
   preview runs on hover (pointer devices) or on scrolling into view (touch — there
   is no hover to read intent from). State classes are set by initMediaVideo():
     • .is-hovering → the silent preview is running (either trigger, hence the name)
     • .is-playing  → the visitor committed; poster/play/duration are gone
   An embed with no poster at all skips the façade and renders the plain iframe. */
.media-block {
    margin: 0;
}

.media-block__img {
    display: block;
    width: 100%;
    height: auto;
    border-radius: 12px;
}

.media-block__video {
    position: relative;
    width: 100%;
    aspect-ratio: 16 / 9;
    border-radius: 12px;
    overflow: hidden;
    background: rgba(0, 0, 0, .06);
}

.media-block__video iframe {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    border: 0;
}

.media-block__caption {
    margin-top: 8px;
    color: var(--body-text);
}

/* Façade ------------------------------------------------------------------- */
/* The whole tile is clickable, but the play button is a REAL <button>, so keyboard
   and screen-reader users get a first-class path — the tile click is a convenience. */
.isv-video {
    cursor: pointer;
}

/* Layers, bottom to top: poster/video → injected iframe → scrim → play button → duration.
   Made explicit because the preview iframe is inserted mid-list at runtime, so DOM order
   alone would decide the stacking. */

/* Poster layer. In file mode the <video> IS the poster (its poster attribute), which
   is what lets one element serve preview, duration read-out and full playback. */
.isv-video__el,
.isv-video__poster {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    z-index: 0;
}

/* The provider iframe injected by site.js — muted preview on hover, full player on click.
   (.media-block__video iframe already gives it inset/size; this is only its layer.) */
.isv-video__frame {
    z-index: 1;
}

/* Scrim: keeps the white play glyph and duration pill legible over a bright poster. */
.isv-video::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 2;
    background: linear-gradient(180deg, rgba(0, 0, 0, .18) 0%, rgba(0, 0, 0, 0) 45%, rgba(0, 0, 0, .35) 100%);
    opacity: 1;
    transition: opacity .25s ease;
    pointer-events: none;
}

.isv-video__play {
    position: absolute;
    z-index: 3;
    top: 50%;
    left: 50%;
    translate: -50% -50%;
    display: grid;
    place-items: center;
    width: 68px;
    height: 68px;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: var(--white);
    color: var(--brand-primary);
    font-size: 22px;
    cursor: pointer;
    box-shadow: 0 4px 16px rgba(0, 0, 0, .25);
    transition: scale .25s ease, background-color .25s ease, color .25s ease, opacity .25s ease;
}

/* Nudge the glyph off-centre — a triangle's optical centre sits left of its bounding box. */
.isv-video__play i {
    translate: 2px 0;
}

.isv-video:hover .isv-video__play,
.isv-video__play:focus-visible {
    scale: 1.12;
    background: var(--brand-primary-strong);
    color: var(--white);
}

.isv-video__play:focus-visible {
    outline: 3px solid var(--focus-ring);
    outline-offset: 3px;
}

.isv-video__duration {
    position: absolute;
    z-index: 3;
    right: 10px;
    bottom: 10px;
    padding: 3px 8px;
    border-radius: 4px;
    background: rgba(0, 0, 0, .75);
    color: var(--white);
    font-size: var(--fs-body-sm);
    font-weight: var(--fw-medium);
    line-height: 1.4;
    font-variant-numeric: tabular-nums;
    pointer-events: none;
    transition: opacity .25s ease;
}

/* Server-rendered empty (an upload whose duration the JS hasn't measured yet) — reserve
   nothing and show nothing rather than flashing an empty pill. */
.isv-video__duration:empty {
    display: none;
}

/* Silent preview running: fade the furniture back so the motion reads, but keep the
   play button faintly visible so the tile still says "click me". */
.isv-video.is-hovering::after {
    opacity: .35;
}

.isv-video.is-hovering .isv-video__play {
    opacity: .8;
}

.isv-video.is-hovering .isv-video__duration {
    opacity: 0;
}

/* Committed to playback — the player owns the tile now. */
.isv-video.is-playing {
    cursor: default;
}

.isv-video.is-playing::after {
    opacity: 0;
}

.isv-video.is-playing .isv-video__play,
.isv-video.is-playing .isv-video__poster,
.isv-video.is-playing .isv-video__duration {
    display: none;
}

/* In file mode the <video> is the only element, so it must stop cropping once real
   playback starts — letterbox instead of cutting the frame. */
.isv-video.is-playing .isv-video__el {
    object-fit: contain;
    background: var(--black);
}

/* Touch: the preview is driven by scroll position instead of hover (see .is-hovering above),
   so the :hover rule has no job here — and worse, it STICKS after a tap on some mobile
   browsers, leaving the button stuck large and inverted. Pin it back to its rest state. */
@media (hover: none) {
    .isv-video:hover .isv-video__play {
        scale: 1;
        background: var(--white);
        color: var(--brand-primary);
    }
}

@media (prefers-reduced-motion: reduce) {
    .isv-video__play,
    .isv-video__duration,
    .isv-video::after {
        transition: none;
    }

    .isv-video:hover .isv-video__play {
        scale: 1;
    }
}

/* Key contacts panel ------------------------------------------------------ */
.key-contacts {
    display: flex;
    flex-direction: column;
    gap: 20px;
    padding-top: 5px;
    border-top: 1px solid var(--border-hairline);
}

.key-contacts__eyebrow {
    margin: 0;
    font-family: var(--font-eyebrow);
    font-size: 12px;
    font-weight: var(--fw-eyebrow);
    line-height: 1.5;
    letter-spacing: 1.2px;
    text-transform: uppercase;
    color: var(--brand-primary);
}

/* Multiple contact cards stack with a gap. */
.key-contacts__list {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.key-contact {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 12px;
    border-radius: 12px;
    /* Figma 119:2882 lays 50% white over --surface-cool — a lighter tint than the solid token,
       so the card reads as a panel inside the rail rather than a second callout. (≈ #f1fafc.) */
    background: color-mix(in srgb, var(--surface-cool) 50%, var(--white));
}

.key-contact__head {
    display: flex;
    align-items: center;
    gap: 12px;
}

.key-contact__photo {
    flex: 0 0 50px;
}

.key-contact__photo img {
    width: 50px;
    height: 50px;
    border-radius: 50%;
    object-fit: cover;
    display: block;
}

.key-contact__id {
    display: flex;
    flex-direction: column;
    font-size: var(--fs-body-sm);
    line-height: 1.5;
}

.key-contact__name {
    font-weight: var(--fw-regular);   /* 550 — Figma 119:2877 is Stevie Sans Regular, not Medium */
    color: var(--black);
}

.key-contact__role {
    font-weight: var(--fw-book);
    color: var(--body-text);
}

.key-contact__details {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding-top: 8px;
    border-top: 1px solid rgba(0, 0, 0, .1);
    font-size: 12px;
}

.key-contact__link {
    color: var(--brand-primary);
    text-decoration: none;
    word-break: break-word;
}

.key-contact__link:hover {
    text-decoration: underline;
}

/* ---- Article responsive --------------------------------------------------- */
@media (max-width: 1024px), (max-width: 1366px) and (pointer: coarse) {   /* the touch band */
    /* There is no room for a 250px sticky column beside the body, but the column is not
       disposable — it carries Key Contacts, which has nowhere else to live. So STACK it under
       the article instead of hiding it (it used to be display:none, which silently dropped the
       contacts on every phone and tablet). Contents navigation is handled separately by the
       jump bar at the top of the body, so only the contacts travel down here. */
    .article__frame {
        flex-direction: column;
    }

    /* .article__frame keeps `align-items: flex-start`, and in COLUMN direction that is a
       cross-axis rule — both children would size to fit-content (floored at min-content, which
       min-width:0 cannot lower) and could be widened by any oversized descendant. Same trap as
       .page-frame above; pin both to the full row width. */
    .article__main,
    .article__aside {
        width: 100%;
    }

    .article__aside {
        flex-basis: auto;
        border-right: 0;
        padding: 0 var(--sp-h-large) var(--sp-v-large);
    }

    /* Nothing to stick to once it is a full-width band at the end of the page. */
    .article__aside-inner {
        position: static;
    }

    /* The contents rail is already presented as the jump bar up at the top of the body —
       an "on this page" list at the BOTTOM of the page would be dead weight, and its
       "Back to the top" duplicates the bar's. Leave just Key Contacts. */
    .article__aside .contents-rail {
        display: none;
    }

    /* Keep the topbar to the design's single 34px row (Figma 191:14913). A long breadcrumb was
       wrapping to a second line and doubling the row's height, so let it shrink instead and
       ellipsise: every flex ancestor needs min-width:0 to drop below its content's min-content
       width, and the truncation itself goes on the crumb's inner <span>, since the crumb is an
       inline-flex box (text-overflow does not apply to the flex container itself). */
    .article__topbar .breadcrumbs,
    .article__topbar .breadcrumbs__list,
    .article__topbar .breadcrumbs__item,
    .article__topbar .breadcrumbs__crumb {
        min-width: 0;
    }

    .article__topbar .breadcrumbs__list {
        flex-wrap: nowrap;
    }

    .article__topbar .breadcrumbs__crumb > span {
        overflow: hidden;
        white-space: nowrap;
        text-overflow: ellipsis;
    }

    .content-section--cards .content-section__body > div > div > div {
        grid-template-columns: repeat(2, 1fr);
    }
}

@media (max-width: 640px) {
    .content-section--cards .content-section__body > div > div > div {
        grid-template-columns: 1fr;
    }
}

/* ============================================================================
   Hub experience — topic hub (e.g. Strategic Planning). A header band (reuses
   .header-section) over a two-column body: a sticky "on this page" section rail
   (left, the shared _ContentsRail) and a stack of titled groups (right), each a
   2-up grid of "resources" cards. Fully generated from the content tree — one
   group section per child listing, cards from each group's children. Figma 47:2280.
   ========================================================================== */

/* Frame sits flush under the header band; the footer's full-width border-top closes it. */
.hub-section {
    padding-block: 0;
}

/* The 1330px content box, centred, framed left/right (matches .listing / .site-nav__inner). */
.hub {
    max-width: 1330px;
    margin: 0 auto;
    border-left: 1px solid var(--border-hairline);
    border-right: 1px solid var(--border-hairline);
    display: grid;
    grid-template-columns: 250px 1fr;
    align-items: start;
}

/* Left column — the sticky section rail (shared .contents-rail markup + styling). */
.hub__rail {
    position: sticky;
    top: var(--rail-top);   /* follows the auto-hiding nav via :root --rail-top */
    max-height: calc(100vh - var(--rail-top) - var(--sp-v-medium));
    overflow-y: auto;
    padding: 30px 20px 30px 30px;
    min-width: 0;
    transition: top 0.25s ease, max-height 0.25s ease;   /* slide with the nav (matches article rails) */
}

/* ---- Section-nav accordion (.side-nav) — Figma node 191:13487 --------------
   Shared by the standalone Side Navigation pane (.side-nav-pane) AND the Grouped (hub) rail
   (.hub__rail). Source name = maroon eyebrow; the body is a RECURSIVE tree: a node with children is a
   native <details> accordion, a leaf is a link that navigates. Top-level rows read as headers (hairline
   separated); nested rows are compact + indented, deeper levels indenting further. The current page's
   link (and any ancestor header on its path) gets .is-active. Markup: _SideNav / _SideNavNode.cshtml. */
/* Also dresses the "ON THIS PAGE" header site.js puts on the jump-to dropdown, so that panel and
   the relocated section nav (which brings this eyebrow with it) are titled identically. */
.side-nav__eyebrow,
.topbar-drop__eyebrow {
    margin: 0 0 20px;
    font-family: var(--font-eyebrow);
    font-size: 13px;
    font-weight: var(--fw-eyebrow);
    letter-spacing: 1.2px;
    text-transform: uppercase;
    color: var(--brand-primary);
}

.side-nav__list {
    list-style: none;
    margin: 0;
    padding: 0;
}

/* A row is either an accordion header (<summary>) or a leaf link (<a>/<span>); both share the layout. */
.side-nav__summary,
.side-nav__link {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    line-height: 1.3;
    text-decoration: none;
    color: var(--body-text);
}

.side-nav__summary {
    cursor: pointer;
    list-style: none;
}

/* Hide the native disclosure triangle (we use our own chevron). */
.side-nav__summary::-webkit-details-marker { display: none; }
.side-nav__summary::marker { content: ""; }

.side-nav__summary:hover .side-nav__title,
.side-nav__link:not(.side-nav__link--static):hover {
    color: var(--brand-primary);
}

.side-nav__chevron {
    flex: 0 0 auto;
    font-size: 12px;
    color: var(--grey);
    transition: transform .15s ease;
}

.side-nav__group[open] > .side-nav__summary .side-nav__chevron {
    transform: rotate(180deg);
}

/* A non-clickable container-only listing appears as muted, non-interactive text. */
.side-nav__link--static {
    color: var(--grey);
    cursor: default;
}

/* --- Top-level rows: prominent header size, hairline separators (the airy Figma spacing). --- */
.side-nav__list > .side-nav__item > .side-nav__group > .side-nav__summary,
.side-nav__list > .side-nav__item > .side-nav__link {
    padding: 14px 0;
    color: var(--text-strong);
    font-size: 15px;
    font-weight: var(--fw-medium);
}

.side-nav__list > .side-nav__item + .side-nav__item {
    border-top: 1px solid var(--border-hairline);
}

/* Current top-level row (you're on that listing page itself) — teal, bolded, no pill. */
.side-nav__list > .side-nav__item > .side-nav__group > .side-nav__summary.is-active .side-nav__title,
.side-nav__list > .side-nav__item > .side-nav__link.is-active {
    color: var(--brand-primary);
    font-weight: var(--fw-medium);
}

/* --- Nested rows: compact + indented (indent compounds with each level of nesting). --- */
.side-nav__sublist {
    list-style: none;
    margin: 4px 0 10px;
    padding: 0 0 0 14px;
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.side-nav__sublist .side-nav__summary,
.side-nav__sublist .side-nav__link {
    padding: 6px 10px;
    margin: 0 -10px;
    border-radius: 6px;
    font-size: var(--fs-body-sm);
    font-weight: normal;
}

/* Current page — the highlighted link in the Figma (teal text on a pale tinted pill). */
.side-nav__sublist .side-nav__link.is-active,
.side-nav__sublist .side-nav__summary.is-active {
    background: var(--surface-cool);
    color: var(--brand-primary);
    font-weight: var(--fw-medium);
}

/* ---- Standalone Side Navigation pane + page frame --------------------------
   _Layout wraps the page content in .page-frame (a flex row) and drops the optional pane beside it.
   When no pane renders, <main> flexes to full width, so pages with the pane Off are unchanged.
   The pane hosts the shared .side-nav accordion; Right places it after the content via flex order. */
.page-frame {
    display: flex;
    align-items: flex-start;
}

.page-frame > main {
    flex: 1 1 auto;
    min-width: 0;   /* let inner grids shrink instead of overflowing the row */
}

/* When a pane is actually present, .page-frame BECOMES the centred 1330 site frame (matching the
   nav/footer/listing), so the pane aligns to the container's left edge and the content column
   compresses to fill the rest — instead of the pane sitting at the viewport edge with the content
   re-centring in the leftover space. Scoped with :has() so pane-Off pages are completely unchanged.
   .page-frame owns the OUTER frame hairlines; the pane owns the seam divider; the inner content
   frames drop their own centring + side borders (redundant now) and fill the column. */
.page-frame:has(> .side-nav-pane) {
    max-width: 1330px;
    margin-inline: auto;
    background: var(--white);
    border-left: 1px solid var(--border-hairline);
    border-right: 1px solid var(--border-hairline);
}

.page-frame:has(> .side-nav-pane) :is(
    .header-section, .listing, .hub, .cat-group,
    .article__frame, .article__main, .article__aside) {
    max-width: none;
    margin-inline: 0;
    border-left: 0;
    border-right: 0;
}

.side-nav-pane {
    flex: 0 0 260px;
    align-self: stretch;                 /* full-height divider beside the content */
    box-sizing: border-box;
    background: var(--white);
    border-right: 1px solid var(--border-hairline);   /* seam between the pane and the content */
}

.side-nav-pane--right {
    order: 2;                            /* push after <main> → sits on the right */
    border-right: none;
    border-left: 1px solid var(--border-hairline);
}

/* The pane's nav has been RELOCATED into the article topbar's ≡ dropdown (site.js →
   initTopbarNav), so the empty shell steps out of the flow. Set only on pages where the move
   actually happened — a listing page with a pane but no topbar keeps the stacked presentation.
   The element stays in the DOM, so `.page-frame:has(> .side-nav-pane)` still matches and the
   1330 frame + hairlines below are unaffected. */
.side-nav-pane.is-collapsed {
    display: none;
}

.side-nav-pane__inner {
    position: sticky;
    top: var(--rail-top);              /* follows the auto-hiding nav via :root --rail-top */
    max-height: calc(100vh - var(--rail-top) - var(--sp-v-medium));
    overflow-y: auto;
    padding: 30px 20px 30px 30px;
    transition: top 0.25s ease, max-height 0.25s ease;   /* slide with the nav (matches article rails) */
}

.side-nav-pane--right .side-nav-pane__inner {
    padding: 30px 30px 30px 20px;
}

/* ---- "docs" skin (content/help hub pages) --------------------------------
   A documentation-style pane (Figma "Help & Support", Tailwind-docs pattern): the hub's topic
   pages are a FLAT, COMPACT list with a filled pill on the current page — rather than the airy,
   hairline-separated headers of the default look. Only the .side-nav-pane--docs WRAPPER is
   scoped, so the shared _SideNav structure and the hub rail (.hub__rail) are untouched. */
.side-nav-pane--docs .side-nav__list {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

/* Top-level rows become compact menu items (like the default look's nested rows). */
.side-nav-pane--docs .side-nav__list > .side-nav__item > .side-nav__group > .side-nav__summary,
.side-nav-pane--docs .side-nav__list > .side-nav__item > .side-nav__link {
    padding: 8px 10px;
    margin: 0 -10px;
    border-radius: 6px;
    font-size: var(--fs-body-sm);
    font-weight: var(--fw-book);
}

/* Flat list — no hairline separators between top-level rows. */
.side-nav-pane--docs .side-nav__list > .side-nav__item + .side-nav__item {
    border-top: none;
}

/* Current page — filled tinted pill + teal text (the Figma docs highlight), replacing the
   default top-level "teal bold, no pill" treatment. */
.side-nav-pane--docs .side-nav__list > .side-nav__item > .side-nav__link.is-active,
.side-nav-pane--docs .side-nav__list > .side-nav__item > .side-nav__group > .side-nav__summary.is-active {
    background: var(--surface-cool);
    color: var(--brand-primary);
    font-weight: var(--fw-medium);
}

.side-nav-pane--docs .side-nav__list > .side-nav__item > .side-nav__group > .side-nav__summary.is-active .side-nav__title {
    color: var(--brand-primary);
}

/* On narrow screens the pane drops out of the row and stacks above the content (matches .hub__rail).
   Uses the touch band so a landscape tablet stacks it too, in step with the article's own aside —
   otherwise the pane would sit beside the content while the aside below it had already stacked. */
@media (max-width: 1024px), (max-width: 1366px) and (pointer: coarse) {
    .page-frame {
        flex-direction: column;
    }

    /* Column direction turns .page-frame's `align-items: flex-start` into a CROSS-axis rule, so
       <main> is sized fit-content instead of stretched — and fit-content floors at min-content,
       which `min-width: 0` above cannot lower. One over-wide descendant would therefore widen
       <main>, .article__frame and the text inside it past the viewport. Pin it to the full row
       width so nothing inside can ever grow the shell. (The pane below does the same thing.) */
    .page-frame > main {
        width: 100%;
    }

    .side-nav-pane {
        flex-basis: auto;
        width: 100%;
        border-right: none;
        border-bottom: 1px solid var(--border-hairline);
    }

    .side-nav-pane--right {
        order: 0;
        border-left: none;
    }

    .side-nav-pane__inner {
        position: static;
        max-height: none;
        overflow: visible;
        padding: 20px 30px;
    }
}

/* Right column — the stacked group sections. */
.hub__groups {
    padding: 30px;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: var(--sp-v-large);
}

.hub__empty {
    color: var(--body-text);
}

/* One group section. scroll-margin-top so a jumped-to section clears the sticky header. */
.hub-group {
    scroll-margin-top: var(--header-h);
    display: flex;
    flex-direction: column;
    gap: var(--sp-grid);
}

.hub-group__head {
    margin: 0;
    color: var(--text-strong);
}

/* Card grid — density driven by the block's "Cards per row" setting (data-cols), same as the flat
   listing grid. Bare cards inherit the "resources" look from the .isv-cards--resources modifier on
   this container (see the .isv-card system). The view always emits data-cols (default 3). */
.hub-group__grid {
    display: grid;
    /* minmax(0, 1fr) — a bare 1fr track floors at min-content, so one long unbroken word or URL
       in a card would push the grid past its container. Matches .cat-group__grid / .listing-grid. */
    grid-template-columns: repeat(var(--cards, 3), minmax(0, 1fr));
    gap: var(--sp-grid);
}

/* Cards per row (editor "size" control): 3 uses the base default, the others override. */
.hub-group__grid[data-cols="1"] { --cards: 1; }
.hub-group__grid[data-cols="2"] { --cards: 2; }
.hub-group__grid[data-cols="4"] { --cards: 4; }

/* Opt hub cards into the hover title recolour, matching the listing grid. */
.hub-group__grid .isv-card {
    --card-title-hover-color: var(--brand-primary-hover);
}

/* Density-aware card sizing (the "Cards per row" setting): as the columns increase the cards get
   narrower, so scale the panel padding, title and description down to keep every look balanced.
   2-up is the hub default and keeps the base Resources look (title h4, pad 25px) — no override. */
.hub-group__grid[data-cols="1"] .isv-card:not(.isv-card--styled) {
    --card-pad: 30px;
    --card-body-gap: 14px;
    --card-title-fs: var(--fs-h3);
}
.hub-group__grid[data-cols="3"] .isv-card:not(.isv-card--styled) {
    --card-pad: 20px;
    --card-body-gap: 10px;
    --card-title-fs: var(--fs-h5);
}
.hub-group__grid[data-cols="4"] .isv-card:not(.isv-card--styled) {
    --card-pad: 16px;
    --card-body-gap: 8px;
    --card-title-fs: var(--fs-h5);
}

/* Description length follows density: roomy 1–2-up cards show the FULL text (intro + the whole
   bullet list); denser 3–4-up cards clamp it to a teaser so the shorter cards stay balanced.
   (Overrides the shared 3-line "resources" clamp — specificity intentionally beats it.) */
.hub-group__grid[data-cols="1"].isv-cards--resources .isv-card:not(.isv-card--styled) .isv-card__summary,
.hub-group__grid[data-cols="2"].isv-cards--resources .isv-card:not(.isv-card--styled) .isv-card__summary {
    display: block;
    -webkit-line-clamp: unset;
    line-clamp: unset;
    overflow: visible;
}
.hub-group__grid[data-cols="3"].isv-cards--resources .isv-card:not(.isv-card--styled) .isv-card__summary {
    display: -webkit-box;
    -webkit-line-clamp: 6;
    line-clamp: 6;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.hub-group__grid[data-cols="4"].isv-cards--resources .isv-card:not(.isv-card--styled) .isv-card__summary {
    display: -webkit-box;
    -webkit-line-clamp: 3;
    line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.hub-group__empty {
    grid-column: 1 / -1;
    color: var(--body-text);
}

/* ---- Responsive ----------------------------------------------------------- */
@media (max-width: 1024px) {
    .hub {
        grid-template-columns: 1fr;
    }

    /* Rail drops out of the sticky column and sits at the top of the body. */
    .hub__rail {
        position: static;
        max-height: none;
        overflow: visible;
        padding: 30px 30px 0;
    }

    /* Tablet: cap dense grids at 2-up (1 and 2 keep their setting), matching the flat listing grid. */
    .hub-group__grid[data-cols="3"] { --cards: 2; }
    .hub-group__grid[data-cols="4"] { --cards: 2; }
}

@media (max-width: 640px) {
    /* Mobile: single column at any density. */
    .hub-group__grid[data-cols] { --cards: 1; }

    /* One roomy column → show the full description regardless of the authored density. */
    .hub-group__grid[data-cols].isv-cards--resources .isv-card:not(.isv-card--styled) .isv-card__summary {
        display: block;
        -webkit-line-clamp: unset;
        line-clamp: unset;
        overflow: visible;
    }
}

/* ---- Downloadable Resource (DownloadableResourceBlock) ---------------------
   Three Style-panel looks share one element:
     • .isv-resource--row     = compact document row (icon + heading + desc + price + Get now)  — default
     • .isv-resource--pdf     = inline PDF viewer (title + print/download bar over a large preview)
     • .isv-resource--button  = a single compact "Download link" outlined button (Figma 102:7134)
   Buttons reuse the shared .btn / .btn--* styling so they match a CTA Button. */
.isv-resource {
    box-sizing: border-box;
    /* Block-owned vertical rhythm, BOTTOM-only: each block spaces only below itself, so a
       stack of blocks in a container gets one gap between each (no 30+30 doubling), the first
       block sits naturally against the container top, and the block never stacks a top margin
       on top of a section's own padding (Figma: 30px between modules). */
    margin-block: 0 var(--sp-grid);
}

/* --- Document row look --- */
.isv-resource--row {
    display: flex;
    align-items: center;
    gap: 20px;
    padding: 25px;
    background: var(--white);
    border: 1px solid var(--border-hairline);
    border-radius: 12px;
}

.isv-resource__icon {
    flex: 0 0 auto;
    display: inline-flex;
    font-size: 50px;              /* Figma node 102:7099 */
    line-height: 1;
    color: var(--blue-doc);
}

/* Uploaded icon image (e.g. a coloured Word/PDF badge) — sized to the FA glyph box. */
.isv-resource__icon-img {
    width: 50px;
    height: 50px;
    object-fit: contain;
}

.isv-resource__body {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.isv-resource__title {
    margin: 0;
    color: var(--text-strong);
}

/* Row title should read as a card title regardless of the chosen heading level. */
.isv-resource--row .isv-resource__title {
    font-size: var(--fs-h4);
}

.isv-resource__desc {
    color: var(--text-body);
}

.isv-resource__desc > :first-child { margin-top: 0; }
.isv-resource__desc > :last-child { margin-bottom: 0; }

.isv-resource__aside {
    flex: 0 0 auto;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 8px;
    text-align: right;
}

.isv-resource__aside-top {
    display: flex;
    align-items: center;
    gap: 12px;
}

.isv-resource__price-pill {
    padding: 5px 12px;
    border-radius: 6px;
    background: var(--surface-cool);
    color: var(--brand-primary);
    font-weight: var(--fw-medium);
    white-space: nowrap;
}

.isv-resource__aside-note {
    display: flex;
    flex-direction: column;
    gap: 2px;
    color: var(--text-body);
    opacity: .75;
}

/* --- PDF viewer look --- */
.isv-resource--pdf {
    border: 1px solid var(--border-hairline);
    border-radius: 12px;
    overflow: hidden;
    background: var(--white);
}

.isv-resource__bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    padding: 14px 18px;
    border-bottom: 1px solid var(--border-hairline);
}

.isv-resource--pdf .isv-resource__title {
    font-size: var(--fs-h5);
}

.isv-resource__tools {
    flex: 0 0 auto;
    display: flex;
    gap: 8px;
}

.isv-resource__tool {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    padding: 0;
    border: 1px solid var(--border-hairline);
    border-radius: 6px;
    background: var(--white);
    color: var(--brand-primary);
    cursor: pointer;
    text-decoration: none;
}

.isv-resource__tool:hover {
    background: var(--surface-cool);
}

.isv-resource__preview {
    background: var(--surface-muted);
}

.isv-resource__cover {
    display: block;
    width: 100%;
    height: auto;
}

.isv-resource__frame {
    display: block;
    width: 100%;
    height: 70vh;
    min-height: 480px;
    border: 0;
}

.isv-resource__placeholder {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 10px;
    min-height: 320px;
    color: var(--text-body);
    opacity: .6;
}

.isv-resource__placeholder i {
    font-size: 64px;
    line-height: 1;
}

.isv-resource__placeholder-img {
    width: 96px;
    height: 96px;
    object-fit: contain;
}

/* --- Download button look (Figma 102:7134) ---
   A single compact "Download link": --border-accent hairline, Royal Blue text, download-to-line icon.
   Builds on the shared .btn base and only overrides the deltas from the design. */
.isv-resource__download {
    gap: 10px;
    padding: 6px 10px;
    border: 1px solid var(--border-accent);
    background: var(--white);
    color: var(--brand-primary);
}

.isv-resource__download:hover {
    border-color: var(--brand-primary);
}

.isv-resource__download.is-disabled {
    opacity: .5;
    pointer-events: none;
}

@media (max-width: 640px) {
    .isv-resource--row {
        flex-wrap: wrap;
    }

    .isv-resource__aside {
        align-items: flex-start;
        text-align: left;
        width: 100%;
    }
}

/* ============================================================================
   Search experience — the site-wide search results page (Figma 237:6742).

   Deliberately thin, because the page reuses the listing chrome wholesale: the
   root is .listing/.listing__body/.listing__main (so the filter rail, the card
   grid and the pager all style and AJAX-swap exactly as on a listing), and the
   search box reuses the .search-field component from the home Resources band.
   What's left below is only what's genuinely search-specific — the page heading,
   widening that field, the section eyebrows and the two empty states.

   The result ROW look is not here: it's .isv-cards--searchrow, which lives with
   its sibling looks in the unified card system near the top of this file.

   NB the class prefix is .search-results__*, NOT .search-* — .search-band,
   .search-panel, .search-field and .search-tag are already taken by the home
   page's Resources band.
   ========================================================================== */
.search-results__heading {
    margin: 0 0 var(--sp-grid);
    color: var(--isv-fg);
}

/* Widen the shared .search-field to the full content column (it is capped at
   565px for the home band) and give it the taller 48px sit from the design. */
.search-results__form {
    max-width: none;
    padding: 8px 8px 8px 18px;
    margin: 0 0 var(--sp-grid);
}

/* ---- Result sections ------------------------------------------------------- */
.search-results__section + .search-results__section {
    margin-top: var(--sp-v-medium);
}

/* Uppercase eyebrow above each band of results (TOPICS / RESOURCES / …). The band
   label is a category display name, so it is authored, not hard-coded. */
.search-results__eyebrow {
    margin: 0 0 12px;
    text-transform: uppercase;
    letter-spacing: .08em;
    font-weight: var(--fw-medium);
    color: var(--grey);
}

/* Rows are always one-up and self-sized, so drop the card grid's tile gap for a
   tighter stack matching the design's 15px row rhythm. */
.search-results__section .listing-grid {
    gap: 15px;
}

/* ---- Empty / initial states ------------------------------------------------ */
.search-results__prompt,
.search-results__empty-hint {
    margin: 0;
    color: var(--body-text);
}

.search-results__empty-hint {
    margin-top: 8px;
}

.search-results__empty-hint a {
    color: var(--brand-primary);
}

/* ---- AI Summary (Figma 237:7065) -------------------------------------------
   Renders only when a summary provider is configured; ISearchSummaryService is a
   deliberate no-op today, so on the live site this block is currently unused. Kept
   styled so enabling the feature needs no CSS work. */
.search-summary {
    background: var(--accent-warm-bg);   /* Cream — the warm accent surface */
    border-radius: 12px;
    padding: 25px;
    margin: 0 0 var(--sp-grid);
}

.search-summary__title {
    margin: 0 0 12px;
    color: var(--brand-primary);
}

.search-summary__body {
    margin: 0;
    color: var(--isv-fg);
}

.search-summary__note {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    margin: 15px 0 0;
    color: var(--grey);
}

/* ---- Responsive ------------------------------------------------------------ */
@media (max-width: 640px) {
    .search-results__form {
        padding: 6px 6px 6px 14px;
    }

    /* Stack the row: icon + title, then snippet, then the pill — three columns
       don't survive a narrow screen without truncating the title to nothing. */
    .isv-cards--searchrow .isv-card:not(.isv-card--styled) .isv-card__link,
    .isv-card--searchrow .isv-card__link {
        grid-template-columns: auto minmax(0, 1fr);
        row-gap: 8px;
    }

    .isv-cards--searchrow .isv-card:not(.isv-card--styled) .isv-card__top,
    .isv-card--searchrow .isv-card__top {
        grid-row: 1;
    }

    .isv-cards--searchrow .isv-card:not(.isv-card--styled) .isv-card__summary,
    .isv-card--searchrow .isv-card__summary {
        grid-column: 1 / span 2;
        white-space: normal;              /* wrap instead of ellipsis when stacked */
    }

    .isv-cards--searchrow .isv-card:not(.isv-card--styled) .isv-card__meta,
    .isv-card--searchrow .isv-card__meta {
        grid-column: 1 / span 2;
        grid-row: auto;
        justify-self: start;
    }
}

/* ==========================================================================
   COMPLIANCE CALENDAR (Figma 1080:11425)
   A banner, a left Role filter rail, and one accordion per month holding that
   month's entry cards. The rail reuses .listing-filters* wholesale — only the
   calendar-specific chrome lives here.
   ========================================================================== */

/* The 1330px content box, framed left/right — matches .listing / .site-nav__inner. */
.calendar {
    max-width: 1330px;
    margin: 0 auto;
    border-left: 1px solid var(--border-hairline);
    border-right: 1px solid var(--border-hairline);
}

/* Two columns, no gap — same rhythm as .listing__body: the rail sits flush against
   the frame's left border and the main column's padding provides the inset. */
.calendar__inner {
    display: grid;
    grid-template-columns: 250px 1fr;
    align-items: start;
}

/* No roles tagged yet → the rail renders nothing, so the months span the full width. */
.calendar__inner:not(:has(> .calendar-filters)) {
    grid-template-columns: 1fr;
}

.calendar__main {
    padding: 30px;
    min-width: 0;
}

/* ---- Toolbar -------------------------------------------------------------- */
/* Holds "Show previous months" and the grid/list view toggle. The share/download/save
   controls on the right of the Figma toolbar are still out of scope. */
.calendar__tools {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 12px;
    margin-bottom: var(--sp-grid);
}

/* Hidden until site.js finds at least one month marked "Previous month", so a calendar
   with nothing held back never shows a dead control. */
.calendar__show-previous[hidden] {
    display: none;
}

.calendar__show-previous {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 7px 14px;
    border: 1px solid var(--border-accent);
    border-radius: 6px;
    background: var(--white);
    color: var(--brand-primary);
    font-family: var(--font-body);
    font-size: var(--fs-body-sm);
    font-weight: var(--fw-medium);
    line-height: 1.3;
    cursor: pointer;
    transition: background-color .15s ease;
}

.calendar__show-previous:hover {
    background: var(--surface-cool);
}

/* ---- Toolbar icon buttons -------------------------------------------------- */
/* The shared 34px square behind BOTH the view toggle (Figma 1080:11558) and the
   share/download/save actions (1080:11559) — identical hairline, radius and Royal Blue
   regular icon in the design, so it is one class here. Only the corner rounding
   differs, and that is overridden per group below. */
.calendar__icon-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    padding: 9px 10px;
    border: 1px solid var(--border-accent);
    border-radius: 6px;
    background: var(--white);
    color: var(--brand-primary);
    font-size: 16px;
    line-height: 1;
    cursor: pointer;
}

.calendar__icon-btn:hover {
    background: var(--surface-cool);
}

/* ---- View toggle (grid | list) -------------------------------------------- */
/* Two segmented icon buttons choosing between the calendar's two layouts. Which one
   reads as selected is derived from the ROOT's data-view, not from a class site.js
   applies — so the toggle is already correct on the first paint of a ?view=list URL,
   with no flash. */
.calendar__view {
    display: inline-flex;
    align-items: center;
}

/* The pair shares one seam: round only the outer corners and pull the second button
   onto the first's border so they read as a single control. */
.calendar__view-option:first-child {
    border-radius: 6px 0 0 6px;
}

.calendar__view-option:last-child {
    border-radius: 0 6px 6px 0;
    margin-left: -1px;
}

/* The selected view: Royal Blue outline, Royal Blue icon, and the SOLID cut of the glyph
   (--fa-style is Font Awesome 7's weight switch, so one declaration turns the regular
   icon solid). z-index lifts its outline above the shared seam. */
.calendar[data-view="grid"] .calendar__view-option[data-calendar-view="grid"],
.calendar[data-view="list"] .calendar__view-option[data-calendar-view="list"] {
    position: relative;
    z-index: 1;
    border-color: var(--brand-primary);
    color: var(--brand-primary);
}

.calendar[data-view="grid"] .calendar__view-option[data-calendar-view="grid"] i,
.calendar[data-view="list"] .calendar__view-option[data-calendar-view="list"] i {
    --fa-style: 900;
}

/* ---- Page actions (share / download / save) -------------------------------- */
/* The trio at the right end of the Figma toolbar (1080:11559). Presentation only for
   now — the buttons carry no behaviour yet, so they are real <button>s with labels
   rather than links, and wiring one up later is only a click handler. Pushed to the
   far right of the toolbar, opposite the view toggle. */
.calendar__actions {
    display: inline-flex;
    align-items: center;
    gap: 12px;
    margin-left: auto;
}

/* ---- Month accordion ------------------------------------------------------ */
.calendar-month {
    border-bottom: 1px solid var(--border-hairline);
    /* Where a jumped-to month lands, clearing the auto-hiding header (see :root --header-h).
       Used by the on-load anchor to the current month and by "Show previous months". */
    scroll-margin-top: var(--header-h);
}

/* Months marked "Previous month" are held back until the visitor asks for them.
   data-month-held-back is false in edit/preview, so editors always see every section. */
.calendar-month[data-month-held-back="true"] {
    display: none;
}

.calendar[data-show-previous="true"] .calendar-month[data-month-held-back="true"] {
    display: block;
}

.calendar-month__summary {
    display: flex;
    align-items: center;
    gap: 15px;
    padding: 15px 0;
    cursor: pointer;
    list-style: none;
}

.calendar-month__summary::-webkit-details-marker { display: none; }
.calendar-month__summary::marker { content: ""; }

.calendar-month__chevron {
    font-size: 16px;
    color: var(--brand-primary);
    transition: transform .15s ease;
}

.calendar-month__accordion[open] > .calendar-month__summary .calendar-month__chevron {
    transform: rotate(180deg);
}

.calendar-month__name {
    color: var(--brand-primary);
    font-weight: var(--fw-medium);
}

.calendar-month__grid {
    padding-bottom: var(--sp-grid);
}

/* The VB grid renders CLASS-LESS divs (.calendar-month__grid > grid > row > column >
   [entry wrappers]) — same shape as .cat-group__grid. Make the column a 4-up grid so
   entries fill the cells and stay individually selectable on the canvas. */
.calendar-month__grid > div > div > div {
    display: grid;
    grid-template-columns: repeat(var(--calendar-cols, 4), 1fr);
    gap: 15px;
    align-items: stretch;
}

/* ---- Entry card ----------------------------------------------------------- */
/* Its own markup rather than the shared .isv-card: that template wraps everything in a
   single <a>, and this card carries several links inside it. Same surface tokens, so it
   still reads as part of the card family. */
.calendar-card {
    display: flex;
    flex-direction: column;
    height: 100%;
    padding: 20px;
    background: var(--white);
    border: 1px solid var(--border-hairline);
    border-radius: 6px;
}

.calendar-card__date {
    margin: 0 0 12px;
    color: var(--black);
    font-weight: var(--fw-medium);
}

.calendar-card__body {
    color: var(--body-text);
}

.calendar-card__body > :first-child { margin-top: 0; }
.calendar-card__body > :last-child { margin-bottom: 0; }

/* Pinned to the bottom so the "Reference" blocks line up across a row of uneven cards. */
.calendar-card__references {
    margin-top: auto;
    padding-top: 16px;
}

.calendar-card__references-heading {
    margin: 0 0 8px;
    color: var(--black);
    font-family: var(--font-body);
    font-size: var(--fs-body-sm);
    font-weight: var(--fw-medium);
    line-height: 1.3;
}

.calendar-card__reference-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 8px;
}

/* Each reference carries a light rule on its left, per the Figma card. */
.calendar-card__reference {
    border-left: 1px solid var(--border-hairline);
    padding-left: 10px;
}

.calendar-card__reference-link {
    color: var(--brand-primary);
    font-family: var(--font-body);
    font-size: var(--fs-body-sm);
    font-weight: var(--fw-book);
    line-height: 1.4;
    text-decoration: none;
}

.calendar-card__reference-link:hover {
    text-decoration: underline;
}

/* ---- List view ------------------------------------------------------------ */
/* The second of the two view options (Figma 1080:11524). Exactly the same entries and the
   same markup as the grid — only the layout changes — so the toggle is pure CSS and
   switching views never re-renders, re-fetches, or loses the visitor's role filter.

   One entry per row, laid out as three columns so the dates, descriptions and Reference
   blocks line up down the whole month: a fixed 75px date, the description, then the
   references pushed out to the right. The row is a soft tinted band rather than a bordered
   white card — that surface change is what distinguishes it from the grid. */
.calendar[data-view="list"] .calendar-month__grid > div > div > div {
    --calendar-cols: 1;
}

.calendar[data-view="list"] .calendar-card {
    display: grid;
    grid-template-columns: 75px minmax(0, 500px) minmax(0, 1fr);
    gap: 20px;
    align-items: start;
    padding: 15px;
    border: 0;
    border-radius: 12px;
    background: var(--surface-muted);  /* fallback for the mix below */
    background: color-mix(in srgb, var(--surface-muted) 50%, var(--white));
}

.calendar[data-view="list"] .calendar-card__date {
    margin: 0;
}

.calendar[data-view="list"] .calendar-card__body {
    min-width: 0;
}

/* The references have their own column here, so drop the grid card's "pin to the bottom
   of the card" spacing — there is nothing above them to push away from. */
.calendar[data-view="list"] .calendar-card__references {
    margin-top: 0;
    padding-top: 0;
}

.calendar[data-view="list"] .calendar-card__references-heading {
    margin-bottom: 6px;
    color: var(--brand-primary);
}

.calendar[data-view="list"] .calendar-card__reference-list {
    gap: 6px;
}

.calendar[data-view="list"] .calendar-card__reference {
    border-left: 2px solid var(--border-accent);
}

/* ---- Role filter ---------------------------------------------------------- */
/* The rail is .listing-filters (shared). These options are BUTTONS, not links, because
   filtering is client-side — so reset the button chrome and let .listing-filters__link
   supply the look. */
.calendar-filters__option {
    width: 100%;
    border: 0;
    background: none;
    text-align: left;
    cursor: pointer;
    font: inherit;
}

/* Hidden by the client-side role filter (initCalendar in site.js). The !important is
   deliberate: a held-back month is re-shown by a MORE SPECIFIC rule once "Show previous
   months" is on, and a month filtered empty must stay hidden regardless of that. */
.calendar-card.is-filtered-out,
.calendar-month.is-filtered-out {
    display: none !important;
}

/* ---- Responsive ----------------------------------------------------------- */
@media (max-width: 1200px) {
    .calendar-month__grid > div > div > div { --calendar-cols: 3; }
}

@media (max-width: 1024px) {
    /* Rail above the months, matching how the listing collapses on tablet. */
    .calendar__inner {
        grid-template-columns: 1fr;
    }

    .calendar-month__grid > div > div > div { --calendar-cols: 2; }

    /* List rows lose the third column: the references drop under the description, still
       indented past the date so the row keeps its date-in-the-margin reading. */
    .calendar[data-view="list"] .calendar-card {
        grid-template-columns: 75px minmax(0, 1fr);
    }

    .calendar[data-view="list"] .calendar-card__references {
        grid-column: 2;
        padding-top: 4px;
    }
}

@media (max-width: 720px) {
    .calendar__main {
        padding: 20px;
    }

    .calendar-month__grid > div > div > div { --calendar-cols: 1; }

    /* Too narrow for a date margin — stack the row and let it read like the grid card. */
    .calendar[data-view="list"] .calendar-card {
        grid-template-columns: 1fr;
        gap: 12px;
    }

    .calendar[data-view="list"] .calendar-card__references {
        grid-column: auto;
        padding-top: 0;
    }
}

/* ============================================================================
   Site search overlay — the modal typeahead (Figma node 102:2727).

   ONE <dialog> mounted in _Layout, opened by every [data-site-search-open] on
   the page (the home Resources field, the nav magnifier). Being a <dialog> it
   renders in the browser's top layer, so it needs no z-index of its own and
   cannot be clipped by an ancestor's overflow — which is why it is a dialog and
   not another .topbar-drop sheet.

   NB the prefix is .site-search*, NOT .search-*: .search-band, .search-panel,
   .search-field, .search-tag, .search-results__* and .search-summary are all
   already taken (see the Search experience block above). The FIELD inside the
   modal is the shared .search-field component, reused as-is.

   Suggestion rows reuse .isv-card--searchrow — the same look the results page
   gives its rows — re-tinted below through the card token layer, so the markup
   and the layout stay in one place.
   ========================================================================== */
.site-search-modal {
    /* The dialog is only ever the panel; the backdrop is ::backdrop, so no full-
       viewport sizing here. Pinned near the top rather than centred: the list
       grows downwards as the visitor types and a centred panel would jump. */
    width: min(620px, calc(100vw - 32px));
    max-width: none;
    max-height: min(70vh, 640px);
    margin: 12vh auto auto;
    padding: 0;
    background: var(--white);
    border: 1px solid var(--border-hairline);
    border-radius: 10px;
    /* Same floating-sheet shadow as .topbar-drop__panel / .listing-filters-drop. */
    box-shadow: var(--shadow-pop);
    overflow: hidden;
    color: var(--isv-fg);
}

.site-search-modal::backdrop {
    background: var(--scrim);
}

.site-search-modal__panel {
    display: flex;
    flex-direction: column;
    max-height: inherit;
    padding: 16px;
    gap: 12px;
    min-height: 0;
}

/* The shared .search-field, un-capped so it fills the panel. */
.site-search-modal__form {
    max-width: none;
    flex: 0 0 auto;
}

/* The only scrolling region — the field above it stays put while results move. */
.site-search-modal__results {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    /* Room for the focus ring on the first/last row, which would otherwise clip. */
    padding: 2px;
    margin: -2px;
}

.site-search-modal__results:empty {
    display: none;
}

/* ---- Recent searches (the empty state) -------------------------------------
   This visitor's own history, from localStorage — see initSiteSearch. Sits above
   the results region and is swapped out for suggestions the moment they arrive,
   so the two never share the panel.

   The chips are the SAME .search-tags/.search-tag component the home Resources
   panel uses. One difference is forced: .search-tag paints itself var(--white),
   which is invisible on this white panel (the home band sits on a tint), so the
   chip gets a border here rather than a second chip class. */
.site-search-modal__recent {
    flex: 0 0 auto;
}

.site-search-modal__recent[hidden] {
    display: none;
}

.site-search-modal__recent-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
}

.site-search-modal__recent-clear {
    padding: 0;
    border: 0;
    background: none;
    color: var(--grey);
    font: inherit;
    text-decoration: underline;
    cursor: pointer;
}

.site-search-modal__recent-clear:hover {
    color: var(--brand-primary-hover);
}

.site-search-modal__recent .search-tag {
    border: 1px solid var(--border-hairline);
}

.site-search-modal__recent .search-tag:hover {
    border-color: var(--brand-primary-hover);
}

/* ---- Groups ---------------------------------------------------------------- */
.site-search__group + .site-search__group {
    margin-top: 18px;
}

/* The design's TOPICS / RESOURCES / NEWS & ARTICLES eyebrows — same treatment as
   the results page's .search-results__eyebrow. */
.site-search__eyebrow {
    margin: 0 0 6px;
    padding: 0 4px;
    color: var(--grey);
    font-weight: var(--fw-medium);
    text-transform: uppercase;
    letter-spacing: 1px;
}

.site-search__list {
    list-style: none;
    margin: 0;
    padding: 0;
}

/* Rows in the overlay sit flush on white and fill on hover/focus, rather than
   carrying the results page's permanent panel fill — the list reads as one
   surface. Both come from the same .isv-card--searchrow look; only the tokens
   differ, so the layout can never drift between the two. */
.site-search-modal .isv-card--searchrow {
    --card-bg: transparent;
    --card-pad: 10px 12px;
}

.site-search-modal .isv-card--searchrow:hover,
.site-search-modal .isv-card--searchrow:focus-within {
    --card-bg: var(--surface-cool);
}

/* Keyboard focus moves between rows (see initSiteSearch), so the ring must be
   clearly visible — it is the only cue to where you are in the list. */
.site-search-modal .isv-card--searchrow .isv-card__link:focus-visible {
    outline: 2px solid var(--focus-ring);
    outline-offset: -2px;
    border-radius: 8px;
}

/* ---- Footer / empty state --------------------------------------------------- */
.site-search__view-all {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    flex: 0 0 auto;
    padding: 12px;
    border-top: 1px solid var(--border-hairline);
    color: var(--brand-primary);
    font-weight: var(--fw-medium);
    text-decoration: none;
}

.site-search__view-all:hover,
.site-search__view-all:focus-visible {
    color: var(--brand-primary-hover);
}

.site-search__empty {
    margin: 0;
    padding: 18px 12px;
    color: var(--body-text);
    text-align: center;
}

/* <dialog> makes the background inert but does not stop it scrolling behind the
   modal; initSiteSearch stamps this while the overlay is open. */
:root.is-search-open,
:root.is-search-open body {
    overflow: hidden;
}

/* Visually hidden until focused — the overlay's keyboard-only close control, so
   the design is unchanged for everyone else. Escape closes it natively too. */
.sr-only-focusable:not(:focus):not(:focus-within) {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

.site-search-modal__close {
    align-self: center;
    flex: 0 0 auto;
    padding: 8px 12px;
    background: var(--white);
    border: 1px solid var(--border-accent);
    border-radius: 6px;
    color: var(--brand-primary);
    font-family: var(--font-body);
    font-weight: var(--fw-medium);
    cursor: pointer;
}

@media (max-width: 640px) {
    .site-search-modal {
        width: calc(100vw - 16px);
        max-height: 85vh;
        margin: 6vh auto auto;
    }

    /* One line of snippet is unreadable at this width; the title carries the row. */
    .site-search-modal .isv-card--searchrow .isv-card__summary {
        display: none;
    }
}
