/* Breakpoints 2xS 320 / xS 390 / Sm 834 / Md 1200 / xL 1600, from the Figma screens.
   Lg (1280) is a screen with no grid mode, so it is not a layout boundary. */
:root {
    --color-primary-light: #87acff;
    --color-primary: #4e7de8;
    --color-primary-dark: #0052b5;
    --color-text: #081f4d;
    --color-text-secondary: #333;
    --color-error: #ff375f;
    --color-success: #30d158;
    --color-info: #0b83ff;
    --color-logo-green-light: #b5fff8;
    --color-logo-green: #82d7c5;
    --color-logo-green-dark: #50a595;
    --color-logo-purple-light: #b0bdff;
    --color-logo-purple: #7c8df6;
    --color-logo-purple-dark: #474eb8;
    --color-logo-yellow-light: #ffe888;
    --color-logo-yellow: #ebb659;
    --color-logo-yellow-dark: #b6862a;
    --color-header-bg: #f8faff;

    /* Button roles, from the Figma Button component set. Pressed reuses hover, so there is no
       separate pressed role. The Outline hover label binds to `neutral:gray/light` in Figma, which
       resolves to #fff; it is read here as plain white, since a grey ramp entry that happens to be
       white is a mis-binding rather than an intent. */
    --color-button-primary-bg: var(--brand-blue-500);
    --color-button-primary-bg-hover: var(--brand-blue-700);
    --color-button-primary-bg-disabled: var(--brand-blue-300);
    --color-button-primary-text: var(--white);
    --color-button-primary-text-disabled: var(--white-64);
    --color-button-secondary-bg: var(--black);
    --color-button-secondary-bg-hover: var(--black-80);
    --color-button-secondary-bg-disabled: var(--black-40);
    --color-button-secondary-text: var(--white);
    --color-button-secondary-text-disabled: var(--white-64);
    /* Tertiary is the on-colour button: its fills are translucent white, so hover and
       disabled work by letting the surface behind show through. On a white background it
       has no visible hover -- it belongs on a tinted band, like Outline-white does. */
    --color-button-tertiary-bg: var(--white);
    --color-button-tertiary-bg-hover: var(--white-80);
    --color-button-tertiary-bg-disabled: var(--white-40);
    --color-button-tertiary-text: var(--brand-blue-500);
    --color-button-tertiary-text-disabled: var(--brand-blue-300);
    --color-button-outline-edge: var(--brand-blue-500);
    --color-button-outline-edge-disabled: var(--brand-blue-300);
    --color-button-outline-text-hover: var(--white);
    --color-button-outline-white-edge: var(--white);
    --color-button-outline-white-edge-disabled: var(--white-32);
    --color-button-outline-white-text-disabled: var(--white-48);
    --color-button-ghost-bg-hover: var(--brand-blue-50);
    --color-button-ghost-text: var(--brand-blue-500);
    --color-button-ghost-text-disabled: var(--brand-blue-300);

    /* The only two button measurements off the Figma spacing scale. They live here rather than in
       tokens.css, which carries Figma primitives only and is regenerated wholesale. */
    --button-pad-y-sm: 10px;
    --button-pad-y-lg: 14px;

    /* Tag roles, from the Figma _Tag-Item set. Colour encodes interactivity: teal for a tag that
       only labels, blue for one that can be clicked. */
    --color-tag-static-text: var(--brand-teal-700);
    --color-tag-static-edge: var(--brand-teal-100);
    --color-tag-static-bg: var(--brand-teal-50);
    --color-tag-action-text: var(--brand-blue-500);
    --color-tag-action-edge: var(--brand-blue-100);
    --color-tag-action-edge-hover: var(--brand-blue-200);
    --color-tag-action-bg: var(--brand-blue-50);
    --color-tag-action-bg-hover: var(--brand-blue-100);
    /* Selection is the one state that has to read across a wrapped panel of dozens of chips, so it
       steps out of the 50/100/200 tints the rest of the set uses. */
    --color-tag-selected-bg: var(--brand-blue-500);
    --color-tag-selected-bg-hover: var(--brand-blue-600);
    --color-tag-selected-text: var(--white);

    /* Slider. The dwell is the whole timer: the rail fill is a CSS animation of this duration, and
       JS advances on its animationend. Pausing is then a one-line animation-play-state, and reduced
       motion removes the animation, which removes the auto-advance with it. 6s is a read of the
       longest item -- a 24px title plus a two-line description -- with a little room after it. */
    --slider-dwell: 6000ms;
    --slider-rail: var(--black-08);
    --slider-rail-fill: var(--black);

    /* ---------- Responsive type steps ----------
       tokens.css carries both Figma endpoints for every type step -- Mobile-Min and Desktop-Max --
       and the design does not say whether type is fluid or stepped. It is stepped, at 834 -- the Sm
       screen. (It read
       830 until the grid tokens were reconciled: the old file had Sm's 834 filed under the name md
       and no screen for 1200 at all.) Resolving it here once
       means a component names a step and gets the right endpoint for the viewport, instead of every
       component repeating the same media query. The block below is the only place the two endpoints
       meet. */
    --type-heading-2xl-size: var(--heading-size-2xl-max);
    --type-heading-2xl-line: var(--heading-line-height-2xl-max);
    --type-heading-2xl-track: var(--heading-letter-space-2xl-max);
    --type-heading-xl-size: var(--heading-size-xl-max);
    --type-heading-xl-line: var(--heading-line-height-xl-max);
    --type-heading-xl-track: var(--heading-letter-space-xl-max);
    --type-heading-lg-size: var(--heading-size-lg-max);
    --type-heading-lg-line: var(--heading-line-height-lg-max);
    --type-heading-lg-track: var(--heading-letter-space-lg-max);
    --type-heading-md-size: var(--heading-size-md-max);
    --type-heading-md-line: var(--heading-line-height-md-max);
    --type-heading-md-track: var(--heading-letter-space-md-max);
    --type-heading-sm-size: var(--heading-size-sm-max);
    --type-heading-sm-line: var(--heading-line-height-sm-max);
    --type-heading-sm-track: var(--heading-letter-space-sm-max);
    --type-heading-xs-size: var(--heading-size-xs-max);
    --type-heading-xs-line: var(--heading-line-height-xs-max);
    --type-heading-xs-track: var(--heading-letter-space-xs-max);
    --type-body-2xl-size: var(--body-size-2xl-max);
    --type-body-2xl-line: var(--body-line-height-2xl-max);
    --type-body-lg-size: var(--body-size-lg-max);
    --type-body-lg-line: var(--body-line-height-lg-max);
    --type-body-xl-size: var(--body-size-xl-max);
    --type-body-xl-line: var(--body-line-height-xl-max);
    --type-body-md-size: var(--body-size-md-max);
    --type-body-md-line: var(--body-line-height-md-max);
    --type-body-sm-size: var(--body-size-sm-max);
    --type-body-sm-line: var(--body-line-height-sm-max);
    --type-body-xs-size: var(--body-size-xs-max);
    --type-body-xs-line: var(--body-line-height-xs-max);
    --type-caption-md-size: var(--caption-size-md-max);
    --type-caption-md-track: var(--caption-letter-space-md-max);
    --type-caption-sm-size: var(--caption-size-sm-max);
    --type-caption-sm-track: var(--caption-letter-space-sm-max);

    /* Header shadows. These are Figma *effect styles* ("Dropdown-Shadow"), not variables, so a
       variable regeneration of tokens.css would not pick them up -- they live here instead. Both
       are single-layer and unrelated to the five-layer --shadow-sharp / --shadow-light stacks. */
    --shadow-dropdown: 0 8px 8px rgb(0 0 0 / 8%);
    --shadow-header-bar: 0 8px 12px rgb(11 29 58 / 8%);
    /* The emoji faces are appended here rather than in tokens.css, which carries the Figma families
       themselves. Heading elements take --font-heading; everything else inherits this from body. */
    --font-family: var(--font-body), 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
    --spacing: 8px;
}

/* The mobile endpoint of every type step, from the Mobile-Min mode of the Figma typography
   collection. Button labels are deliberately not in here: a button's label is fixed, and its size
   variant -- one of the three the component defines -- is what changes across breakpoints. */
@media (max-width: 833.95px) {
    :root {
        --type-heading-xl-size: var(--heading-size-xl-min);
        --type-heading-xl-line: var(--heading-line-height-xl-min);
        --type-heading-xl-track: var(--heading-letter-space-xl-min);
        --type-heading-lg-size: var(--heading-size-lg-min);
        --type-heading-lg-line: var(--heading-line-height-lg-min);
        --type-heading-lg-track: var(--heading-letter-space-lg-min);
        --type-heading-md-size: var(--heading-size-md-min);
        --type-heading-md-line: var(--heading-line-height-md-min);
        --type-heading-md-track: var(--heading-letter-space-md-min);
        --type-heading-sm-size: var(--heading-size-sm-min);
        --type-heading-sm-line: var(--heading-line-height-sm-min);
        --type-heading-sm-track: var(--heading-letter-space-sm-min);
        --type-heading-xs-size: var(--heading-size-xs-min);
        --type-heading-xs-line: var(--heading-line-height-xs-min);
        --type-heading-xs-track: var(--heading-letter-space-xs-min);
        --type-body-2xl-size: var(--body-size-2xl-min);
        --type-body-2xl-line: var(--body-line-height-2xl-min);
        --type-body-lg-size: var(--body-size-lg-min);
        --type-body-lg-line: var(--body-line-height-lg-min);
        --type-body-xl-size: var(--body-size-xl-min);
        --type-body-xl-line: var(--body-line-height-xl-min);
        --type-body-md-size: var(--body-size-md-min);
        --type-body-md-line: var(--body-line-height-md-min);
        --type-body-sm-size: var(--body-size-sm-min);
        --type-body-sm-line: var(--body-line-height-sm-min);
        --type-body-xs-size: var(--body-size-xs-min);
        --type-body-xs-line: var(--body-line-height-xs-min);
        --type-caption-md-size: var(--caption-size-md-min);
        --type-caption-md-track: var(--caption-letter-space-md-min);
        --type-caption-sm-size: var(--caption-size-sm-min);
        --type-caption-sm-track: var(--caption-letter-space-sm-min);
    }
}

/* 2xL is the exception to the 834 step above: the hero is 56/64 at both 1600 and 1200, and 32/40 at
   both 834 and 390, so its endpoint changes at the layout breakpoint rather than the type one. It is
   the only step that does, and the hero is the only thing that uses it. */
@media (max-width: 1199.95px) {
    :root {
        --type-heading-2xl-size: var(--heading-size-2xl-min);
        --type-heading-2xl-line: var(--heading-line-height-2xl-min);
        --type-heading-2xl-track: var(--heading-letter-space-2xl-min);
    }
}

/* ---------- Reset & base ---------- */
*,
*::before,
*::after {
    box-sizing: border-box;
}

/* Bare URLs in the legal copy are one unbreakable word wider than a phone's
   screen; the old reset let any word break rather than run off the edge. */
html {
    word-break: break-word;
    /* Opening the mobile/tablet menu locks the page (body overflow:hidden, js/menu.js). Where
       scrollbars take space rather than overlay the page, that removed the page's scrollbar and
       everything widened by ~15px, then narrowed again on close. Keeping the gutter reserved
       holds the layout still. A no-op where scrollbars overlay. */
    scrollbar-gutter: stable;
}

body {
    margin: 0;
    overflow-x: hidden;
    font-family: var(--font-family);
    font-size: 1rem;
    font-weight: var(--body-weight-regular);
    line-height: 1.75;
    /* Figma sets body tracking to zero at every step in both modes, so this needs no step chosen. */
    letter-spacing: var(--body-letter-space);
    color: var(--color-text);
    background: #fff;
}

/* Without height:auto the width/height attributes stretch scaled images. */
img {
    max-width: 100%;
    height: auto;
}

.site-header__logo img {
    height: 40px;
    width: auto;
}

/* Family only. Which Figma heading step (2xl..xs) each level takes is decided against the page
   frames; the sizes, weights and spacing below are still the old scale's. */
h1,
h2,
h3,
h4,
h5,
h6 {
    font-family: var(--font-heading);
}

h1,
h2,
h3 {
    line-height: 1.379;
    letter-spacing: -0.5px;
    font-weight: 700;
    margin: 0 0 1rem;
}

h1 {
    font-size: 2rem;
}

h2 {
    font-size: 1.8rem;
}

h3 {
    font-size: 1.5rem;
}

h4 {
    font-size: 1.35rem;
    font-weight: 700;
    line-height: 1.6;
    letter-spacing: 0.25px;
    margin: 0 0 1rem;
}

h5 {
    font-size: 1.2rem;
    font-weight: 700;
    line-height: 1.4;
    margin: 0 0 0.75rem;
}

h6 {
    font-size: 1em;
    font-weight: 600;
    line-height: 1.6;
    margin: 0 0 0.5rem;
}

a {
    color: var(--color-primary);
    text-decoration: none;
}

a:hover {
    text-decoration: underline;
}

:focus-visible {
    outline: 3px solid var(--color-primary);
    outline-offset: 2px;
}

.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
}

.skip-link {
    position: absolute;
    left: -9999px;
    top: 0;
    z-index: 100;
    background: var(--color-primary-dark);
    color: #fff;
    padding: 8px 16px;
}

.skip-link:focus {
    left: 0;
}

/* ---------- Layout container ----------
   The Figma grid, from the margins of the Size & Spacing modes: 20 at 2xS and xS, 33 at Sm, and 32
   at Md with the content capped at 1312.

   The element stays full width and holds its contents in by padding alone; it is never capped with
   max-width. That is how the file draws it -- a section spans the frame, a Container:margin inside
   it holds the content at 1312 -- and it is what lets a section paint edge to edge, as the hero
   gradient and the footer do, while a panel inside the content box stays inset, as the capabilities
   and final-CTA panels do.

   Above 1200 there is no breakpoint. max() is the whole desktop rule: the floor of 32 holds until
   the viewport is wide enough for 1312 of content, and past that the remainder splits either side.
   That lands on Figma's numbers exactly -- 1136 of content at 1200, and 144 margins at 1600, which
   is the xL margin arriving on its own rather than being applied. A hard step at 1600 was rejected:
   it would give 1535 of content at 1599 and then shrink it to 1312 as the window grew.

   2xS and xS share a margin of 20, so they need no rule of their own.

   The gutter is deliberately not here. It is the gap between columns, so it belongs to whatever
   grid sits in the container -- and unlike the margin it does need a 1600 breakpoint, since Figma
   steps it 24 at Sm, 16 at Md and 32 at xL. */
.section {
    padding-inline: var(--grid-xs-margin);
}

@media (min-width: 834px) {
    .section {
        padding-inline: var(--grid-sm-margin);
    }
}

@media (min-width: 1200px) {
    .section {
        padding-inline: max(var(--grid-md-margin), calc((100% - var(--grid-xl-col-12)) / 2));
    }
}

.grid {
    display: grid;
    gap: calc(var(--spacing) * 3);
}

@media (min-width: 960px) {
    .grid--2 {
        grid-template-columns: 1fr 1fr;
    }

    .grid--3 {
        grid-template-columns: repeat(3, 1fr);
    }
}

/* ---------- Buttons ----------
   Figma: Button component set 9705:10957 -- 5 styles (Primary, Secondary, Outline, Outline-white,
   Ghost) x 3 sizes (Small, Medium, Large) x 3 states (Default, Hover, Disable). Pressed reuses
   hover. Heights come out at 40 / 48 / 52px, matching the Figma frames.

   Three places where this departs from a literal reading of the file, each settled in review:

   - Outline draws its 1px stroke as an inset shadow rather than a border. Figma strokes sit inside
     the frame, so Outline and Primary are the same height on canvas; a real border would make
     Outline 2px taller and push the padding off the token scale to compensate.
   - Figma has no focus state. These are keyboard-reachable controls, so one is defined here: a 2px
     ring in the variant's own colour at 2px offset, drawn with `outline` so it composes with the
     inset stroke above rather than fighting it.
   - Sizes are set through private custom properties, so the mobile step-down below reassigns four
     values instead of restating every rule.

   Label type is deliberately pinned to the Desktop-Max step at every breakpoint: button text does
   not scale with the viewport, only the size variant does. */
.button {
    /* Large is the default variant in Figma, so an unsized .button is a large one. */
    --_pad-y: var(--button-pad-y-lg);
    --_pad-x: var(--space-32);
    --_font-size: var(--body-size-md-max);
    --_line-height: var(--body-line-height-md-max);
    --_ring: var(--brand-blue-500);

    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-8);
    padding: var(--_pad-y) var(--_pad-x);
    font-family: inherit;
    font-size: var(--_font-size);
    font-weight: var(--body-weight-bold);
    line-height: var(--_line-height);
    letter-spacing: var(--body-letter-space);
    white-space: nowrap;
    text-align: center;
    text-decoration: none;
    background: transparent;
    border: 0;
    border-radius: var(--radius-full);
    cursor: pointer;
    transition:
        background-color 150ms ease,
        box-shadow 150ms ease,
        color 150ms ease;
}

.button:hover {
    text-decoration: none;
}

.button:focus-visible {
    outline: 2px solid var(--_ring);
    outline-offset: 2px;
}

/* ---------- Button sizes ---------- */
.button--sm {
    --_pad-y: var(--button-pad-y-sm);
    --_pad-x: var(--space-16);
    --_font-size: var(--body-size-sm-max);
    --_line-height: var(--body-line-height-sm-max);
}

.button--md {
    --_pad-y: var(--space-12);
    --_pad-x: var(--space-24);
}

.button--lg {
    --_pad-y: var(--button-pad-y-lg);
    --_pad-x: var(--space-32);
}

/* Two steps down, at 1200px and 834px: Large above 1200, Medium between, Small below 834. The
   first selector is "Large or unsized"; the second adds an explicit Medium, so a button authored at
   Medium still reaches Small. An explicit Small is already the floor and never moves.

   The lower step read 830 until the grid tokens were corrected. That was a guess made when the
   token file had Sm's 834 filed under the name md and no screen for 1200 at all; with the export
   reconciled, Sm is 834, so the step belongs there. A button between 830 and 833 wide was rendering
   Medium when the scale says Small. */
/* Bounded below, not just above. Open-ended, this rule also applied under 834, where its
   :not():not() selector outweighs the Small step's single :not() -- so a Large button took Medium's
   padding with Small's type and rendered 44px, a height the component does not define. */
@media (min-width: 834px) and (max-width: 1199.95px) {
    .button:not(.button--sm):not(.button--md) {
        --_pad-y: var(--space-12);
        --_pad-x: var(--space-24);
    }
}

@media (max-width: 833.95px) {
    .button:not(.button--sm) {
        --_pad-y: var(--button-pad-y-sm);
        --_pad-x: var(--space-16);
        --_font-size: var(--body-size-sm-max);
        --_line-height: var(--body-line-height-sm-max);
    }
}

/* Opt-in until the page designs show which containers call for it. */
.button--block {
    display: flex;
    width: 100%;
}

/* ---------- Button styles ---------- */
/* `--contained`, `--outlined` and `--text` are the pre-redesign names, kept as aliases so call
   sites can migrate one at a time. Delete each once no template uses it. */
.button--primary,
.button--contained {
    background: var(--color-button-primary-bg);
    color: var(--color-button-primary-text);
}

.button--primary:hover,
.button--primary:active,
.button--contained:hover,
.button--contained:active {
    background: var(--color-button-primary-bg-hover);
}

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

.button--secondary:hover,
.button--secondary:active {
    background: var(--color-button-secondary-bg-hover);
}

/* No edge and no shadow in any state, as drawn: the white fill is the whole shape. */
.button--tertiary {
    background: var(--color-button-tertiary-bg);
    color: var(--color-button-tertiary-text);
}

.button--tertiary:hover,
.button--tertiary:active {
    background: var(--color-button-tertiary-bg-hover);
}

.button--outline,
.button--outlined {
    color: var(--color-button-outline-edge);
    box-shadow: inset 0 0 0 1px var(--color-button-outline-edge);
}

.button--outline:hover,
.button--outline:active,
.button--outlined:hover,
.button--outlined:active {
    background: var(--color-button-outline-edge);
    color: var(--color-button-outline-text-hover);
}

.button--outline-white {
    --_ring: var(--white);

    color: var(--color-button-outline-white-edge);
    box-shadow: inset 0 0 0 1px var(--color-button-outline-white-edge);
}

.button--outline-white:hover,
.button--outline-white:active {
    background: var(--color-button-outline-white-edge);
    color: var(--color-button-outline-edge);
}

.button--ghost,
.button--text {
    color: var(--color-button-ghost-text);
}

.button--ghost:hover,
.button--ghost:active,
.button--text:hover,
.button--text:active {
    background: var(--color-button-ghost-bg-hover);
}

/* The wash is a pointer affordance; on touch it would stick after the tap. */
@media (hover: none) {
    .button--ghost:hover,
    .button--text:hover {
        background: transparent;
    }
}

/* ---------- Button disabled ---------- */
/* `<a class="button">` cannot take :disabled, so links carry aria-disabled instead -- which is also
   what tells assistive tech, since a disabled-looking link is still focusable and still navigates.
   Anything using the aria form needs its href removed or its activation prevented as well. */
.button:disabled,
.button[aria-disabled='true'] {
    cursor: not-allowed;
    pointer-events: none;
}

.button--primary:disabled,
.button--primary[aria-disabled='true'],
.button--contained:disabled,
.button--contained[aria-disabled='true'] {
    background: var(--color-button-primary-bg-disabled);
    color: var(--color-button-primary-text-disabled);
}

.button--secondary:disabled,
.button--secondary[aria-disabled='true'] {
    background: var(--color-button-secondary-bg-disabled);
    color: var(--color-button-secondary-text-disabled);
}

.button--tertiary:disabled,
.button--tertiary[aria-disabled='true'] {
    background: var(--color-button-tertiary-bg-disabled);
    color: var(--color-button-tertiary-text-disabled);
}

.button--outline:disabled,
.button--outline[aria-disabled='true'],
.button--outlined:disabled,
.button--outlined[aria-disabled='true'] {
    background: transparent;
    color: var(--color-button-outline-edge-disabled);
    box-shadow: inset 0 0 0 1px var(--color-button-outline-edge-disabled);
}

.button--outline-white:disabled,
.button--outline-white[aria-disabled='true'] {
    background: transparent;
    color: var(--color-button-outline-white-text-disabled);
    box-shadow: inset 0 0 0 1px var(--color-button-outline-white-edge-disabled);
}

.button--ghost:disabled,
.button--ghost[aria-disabled='true'],
.button--text:disabled,
.button--text[aria-disabled='true'] {
    background: transparent;
    color: var(--color-button-ghost-text-disabled);
}

/* ---------- Button icon ---------- */
/* The base is already a flex row with the designed gap, so this is a no-op alias kept for the
   templates that still set it. */
.button--with-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

.button__icon {
    width: var(--icon-xs);
    height: var(--icon-xs);
    flex-shrink: 0;
}

/* FontAwesome duotone: the secondary layer is drawn at 40%. Buttons are solid-only now, but the
   duotone icons elsewhere on the page still need this. */
.fa-secondary {
    opacity: 0.4;
}

/* ---------- Tags ----------
   Figma: _Tag-Item 27264:35582 -- Type (Not Clickable / Clickable) x Style (Outline / Fill).

   Type is the axis that matters: teal means the tag only labels something, blue means it can be
   clicked. Style is a presentation choice on top of that, and which usage takes which is settled
   with the page designs, so both are available and the component's own default -- Not Clickable,
   Outline -- is what a bare `.tag` gets.

   Three departures from the file, each settled in review:

   - Outline draws its stroke as an inset shadow, so Outline and Fill are both 28px tall. Figma
     draws this one outside the frame, which makes Outline 30px and 2px wider; mixed in a row the
     two styles would not line up.
   - The Not Clickable hover variants are dropped. A tag that cannot be clicked has no hover.
   - Figma has no selected state, but the publications keyword filter is multi-select and toggles
     aria-pressed on each chip. Without one, nothing shows which keywords are active once the
     pointer moves away -- and on touch there is no hover at all. It is defined below. */
.tag {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-8);
    flex-shrink: 0;
    padding: var(--space-4) var(--space-12);
    font-family: inherit;
    font-size: var(--body-size-sm-max);
    font-weight: var(--body-weight-bold);
    line-height: var(--body-line-height-sm-max);
    letter-spacing: var(--body-letter-space);
    color: var(--color-tag-static-text);
    background: transparent;
    border: 0;
    border-radius: var(--radius-full);
    box-shadow: inset 0 0 0 1px var(--color-tag-static-edge);
    transition:
        background-color 150ms ease,
        box-shadow 150ms ease,
        color 150ms ease;
}

/* The chips are inline-level so they wrap in the filter panel and scroll in the card row; the
   class's own display would otherwise beat the hidden attribute the filter search uses. */
.tag[hidden] {
    display: none;
}

.tag--fill {
    background: var(--color-tag-static-bg);
    box-shadow: none;
}

/* ---------- Clickable tags ---------- */
.tag--clickable {
    color: var(--color-tag-action-text);
    box-shadow: inset 0 0 0 1px var(--color-tag-action-edge);
    cursor: pointer;
}

.tag--clickable:hover {
    background: var(--color-tag-action-bg);
    box-shadow: inset 0 0 0 1px var(--color-tag-action-edge-hover);
}

.tag--clickable.tag--fill {
    background: var(--color-tag-action-bg);
    box-shadow: none;
}

.tag--clickable.tag--fill:hover {
    background: var(--color-tag-action-bg-hover);
    box-shadow: none;
}

.tag--clickable:focus-visible {
    outline: 2px solid var(--brand-blue-500);
    outline-offset: 2px;
}

/* Selected wins over both styles and over hover: a filter's active set has to stay legible while
   the pointer is somewhere else in the panel. */
.tag--clickable[aria-pressed='true'],
.tag--clickable[aria-pressed='true']:hover {
    background: var(--color-tag-selected-bg);
    color: var(--color-tag-selected-text);
    box-shadow: none;
}

.tag--clickable[aria-pressed='true']:hover {
    background: var(--color-tag-selected-bg-hover);
}

/* The wash is a pointer affordance; on touch it would stick after the tap. */
@media (hover: none) {
    .tag--clickable:hover {
        background: transparent;
        box-shadow: inset 0 0 0 1px var(--color-tag-action-edge);
    }

    .tag--clickable.tag--fill:hover {
        background: var(--color-tag-action-bg);
        box-shadow: none;
    }
}

/* ---------- Section head ----------
   The caption, heading and description that open a section. One definition, two placements: a
   section calls it above a component, or a component calls it inside its own box. Which applies is
   decided by what encloses the head -- see partials/section-head.html.

   The heading's width comes from the caller through --section-head-measure. What makes a headline
   break over two lines is a property of the section it opens, and the frames give a different
   number for each; the head itself has no opinion. */
.section-head {
    display: flex;
    flex-direction: column;
    align-items: center;
    margin-bottom: var(--space-48);
    text-align: center;
}

.section-head--start {
    align-items: flex-start;
    text-align: left;
}

.section-head__caption {
    /* Inherit, not the heading face: a caption with no headline renders as the h2 (see
       partials/section-head.html), and the global h1-h6 rule would otherwise switch it to Geist. */
    font-family: inherit;
    margin: 0 0 var(--section-head-caption-gap, var(--space-12));
    font-size: var(--type-caption-md-size);
    font-weight: var(--caption-weight);
    line-height: var(--caption-line-height-md);
    letter-spacing: var(--type-caption-md-track);
    text-transform: uppercase;
    /* Teal everywhere the redesign has used it so far; the security panel is violet and sets this
       on the head. The colour belongs to the section the head opens, not to the head. */
    color: var(--section-head-caption-color, var(--brand-teal-500));
}

/* The size is a custom property with the Lg step as its default, the way the description's
   already is: Request a Demo draws its hero a step up at Heading xL, and every other caller is
   untouched because the fallback is what was here. */
.section-head__headline {
    max-width: var(--section-head-measure, none);
    margin: 0;
    font-family: var(--font-heading);
    font-size: var(--section-head-headline-size, var(--type-heading-lg-size));
    font-weight: var(--section-head-headline-weight, var(--heading-weight-lg));
    line-height: var(--section-head-headline-line, var(--type-heading-lg-line));
    letter-spacing: var(--section-head-headline-track, var(--type-heading-lg-track));
    color: var(--black);
    text-wrap: balance;
}

/* Size and colour are overridable, in the same idiom as the measure and the caption colour above,
   because the feature pages disagree with the homepage about both and neither is wrong. The
   homepage draws this at Body Lg in black-80; the feature pages draw it in black, at Body xL on the
   plain sections and Body Lg inside a panel. The defaults are the homepage's, so nothing already
   shipped moves. */
.section-head__description {
    margin: var(--space-16) 0 0;
    font-size: var(--section-head-description-size, var(--type-body-lg-size));
    font-weight: var(--body-weight-regular);
    line-height: var(--section-head-description-line, var(--type-body-lg-line));
    letter-spacing: var(--body-letter-space);
    color: var(--section-head-description-color, var(--black-80));
}

/* ---------- Slider ----------
   Figma: #Slider 26537:9211; the applied states at 28449:106320 carry the real measurements and are
   what these numbers come from.

   Every string and image is passed in by the caller, so one component carries different content on
   different pages; see templates/partials/slider.html for the shape.

   Two structural notes:

   - The second column holds an empty .slider__stage. The visual belongs to whichever item is open,
     so it lives inside that item -- which is exactly where the mobile layout needs it -- and is
     positioned over the stage on desktop. The stage is what gives the row the visual's height;
     without it the row collapses to the height of the list and the visual hangs out of the section.
   - The rail is a flex sibling of the content rather than an absolute overlay, which is how Figma
     builds it: a 4px track, 20px gap, then the text column. */
 /* 6 of 12 columns: capping the headline is what breaks it over two lines rather than letting it
   run the width of the container. Only the "left" layout renders a head inside the slider -- a
   "top" section renders section-head above the component instead -- but the measure is set here
   for both, so a slider that does carry a head breaks it the same way. */
.slider .section-head {
    --section-head-measure: var(--grid-xl-col-6);
}

/* ---------- Tabs ----------
   Figma: the row above the homepage slider (26537:9211) and `Tabs` on the Request a Demo card
   (28955:277004). The two measure the same, so this is one component; see partials/tabs.html.

   The pills are Button Medium; only the selected repaint is the component's own.

   The pill row hugs its labels at every width -- Figma's Hug -- and is never squeezed to its
   container, so it keeps both rounded ends. When it is wider than the space it has, the scroller
   around it slides it sideways with its scrollbar hidden; nothing else in the section moves. */
.tabs-scroller {
    display: flex;
    margin-bottom: var(--space-32);
    overflow-x: auto;
    scrollbar-width: none;
}

.tabs-scroller::-webkit-scrollbar {
    display: none;
}

/* Auto margins centre the row while it fits and fall to zero once it does not, so an overflowing
   row starts flush at the left and scrolls rather than spilling off both sides. flex-shrink 0 is
   the hug: as a plain flex item the row would shrink to the scroller instead. */
.tabs {
    display: flex;
    flex-shrink: 0;
    align-items: center;
    gap: var(--space-4);
    margin-inline: auto;
    padding: var(--space-4);
    background: var(--white);
    border: 1px solid var(--black-12);
    border-radius: var(--radius-full);
}

/* On phones the slider's tab row runs to the screen edges, so the pill can run past the edge as
   the frames draw it rather than stopping at the section's padding. The padding restores the
   margin, so at rest the pill still starts on the content edge. */
@media (max-width: 833.95px) {
    .slider .tabs-scroller {
        margin-inline: calc(-1 * var(--grid-xs-margin));
        padding-inline: var(--grid-xs-margin);
    }
}

/* The tabs keep their own width. As plain flex children they would shrink to fit the container
   instead, which squeezes the pills narrower than their labels -- nowrap means the text then runs
   past the pill edge -- and leaves the row nothing to overflow, so it never scrolls. */
.tabs__tab {
    flex-shrink: 0;
}

.tabs__tab[aria-selected='true'] {
    background: var(--color-button-secondary-bg);
    color: var(--color-button-secondary-text);
}

.tabs__tab[aria-selected='true']:hover {
    background: var(--color-button-secondary-bg-hover);
}

/* ---------- Slider items ---------- */
.slider__panel {
    position: relative;
}

.slider__list {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-32);
}

.slider__items {
    display: flex;
    flex-direction: column;
    gap: var(--space-24);
    width: 100%;
    margin: 0;
    padding: 0;
    list-style: none;
}

.slider-item {
    display: flex;
    align-items: flex-start;
    /* 20px beside the rail on desktop, 16px on the mobile endpoint. */
    gap: var(--space-16);
}

@media (min-width: 830px) {
    .slider-item {
        gap: var(--space-20);
    }
}

.slider-item__rail {
    align-self: stretch;
    flex-shrink: 0;
    width: 4px;
    background: var(--slider-rail);
    border-radius: var(--radius-full);
    overflow: hidden;
}

.slider-item__fill {
    display: block;
    width: 100%;
    height: 0;
    background: var(--slider-rail-fill);
    border-radius: var(--radius-full);
}

.slider-item.is-open .slider-item__fill {
    animation: slider-fill var(--slider-dwell) linear forwards;
}

@keyframes slider-fill {
    from { height: 0; }
    to { height: 100%; }
}

/* Focus in the text column holds the timer, and .is-paused carries every other reason the script
   knows about: the pointer resting on the item text, a finger held down on the section, the section
   scrolled out of view, the browser tab in the background. Together they are the pause mechanism
   WCAG 2.2.2 asks for.

   Scoped to .slider__list rather than the whole section, because the tabs sit outside it. Clicking
   a tab with a mouse leaves that button focused, and on the section the rule then held the timer
   until something outside the slider was clicked -- the slider looked broken after every tab
   change. The item triggers are inside the list and still pause, which is wanted: clicking a title
   to read it should stop the clock.

   A .slider:hover rule used to sit here as well. It paused on any part of the section, which is no
   longer what the script does -- only the item text holds the timer -- and a selector cannot carve
   out the exception, because the open item's visual is a DOM descendant of the text column even
   while it renders over the stage in the other one. It also guarded nothing by itself: the
   auto-advance is the script's animationend handler, so with no script there is no timer for a
   CSS-only hover to stop. */
.slider__list:focus-within .slider-item__fill,
.slider.is-paused .slider-item__fill {
    animation-play-state: paused;
}

/* A tab reached by keyboard still holds the timer -- that is the one pause the scoping above gives
   up, and a keyboard user has no hover to fall back on. :focus-visible is what separates the two:
   a mouse click on a button does not match it, arrowing or tabbing to one does.

   Its own rule rather than a third selector beside the two above, because an unsupported :has()
   invalidates the entire rule it appears in. In the list it would take .is-paused down with it and
   cost the hover, offscreen and backgrounded pauses as well; alone, an old browser just drops it
   and the rest stands. */
.slider:has(.tabs__tab:focus-visible) .slider-item__fill {
    animation-play-state: paused;
}

/* One 16px rhythm runs the whole open item: title, description, button. That is what makes it
   28 + 16 + 56 + 16 + 48 = 164px, the height Figma gives. */
.slider-item__content {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-16);
}

.slider-item__heading {
    margin: 0;
}

.slider-item__trigger {
    display: block;
    width: 100%;
    padding: 0;
    font-family: var(--font-heading);
    font-size: var(--type-heading-sm-size);
    font-weight: var(--heading-weight-sm);
    line-height: var(--type-heading-sm-line);
    letter-spacing: var(--type-heading-sm-track);
    text-align: left;
    color: var(--black);
    background: none;
    border: 0;
    cursor: pointer;
    transition: color 150ms ease;
}

.slider-item__trigger:hover {
    color: var(--brand-blue-500);
}

.tabs__tab:focus-visible,
.slider-item__trigger:focus-visible {
    outline: 2px solid var(--brand-blue-500);
    outline-offset: 2px;
}

.slider-item__body {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-16);
}

/* The script closes an item by setting the hidden attribute, and an author display rule outranks
   the browser's own [hidden] { display: none } -- without this every item renders open. Same guard
   as .capability-item__panel, for the same reason. */
.slider-item__body[hidden] {
    display: none;
}

.slider-item__description {
    margin: 0;
    font-size: var(--type-body-lg-size);
    line-height: var(--type-body-lg-line);
    color: var(--black-64);
}

/* The list and the open item both align their children to the start, which left the figure at the
   image's intrinsic 752px and a gap to its right on anything between roughly 800 and 1200. Stretch
   fills the column; the aspect ratio below keeps the height in proportion. Above 1200 the visual is
   positioned over the stage, where align-self does not apply. */
.slider-item__visual {
    align-self: stretch;
    margin: 0;
}

.slider-item__visual img {
    display: block;
    width: 100%;
    height: auto;
    aspect-ratio: 752 / 564;
    object-fit: cover;
    border-radius: var(--radius-16);
}

.slider__stage {
    display: none;
}

/* The head is a flex child of .slider__list now, so the list's own 32px gap already separates it
   from the items. Its default 48px bottom margin would compose with that gap and make 80. */
.slider--left .section-head {
    margin-bottom: 0;
}

/* The Content=Left layout renders its own head, so by the project's own rule it carries the
   section's vertical padding -- unlike Content=Top, where the page renders the head above the
   slider and `.home-section` carries it. 96 is (756 - 564) / 2 from the feature page frames, and 48
   below 834 is the step every other section on those pages takes. */
.slider--left {
    padding-block: var(--space-96);
}

@media (max-width: 833.95px) {
    .slider--left {
        padding-block: var(--space-48);
    }
}

/* ---------- Two-column desktop ----------
   One breakpoint, at 1200px. Above it the section is the two-column desktop design; below it, at
   every width down to the smallest phone, it is the stacked layout -- header, tabs, then each item
   carrying its own visual. Figma draws 1600 and 390 and nothing between, so rather than invent a
   tablet arrangement the desktop one simply stops being used once there is no longer room for a
   528px list beside a 752px visual. */
/* Stacked, the CTA spans the column rather than sitting at its label width -- the same full-bleed
   treatment the buttons take in the header sheet. Above 1200 it keeps its natural width beside the
   visual. */
@media (max-width: 1199.95px) {
    .slider__cta {
        align-self: stretch;
    }
}

@media (min-width: 1200px) {
    /* 528 : 752 with a 32px gutter, as the 1312 container divides. align-items centres the list
       against the visual, which is what Figma's 44px of space above and below the items is. */
    .slider__panel {
        display: grid;
        grid-template-columns: 528fr 752fr;
        column-gap: var(--space-32);
        align-items: center;
    }

    /* As above: the grid would otherwise show every tab's panel at once at this width. */
    .slider__panel[hidden] {
        display: none;
    }

    .slider__stage {
        display: block;
        aspect-ratio: 752 / 564;
    }

    .slider-item.is-open .slider-item__visual {
        position: absolute;
        top: 50%;
        right: 0;
        width: calc((100% - var(--space-32)) * 752 / 1280);
        transform: translateY(-50%);
    }

    /* Content=Left moves the header into the left column beside the visual and drops the tabs. */
    .slider--left {
        position: relative;
    }

    /* 64 is the left column's whole rhythm in Figma: head, 64, items. Setting it on the column
       rather than on the head keeps one number in one place -- the head's margin is zero and the
       gap does all of the spacing. It read 32 until the feature pages were built, which is the
       first time this layout has rendered on a page; the homepage uses Content=Top for both of its
       sliders, so nothing already shipped moves. */
    .slider--left .slider__list {
        gap: var(--space-64);
    }

    /* No max-width any more. The head sits inside .slider__list, so the grid column already holds
       it to 528; the old percentage was measured against the whole slider and would resolve to 212
       in its new home. */
    .slider--left .section-head {
        --section-head-measure: none;

        align-items: flex-start;
        text-align: left;
    }
}

@media (prefers-reduced-motion: reduce) {
    /* No animation means no animationend, which means no auto-advance: the slider becomes a plain
       accordion that only moves when someone clicks it. */
    .slider-item.is-open .slider-item__fill {
        animation: none;
        height: 100%;
    }

    .tabs__tab,
    .slider-item__trigger {
        transition: none;
    }
}

/* ---------- Case study item ----------
   Figma: _CaseStudies-Item 27365:80601 -- Style (Card / List) x State (Default / Hover), with
   _CaseStudy-Universities 27522:57019 as the partner row inside it.

   The item only: the grid or row that places these decides how many fit at a given width, and the
   item fills whatever column or row it is handed. Card is drawn at 416 and List at 1312, but those
   are the widths of one drawing, not rules -- the width comes from the caller.

   Card takes no height either. Figma draws it at 704.5 and the instances in a real row measure
   712, 691.75, 787 and 576.75 at the four screens, all different: what is constant is that the
   three cards in a row match each other, which is what a stretched flex or grid track already does.
   So the card is as tall as its contents and the row equalises it. Only the description is clamped,
   at the three lines Figma draws, because it is the one field a future API could return unbounded.

   List still carries its drawn 220px height, so the clamps that defend it stay scoped to it.

   Card steps its padding once, at 1200 -- 32 above, 16 below -- with the rest of this design.

   The border is an inset shadow rather than a real border, as on the tag: hover takes it from 1px
   to 2px, and a real border would reflow the contents by a pixel on the way in and out. */
.case-study {
    --_case-study-list-height: 220px;
    /* The title and description type, held here so the phone step below swaps the tokens in one
       place and the card's reserved text height follows whichever pair is in force. */
    --_case-study-title-size: var(--heading-size-xs-max);
    --_case-study-title-line: var(--heading-line-height-xs-max);
    --_case-study-title-track: var(--heading-letter-space-xs-max);
    --_case-study-description-size: var(--body-size-md-max);
    --_case-study-description-line: var(--body-line-height-md-max);

    position: relative;
    display: flex;
    background: var(--white);
    border-radius: var(--radius-16);
    box-shadow: inset 0 0 0 1px var(--black-12);
    /* Keeps the image inside the rounded corner, and keeps List's fixed height from spilling. */
    overflow: hidden;
    transition: box-shadow 150ms ease;
}

.case-study:hover {
    box-shadow: inset 0 0 0 2px var(--brand-blue-500);
}

/* The ring belongs to the item, not to the title inside it, since the whole surface is the link. */
.case-study:focus-within {
    outline: 2px solid var(--brand-blue-500);
    outline-offset: 2px;
}

/* A pointer affordance only; on touch it would stick after the tap, as on the tag. */
@media (hover: none) {
    .case-study:hover {
        box-shadow: inset 0 0 0 1px var(--black-12);
    }
}

.case-study__body {
    display: flex;
    min-width: 0;
}

.case-study__main {
    display: flex;
    flex-direction: column;
    gap: var(--space-12);
    min-width: 0;
}

.case-study__category {
    margin: 0;
}

.case-study__text {
    display: flex;
    flex-direction: column;
    gap: var(--space-8);
    min-width: 0;
}

.case-study__title {
    margin: 0;
    font-family: var(--font-heading);
    font-size: var(--_case-study-title-size);
    font-weight: var(--heading-weight-xs);
    line-height: var(--_case-study-title-line);
    letter-spacing: var(--_case-study-title-track);
    color: var(--black);
}

/* Card clamps the title to two lines, as the 1200 frame draws it (see the card rules below); List
   scopes its own clamp further down. */
.case-study__link {
    color: inherit;
    text-decoration: none;
    transition: color 150ms ease;
}

/* Blue rather than the global a:hover underline, like the site's other text links. The link's
   ::after covers the item, so this follows the pointer anywhere on the card, alongside the ring. */
.case-study__link:hover {
    color: var(--brand-blue-500);
    text-decoration: none;
}

@media (hover: none) {
    .case-study__link:hover {
        color: inherit;
    }
}

/* The link's own box is the title; this is what makes the rest of the item clickable. Everything
   drawn above it -- nothing is interactive inside the item today -- stays hit-testable because the
   overlay sits at the item's own stacking level. */
.case-study__link::after {
    content: '';
    position: absolute;
    inset: 0;
}

.case-study__description {
    margin: 0;
    font-size: var(--_case-study-description-size);
    font-weight: var(--body-weight-regular);
    line-height: var(--_case-study-description-line);
    letter-spacing: var(--body-letter-space);
    color: var(--black-80);
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 3;
    overflow: hidden;
}

.case-study__details {
    display: flex;
    flex-direction: column;
    gap: var(--space-12);
    min-height: 0;
}

/* Figma draws the stroke outside a zero-height frame, so the rule costs the column nothing but the
   gap either side of it. The negative margin is what reproduces that: a 1px border that took part
   in the layout would make the item 2px taller than the frame it is built from. */
.case-study__rule {
    width: 100%;
    height: 0;
    margin: 0 0 -1px;
    border: 0;
    border-top: 1px solid var(--black-12);
}

.case-study__partners {
    display: flex;
    gap: var(--space-12);
    align-items: center;
}

.case-study__partners-label {
    flex: 1 1 0;
    min-width: 0;
    margin: 0;
    font-size: var(--body-size-sm-max);
    font-weight: var(--body-weight-regular);
    line-height: var(--body-line-height-sm-max);
    letter-spacing: var(--body-letter-space);
    color: var(--black);
}

/* The marks sit bare, as the frames draw them -- no plate behind them. 40px box, fitted rather than
   cropped, since a logo cropped is a logo damaged. */
.case-study__logos {
    display: flex;
    gap: 9px;
    align-items: center;
    justify-content: flex-end;
    flex-shrink: 0;
}

.case-study__logo {
    width: var(--space-40);
    height: var(--space-40);
    object-fit: contain;
}

.case-study__goal {
    display: flex;
    align-items: flex-start;
    gap: var(--space-8);
    margin: 0;
    font-size: var(--body-size-md-max);
    font-weight: var(--body-weight-bold);
    line-height: var(--body-line-height-md-max);
    letter-spacing: var(--body-letter-space);
    color: var(--black);
}

/* 16 on the 24 line, so 4 down centres it on the first line however far the text wraps. */
.case-study__goal-icon {
    flex-shrink: 0;
    width: var(--space-16);
    height: var(--space-16);
    margin-top: var(--space-4);
    color: var(--brand-teal-500);
}

/* Card lets the tags wrap as far as they need; the row equalises around the tallest card. The
   two-row cap List depends on is scoped to it below. */
.case-study__features {
    display: flex;
    flex-wrap: wrap;
    align-content: flex-start;
    gap: var(--space-4);
    padding: var(--space-8) 0;
    margin: 0;
    list-style: none;
}

.case-study__feature {
    display: flex;
}

/* ---------- Case study: Card ---------- */
.case-study--card {
    flex-direction: column;
    gap: var(--space-20);
    padding: var(--space-16);
}

@media (min-width: 1200px) {
    .case-study--card {
        padding: var(--space-32);
    }
}

.case-study--card .case-study__body {
    flex-direction: column;
    gap: var(--space-20);
}

.case-study--card .case-study__media {
    aspect-ratio: 260 / 195;
    flex: 0 1 auto;
    min-height: 0;
    border-radius: var(--radius-16);
    overflow: hidden;
}

/* Every card in a row keeps the same room for its copy -- two title lines, the 8px gap and three
   description lines -- so the rule and the partners row below sit at one height across the row.
   The room is held by the text block, not by the title, so the description always sits 8px under
   the title's last line and any spare space falls below it, as the frames' bottom padding does. */
.case-study--card .case-study__title {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    overflow: hidden;
}

.case-study--card .case-study__text {
    min-height: calc(2 * var(--_case-study-title-line) + var(--space-8) + 3 * var(--_case-study-description-line));
}

/* Phones step the title to 16 and the description to 14, each with its token's own line height. */
@media (max-width: 833.95px) {
    .case-study {
        --_case-study-title-size: var(--heading-size-xs-min);
        --_case-study-title-line: var(--heading-line-height-xs-min);
        --_case-study-title-track: var(--heading-letter-space-xs-min);
        --_case-study-description-size: var(--body-size-md-min);
        --_case-study-description-line: var(--body-line-height-md-min);
    }
}

.case-study--card .case-study__main,
.case-study--card .case-study__details {
    flex: 0 0 auto;
}

/* ---------- Case study: List ----------
   Two layouts, one class. From 1200 this is the row the frames draw: a fixed 220px tall, the media
   beside the body, the details column holding 416. Below 1200 there is no room for a row, so it
   becomes the Card layout exactly -- stacked, as tall as its contents, 16px of padding -- and the
   clamps that exist only to defend the 220 lift with it.

   Written stacked-first, so the base below is the Card layout and the query adds the row. */
.case-study--list {
    flex-direction: column;
    gap: var(--space-20);
    padding: var(--space-16);
}

.case-study--list .case-study__body {
    flex-direction: column;
    gap: var(--space-20);
}

.case-study--list .case-study__media {
    aspect-ratio: 260 / 195;
    flex: 0 1 auto;
    min-height: 0;
    border-radius: var(--radius-16);
    overflow: hidden;
}

.case-study--list .case-study__main,
.case-study--list .case-study__details {
    flex: 0 0 auto;
}

@media (min-width: 1200px) {
    .case-study--list {
        flex-direction: row;
        gap: var(--space-64);
        height: var(--_case-study-list-height);
        padding: var(--space-20);
    }

    .case-study--list .case-study__body {
        flex: 1 1 0;
        flex-direction: row;
        gap: var(--space-32);
    }

    /* The media goes back to the drawn 240x180 box, so the aspect the stacked layout sets has to
       be cleared rather than left to fight the fixed height. */
    .case-study--list .case-study__media {
        aspect-ratio: auto;
        flex: 0 0 auto;
        width: 240px;
        height: 180px;
    }

    .case-study--list .case-study__main {
        flex: 1 1 0;
    }

    /* The details column is drawn at a fixed 416 beside a flexible body. It holds that until the
       row can no longer give it away, at which point it starts sharing -- the section placing the
       item is what decides when List stops being the right style. */
    .case-study--list .case-study__details {
        flex: 0 1 416px;
    }

    /* The 220px is a fixed height, so everything inside it that can vary is clamped: a title to a
       line, a partner label to two, the tags to two rows. None of this applies below 1200, where
       the item is as tall as its contents. The tag cap is two 28px rows, the 4px between them and
       the 8px above and below, and it falls on a row boundary so what shows is never a sliced row. */
    .case-study--list .case-study__link {
        display: -webkit-box;
        -webkit-box-orient: vertical;
        -webkit-line-clamp: 1;
        overflow: hidden;
    }

    .case-study--list .case-study__partners-label {
        display: -webkit-box;
        -webkit-box-orient: vertical;
        -webkit-line-clamp: 2;
        overflow: hidden;
    }

    .case-study--list .case-study__features {
        max-height: 76px;
        overflow: hidden;
    }
}

.case-study__image {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* ---------- Case studies section ----------
   Figma: Case Studies 28637:79545, drawn at 390 / 834 / 1200 / 1600.

   The track that places the case study item, and nothing else -- the head above it is section-head,
   rendered by the page, and the item itself is partials/case-study.html.

   Two layouts at one breakpoint. From 1200 three equal columns consume the container exactly:
   3 x 416 + 2 x 32 = 1312 at xL, and 3 x 357 + 2 x 32 = 1135 at Md. Below it the track is wider
   than its container -- 899 inside 350 at xS, 1940 inside 768 at Sm -- so it scrolls and the next
   card peeks. The card is 82.6% of the container at 390 and 82.8% at 834, which is one rule. */
/* Figma draws this heading at 754 of the 1312 it sits in, which is what breaks it over two
   lines. As with the capability section the number is the frame's, not a chosen measure. */
.case-studies .section-head {
    --section-head-measure: 754px;
}

.case-studies__items {
    display: flex;
    gap: var(--space-16);
    overflow-x: auto;
    scroll-snap-type: x mandatory;
}

/* Stretch is what makes the cards in a row match each other, since the item carries no height. */
.case-studies__items > * {
    flex: 0 0 82.6%;
    scroll-snap-align: start;
}

/* Below 1200 the carousel runs to the screen edges rather than stopping at the section's padding,
   which clipped the peeking card and left that padding blank beside it. The negative margin takes
   the padding back and the track's own padding restores it, so the first card still starts on the
   content edge and snaps back to it. The track scrolls by touch, wheel and keyboard; its scrollbar,
   15px under the cards wherever scrollbars take space, is hidden as the other tracks here are. */
@media (max-width: 1199.95px) {
    .case-studies:not(.case-studies--list) .case-studies__items {
        --case-studies-bleed: var(--grid-xs-margin);
        margin-inline: calc(-1 * var(--case-studies-bleed));
        padding-inline: var(--case-studies-bleed);
        scroll-padding-inline: var(--case-studies-bleed);
        scrollbar-width: none;
    }

    .case-studies:not(.case-studies--list) .case-studies__items::-webkit-scrollbar {
        display: none;
    }
}

@media (min-width: 834px) and (max-width: 1199.95px) {
    .case-studies:not(.case-studies--list) .case-studies__items {
        --case-studies-bleed: var(--grid-sm-margin);
    }
}

.case-studies__cta {
    display: flex;
    justify-content: center;
    margin-top: var(--space-32);
}

@media (min-width: 1200px) {
    .case-studies__items {
        display: grid;
        grid-template-columns: repeat(3, 1fr);
        gap: var(--space-32);
        overflow-x: visible;
    }

    .case-studies__items > * {
        flex: none;
    }
}

/* The research-area pages place List-style items instead of three cards -- Diet and Nutrition,
   Oncology and Infectious Diseases each show one (27640:157295 and siblings). The item already
   carries that layout; what changes is the track. It stops being a three-column grid, in which a
   single item would sit at a third width, and stops being a carousel, because the narrow frames
   draw the item full width with no card peeking beside it (27640:157523).

   So the track is a stack at every width, and the modifier beats both the base rule and the one at
   1200 on specificity rather than on order. The 32 between rows is a reading: no frame draws two
   of these together, and 32 is the gap the card track uses from 1200. */
.case-studies--list .case-studies__items {
    display: flex;
    flex-direction: column;
    gap: var(--space-32);
    overflow-x: visible;
    scroll-snap-type: none;
}

/* The base track sizes items with a flex-basis percentage. Once the axis turns, that percentage
   resolves against the height rather than the width, so it has to go rather than be restated. */
.case-studies--list .case-studies__items > * {
    flex: none;
}

/* ---------- Final CTA ----------
   Figma: #FinalCTA 28648:91462, drawn at 390 / 834 / 1200 / 1600.

   A full-bleed band holding one rounded blue panel, capped at the 1312 container. The panel carries
   two decorative layers under its content: a photograph at 12%, and a field of dots at 8% that the
   design masks away across the middle so it reads only near the left and right edges.

   The stacked-to-side-by-side switch is not a media query. The text column is given a 416 minimum
   and the row wraps, so the buttons drop below the moment the panel can no longer hold both. That
   happens between 834 and 1200 on its own, which is where the frames put it. What the query at
   1200 does is what wrapping cannot: make the buttons fill the width and sit one above the other. */
.final-cta {
    padding-block: var(--space-48);
}

.final-cta__panel {
    position: relative;
    /* Keeps the two decorative layers' negative z-index inside the panel rather than letting them
       sink behind the page. */
    isolation: isolate;
    /* The dot field is drawn wider than the panel, and the photo is cropped by it. */
    overflow: hidden;
    max-width: var(--grid-xl-col-12);
    margin-inline: auto;
    padding: var(--space-32) var(--space-20);
    background: var(--brand-blue-600);
    border-radius: var(--radius-32);
}

/* Cover rather than the drawn size: the frames place a fixed 703 or 1312 crop, which only holds at
   the width each was drawn at. Anchored to the top so the faces stay in view as the panel shortens. */
.final-cta__photo {
    position: absolute;
    inset: 0;
    z-index: -2;
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: top;
    opacity: 0.12;
    pointer-events: none;
}

/* Stacked, the photo is faded out towards the foot so the buttons sit on flat colour. Figma does
   this with a blue-500 overlay; a mask is the same result without a second colour to keep in step
   with the panel's own. */
@media (max-width: 1199.95px) {
    .final-cta__photo {
        mask-image: linear-gradient(to top, transparent, var(--black) 50%);
    }
}

/* The dots are a plain 13.5 x 14 grid, so they are drawn rather than fetched: an image would be one
   more request, and would either tile visibly or upscale. The mask is the design's own -- opaque at
   both edges, clear in the middle -- so the field fades out across the centre of the panel. */
.final-cta__pattern {
    position: absolute;
    inset: 0;
    z-index: -1;
    opacity: 0.08;
    background-image: radial-gradient(circle, var(--white) 1.75px, transparent 1.75px);
    background-size: 13.5px 14px;
    mask-image: linear-gradient(to right, var(--black), transparent 50%, var(--black));
    pointer-events: none;
}

.final-cta__body {
    display: flex;
    flex-direction: column;
    gap: var(--space-24);
}

.final-cta__content {
    display: flex;
    flex-direction: column;
    gap: var(--space-16);
}

/* The gap the design leaves between the eyebrow and the heading, which is most of the panel's top
   half and is what the photograph shows through. */
.final-cta__lead {
    display: flex;
    flex-direction: column;
    gap: var(--space-128);
}

.final-cta__caption {
    margin: 0;
    font-size: var(--type-caption-md-size);
    font-weight: var(--caption-weight);
    line-height: var(--caption-line-height-md);
    letter-spacing: var(--type-caption-md-track);
    text-transform: uppercase;
    color: var(--white);
}

.final-cta__heading {
    margin: 0;
    font-family: var(--font-heading);
    font-size: var(--type-heading-xl-size);
    font-weight: var(--heading-weight-xl);
    line-height: var(--type-heading-xl-line);
    letter-spacing: var(--type-heading-xl-track);
    color: var(--white);
}

.final-cta__description {
    margin: 0;
    font-size: var(--type-body-xl-size);
    line-height: var(--type-body-xl-line);
    color: var(--white-80);
}

.final-cta__actions {
    display: flex;
    flex-direction: column;
    gap: var(--space-8);
}

.final-cta__actions .button {
    width: 100%;
}

@media (min-width: 834px) {
    .final-cta__panel {
        padding: var(--space-40);
    }
}

@media (min-width: 1200px) {
    .final-cta {
        padding-block: var(--space-96);
    }

    .final-cta__panel {
        padding: var(--space-80);
    }

    /* Wrapping, not switching: the row only holds while the text column can keep its 416. */
    .final-cta__body {
        flex-direction: row;
        flex-wrap: wrap;
        align-items: flex-end;
        justify-content: space-between;
        gap: var(--space-48);
    }

    .final-cta__content {
        flex: 1 0 0;
        min-width: var(--grid-xl-col-4);
        max-width: 640px;
    }

    .final-cta__lead {
        gap: var(--space-96);
    }

    /* Reversed: the markup puts the main action first, which is the stacked order, and the row
       draws it on the right. */
    .final-cta__actions {
        flex-direction: row-reverse;
    }

    .final-cta__actions .button {
        width: auto;
    }
}

/* ---------- Feature page hero ----------
   Figma: HeroPage 27338:46427, drawn at 1600 and 390. The hero of the 13 feature pages, and
   deliberately not the homepage's -- see partials/page-hero.html.

   Two columns of 624 with a 64px gutter, which is what 1312 divides into, and the text column is
   centred against the visual rather than sitting at its top. Below 1200 it stacks, and the visual
   goes ABOVE the text, as the 390 frame draws it. That is an order change, not a reflow, so it is
   done with grid areas rather than by putting the visual first in the DOM: the h1 should open the
   document.

   The 1200 boundary is a reading, not a transcription -- the file draws 1600 and 390 and nothing
   between. Two 352px columns at 834 would put a 48px heading in a column too narrow for it, so the
   stack starts one step earlier, the same call the slider's two-column rule makes.

   The visual is one flat image, 4:3, with the dot pattern and the fourteen floating labels baked
   in. Its corners are rounded in the export -- filled white, since JPEG has no alpha -- and the
   radius is repeated here so the shape stays right if the image is ever placed on a tint. */
.feature-hero {
    padding-block: var(--space-48) var(--space-96);
}

.feature-hero__inner {
    display: grid;
    grid-template-areas:
        "visual"
        "text";
    gap: var(--space-32);
}

.feature-hero__text {
    grid-area: text;
}

/* Capped at the width it is drawn at, and centred once it is. Stacked, the visual would otherwise
   take the full container -- 819 wide and 614 tall at a 900px viewport, which is most of a screen
   of artwork before a word of the page is read. 624 is not a column width; it is (1312 - 64) / 2,
   the hero's own gutter, so it is written literally rather than borrowed from a grid token. Below
   624 the cap does nothing, which is every phone. */
.feature-hero__image {
    grid-area: visual;
    width: 100%;
    max-width: 624px;
    height: auto;
    margin-inline: auto;
    aspect-ratio: 4 / 3;
    object-fit: cover;
    border-radius: var(--radius-16);
}

.feature-hero__caption {
    margin: 0 0 var(--space-16);
    font-size: var(--type-caption-md-size);
    font-weight: var(--caption-weight);
    line-height: var(--caption-line-height-md);
    letter-spacing: var(--type-caption-md-track);
    text-transform: uppercase;
    color: var(--brand-teal-500);
}

.feature-hero__headline {
    margin: 0;
    font-family: var(--font-heading);
    font-size: var(--type-heading-xl-size);
    font-weight: var(--heading-weight-xl);
    line-height: var(--type-heading-xl-line);
    letter-spacing: var(--type-heading-xl-track);
    color: var(--black);
}

/* Black, not black-80. The card's description is the muted one; the hero's is not. */
.feature-hero__description {
    margin: var(--space-16) 0 0;
    font-size: var(--type-body-2xl-size);
    font-weight: var(--body-weight-regular);
    line-height: var(--type-body-2xl-line);
    letter-spacing: var(--body-letter-space);
    color: var(--black);
}

/* Only the Why Avicenna hero has these so far. A list rather than a row of loose spans: it is a set
   of claims about the product, and the count is worth announcing. 48 below the text and 4 between
   them are the frame's own numbers; the tags bring their own 28px height. */
.feature-hero__tags {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-4);
    margin: var(--space-48) 0 0;
    padding: 0;
    list-style: none;
}

@media (max-width: 833.95px) {
    /* No top padding: the 390 frames start the hero on the page offset's line (.site-main--offset),
       which already clears the header. */
    .feature-hero {
        padding-block: 0 var(--space-48);
    }
}

@media (min-width: 1200px) {
    .feature-hero__inner {
        grid-template-areas: "text visual";
        grid-template-columns: 1fr 1fr;
        gap: var(--space-64);
        align-items: center;
    }
}

/* ---------- Card grid section ----------
   Figma: the `Section` frames carrying `Items` across the Features canvas 27654:173406 --
   27346:42534, 27346:43650 and 27346:44671 on Surveys & Outcome Assessments.

   42 of the 66 sections on the 13 feature pages are this one component. Two surfaces: plain on the
   white page with outline cards in a 1312 container, and a teal-50 panel with inline cards in a
   1152 container inside 80px of padding. 1312 - 160 = 1152, so the panel is not a second grid, it
   is the same grid with the panel's padding taken out of it.

   Every card width in the file is (container - gutters) / columns at a 32px gutter, which is why
   nothing here sets a card width: the grid divides and the card fills what it is given.

   The head is rendered here rather than by the page because in both surfaces it sits inside the box
   this component owns -- and by the same rule, the vertical padding is here too. */
.card-grid {
    padding-block: var(--space-96);
}

.card-grid__items {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-32);
}

/* The head is 10 of 12 columns on the plain surface, and the full width of the content inside a
   panel. Figma gives the description a step more size out on the page than inside a panel, and
   black rather than the head's default black-80 in both. */
.card-grid > .section-head {
    --section-head-description-color: var(--black);

    max-width: var(--grid-xl-col-10);
    margin-inline: auto;
}

.card-grid:not(.card-grid--panel) > .section-head {
    --section-head-description-size: var(--type-body-xl-size);
    --section-head-description-line: var(--type-body-xl-line);
}

.card-grid--panel {
    padding: var(--space-80);
    background: var(--brand-teal-50);
    border-radius: var(--radius-32);
}

.card-grid--panel > .section-head {
    max-width: none;
}

/* Below 1200 a 3- or 4-column grid drops to 2, and below 834 everything is a single column. The
   390 frames draw the single column; the middle step is a reading, since the file has no frame
   between 390 and 1600. */
@media (min-width: 834px) {
    .card-grid--2 .card-grid__items,
    .card-grid--3 .card-grid__items,
    .card-grid--4 .card-grid__items {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

@media (min-width: 1200px) {
    .card-grid--3 .card-grid__items {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }

    .card-grid--4 .card-grid__items {
        grid-template-columns: repeat(4, minmax(0, 1fr));
    }
}

/* The panel runs to the edge of the viewport at 390, so it escapes the container's margin with a
   negative one -- the escape hatch the layout container is designed around. Its padding drops with
   it, and the radius goes: a rounded corner against the screen edge reads as a mistake. */
@media (max-width: 833.95px) {
    .card-grid {
        padding-block: var(--space-48);
    }

    .card-grid--panel {
        padding: var(--space-40) var(--grid-xs-margin);
        margin-inline: calc(var(--grid-xs-margin) * -1);
        border-radius: 0;
    }
}

/* ---------- Badge divider ----------
   Figma: `SeparateLine with Badge` 28955:277429, in the Request a Demo trust band (28955:277423)
   at 1600 and 390.

   A rule across the panel with a row of pills centred on it. The rules grow and the pills do not,
   so the row centres itself at any width without anything here knowing how wide a label is.

   Both the capability section and the Request a Demo trust band wear this. They differ in one
   thing, the uppercase variant below; the three other differences the capability section used to
   carry were drift, and converging corrected them. See partials/badge-divider.html.

   The rules are kept only where the whole row fits on one line. The three pills measure 687 and
   want 16 of clearance, so they fit the panel's content width at 1200 (976) and not at 834 (608) --
   below the step the rules would collapse to nothing and the pills would wrap against them, which
   reads as broken rather than as a divider. So the row stacks from 1200 down, the same breakpoint
   the grid drops its third column at, and the 390 frame's stacked pills fall out of it. */
.badge-divider {
    display: flex;
    align-items: center;
    gap: var(--space-8);
}

/* The frame leaves 32 between the row and the cards: the row ends 112 down the panel and the
   cards start at 144. .card-grid is a plain block, so nothing supplies that -- its head carries
   its own bottom margin for the same reason. It belongs to the grid rather than to the divider,
   which should not know what follows it. */
.card-grid .badge-divider {
    margin-block-end: var(--space-32);
}

/* The rules fade out towards the panel's edges rather than running flat: sampled off the frame,
   each is teal-300 where it meets a pill and the panel's own tint where it ends, so what is drawn
   is a gradient to transparent. The second rule is the mirror, which :last-child picks out --
   the divider's children are rule, list, rule. */
.badge-divider__rule {
    flex: 1 1 auto;
    height: 1px;
    background: linear-gradient(to right, transparent, var(--brand-teal-300));
}

.badge-divider__rule:last-child {
    background: linear-gradient(to left, transparent, var(--brand-teal-300));
}

.badge-divider__items {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-8);
    justify-content: center;
    margin: 0;
    padding: 0;
    list-style: none;
}

/* The frame puts the icon 16 from the pill's edge and the label 4 from the icon, and makes the
   pill 32 tall against a 20px line -- so the block padding is 6, which the spacing scale does not
   carry. It is written as the number rather than rounded to 4 or 8, because 28 or 36 would both
   miss the drawn height. */
.badge-divider__pill {
    display: inline-flex;
    flex-shrink: 0;
    gap: var(--space-4);
    align-items: center;
    padding: 6px var(--space-16);
    font-size: var(--body-size-sm-max);
    font-weight: var(--body-weight-bold);
    line-height: var(--body-line-height-sm-max);
    letter-spacing: var(--body-letter-space);
    color: var(--white);
    background: var(--brand-teal-700);
    border-radius: var(--radius-full);
}

/* The capability section's badge is set in caps and the trust band's is not; it is the only thing
   that differs between the two callers. */
.badge-divider--uppercase .badge-divider__pill {
    text-transform: uppercase;
}

/* Drawn at 16 in a 24px box, so it is sized rather than left at the box's dimensions. */
.badge-divider__icon {
    flex-shrink: 0;
    width: var(--icon-xs);
    height: var(--icon-xs);
}

/* Stacking is for a row that cannot fit, which is a question of how many pills there are rather
   than of the width alone. The trust band's three measure 687 and want 16 of clearance, so they
   fit the panel at 1200 and not at 834, where the rules would collapse to nothing and the pills
   would wrap against them. The capability section carries one pill, which fits at every width and
   keeps its rules.

   :has() asks the question directly -- more than one pill -- rather than making the caller declare
   it. Where it is unsupported the row simply does not stack, which is what both callers did before
   this component existed. */
@media (max-width: 1199.95px) {
    .badge-divider:has(.badge-divider__pill + .badge-divider__pill) {
        flex-direction: column;
    }

    .badge-divider:has(.badge-divider__pill + .badge-divider__pill) .badge-divider__rule {
        display: none;
    }

    .badge-divider:has(.badge-divider__pill + .badge-divider__pill) .badge-divider__items {
        flex-direction: column;
        align-items: center;
    }
}

/* ---------- List panel ----------
   Figma: 28759:80142 and its three siblings, collected at 28759:81027.

   A tinted panel, head left and a marked list right, in two equal columns of the 1152 the panel's
   80px padding leaves: (1152 - 96) / 2 = 528. The four instances differ only in item count and
   tint, so the item count is data and the tint is one modifier.

   The panel surface is drawn here rather than shared with .card-grid--panel. That is the pattern
   the file already follows -- .security__panel draws its own violet surface too -- and the two are
   owned by different components even where they currently agree.

   Below 834 the columns stack with the 48px gap the 390 frame draws, and the panel goes full-bleed
   with its padding dropping to 40/20, exactly as the card grid's panel does. */
.list-panel {
    --marker-accent: var(--brand-teal-500);

    padding: var(--space-80);
    background: var(--brand-teal-50);
    border-radius: var(--radius-32);
}

/* Study Management's "Validate your study before launch" is the only violet one. */
.list-panel--violet {
    --marker-accent: var(--brand-violet-500);

    background: var(--brand-violet-50);
}

.list-panel__main {
    display: flex;
    gap: var(--space-96);
    align-items: flex-start;
}

.list-panel__text {
    flex: 1 1 0;
    min-width: 0;
}

.list-panel__headline {
    margin: 0;
    font-family: var(--font-heading);
    font-size: var(--type-heading-md-size);
    font-weight: var(--heading-weight-md);
    line-height: var(--type-heading-md-line);
    letter-spacing: var(--type-heading-md-track);
    color: var(--black);
}

/* The size is a hook because the two pages that use this slot disagree: About Us leaves it at the
   house Body Lg, and the research-area pages raise it a step, since there the panel's left column
   is a caption over this paragraph with no headline between them and it carries the section's
   opening line (27616:136132). A page sets the pair on its own wrapper. */
.list-panel__description {
    margin: var(--space-16) 0 0;
    font-size: var(--list-panel-description-size, var(--type-body-lg-size));
    font-weight: var(--body-weight-regular);
    line-height: var(--list-panel-description-line, var(--type-body-lg-line));
    letter-spacing: var(--body-letter-space);
    color: var(--list-panel-body-color, var(--black-80));
}

.list-panel__caption {
    margin: 0 0 var(--space-16);
    font-size: var(--type-caption-md-size);
    font-weight: var(--caption-weight);
    line-height: var(--caption-line-height-md);
    letter-spacing: var(--type-caption-md-track);
    color: var(--list-panel-caption-color, var(--brand-teal-500));
    text-transform: uppercase;
}

/* Prose in the right column instead of the marked list. Same flex share as .marker-list so the two
   shapes split the panel identically. */
.list-panel__prose {
    flex: 1 1 0;
    min-width: 0;
}

/* Body Xl by default, which is what About Us draws (27309:41761). The research-area pages draw
   this column a step down at Body Lg (27616:136180) while raising the lead beside it, so the two
   sizes trade places between pages and neither can be the component's only answer. */
.list-panel__prose p {
    margin: 0;
    font-size: var(--list-panel-prose-size, var(--type-body-xl-size));
    line-height: var(--list-panel-prose-line, var(--type-body-xl-line));
    color: var(--list-panel-body-color, var(--black-80));
}

.list-panel__prose p + p {
    margin-top: var(--space-32);
}

/* Blue is a surface, not a wash: solid blue-600 with white text and a headline a step larger, which
   is how the frames draw it and how the final CTA band already treats this colour. */
.list-panel--blue {
    --list-panel-caption-color: var(--white);
    --list-panel-body-color: var(--white);

    position: relative;
    overflow: hidden;
    background: var(--brand-blue-600);
}

.list-panel--blue .list-panel__headline {
    font-size: var(--type-heading-lg-size);
    font-weight: var(--heading-weight-lg);
    line-height: var(--type-heading-lg-line);
    letter-spacing: var(--type-heading-lg-track);
    color: var(--white);
}

/* The final CTA's dots exactly -- same blue, same 1.75px on a 13.5x14 grid, same 0.08. The feature
   images carry the same texture, but baked into the exports rather than drawn, so there is no CSS
   to share with them; measured off one, their grid is the same ~13px.
   Unmasked, unlike the CTA: there it fades across the middle so it reads only near the edges, and
   these frames carry it the whole way. */
.list-panel__pattern {
    position: absolute;
    inset: 0;
    opacity: 0.08;
    background-image: radial-gradient(circle, var(--white) 1.75px, transparent 1.75px);
    background-size: 13.5px 14px;
    pointer-events: none;
}

/* Above the pattern, which is the only reason this needs a stacking position at all. */
.list-panel--blue .list-panel__main {
    position: relative;
}

/* The stack layout: one full-width column with the head above the prose instead of beside it.
   About Us "Avicenna today" (27286:43495) is the only frame that draws it. The 40 gap holds at both
   breakpoints -- the narrow frame draws the same 40 the wide one does, not the 48 the split layout
   stacks to -- so the modifier beats the narrow rule on specificity rather than on order.

   Its paragraphs sit 16 apart rather than 32: the prose is a full 1152 wide here instead of a 528
   column, so the same 32 would read as a gap between blocks rather than between paragraphs. */
.list-panel--stack .list-panel__main {
    flex-direction: column;
    /* The panel's own flex-start is a cross-axis rule, so once the axis turns it stops meaning
       "top" and starts meaning "as narrow as the text" -- the head would no longer span the
       column. */
    align-items: stretch;
    gap: var(--space-40);
}

.list-panel--stack .list-panel__prose p + p {
    margin-top: var(--space-16);
}

/* The ruled footer. It is a sibling of __main rather than a third child of it, so the rule spans
   the panel whatever the column above it is doing, and so the split layout cannot grow a third
   column by accident. The 8 below the rule and the two lines' own leading are the whole block:
   the frames set no gap between the name and the place. */
.list-panel__signature {
    margin-top: var(--space-40);
    padding-top: var(--space-8);
    border-top: 1px solid var(--black-12);
}

.list-panel__signature-name {
    margin: 0;
    font-size: var(--type-body-xl-size);
    font-weight: var(--body-weight-bold);
    line-height: var(--type-body-xl-line);
    letter-spacing: var(--body-letter-space);
    color: var(--list-panel-body-color, var(--black-80));
}

.list-panel__signature-detail {
    margin: 0;
    font-size: var(--type-body-md-size);
    font-weight: var(--body-weight-regular);
    line-height: var(--type-body-md-line);
    letter-spacing: var(--body-letter-space);
    color: var(--list-panel-body-color, var(--black-80));
}

/* ---------- Marker list ----------
   The ringed list inside the panel, and -- once it is built -- inside the white card of the
   composite at 28759:80302. See partials/marker-list.html.

   Items sit on a 44px rhythm: a 28px line plus the 16px gap. The ring is 12px with a 3px edge,
   centred in a 20px slot and on the first line of its item: (28 - 12) / 2 = 8 from the top, which
   is the padding below. Its hole is left transparent so the surface behind shows through, which is
   the panel tint here and white in the card. */
.marker-list {
    display: flex;
    flex: 1 1 0;
    flex-direction: column;
    gap: var(--space-16);
    min-width: 0;
    margin: 0;
    padding: 0;
    list-style: none;
}

.marker-list__item {
    display: flex;
    gap: var(--space-4);
    align-items: flex-start;
}

.marker-list__marker {
    flex: 0 0 auto;
    box-sizing: border-box;
    width: 20px;
    /* 8 centres the ring on a 28px line. The boxed feature section sets its own, because its text
       is a step smaller; the default is left as a number so nothing already shipped moves. */
    padding-top: var(--marker-offset, 8px);

    /* The ring is the ::before so the 20px slot can stay a plain box. */
}

.marker-list__marker::before {
    display: block;
    width: 12px;
    height: 12px;
    margin-inline: auto;
    border: 3px solid var(--marker-accent, var(--brand-teal-500));
    border-radius: 50%;
    content: "";
}

/* Size is overridable for the same reason the accent is: the list panel sets it at Body Lg and the
   boxed feature section a step down, and both are as drawn. The defaults are the list panel's. */
.marker-list__text {
    flex: 1 1 0;
    min-width: 0;
    font-size: var(--marker-text-size, var(--type-body-lg-size));
    font-weight: var(--body-weight-bold);
    line-height: var(--marker-text-line, var(--type-body-lg-line));
    letter-spacing: var(--body-letter-space);
    color: var(--black-80);
}

@media (max-width: 833.95px) {
    .list-panel {
        padding: var(--space-40) var(--grid-xs-margin);
        margin-inline: calc(var(--grid-xs-margin) * -1);
        border-radius: 0;
    }

    .list-panel__main {
        flex-direction: column;
        gap: var(--space-48);
    }
}

/* ---------- Content card ----------
   Figma: _Content-CardItem 27407:79643 -- Style (Outline / Inline) x Device (Desktop / Mobile).

   Two things in the component set are not reproduced literally, each settled against the page
   frames rather than the component canvas:

   - The 416 / 350 / 560 widths are the component canvas, not the card. It takes the width of the
     column the section gives it, like every other card here.
   - The head-to-body gap is 80 on Outline/Desktop and 48 on Inline/Desktop. The two variants differ
     by nothing but a 1px border, so 32px cannot be the border's doing -- but the frames do draw the
     wider gap on the outline grid and the narrower one on the panel cards, and in both the
     description sits a fixed distance below the headline with the slack falling at the foot of the
     card. So the gap is kept per style, and it is NOT a margin-top:auto push to the bottom edge: in
     a row of unequal cards the short one ends early, which is what the frames show.

   The optical offset on the title is the one off-ramp number in the block. Figma hand-applies 6px
   at desktop and 8px at mobile; both are exactly (40 - line-height) / 2 -- the tile is 40px, the
   headline 28px then 24px -- so it is the first line being centred on the tile, not a spacing step.
   It is an adjacent-sibling rule so a card with no icon does not carry 6px of dead space at its top.

   Device is not a variant. --type-heading-sm-* and --type-body-lg-* already step at 834, so only
   padding and gap are restated below. */
.content-card {
    display: flex;
    flex-direction: column;
    gap: var(--space-48);
    padding: var(--space-32);
    background: var(--white);
    border-radius: var(--radius-16);
}

/* Figma draws the stroke inside the frame. A real border makes an outline card 2px taller than an
   inline one at the same padding; the site's buttons and tags avoid that with an inset shadow, but
   a card is never height-matched against an inline sibling, so the plain border is kept. */
.content-card--outline {
    gap: var(--space-80);
    border: 1px solid var(--black-12);
}

.content-card__head {
    display: flex;
    gap: var(--space-12);
    align-items: flex-start;
}

.content-card__icon {
    display: flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: center;
    padding: var(--space-12);
    color: var(--brand-teal-600);
    background: var(--brand-teal-50);
    border-radius: var(--radius-8);
}

.content-card__glyph {
    display: block;
    width: 16px;
    height: 16px;
}

.content-card__title {
    margin: 0;
    font-family: var(--font-heading);
    font-size: var(--type-heading-sm-size);
    font-weight: var(--heading-weight-sm);
    line-height: var(--type-heading-sm-line);
    letter-spacing: var(--type-heading-sm-track);
    color: var(--black);
}

/* Only when there is a tile to centre against. */
.content-card__icon + .content-card__title {
    padding-top: 6px;
}

.content-card__body {
    display: flex;
    flex-direction: column;
    gap: var(--space-16);
    align-items: flex-start;
}

/* Figma's `bottomHeadline`: the head keeps the icon alone and the heading drops onto the
   description, 8 above it rather than the body's 16. The trust band's three cards are the only
   instances of it in the file -- every feature-page card puts its heading in the head beside the
   icon, which is the default. The gap is set on the body rather than between two selectors because
   the heading is the body's first child whenever it is here at all. */
.content-card--headline-bottom .content-card__body {
    gap: var(--space-8);
}

/* The variant steps the type down with it: the frame sets the heading to Heading xS (20/24) and
   the line under it to Body Sm (14/20), against the Heading sM and Body Lg every other card uses.
   It is a smaller card -- three to a 1152 panel with an icon tile above the text -- so it reads at
   a smaller size, and taking the step here keeps the default untouched. */
/* The tile stays the 40 the component draws, but the glyph inside it is 20 rather than 16 -- the
   frame's stroke measures 2px against the 1px a 16px light glyph renders. Set as the box rather
   than as padding, because (40 - 20) / 2 is 10 and the spacing scale has no 10. */
.content-card--headline-bottom .content-card__icon {
    width: 40px;
    height: 40px;
    padding: 0;
}

.content-card--headline-bottom .content-card__glyph {
    width: 20px;
    height: 20px;
}

.content-card--headline-bottom .content-card__title {
    font-size: var(--type-heading-xs-size);
    font-weight: var(--heading-weight-xs);
    line-height: var(--type-heading-xs-line);
    letter-spacing: var(--type-heading-xs-track);
}

.content-card--headline-bottom .content-card__description {
    font-size: var(--body-size-sm-max);
    line-height: var(--body-line-height-sm-max);
}

.content-card__description {
    margin: 0;
    font-size: var(--type-body-lg-size);
    font-weight: var(--body-weight-regular);
    line-height: var(--type-body-lg-line);
    letter-spacing: var(--body-letter-space);
    color: var(--black-80);
}

/* The mobile step. Both styles take the same 40px gap below 834 -- the frames draw Outline and
   Inline identically there, which is the second reason to read the desktop 80/48 split as drawn
   rather than derived. */
@media (max-width: 833.95px) {
    .content-card,
    .content-card--outline {
        gap: var(--space-40);
        padding: var(--space-20);
    }

    .content-card__icon + .content-card__title {
        padding-top: 8px;
    }
}

/* ---------- Capability card ----------
   Figma: the Left/Mid/Right cards of #ProblemsWithSolutions (28546:82985), with
   _CardItem-Accordion 26697:30399 -- State (Default / Hover / Opened) -- as the row inside it.

   The card only, and it carries no breakpoint: the section placing it decides how many fit across
   and whether they scroll. Unlike the case study card this one has no fixed height -- opening a row
   grows the card and the section with it, which is the behaviour the design asks for.

   Featured is one type step on the heading and nothing else. That is the whole difference in the
   file: the middle card's extra height is entirely its two extra rows and its taller heading, not a
   different padding, gap or surface. It is a class rather than a caller property so the section can
   move it onto whichever card is centred once the cards scroll. */
.capability-card {
    display: flex;
    flex-direction: column;
    gap: var(--space-80);
    padding: var(--space-32);
    background: var(--white);
    border-radius: var(--radius-16);
}

.capability-card__head {
    display: flex;
    flex-direction: column;
    gap: var(--space-12);
}

.capability-card__title {
    margin: 0;
    font-family: var(--font-heading);
    font-size: var(--type-heading-xs-size);
    font-weight: var(--heading-weight-xs);
    line-height: var(--type-heading-xs-line);
    letter-spacing: var(--type-heading-xs-track);
    color: var(--brand-teal-600);
}

.capability-card--featured .capability-card__title {
    font-size: var(--type-heading-sm-size);
    line-height: var(--type-heading-sm-line);
    letter-spacing: var(--type-heading-sm-track);
}

.capability-card__description {
    margin: 0;
    font-size: var(--body-size-md-max);
    font-weight: var(--body-weight-regular);
    line-height: var(--body-line-height-md-max);
    letter-spacing: var(--body-letter-space);
    color: var(--black-80);
}

.capability-card__list {
    display: flex;
    flex-direction: column;
    margin: 0;
    padding: 0;
    list-style: none;
}

/* The rule belongs to the row, and the last row does not draw one -- the card's own padding closes
   the list off. */
.capability-item {
    border-bottom: 1px solid var(--black-12);
}

.capability-item:last-child {
    border-bottom: 0;
}

.capability-item__heading {
    margin: 0;
    font: inherit;
}

.capability-item__trigger {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-8);
    width: 100%;
    padding: var(--space-12) 0;
    font-family: inherit;
    font-size: var(--body-size-md-max);
    font-weight: var(--body-weight-bold);
    line-height: var(--body-line-height-md-max);
    letter-spacing: var(--body-letter-space);
    color: var(--black);
    text-align: left;
    background: none;
    border: 0;
    cursor: pointer;
    transition: color 150ms ease;
}

/* Hover takes the label and the chevron together -- the icon inherits currentColor, and the file
   draws both in blue-500. */
.capability-item__trigger:hover {
    color: var(--brand-blue-500);
}

.capability-item__trigger:focus-visible {
    outline: 2px solid var(--brand-blue-500);
    outline-offset: 2px;
}

/* Figma's open row is py-12 with an 8px gap between the label and the copy. Dropping the trigger's
   lower padding to 8 on open reproduces that exactly, and keeps the closed row's 48px target -- a
   negative margin on the panel would have done the same arithmetic less legibly. */
.capability-item__trigger[aria-expanded='true'] {
    padding-bottom: var(--space-8);
}

.capability-item__icon {
    flex-shrink: 0;
    transition: transform 150ms ease;
}

.capability-item__trigger[aria-expanded='true'] .capability-item__icon {
    transform: rotate(180deg);
}

.capability-item__panel {
    padding-bottom: var(--space-12);
}

.capability-item__panel[hidden] {
    display: none;
}

.capability-item__description {
    margin: 0;
    font-size: var(--body-size-md-max);
    font-weight: var(--body-weight-regular);
    line-height: var(--body-line-height-md-max);
    letter-spacing: var(--body-letter-space);
    color: var(--black-80);
}

@media (prefers-reduced-motion: reduce) {
    .capability-item__trigger,
    .capability-item__icon {
        transition: none;
    }
}

/* A pointer affordance; on touch it would stick after the tap, as on the tag and the case study. */
@media (hover: none) {
    .capability-item__trigger:hover {
        color: var(--black);
    }
}

/* ---------- Capability section ----------
   Figma: #ProblemsWithSolutions 28546:82985, drawn at Mobile 390, Tablet P 834, Tablet L 1200 and
   Desktop 1600.

   One breakpoint, 1200px -- the same one the slider uses. Above it the cards are a row of equal
   columns with the outer ones dropped 48px, which is how the design gives the middle one its
   prominence. Below it the same markup becomes a horizontal scroller: each card snaps to the
   centre, and capability-section.js moves the featured class onto whichever card is centred --
   behaviour, not layout. Only desktop styles that class, so the slides all look alike.

   The panel is the component. Its own margins on the page belong to the grid, which Phase 5 of the
   redesign still owns, so nothing here sets them. */
.capability-section {
    padding-block: var(--space-40);
    background: var(--brand-teal-50);
    border-radius: var(--radius-16);
}

/* Below 1200 the panel runs to both edges of the viewport, so it pulls back out of the margins the
   .section wrapper gives it. The radius stays, so the page shows through at the four corners, which
   is what the frames draw -- it is a full-width rounded band, not a square one. The two margins are
   the two the container uses at these widths; .section is still the only place they are chosen. */
@media (max-width: 1199.95px) {
    .capability-section {
        margin-inline: calc(var(--grid-xs-margin) * -1);
    }

    /* Stacked, the card's own head centres with the section's. It goes back to the left at 1200,
       where the cards are columns rather than slides. */
    .capability-card__title,
    .capability-card__description {
        text-align: center;
    }

    /* The titles' forced breaks are drawn for the desktop columns; a slide wraps naturally. */
    .capability-card__break {
        display: none;
    }
}

@media (min-width: 834px) and (max-width: 1199.95px) {
    .capability-section {
        margin-inline: calc(var(--grid-sm-margin) * -1);
    }
}

/* Phones tighten the card: 24 of padding, and the description, the row labels and the opened
   copy all step from 16 to 14, each with its token's own line height. */
@media (max-width: 833.95px) {
    .capability-card {
        padding: var(--space-24);
    }

    .capability-card__description,
    .capability-item__trigger,
    .capability-item__description {
        font-size: var(--body-size-md-min);
        line-height: var(--body-line-height-md-min);
    }
}

/* The head and the badge sit inside the panel's inset; the track deliberately does not, so a card
   either side of the centred one can bleed past the panel edge as the frames draw it. */
.capability-section > .section-head,
.capability-section .badge-divider {
    padding-inline: var(--space-20);
}

/* Figma hugs this heading at 456 of the 1152 it sits in, which is what breaks it over two lines.
   The number is the frame's, not a chosen measure -- worth revisiting with real copy. */
.capability-section .section-head {
    --section-head-measure: 456px;
}

/* ---------- Capability section: badge ----------
   The row itself is partials/badge-divider.html. What stays here is the space under it, which
   belongs to the section rather than to the component -- the trust band sets its own, through
   .card-grid .badge-divider, for the same reason. */
.capability-section .badge-divider {
    margin-bottom: var(--space-32);
}

/* ---------- Capability section: the track ----------
   Below 1200 this is the scroller. The card is 75% of the panel's width and the padding either
   side is half what is left, so every card -- the first and the last included -- can come to rest
   in the centre. That matters because the scroller opens on the featured card, which can be any of
   them: a card that could only ever align to an edge could never open centred.

   The arithmetic is why the card is flex-basis 100% rather than 75%: a percentage basis resolves
   against the flex container's content box, which the 12.5% padding has already reduced to exactly
   the 75% wanted. */
.capability-section__track {
    display: flex;
    gap: var(--space-16);
    padding-inline: 12.5%;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    /* The peek is the point; a scrollbar drawn over it is not. */
    scrollbar-width: none;
}

.capability-section__track::-webkit-scrollbar {
    display: none;
}

.capability-section__item {
    flex: 0 0 100%;
    scroll-snap-align: center;
}

/* Covers the 48px drop the outer cards take at desktop, so a card does not jump when the state
   moves. */
.capability-section__item {
    transition: padding-top 200ms ease;
}

@media (prefers-reduced-motion: reduce) {
    .capability-section__item {
        transition: none;
    }
}

/* ---------- Capability section: desktop ----------
   Columns rather than a scroller, and the outer cards drop 48px. `grid-auto-columns` rather than a
   fixed three, so a section with two or four cards still divides its row evenly. */
@media (min-width: 1200px) {
    .capability-section {
        padding-block: var(--space-80);
    }

    .capability-section > .section-head,
    .capability-section .badge-divider {
        padding-inline: var(--space-80);
    }

    .capability-section__track {
        display: grid;
        grid-auto-flow: column;
        grid-auto-columns: 1fr;
        gap: var(--space-32);
        align-items: start;
        padding-inline: var(--space-80);
        overflow-x: visible;
        scroll-snap-type: none;
    }

    .capability-section__item:not(.is-featured) {
        padding-top: var(--space-48);
    }

    /* The featured card's heading is one step up, as the desktop frame draws the middle card.
       Desktop only: below 1200 the state follows whichever card is centred, and a title growing
       and shrinking as the row is swiped read as a glitch, so every slide keeps one size there. */
    .capability-section__item.is-featured .capability-card__title {
        font-size: var(--type-heading-sm-size);
        line-height: var(--type-heading-sm-line);
        letter-spacing: var(--type-heading-sm-track);
    }
}

/* ---------- Security & compliance ----------
   Figma: Security & Compliance 28571:129557, drawn at 390 / 834 / 1200 / 1600.

   A violet panel holding the claim on one side and the compliance badges on the other. One layout
   breakpoint, 1200 -- the site's -- with the two sides side by side above it and stacked below.

   The panel is the component, and like the capability section's it runs to both edges of the
   viewport below 1200 and sits in the grid above it. Its own padding then stands in for the grid
   margin, which is why the inline padding below 1200 is the margin itself: the content inside goes
   on lining up with the sections above and below even though the panel around it does not. */
/* The component renders its own head, so it carries the section's vertical padding too, and the
   page gives it a plain .section rather than a rhythm class. The frames give it 24 at xS and 48 at
   every width above, which is its own rhythm and not the 48/96 the sliders take. */
.security {
    padding-block: var(--space-24);
}

/* Below 1200 the panel runs to both edges, pulling out of the margin .section gives it and padding
   back in by the same amount. Same construction as the capability panel, and the same reason: the
   frames draw a full-width rounded band there, not an inset card. */
.security__panel {
    position: relative;
    margin-inline: calc(var(--grid-xs-margin) * -1);
    padding: var(--space-40) var(--grid-xs-margin);
    background: var(--brand-violet-50);
    border-radius: var(--radius-24);
    /* The burst is wider than the panel at the narrow sizes and would otherwise spill past the
       rounded corners. */
    overflow: hidden;
}

/* Violet here rather than the teal the other sections use.

   Figma hugs this heading at 387 of the column it sits in. Without a cap the headline fits on one
   line at 1600 and the panel comes up 44 short; 387 is the frame's own number, as the capability
   section's 456 is, and it gives the two lines the frames draw at every width.

   It does not give the same break. Figma breaks after "Designed for", which no width can reproduce
   here: in Geist as the browser renders it "sensitive research data" is 378 and "Designed for
   sensitive" is 365, so any cap wide enough for the second line is wide enough to pull "sensitive"
   up onto the first. Figma's text node carries a manual break -- its 387 is just the width of the
   longer line. Matching it would mean putting markup in the copy, which the hero already rejected
   for the same reason: it would then break that way at every width, whether or not it should. */
.security .section-head {
    --section-head-caption-color: var(--brand-violet-500);
    --section-head-measure: 387px;
    margin-bottom: var(--space-24);
}

/* The burst is a positioned element, so without this it would paint over everything that is not
   positioned -- which is the whole left side. Below 1200 it reaches a long way up behind the list,
   and its opaque centre was blanking the text it crossed. Lifting the two content blocks over it
   puts it back where it belongs: behind. */
.security__details,
.security__badges {
    position: relative;
    z-index: 1;
}

/* ---------- Security: the list ----------
   One column below 1200, two above it. The frames give the last item the full width when it is the
   odd one out, which :last-child:nth-child(odd) says exactly -- and says it about the count rather
   than about these seven points, so the rule survives an eighth being added. */
.security__list {
    display: grid;
    gap: var(--space-8);
    margin: 0;
    padding: 0;
    list-style: none;
}

.security__item {
    display: flex;
    align-items: flex-start;
    gap: var(--space-4);
    font-size: var(--type-body-md-size);
    font-weight: var(--body-weight-bold);
    line-height: var(--type-body-md-line);
    color: var(--black-80);
}

/* A 12px ring in a 20px box, which is how the frames draw it. The box is the height of one line and
   the ring centres in it, so an item that wraps keeps its marker against the first line instead of
   drifting to the middle of two. */
.security__marker {
    display: flex;
    flex: 0 0 20px;
    align-items: center;
    justify-content: center;
    height: 20px;
}

.security__marker::before {
    content: "";
    width: 12px;
    height: 12px;
    border: 3px solid var(--brand-violet-500);
    border-radius: var(--radius-full);
}

/* ---------- Security: the badges ----------
   The row is never laid out by hand: the badges are given a height and wrap when they run out of
   room, which is what puts three on one line and GDPR under them at 1200 and keeps all four on one
   line at 1600. Each badge's width follows from its own ratio, as with the logo strip. */
.security__visual {
    display: flex;
    position: relative;
    align-items: center;
    justify-content: center;
    padding-block: var(--space-48);
}

/* Dots under a white glow, both centred on this box at every width -- the frames move the box, not
   the burst inside it. Backgrounds paint first-listed on top, so the glow is the first layer. The
   glow is a gradient rather than part of the image: it is a plain radial fade, and keeping it out
   of the PNG is what let the dots be exported on their own and keyed to transparent, so they sit on
   the panel colour instead of carrying a copy of it. */
.security__art {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 380px;
    height: 380px;
    background:
        radial-gradient(circle closest-side, var(--white) 0 32%, transparent 89%),
        url('../images/security-dots.png') center / contain no-repeat;
    transform: translate(-50%, -50%);
    pointer-events: none;
}

.security__badges {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: var(--space-16);
    margin: 0;
    padding: 0;
    list-style: none;
}

.security__badge img {
    display: block;
    width: auto;
    height: 48px;
}

/* ---------- Security: from Sm ---------- */
@media (min-width: 834px) {
    .security {
        padding-block: var(--space-48);
    }

    .security__panel {
        margin-inline: calc(var(--grid-sm-margin) * -1);
        padding-inline: var(--grid-sm-margin);
    }

    .security__badge img {
        height: 80px;
    }
}

/* ---------- Security: from Md ----------
   The panel joins the grid, the two sides go side by side, and the list doubles up. The columns are
   54/46, which is the split the 1600 frame draws; at 1200 it puts the badge row just under the
   width of four badges, which is what wraps the fourth onto its own line there and not at 1600. */
@media (min-width: 1200px) {
    .security__panel {
        display: grid;
        grid-template-columns: 54fr 46fr;
        margin-inline: 0;
        padding: var(--space-80) var(--space-40);
    }

    .security .section-head {
        margin-bottom: var(--space-48);
    }

    .security__list {
        grid-template-columns: 1fr 1fr;
        gap: var(--space-16);
    }

    .security__item:last-child:nth-child(odd) {
        grid-column: 1 / -1;
    }

    .security__visual {
        padding-block: 0;
    }

    .security__badges {
        gap: var(--space-8);
    }
}

/* The xL frame opens the panel's inset out to 80, as the footer's xL frame rearranges its columns.
   Everything else about the panel is already settled by 1200. */
@media (min-width: 1600px) {
    .security__panel {
        padding-inline: var(--space-80);
    }
}

/* ---------- Page offset ----------
   Every inner page frame floats the header over the page and starts its content 176 down at 1600
   and 128 at 390 -- the 834 homepage frame's 128 too, so that holds below 1200. The header here is
   in flow and 82 tall, so main carries the rest; the first section's own padding then follows, as
   it does in the frames. Pages that place their first section against the page top opt out in
   base.html. */
.site-main--offset {
    padding-top: 46px;
}

@media (min-width: 1200px) {
    .site-main--offset {
        padding-top: 94px;
    }
}

/* ---------- Page wash ----------
   Figma: Visual-Top, the same layer on every page (the homepage Hero 28571:129326, Request a demo
   28955:276325).

   The soft corner-lit gradient the redesigned pages run behind the top of the page: four glows
   hanging off the top edge -- coral at the left, violet, blue, teal at the right -- with white
   between them, the footer's washes upside down. Figma states each glow relative to the layer's
   box, so the radii are percentages of the wash, not pixels: a shorter wash is a flatter one, and
   no glow runs past its bottom edge. The 390 frames draw wider, shallower glows than the rest. Over
   them, the frames' LightShadow-Top fades the last 480px to white. Colours are the footer's
   nearest tokens.

   It is a page-level backdrop rather than part of any one section because the frames run it up
   behind the header, which floats on it as a white pill instead of sitting on white. Absolute with
   no positioned ancestor anchors it to the page itself, so it reaches the top without anything here
   needing to know the header's height -- which differs by 4px between screens and would be a second
   place to keep in step.

   How far down it reaches is the page's own: 900 on the homepage, 360 on the 404. */
.page-wash {
    /* The house default, which is what the frames draw nearly everywhere: 236 at 390 and 360 from
       1200. A page that wants another height sets --page-wash-height on its own class, and page
       stylesheets load after this file, so theirs wins at equal specificity -- the homepage's 900
       still does. The default exists so a page can use the wash without a stylesheet of its own,
       which is how the thirteen feature pages get one. */
    --page-wash-height: 236px;
    /* Each glow's radii, width then height, as fractions of the wash. */
    --_wash-coral: 20.16% 43.53%;
    --_wash-violet: 15.79% 34.09%;
    --_wash-blue: 16% 28.44%;
    --_wash-teal: 30.71% 54.59%;

    position: absolute;
    inset: 0 0 auto;
    z-index: -1;
    height: var(--page-wash-height);
    background:
        linear-gradient(transparent max(0px, calc(100% - 480px)), var(--white)),
        radial-gradient(var(--_wash-coral) at 0% 0%, color-mix(in srgb, var(--brand-coral-500) 24%, transparent), transparent),
        radial-gradient(var(--_wash-blue) at 73.66% 0%, color-mix(in srgb, var(--brand-blue-500) 24%, transparent), transparent),
        radial-gradient(var(--_wash-teal) at 91.91% 0%, color-mix(in srgb, var(--brand-teal-400) 24%, transparent), transparent),
        radial-gradient(var(--_wash-violet) at 27.47% 0%, color-mix(in srgb, var(--brand-violet-400) 20%, transparent), transparent);
    pointer-events: none;
}

@media (max-width: 833.95px) {
    .page-wash {
        --_wash-coral: 48.64% 26.45%;
        --_wash-violet: 41.18% 22.4%;
        --_wash-blue: 34.85% 18.95%;
        --_wash-teal: 56.33% 30.64%;
    }
}

@media (min-width: 1200px) {
    .page-wash {
        --page-wash-height: 360px;
    }
}

/* ---------- Logo strip ----------
   Figma: Carousel-Logos 28571:129344, drawn at 390 / 834 / 1200 / 1600.

   Caption, a row of five stats, and the partner logos moving past underneath. The section carries
   its own 24px of padding at every width -- the frames give it the same at all four -- so the page
   does not wrap it in a rhythm class the way it does the sliders.

   The head sits in a .section and the marquee deliberately does not: the logos run edge to edge
   and are faded out at both ends, which is what makes them read as passing through rather than
   stopping. */
.logo-strip {
    padding-block: var(--space-24);
    overflow: hidden;
}

.logo-strip__head {
    margin-bottom: var(--space-40);
}

.logo-strip__caption {
    margin: 0 0 var(--space-12);
    font-size: var(--type-body-sm-size);
    font-weight: var(--body-weight-bold);
    line-height: var(--type-body-sm-line);
    color: var(--black-80);
    text-align: center;
}

/* Figma draws five 140px columns butted together, 700px centred, with a hairline between them.
   Equal flex children capped at that width land on the same result without the track being a
   number: above 834 each column reaches its cap and the row is 700 wide; below it they share out
   whatever the margins leave, which at 390 is the 63.6 the frame draws. */
.logo-strip__stats {
    display: flex;
    justify-content: center;
    gap: var(--space-8);
    margin: 0;
}

.logo-strip__stat {
    flex: 1 1 0;
    max-width: 140px;
    text-align: center;
}

.logo-strip__value {
    font-family: var(--font-heading);
    font-size: var(--type-heading-xs-size);
    font-weight: var(--heading-weight-xs);
    line-height: var(--type-heading-xs-line);
    letter-spacing: var(--type-heading-xs-track);
    color: var(--brand-teal-500);
}

.logo-strip__label {
    margin: 0;
    font-size: var(--type-body-xs-size);
    line-height: var(--type-body-xs-line);
    color: var(--black-80);
}

/* The design substitutes a shorter label below 834, where 63px of column cannot hold the full one
   without wrapping and pushing the row taller than the 32px it is drawn at. */
.logo-strip__label-full {
    display: none;
}

@media (min-width: 834px) {
    .logo-strip__stats {
        gap: 0;
    }

    /* The hairline is the boundary between two columns, so it belongs to one of them: a left
       border on every column but the first draws four rules for five columns. */
    .logo-strip__stat + .logo-strip__stat {
        border-left: 1px solid var(--black-12);
    }

    .logo-strip__label-full {
        display: inline;
    }

    .logo-strip__label-short {
        display: none;
    }
}

/* ---------- Logo strip: the marquee ----------
   The logos are faded out at both ends rather than cut, which the frames draw as a white block
   over each end. A mask is the same result without depending on what is behind the strip, and the
   page's wash does reach this far. */
.logo-strip__marquee {
    position: relative;
    mask-image: linear-gradient(to right, transparent, var(--black) 64px,
        var(--black) calc(100% - 64px), transparent);
}

.logo-strip__track {
    display: flex;
    width: max-content;
    animation: logo-strip-scroll 60s linear infinite;
}

/* A third of the track is one full copy of the list, so this is the point at which the second copy
   has arrived exactly where the first began and the loop can restart unseen. It is tied to the
   three copies the template renders: change one and the other has to follow. */
@keyframes logo-strip-scroll {
    from {
        transform: translateX(0);
    }

    to {
        transform: translateX(calc(-100% / 3));
    }
}

.logo-strip__logos {
    display: flex;
    align-items: center;
    gap: var(--space-32);
    /* Each copy carries the gap that separates it from the next, so the seam matches every other
       join rather than closing up as the loop comes round. */
    margin: 0 var(--space-32) 0 0;
    padding: 0;
    list-style: none;
}

.logo-strip__item {
    display: flex;
    align-items: center;
}

/* The design sizes each logo by height rather than to a common box -- 32, 40 or 48 at 1200 and
   above, chosen per logo so that a wordmark and a crest read as the same weight -- and scales every
   one of them by three quarters below 1200. Setting the height and letting the width follow puts
   each logo back at the width the frames draw, to the pixel; capping a shared box did not, because
   there is no shared box to cap. The height itself is the logo's datum, so it comes from the
   template. */
.logo-strip__logo {
    width: auto;
    height: calc(var(--logo-strip-logo-height) * 0.75);
    /* The frames fade the logos back so they read as a quiet band rather than competing with the
       stats above them. Measured rather than taken from a panel: fitting the rendered logos against
       the source files puts it at 0.64, both in context and on a logo rendered on its own, which is
       the same 64 the black scale steps at. The sources are already greyscale, so this is the whole
       of the treatment. */
    opacity: 0.64;
}

@media (min-width: 1200px) {
    .logo-strip__logos {
        gap: var(--space-64);
        margin-right: var(--space-64);
    }

    .logo-strip__logo {
        height: var(--logo-strip-logo-height);
    }

    .logo-strip__marquee {
        mask-image: linear-gradient(to right, transparent, var(--black) 96px,
            var(--black) calc(100% - 96px), transparent);
    }
}

/* Stopped, the track is wider than the viewport and everything past the first screen would be out
   of reach, so it becomes a scroller instead -- the logos stay reachable, and nothing moves that
   the reader did not move. */
@media (prefers-reduced-motion: reduce) {
    .logo-strip__track {
        animation: none;
    }

    .logo-strip__marquee {
        overflow-x: auto;
        scrollbar-width: none;
    }

    .logo-strip__marquee::-webkit-scrollbar {
        display: none;
    }
}

/* ---------- Rendered markdown ---------- */
.prose h1 {
    margin: 0.67em 0 calc(var(--spacing) * 2);
}

.prose h2 {
    margin: calc(var(--spacing) * 10) 0 calc(var(--spacing) * 2);
}

.prose h3 {
    margin: calc(var(--spacing) * 5) 0 var(--spacing);
}

.prose h4,
.prose h5,
.prose h6,
.prose p {
    margin: 0 0 var(--spacing);
}

.prose p,
.prose li {
    font-size: 1rem;
    line-height: 1.75;
    letter-spacing: 0.02em;
}

/* The old reset zeroed the browser's list padding, so the markers below sit in
   the left margin instead. */
.prose ul,
.prose ol {
    margin: 0 0 calc(var(--spacing) * 2) calc(var(--spacing) * 2);
    padding: 0;
    list-style: none;
}

.prose li {
    margin: 0;
    padding-left: calc(var(--spacing) * 3);
    position: relative;
}

.prose ul > li::before {
    content: '';
    position: absolute;
    top: 6px;
    left: -5px;
    width: 16px;
    height: 16px;
    border-radius: 50%;
    background: var(--color-primary);
}

.prose ol {
    counter-reset: li;
}

.prose ol > li {
    counter-increment: li;
}

.prose ol > li::before {
    content: counter(li);
    position: absolute;
    top: 0;
    left: -9px;
    color: var(--color-primary);
    font-size: 1.2rem;
    font-weight: 700;
    line-height: 1.4;
}

/* ---------- Loading / error block (components/loading) ---------- */
.loading {
    text-align: center;
    padding: var(--spacing);
}

.loading__bar {
    position: relative;
    display: inline-block;
    overflow: hidden;
    width: 80px;
    height: 4px;
    margin: calc(var(--spacing) * 3);
    border-radius: 5px;
    background: var(--color-primary);
}

.loading__bar span {
    position: absolute;
    top: 0;
    bottom: 0;
    transition: transform 0.2s linear;
}

.loading__bar span:first-child {
    background: var(--color-logo-yellow);
    animation: loading-bar-1 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;
}

.loading__bar span:last-child {
    background: var(--color-logo-green);
    animation: loading-bar-2 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) 1.15s infinite;
}

@keyframes loading-bar-1 {
    0% {
        left: -35%;
        right: 100%;
    }

    60%,
    100% {
        left: 100%;
        right: -90%;
    }
}

@keyframes loading-bar-2 {
    0% {
        left: -200%;
        right: 100%;
    }

    60%,
    100% {
        left: 107%;
        right: -8%;
    }
}

@media (prefers-reduced-motion: reduce) {
    .loading__bar span {
        animation: none;
    }
}

.loading__error {
    color: var(--color-error);
    margin: 0;
}

.loading__retry {
    margin: calc(var(--spacing) * 3);
    background: #fff;
    color: var(--color-primary-dark);
}

/* ---------- Announcement banner ---------- */
.announcement-banner {
    color: #fff;
    text-align: center;
    padding: calc(var(--spacing) * 1.5);
}

.announcement-banner p {
    margin: 0;
}

.announcement-banner a {
    color: #fff;
    text-decoration: underline;
}

/* ---------- Header ----------
   Figma: Header/Nav section 26959:26187.

   The bar is a floating white pill over the page, and each mega panel is a separate rounded card
   below it. Both carry shadows that are effect styles in Figma rather than variables, so they are
   named here as component tokens: Dropdown-Shadow for the panels, and a second, bluer one for the
   bar.

   Below --screen-md the same markup becomes a sheet: the panels drop out of absolute positioning
   and stack as accordions. The frames settle where that happens: the header is the full bar at
   1200 and the hamburger already at 834, so the switch is Md. The bar is not attempted below it --
   there is no room for four nav items, two CTAs and a 1088px panel, and the mobile accordion
   already handles that content. */
.site-header {
    position: sticky;
    top: 0;
    z-index: 100;
    padding: var(--space-16) var(--space-16) 0;
}

.site-header__inner {
    position: relative;
    max-width: 1088px;
    margin: 0 auto;
}

.site-header__bar {
    display: flex;
    align-items: center;
    gap: var(--space-32);
    min-height: 66px;
    padding: 0 var(--space-24);
    background: var(--white);
    /* Figma puts a 1px black-12 stroke on the bar in both the desktop and the mobile variant. With
       border-box the 66px stays the outer height, so the logo and nav items still centre in the
       64px that is left -- which is how Figma gets its 13px and 15px insets. */
    border: 1px solid var(--black-12);
    border-radius: var(--radius-full);
    box-shadow: var(--shadow-header-bar);
}

.site-header__logo {
    margin: 0;
    flex-shrink: 0;
    line-height: 0;
}

.site-header__menu-toggle {
    display: none;
    margin-left: auto;
    padding: var(--space-8);
    background: none;
    border: 0;
    color: var(--black-80);
    cursor: pointer;
}

/* The hamburger and the close mark sit beside the accordion carets in the sheet, so they take the
   caret's size as well as its treatment: the same solid Font Awesome glyphs at --icon-xs in
   --black-80, which is also the 16px the mobile header frame draws. */
.site-header__menu-toggle-icon {
    width: var(--icon-xs);
    height: var(--icon-xs);
    display: block;
}

.site-header__menu-toggle:focus-visible,
.site-nav__link:focus-visible,
.nav-link:focus-visible,
.nav-featured:focus-visible {
    outline: 2px solid var(--brand-blue-500);
    outline-offset: 2px;
    border-radius: var(--radius-8);
}

/* ---------- Nav bar ---------- */
/* The nav, its list and each item stretch to the full height of the bar rather than shrinking to
   the 36px trigger. That is what makes the hover region continuous: a centred item would leave a
   15px dead strip of bar between its bottom edge and the panel below, and dragging the pointer
   across that strip fires mouseleave and shuts the panel before it can be reached. */
.site-nav {
    flex: 1;
    align-self: stretch;
    display: flex;
    align-items: stretch;
    justify-content: space-between;
    gap: var(--space-24);
}

.site-nav__list {
    display: flex;
    align-items: stretch;
    gap: var(--space-8);
    margin: 0;
    padding: 0;
    list-style: none;
}

.site-nav__item {
    position: static;
    display: flex;
    align-items: center;
}

.site-nav__link {
    display: inline-flex;
    align-items: center;
    gap: var(--space-4);
    padding: var(--space-8);
    font-family: inherit;
    font-size: var(--body-size-sm-max);
    font-weight: var(--body-weight-bold);
    line-height: var(--body-line-height-sm-max);
    letter-spacing: var(--body-letter-space);
    color: var(--black-80);
    background: none;
    border: 0;
    border-radius: var(--radius-8);
    text-decoration: none;
    white-space: nowrap;
    cursor: pointer;
    transition:
        color 150ms ease,
        background-color 150ms ease;
}

.site-nav__link:hover,
.site-nav__link[aria-expanded='true'] {
    color: var(--brand-blue-500);
    text-decoration: none;
}

.site-nav__caret {
    width: 12px;
    height: 12px;
    flex-shrink: 0;
    transition: transform 150ms ease;
}

.site-nav__link[aria-expanded='true'] .site-nav__caret {
    transform: rotate(180deg);
}

.site-nav__actions {
    display: flex;
    align-items: center;
    gap: var(--space-8);
    flex-shrink: 0;
    align-self: center;
}

/* ---------- Mega panel ---------- */
/* padding-top is the hover bridge: it covers the gap between bar and card, so moving the pointer
   down into the panel never leaves the hover region and the menu does not flicker shut. */
.nav-panel {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    padding-top: var(--space-12);
}

.nav-panel__card {
    padding: var(--space-32);
    background: var(--white);
    border: 1px solid var(--black-12);
    border-radius: var(--radius-16);
    box-shadow: var(--shadow-dropdown);
}

.nav-panel__columns {
    display: grid;
    gap: var(--space-32);
}

.nav-panel__card--features .nav-panel__columns {
    grid-template-columns: repeat(3, 1fr);
}

/* Research Areas is a single uncaptioned group, so its two columns are the items flowing across
   the list -- not one column per group, the way Features and Resources work. */
.nav-panel__card--areas .nav-group__list {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: var(--space-8) var(--space-32);
}

/* The featured card is 3 of 12 columns; the link groups share what is left. */
.nav-panel__card--resources {
    display: grid;
    grid-template-columns: 304px 1fr;
    gap: var(--space-32);
}

.nav-panel__card--resources .nav-panel__columns {
    grid-template-columns: repeat(3, 1fr);
    align-content: start;
}

.nav-group__caption {
    margin: 0 0 var(--space-12);
    padding-left: var(--space-8);
    font-size: var(--caption-size-sm-max);
    line-height: var(--caption-line-height-sm);
    letter-spacing: var(--caption-letter-space-sm-max);
    font-weight: 400;
    text-transform: uppercase;
    color: var(--brand-teal-500);
}

.nav-group__list {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    margin: 0;
    padding: 0;
    list-style: none;
}

/* ---------- Mega panel links ---------- */
.nav-link {
    display: flex;
    align-items: center;
    gap: var(--space-12);
    padding: var(--space-8);
    border-radius: var(--radius-8);
    text-decoration: none;
    transition: background-color 150ms ease;
}

.nav-link:hover {
    background: var(--brand-blue-50);
    text-decoration: none;
}

/* A row carrying a description is two lines, so the tile aligns to the first one. */
.nav-link:has(.nav-link__description) {
    align-items: flex-start;
}

.nav-link__icon {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: var(--icon-lg);
    height: var(--icon-lg);
    background: var(--brand-teal-50);
    border-radius: var(--radius-8);
    color: var(--brand-teal-500);
}

.nav-link__icon svg {
    width: var(--icon-xs);
    height: var(--icon-xs);
}

.nav-link__text {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    min-width: 0;
}

.nav-link__title {
    font-size: var(--body-size-md-max);
    font-weight: var(--body-weight-bold);
    line-height: var(--body-line-height-md-max);
    color: var(--black-80);
    transition: color 150ms ease;
}

.nav-link:hover .nav-link__title {
    color: var(--brand-blue-500);
}

.nav-link__description {
    font-size: var(--body-size-xs-max);
    font-weight: var(--body-weight-regular);
    line-height: var(--body-line-height-xs-max);
    color: var(--black-64);
}

/* ---------- Featured card ---------- */
/* Figma: _Nav-Columns-Publication 27562:115909, States=Default / Hover. The card is 24 in from
   every edge and its parts are spaced 16 / 4 / 12 down the column; Hover differs only in the blue.
   The spacing used to live as per-element padding, which is why it read tighter than the frame. */
.nav-featured {
    display: flex;
    flex-direction: column;
    padding: var(--space-24);
    background: var(--brand-blue-500);
    border-radius: var(--radius-12);
    text-decoration: none;
    transition: background-color 150ms ease;
}

.nav-featured:hover {
    background: var(--brand-blue-600);
    text-decoration: none;
}

.nav-featured__image {
    display: block;
    margin-bottom: var(--space-16);
    border-radius: var(--radius-8);
    overflow: hidden;
    line-height: 0;
}

.nav-featured__image img {
    width: 100%;
    height: auto;
    aspect-ratio: 260 / 195;
    object-fit: cover;
}

.nav-featured__eyebrow {
    margin-bottom: var(--space-4);
    font-size: var(--caption-size-sm-max);
    font-weight: var(--caption-weight);
    line-height: var(--caption-line-height-sm);
    letter-spacing: var(--caption-letter-space-sm-max);
    text-transform: uppercase;
    color: var(--brand-aqua-300);
}

.nav-featured__title {
    display: flex;
    align-items: center;
    gap: var(--space-8);
    margin-bottom: var(--space-12);
    font-size: var(--body-size-lg-max);
    font-weight: var(--body-weight-bold);
    line-height: var(--body-line-height-lg-max);
    color: var(--white);
}

.nav-featured__arrow {
    width: var(--icon-xs);
    height: var(--icon-xs);
    flex-shrink: 0;
}

.nav-featured__description {
    font-size: var(--body-size-sm-max);
    font-weight: var(--body-weight-regular);
    line-height: var(--body-line-height-sm-max);
    color: var(--white);
}

/* Below Sm the card is a full-width block in a ~300px column rather than a 3-of-12 track, so 24 in
   from every edge leaves too little for the copy. 16 is the next step down the ramp and keeps the
   image and text off the card's rounded corners. The rest of the component is unchanged. */
/* The sheet keeps the mobile card through the tablet range too, where it sits beside the links
   rather than above them (see the tablet block below). */
@media (max-width: 1199.95px) {
    .nav-featured {
        padding: var(--space-16);
    }
}

/* ---------- Mobile / tablet sheet ----------
   Figma: "Mobile Nav -> Features -> FullView" 28295:71439.

   The sheet is not an overlay: it is the header card itself growing downwards. The logo and the
   toggle stay in place as the first row, the nav wraps onto a second row inside the same white
   card, and the pill squares off into a 16px rounded card that scrolls internally. So the mobile
   markup is the desktop markup with the bar allowed to wrap -- no second copy, no fixed layer. */
@media (max-width: 1199.95px) {
    .site-header {
        padding: var(--space-16) var(--space-20) 0;
    }

    .site-header__bar {
        gap: 0;
    }

    .site-header__menu-toggle {
        display: block;
    }

    .site-header__menu-toggle-icon {
        width: var(--icon-md);
        height: var(--icon-md);
    }

    .site-header__menu-toggle[aria-expanded='false'] .site-header__menu-toggle-icon--open,
    .site-header__menu-toggle[aria-expanded='true'] .site-header__menu-toggle-icon--closed {
        display: none;
    }

    /* Open, the card fills the viewport and becomes a two-row grid: the logo and X stay pinned on
       the first row while the nav takes the rest of the height and scrolls inside it. A wrapped
       flex row could not give the second line the leftover height. */
    .site-header.is-nav-open .site-header__bar {
        display: grid;
        grid-template-columns: 1fr auto;
        grid-template-rows: auto 1fr;
        align-items: center;
        padding: var(--space-16);
        border-radius: var(--radius-16);
        height: calc(100vh - var(--space-32));
        height: calc(100dvh - var(--space-32));
        overflow: hidden;
    }

    /* Opened by class rather than [hidden] so the sheet stays shut when the script does not run,
       the way the nav it replaces did. */
    .site-nav {
        display: none;
    }

    .site-nav.is-open {
        grid-column: 1 / -1;
        /* The grid centres its first row; the nav must override that and fill its own row, or the
           1fr track collapses to content height and nothing scrolls. */
        align-self: stretch;
        display: flex;
        flex-direction: column;
        align-items: stretch;
        justify-content: flex-start;
        gap: 0;
        margin-top: var(--space-16);
        /* min-height:0 is what lets a grid child actually scroll rather than stretch its parent. */
        min-height: 0;
        overflow-y: auto;
        overscroll-behavior: contain;
        /* Scrolls, but draws no scrollbar. Where the platform's scrollbar is classic rather than
           overlay (Windows, or macOS set to always show them) it takes ~15px out of the sheet, so
           the same menu lays out narrower on some machines than others. The fade above the CTAs
           is the scroll cue instead. Two rules because the engines share none: scrollbar-width
           for Firefox and current Chrome/Edge, ::-webkit-scrollbar for Safari. */
        scrollbar-width: none;
    }

    .site-nav.is-open::-webkit-scrollbar {
        display: none;
    }

    .site-nav__list {
        flex-direction: column;
        align-items: stretch;
        gap: 0;
    }

    .site-nav__item {
        display: block;
        border-bottom: 1px solid var(--black-12);
    }

    .site-nav__item:last-child {
        border-bottom: 0;
    }

    .site-nav__link {
        display: flex;
        justify-content: space-between;
        align-items: center;
        width: 100%;
        padding: var(--space-12) var(--space-4);
        font-size: var(--body-size-lg-max);
        line-height: var(--body-line-height-lg-max);
        border-radius: 0;
    }

    .site-nav__caret {
        width: var(--icon-xs);
        height: var(--icon-xs);
    }

    /* In the sheet the panel is in flow: no card, no shadow, no absolute positioning. The 8px
       separates the open item from its first link or card. */
    .nav-panel {
        position: static;
        padding-top: var(--space-8);
    }

    .nav-panel__card,
    .nav-panel__card--resources {
        display: block;
        padding: 0 0 var(--space-16);
        background: none;
        border: 0;
        border-radius: 0;
        box-shadow: none;
    }

    .nav-panel__columns,
    .nav-panel__card--features .nav-panel__columns,
    .nav-panel__card--resources .nav-panel__columns {
        display: flex;
        flex-direction: column;
        gap: var(--space-24);
    }

    .nav-panel__card--areas .nav-group__list {
        grid-template-columns: 1fr;
    }

    /* The mobile design drops the featured card; the same links are in the columns beside it. */
    /* The featured card stacks above the columns and fills the sheet's width. It needs no sizing of
       its own -- the image is width:100% on a fixed aspect ratio, so the card is already fluid --
       only the same rhythm the columns keep between themselves. */
    .nav-featured {
        margin-bottom: var(--space-24);
    }

    /* The three CTAs are pinned to the foot of the sheet and the nav scrolls behind them, which is
       how Figma draws them: full-bleed across the sheet's content width, Ghost / Outline / Primary.

       Sticky rather than a fourth grid row, because the actions live inside the nav that scrolls --
       pinning them any other way would mean rendering them twice or moving them in the markup, and
       the desktop bar needs them where they are. margin-top:auto still drops the block when the nav
       is short; sticky takes over once it overflows.

       Sizing stays on the component: these are .button--sm, so 40px with the scale's own padding.
       Figma draws them 36 tall, which is a squashed instance -- its own py/px values are the Small
       token pair. */
    .site-nav__actions {
        position: sticky;
        bottom: 0;
        z-index: 1;
        flex-direction: column;
        align-items: stretch;
        gap: var(--space-8);
        margin-top: auto;
        padding: var(--space-16) var(--space-8) var(--space-8);
        background: var(--white);
        align-self: stretch;
        flex-shrink: 0;
    }

    /* The scroll cue: content fades into the pinned block rather than sliding under a hard edge.
       A gradient rather than backdrop-filter -- it needs no compositing layer, degrades to nothing
       where it is unsupported, and reads the same over text as over whitespace. */
    .site-nav__actions::before {
        content: '';
        position: absolute;
        right: 0;
        bottom: 100%;
        left: 0;
        height: var(--space-32);
        background: linear-gradient(to top, var(--white), transparent);
        pointer-events: none;
    }
}

/* Phone sheet: the top-level items step down from 18 to 16. After the sheet block so it wins. */
@media (max-width: 833.95px) {
    .site-nav__link {
        font-size: var(--body-size-md-max);
    }
}

/* ---------- Tablet sheet (834-1199) ----------
   Stretched to the sheet, the Resources card grew to ~930x800 and pushed every link below the
   fold. Here it keeps its mobile width and the groups sit in a grid beside it: two columns at 834,
   three once 193px each fits -- the widest label, "Community & Support", needs 192, and two 193s
   plus the gap still fit the 412 left at 834. Only the Resources panel changes; Features and
   Research Areas keep the stacked sheet layout. The CTAs go side by side, since one row of three
   fits at this width, each at its own width and the row centred. */
@media (min-width: 834px) and (max-width: 1199.95px) {
    .nav-panel__card--resources {
        display: grid;
        grid-template-columns: 316px 1fr;
        column-gap: var(--space-32);
        align-items: start;
    }

    .nav-panel__card--resources .nav-featured {
        margin-bottom: 0;
    }

    .nav-panel__card--resources .nav-panel__columns {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(193px, 1fr));
        gap: var(--space-24);
    }

    .site-nav__actions {
        flex-direction: row;
        justify-content: center;
    }

    .site-nav__actions > .button {
        width: fit-content;
    }
}

/* ---------- Desktop bar layout ----------
   Figma: _Header-Nav 27547:65317, States=Default / Sticky, Device=Desktop.

   Default lays the bar out as three equal tracks -- logo, nav, actions -- so the nav items centre
   on the bar itself (Figma puts their midpoint at 544, exactly half of the 1088 container) while
   the actions stay hard right. 1fr/auto/1fr reproduces that without pinning the nav to a fixed
   width the labels would outgrow.

   `display: contents` on the nav is what lets the list and the actions become tracks of that grid
   while still sharing one <nav> element with the mobile sheet, which needs them in a real box to
   scroll them together. */
@media (min-width: 1200px) {
    .site-header__bar {
        display: grid;
        grid-template-columns: 1fr auto 1fr;
        column-gap: var(--space-24);
        align-items: center;
        padding: 0 var(--space-16);
    }

    .site-nav {
        display: contents;
    }

    /* The logo is pinned to its own width so the collapsing first track cannot squeeze it. Without
       this the track's auto floor is the only thing holding it, and `img { max-width: 100% }` in
       the reset would clamp the logo against its fixed 40px height and distort it. Pinned, the
       track stops at the logo and the logo itself neither moves nor resizes: it sits at the start
       of that track in both states, so only the nav travels. */
    .site-header__logo {
        min-width: max-content;
    }

    .site-nav__list {
        justify-self: center;
        /* Stretching to the bar height is what keeps the hover region continuous down to the
           panel; see the nav bar block above. */
        align-self: stretch;
        gap: 0;
    }

    .site-nav__actions {
        justify-self: end;
    }

    /* The bar's new bottom border sits between the nav list, which stretches only to the content
       box, and the panel below it -- a 1px strip outside both, and so outside the hover region.
       Starting the panel a pixel higher and paying it back in padding closes that strip without
       moving the card. */
    .nav-panel {
        top: calc(100% - 1px);
        padding-top: calc(var(--space-12) + 1px);
    }

    /* Default carries two CTAs. The third is present but collapsed, so it can grow in when the bar
       goes sticky; as it widens it pushes the other two left, which is where Figma puts them.
       Collapsed, it is still a flex item, so the actions' gap would sit after the last visible
       button as empty space; the negative margin takes that gap back. */
    .site-nav__actions-cta {
        display: inline-flex;
        max-width: 0;
        margin-left: calc(-1 * var(--space-8));
        padding-left: 0;
        padding-right: 0;
        overflow: hidden;
        opacity: 0;
        transform: translateX(6px);
    }

    /* Sticky: the nav closes up against the logo to make room for the third CTA, and the buttons
       tighten from 8px to 4px. The nav items keep their 8px padding and no gap in both states.

       Every difference between the two states is expressed as a continuously interpolatable value
       so the change can be animated rather than snapped:

       - The first track goes 1fr -> 0fr, not 1fr -> auto. Both are <flex> values, so the track
         animates; `auto` would not interpolate. 0fr still floors at the logo's min-content width
         (a bare `<flex>` track is really minmax(auto, <flex>)), so the track collapses to exactly
         the logo and the nav lands 24px after it -- Figma has it at 181px, and this computes to
         180px.
       - The list keeps justify-self:center in both states. Once the first track has collapsed the
         auto track is already the nav's own width, so centring and starting are the same place,
         and a discrete justify-self flip is avoided.
       - The third CTA is revealed by max-width and padding rather than display, which cannot
         animate. Collapsing padding as well as width matters: a border-box element will not shrink
         below its own horizontal padding. */
    .site-header.is-sticky .site-header__bar {
        grid-template-columns: 0fr auto 1fr;
    }

    .site-header.is-sticky .site-nav__actions {
        gap: var(--space-4);
    }

    /* Wide enough for the label plus headroom; the used width still settles at the text's own
       width, so this only has to be an upper bound. */
    .site-header.is-sticky .site-nav__actions-cta {
        max-width: 160px;
        margin-left: 0;
        padding-left: var(--_pad-x);
        padding-right: var(--_pad-x);
        opacity: 1;
        transform: none;
    }

    /* ---------- Sticky transition ----------
       Gated on .is-ready, which the script sets one frame after it first reads the scroll
       position. A page restored mid-scroll therefore paints sticky rather than animating into it
       on load. */
    .site-header.is-ready .site-header__bar,
    .site-header.is-ready .site-nav__link,
    .site-header.is-ready .site-nav__actions,
    .site-header.is-ready .site-nav__actions-cta {
        transition-duration: 320ms;
        transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
    }

    .site-header.is-ready .site-header__bar {
        transition-property: grid-template-columns;
    }

    .site-header.is-ready .site-nav__actions {
        transition-property: gap;
    }

    .site-header.is-ready .site-nav__link {
        transition-property: color, background-color;
    }

    .site-header.is-ready .site-nav__actions-cta {
        transition-property: max-width, margin-left, padding-left, padding-right, opacity, transform;
    }
}

/* Reduced motion: the caret spin and hover washes are decorative. */
@media (prefers-reduced-motion: reduce) {
    .site-nav__link,
    .site-nav__caret,
    .nav-link,
    .nav-link__title,
    .nav-featured {
        transition: none;
    }
}

/* ---------- Footer ----------
   Figma: #Footer 28610:85080, drawn at 390 / 834 / 1200 / 1600.

   Light, where the old one was navy. A white base carries four radial washes rising off the bottom
   edge, and the wordmark sits over them: its letters are white, so they read by erasing the wash
   behind them rather than by adding ink of their own.

   One markup at every width. From 1200 the groups are columns; below it footer.js folds them into
   an accordion -- the same width the header swaps its bar for a sheet. */
.site-footer {
    position: relative;
    /* Keeps the wordmark's negative z-index inside the footer: without it the img would sink
       behind the page background instead of behind the footer's own content. */
    isolation: isolate;
    /* The wordmark is drawn bleeding off the right edge. */
    overflow: hidden;
    padding-block: var(--space-96) var(--space-32);
    /* Each wash is an ellipse standing on the bottom edge. The centres and radii are Figma's own
       gradient transforms converted to CSS -- Figma states a large radius and fades out at 26% of
       it, which is the same ellipse as a radius of that 26%.
       Colours are snapped to the nearest brand tokens: coral and blue land on theirs, teal and
       violet are one step off. color-mix is the only way to take an alpha of a token whose value
       is an opaque hex, and this is the first use of it in this file. */
    background:
        radial-gradient(356px 354px at 0% 100%, color-mix(in srgb, var(--brand-coral-500) 24%, transparent), transparent),
        radial-gradient(447px 444px at 27.5% 100%, color-mix(in srgb, var(--brand-violet-400) 20%, transparent), transparent),
        radial-gradient(317px 259px at 73.7% 100%, color-mix(in srgb, var(--brand-blue-500) 24%, transparent), transparent),
        radial-gradient(723px 592px at 91.9% 100%, color-mix(in srgb, var(--brand-teal-400) 24%, transparent), transparent),
        var(--white);
}

/* The export is the band Figma leaves visible, 1600x259, so it sits flush at the bottom rather
   than hanging 18px below it. It stretches with the footer instead of tiling or cropping, which is
   what a watermark should do at widths nobody drew. */
.site-footer__wordmark {
    position: absolute;
    inset-inline: 0;
    bottom: 0;
    z-index: -1;
    width: 100%;
    height: auto;
    pointer-events: none;
}

.site-footer__columns {
    display: flex;
    flex-direction: column;
    gap: var(--space-48);
}

/* ---------- Footer: the about column ---------- */
.site-footer__about {
    display: flex;
    flex-direction: column;
    gap: var(--space-32);
}

.site-footer__brand {
    display: flex;
    flex-direction: column;
    gap: var(--space-16);
}

.site-footer__logo {
    display: inline-flex;
    align-self: flex-start;
}

.site-footer__logo img {
    width: auto;
    height: 40px;
}

.site-footer__blurb {
    margin: 0;
    font-size: var(--body-size-md-max);
    line-height: var(--body-line-height-md-max);
    color: var(--black-80);
}

.site-footer__address {
    margin: 0;
    font-size: var(--body-size-sm-max);
    line-height: var(--body-line-height-sm-max);
    color: var(--black-64);
}

.site-footer__stores {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-12);
}

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

/* The badges are fixed artwork with their own proportions, so height is the one dimension set. */
.site-footer__store img {
    width: auto;
    height: 40px;
}

.site-footer__social {
    display: flex;
    gap: var(--space-8);
    margin: 0;
    padding: 0;
    list-style: none;
}

.site-footer__social-link {
    display: grid;
    place-items: center;
    width: 40px;
    height: 40px;
    background: var(--brand-blue-50);
    border-radius: var(--radius-8);
    color: var(--brand-blue-500);
    transition: background 150ms ease;
}

.site-footer__social-link:hover {
    background: var(--brand-blue-100);
    text-decoration: none;
}

.site-footer__social-icon {
    width: var(--icon-md);
    height: var(--icon-md);
}

/* ---------- Footer: the link groups ---------- */
.site-footer__links {
    display: flex;
    flex-direction: column;
}

/* Stacked, each group is an accordion row and the rule under it is the divider. The first row
   takes a rule above as well, closing the list off against the about column. */
.footer-group {
    display: flex;
    flex-direction: column;
    border-bottom: 1px solid var(--black-12);
}

.site-footer__links > .footer-group:first-child {
    border-top: 1px solid var(--black-12);
}

.footer-group__heading {
    margin: 0;
}

.footer-group__toggle {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-8);
    width: 100%;
    padding: var(--space-16) 0;
    font-family: var(--font-body);
    font-size: var(--body-size-md-max);
    font-weight: var(--body-weight-bold);
    line-height: var(--body-line-height-md-max);
    color: var(--black);
    text-align: left;
    background: none;
    border: 0;
    cursor: pointer;
}

.footer-group__caret {
    flex-shrink: 0;
    width: var(--icon-xs);
    height: var(--icon-xs);
    transition: transform 150ms ease;
}

.footer-group__toggle[aria-expanded='true'] .footer-group__caret {
    transform: rotate(180deg);
}

.footer-group__items {
    display: flex;
    flex-direction: column;
    gap: var(--space-8);
    margin: 0 0 var(--space-16);
    padding: 0;
    list-style: none;
}

/* The script closes a group by setting the hidden attribute, and the display rule above outranks
   the browser's own [hidden] { display: none }. Same guard as .capability-item__panel. */
.footer-group__items[hidden] {
    display: none;
}

.footer-group__link {
    display: inline-flex;
    align-items: center;
    gap: var(--space-8);
    font-size: var(--body-size-md-max);
    line-height: var(--body-line-height-md-max);
    color: var(--black);
    text-decoration: none;
}

/* One line each, as drawn. The column is sized from these, so a label that would wrap is a sign
   the row has run out of width and should wrap a whole column instead. */
@media (min-width: 1200px) {
    .footer-group__link {
        white-space: nowrap;
    }
}

.footer-group__link:hover {
    color: var(--brand-blue-500);
    text-decoration: none;
}

.footer-group__status {
    flex-shrink: 0;
    width: var(--space-8);
    height: var(--space-8);
    background: var(--brand-teal-500);
    border-radius: var(--radius-full);
}

/* ---------- Footer: the bottom row ---------- */
.site-footer__bottom {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-16);
    margin-top: var(--space-96);
}

.site-footer__copyright,
.site-footer__trust {
    display: flex;
    align-items: center;
    gap: var(--space-8);
    margin: 0;
    font-size: var(--body-size-xs-max);
    line-height: var(--body-line-height-xs-max);
    color: var(--black-80);
}

.site-footer__trust-icon {
    flex-shrink: 0;
    width: 15px;
    height: 15px;
}

/* ---------- Footer: columns, from Md ---------- */
@media (min-width: 1200px) {
    .site-footer__columns {
        flex-direction: row;
        align-items: flex-start;
        gap: var(--space-48);
    }

    .site-footer__about {
        flex: 1 0 0;
        min-width: var(--grid-xl-col-3);
        max-width: var(--grid-xl-col-4);
    }

    .site-footer__links {
        flex: 1 0 0;
        flex-direction: row;
        flex-wrap: wrap;
        gap: var(--space-48);
        /* A wrap row of nowrap columns hands its widest line up as a min-content contribution,
           which would stop the about column ever shrinking. */
        min-width: 0;
    }

    /* Basis auto, not 0: the columns are as wide as their longest link and then share what is
       left, which is how the frames are drawn. A zero basis would divide the row evenly instead
       and squeeze Features, whose labels are the longest, into two lines. */
    .footer-group,
    .site-footer__links > .footer-group:first-child {
        flex: 1 1 auto;
        gap: var(--space-16);
        min-width: 170px;
        border: 0;
    }

    /* The heading stops being a control and becomes the caption Figma draws. */
    .footer-group__toggle {
        justify-content: flex-start;
        padding: 0;
        font-size: var(--caption-size-sm-max);
        font-weight: var(--caption-weight);
        line-height: var(--caption-line-height-sm);
        letter-spacing: var(--caption-letter-space-sm-max);
        text-transform: uppercase;
        color: var(--brand-teal-500);
        cursor: default;
    }

    .footer-group__caret {
        display: none;
    }

    .footer-group__items {
        margin-bottom: 0;
    }
}

/* Company and Compare share one column from 1200 up, and are two accordion rows below it, where
   display:contents makes the wrapper disappear so its two groups are rows of .site-footer__links
   like the other three.

   The column used to start at 1600 only, which left the two placed by wherever a five-item wrap
   row happened to break: beside each other at 1200 and 1280, and from 1366 to 1599 Compare wrapped
   alone and grew to the full 848px row, putting its heading under Features instead of under
   Company. Company itself does not move at any of those widths -- measured 1200 to 1599, its left
   edge and top are the same before and after; only Compare does.

   Grow 0, because at 1200 and 1280 four columns do not fit (4x170 + 3x48 = 824 against 672 of
   room), so the pair still takes a line of its own and should stay its own width rather than
   stretch across it. From 1600 it is one of four columns sharing the row and grows with the other
   three, which the block further down restores -- without that the column lands 5px left of where
   it sat before, the one width that was already correct. */
.footer-pair {
    display: contents;
}

@media (min-width: 1200px) {
    .footer-pair {
        display: flex;
        flex: 0 1 auto;
        flex-direction: column;
        gap: var(--space-48);
        min-width: 170px;
    }
}

/* See .footer-pair above: restores the growth the pair had when the column started here. */
@media (min-width: 1600px) {
    .footer-pair {
        flex-grow: 1;
    }
}

@media (prefers-reduced-motion: reduce) {
    .footer-group__caret,
    .site-footer__social-link {
        transition: none;
    }
}

/* The sticky choreography is desktop-only, so its reduced-motion opt-out is scoped the same way
   rather than widened into the shared block above. The state still changes; it just arrives. */
@media (min-width: 1200px) and (prefers-reduced-motion: reduce) {
    .site-header.is-ready .site-header__bar,
    .site-header.is-ready .site-nav__list,
    .site-header.is-ready .site-nav__link,
    .site-header.is-ready .site-nav__actions,
    .site-header.is-ready .site-nav__actions-cta {
        transition: none;
    }
}

/* ---------- Table of contents ----------
   Figma: component set 27323:54805, in place on the Privacy Policy page at 28788:82391.

   Two layouts, one list. Below 1200 it is a bar pinned to the bottom of the viewport that opens a
   sheet; at 1200 and up it is a rail of ticks in the page's own column that expands into a card.
   The breakpoint is the site's layout step, and the same one the mega menu uses.

   Nothing here counts items. js/toc.js publishes --toc-count, and the rail divides whatever height
   it is given between that many ticks, so twelve headings draw the 12px pitch the frames show and
   a hundred simply draw a denser rail. The panel caps against the viewport and scrolls.

   The component does not place itself on desktop: it is `position: sticky`, so the page gives it a
   column and it sticks inside it. --toc-top is how far below the viewport top it settles; a page
   with a taller header overrides it. Below 1200 it pins to the viewport, which needs no help. */
.toc {
    --toc-top: var(--space-96);
    --toc-panel-width: 304px;
    --toc-tick-pitch: 12px;
    --toc-rail-max: 45dvh;

    position: fixed;
    inset-inline: 0;
    bottom: 0;
    z-index: 90;
}

.toc[hidden] {
    display: none;
}

/* The scrim is a child so that the whole component is one stacking context; the panel paints over
   it without either needing a z-index of its own. */
.toc__scrim {
    position: fixed;
    inset: 0;
    background: var(--black-32);
}

.toc__scrim[hidden] {
    display: none;
}

/* ---------- The trigger ---------- */

.toc__trigger {
    display: flex;
    position: relative;
    align-items: center;
    gap: var(--space-12);
    width: calc(100% - var(--grid-xs-margin) * 2);
    margin: 0 var(--grid-xs-margin) var(--space-12);
    padding: var(--space-12) var(--space-16);
    border: 1px solid var(--black-12);
    border-radius: var(--radius-12);
    background: var(--white);
    box-shadow: var(--shadow-light);
    cursor: pointer;
    transition:
        opacity 200ms ease,
        transform 200ms ease,
        visibility 200ms ease;
}

/* Once the end of the indexed content has risen above the fold there is nothing left to index, so
   the bar gets out of the way rather than riding over the final CTA and the footer. */
.toc.is-past .toc__trigger {
    transform: translateY(var(--space-8));
    visibility: hidden;
    opacity: 0;
}

.toc__rail {
    display: none;
}

.toc__current {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    font-size: var(--type-body-md-size);
    line-height: var(--type-body-md-line);
    color: var(--black-80);
    text-align: start;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.toc__caret {
    flex: none;
    width: 12px;
    height: 12px;
    color: var(--black-40);
    transition: transform 200ms ease;
}

.toc.is-open .toc__caret {
    transform: rotate(180deg);
}

/* ---------- The panel ---------- */

.toc__panel {
    display: flex;
    position: fixed;
    inset-inline: 0;
    bottom: 0;
    flex-direction: column;
    max-height: 72dvh;
    padding: var(--space-24) var(--grid-xs-margin) var(--space-32);
    border-radius: var(--radius-24) var(--radius-24) 0 0;
    background: var(--white);
    box-shadow: var(--shadow-light);
}

/* An author rule sets display on the panel, so the browser's own [hidden] rule loses without this.
   The same omission has bitten the slider and the footer. */
.toc__panel[hidden] {
    display: none;
}

.toc__caption {
    flex: none;
    margin: 0 0 var(--space-12);
    font-size: var(--type-caption-sm-size);
    font-weight: var(--caption-weight);
    line-height: var(--caption-line-height-sm);
    letter-spacing: var(--type-caption-sm-track);
    color: var(--brand-teal-500);
    text-transform: uppercase;
}

/* min-height is what lets this scroll inside a flex column that is already capped. */
.toc__list {
    flex: 1 1 auto;
    min-height: 0;
    margin: 0;
    padding: 0;
    overflow-y: auto;
    list-style: none;
}

.toc__item + .toc__item {
    margin-top: var(--space-8);
}

.toc__link {
    display: block;
    padding: var(--space-8) var(--space-16);
    border-radius: var(--radius-8);
    background: var(--black-04);
    overflow: hidden;
    font-size: var(--type-body-md-size);
    line-height: var(--type-body-md-line);
    color: var(--black-80);
    text-decoration: none;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.toc__link.is-current {
    font-weight: var(--body-weight-bold);
    color: var(--black);
}

.toc__link:hover {
    background: var(--black-08);
}

/* ---------- Desktop: rail and card ---------- */

@media (min-width: 1200px) {
    /* The page owns the column; the component only sticks inside it. align-self keeps it from
       stretching to the height of a grid row that is as tall as the whole document. */
    .toc {
        position: sticky;
        inset-inline: auto;
        top: var(--toc-top);
        bottom: auto;
        z-index: 10;
        align-self: start;
        width: max-content;
        margin-inline-start: auto;
    }

    .toc.is-past .toc__trigger {
        transform: none;
        visibility: visible;
        opacity: 1;
    }

    /* The card is wider than the rail and shares its right edge, so it simply covers it. Leaving
       the rail in place keeps the pointer inside the component, which is what holds hover open. */
    .toc__trigger {
        display: block;
        width: auto;
        margin: 0;
        padding: var(--space-16);
        border-color: transparent;
        background: none;
        box-shadow: none;
    }

    .toc__current,
    .toc__caret {
        display: none;
    }

    /* Each tick owns a slot and the rule sits inside it, which is how the frames draw it: twelve
       12px slots make the 176px rail. The slot keeps that pitch until the rail would outgrow
       --toc-rail-max, and only then divides the space it has -- so a page with a hundred headings
       draws a denser rail instead of a clipped one. No count appears here; --toc-count comes from
       the script. */
    .toc__rail {
        --toc-tick-slot: min(var(--toc-tick-pitch), calc(var(--toc-rail-max) / var(--toc-count, 12)));

        display: flex;
        flex-direction: column;
        width: 56px;
    }

    .toc__tick {
        display: flex;
        flex: none;
        align-items: center;
        justify-content: flex-end;
        height: var(--toc-tick-slot);
    }

    .toc__tick::before {
        width: 40px;
        height: 2px;
        border-radius: var(--radius-2);
        background: var(--black-20);
        content: '';
        transition:
            width 200ms ease,
            background-color 200ms ease;
    }

    .toc__tick.is-current::before {
        width: 56px;
        background: var(--black-80);
    }

    .toc__panel {
        position: absolute;
        inset-inline: auto 0;
        top: 0;
        bottom: auto;
        width: var(--toc-panel-width);
        /* --toc-space is the room below the card's own top edge, measured by the script. The
           fallback is the stuck position, which is all a no-script page could assume. */
        max-height: var(--toc-space, calc(100dvh - var(--toc-top) - var(--space-32)));
        padding: var(--space-16);
        border: 1px solid var(--black-12);
        border-radius: var(--radius-12);
    }

    .toc__item + .toc__item {
        margin-top: 0;
    }

    .toc__link {
        padding: var(--space-4) var(--space-8);
        border-radius: var(--radius-4);
        background: none;
        color: var(--black-40);
    }

    .toc__link:hover {
        background: var(--black-04);
        color: var(--black-80);
    }

    .toc__link.is-current {
        color: var(--black-80);
    }
}

@media (prefers-reduced-motion: reduce) {
    .toc__trigger,
    .toc__caret,
    .toc__tick::before {
        transition: none;
    }
}

/* ---------- Numbered list ----------
   Figma: the Section frames carrying Items on Why Avicenna (28799:80258) -- 27309:40931
   "Flexibility by design" with three entries, and "Built for real-world participation" with four.

   A head above a numbered sequence. The ordinal sits in its own 192px column, which is the grid's
   2-of-12 and a fixed width rather than a share, so the text keeps the same left edge whatever the
   container is.

   The rule goes ABOVE each row rather than between them: the frames draw a line over the first row
   and none under the last, which is what a top border on every item gives for free.

   Below 834 the ordinal moves above the title instead of keeping a narrower column. The frames
   disagree here -- the three-entry section stacks and the four-entry one holds the columns at 106
   -- and stacking is the reading that leaves the description a full measure rather than 228px.

   The section drops to 48 below 834, which every mobile frame that carries this component draws --
   the two on Why Avicenna (27309:50017, 27309:50303) and the one on About Us (27286:43111). It had
   no narrow rule at all until About Us was measured, so Why Avicenna was rendering 96 there.

   Those same frames disagree about the gap under the head: Why Avicenna keeps the 64 the wide
   layout uses and About Us draws 32. Two to one, and 64 is what the component already says, so it
   holds at both widths. */
.numbered-list {
    --numbered-list-measure: var(--grid-xl-col-8);

    padding-block: var(--space-48);
}

/* The head is Body xL here, a step up from the shared default, but it keeps the default black-80 --
   unlike the card grid, which takes its description to black. Both are as drawn. */
.numbered-list .section-head {
    --section-head-description-size: var(--type-body-xl-size);
    --section-head-description-line: var(--type-body-xl-line);

    max-width: var(--numbered-list-measure);
    /* 64, not the head's own 48: the frames set the items 64 below a head that ends at 232. */
    margin-inline: auto;
    margin-bottom: var(--space-64);
}

.numbered-list__items {
    max-width: var(--numbered-list-measure);
    margin: 0 auto;
    padding: 0;
    list-style: none;
}

.numbered-list__item {
    display: grid;
    gap: var(--space-16);
    padding-block: var(--space-32);
    border-top: 1px solid var(--black-12);
}

.numbered-list__ordinal {
    font-family: var(--font-heading);
    font-size: var(--type-heading-lg-size);
    font-weight: var(--heading-weight-lg);
    line-height: var(--type-heading-lg-line);
    letter-spacing: var(--type-heading-lg-track);
    color: var(--black-40);
}

.numbered-list__title {
    margin: 0;
    font-family: var(--font-heading);
    font-size: var(--type-heading-sm-size);
    font-weight: var(--heading-weight-sm);
    line-height: var(--type-heading-sm-line);
    letter-spacing: var(--type-heading-sm-track);
    color: var(--black);
}

.numbered-list__description {
    margin: var(--space-8) 0 0;
    font-size: var(--type-body-lg-size);
    line-height: var(--type-body-lg-line);
    color: var(--black-80);
}

@media (min-width: 834px) {
    .numbered-list {
        padding-block: var(--space-96);
    }

    .numbered-list__item {
        grid-template-columns: var(--grid-xl-col-2) 1fr;
        gap: var(--space-32);
    }
}

/* ---------- Comparison table ----------
   Figma: 27309:41882 on Why Avicenna (28799:80258), with the narrow behaviour at 27338:40200.

   A capability matrix. The frames draw it at 1312 -- a 416 key column, which is the grid's 4-of-12,
   and six equal platform columns of 149.33 -- and it cannot shrink to a phone, so it scrolls
   sideways with the key column and the Avicenna column pinned.

   --comparison-key is both the key column's width and the offset the Avicenna column sticks at, so
   the two can never disagree. It is the only number that changes between the two layouts.

   There are NO vertical rules: the columns are separated by nothing but space, and the only
   verticals in the whole table are the blue ones bounding the Avicenna column. What reads as a line
   beside the key column is the group rows' tint ending, not a border.

   Sticky cells must paint their own background or the scrolled columns show through them, which is
   why the key and Avicenna cells restate white, teal-50 and black-04 rather than inheriting. */
.comparison {
    --comparison-key: 167px;
    --comparison-us: 40px;

    padding-block: var(--space-96);
}

/* The scroller is focusable on purpose. It is the only way a keyboard user can reach the columns
   that start off-screen, and a bare overflow container is not reachable at all. */
.comparison__scroll {
    overflow-x: auto;
    overscroll-behavior-x: contain;
}

.comparison__scroll:focus-visible {
    outline: 2px solid var(--brand-blue-500);
    outline-offset: 2px;
}

.comparison__table {
    /* Contains the visually hidden <caption>, for the same reason the marks are positioned. */
    position: relative;
    width: 100%;
    /* max-content stops the columns compressing into unreadable slivers; the scroller takes over
       instead. Separate borders because the cells carry their own rules and sticky needs them. */
    min-width: max-content;
    border: 2px solid var(--brand-teal-100);
    border-radius: var(--radius-12);
    border-collapse: separate;
    border-spacing: 0;
    text-align: left;
}

/* Every rule in this table is an inset shadow rather than a border, which is how Figma draws them
   -- a stroke inside the cell. A real border would add its width to the row, and the frames' 52 and
   48 would come out 53 and 55 instead. */
.comparison__table {
    --_sep: inset 0 -1px 0 var(--brand-teal-100);
    --_us-sides: inset 2px 0 0 var(--brand-blue-500), inset -2px 0 0 var(--brand-blue-500);
}

.comparison__table th,
.comparison__table td {
    padding: var(--space-12) var(--space-16);
    background: var(--white);
    box-shadow: var(--_sep);
    font-weight: var(--body-weight-regular);
    vertical-align: middle;
}

/* The table's own border closes the bottom; a cell rule there would double it. */
.comparison__table tbody:last-child tr:last-child th,
.comparison__table tbody:last-child tr:last-child td {
    box-shadow: none;
}

/* ---------- Header row ---------- */

.comparison__table thead th {
    background: var(--brand-teal-50);
    font-size: var(--type-body-lg-size);
    font-weight: var(--body-weight-bold);
    line-height: var(--type-body-lg-line);
    color: var(--black-80);
    white-space: nowrap;
}

/* The corner cells' own backgrounds would otherwise square off the corners the table's radius just
   rounded, painting over the border's curve: the header tint at the top, the last row's white at
   the bottom. Each takes the table's radius less its 2px border. overflow:hidden on the table would
   clip them in one line, but it would also stop the pinned columns sticking to the scroller. */
.comparison__table thead th:first-child {
    border-top-left-radius: calc(var(--radius-12) - 2px);
}

.comparison__table thead th:last-child {
    border-top-right-radius: calc(var(--radius-12) - 2px);
}

.comparison__table tbody:last-child tr:last-child > :first-child {
    border-bottom-left-radius: calc(var(--radius-12) - 2px);
}

.comparison__table tbody:last-child tr:last-child > :last-child {
    border-bottom-right-radius: calc(var(--radius-12) - 2px);
}

.comparison__platform {
    text-align: center;
}

.comparison__platform.is-us .comparison__platform-name {
    color: var(--brand-blue-500);
}

/* ---------- The key column ---------- */

.comparison__key {
    position: sticky;
    left: 0;
    z-index: 1;
    width: var(--comparison-key);
    min-width: var(--comparison-key);
    font-size: var(--type-body-md-size);
    line-height: var(--type-body-md-line);
    color: var(--black-80);
}

/* The group label tints only its own cell -- the rest of that row stays white, as drawn. */
.comparison__group .comparison__key {
    background: var(--black-04);
    color: var(--black-48);
    font-weight: var(--body-weight-regular);
}

/* ---------- The highlighted column ---------- */

/* The blue is listed first because the first shadow paints on top: listed after the row rule, the
   teal line crossed over the blue one at every row. Sides 2px, top and bottom 4px, as drawn. */
.comparison__table .is-us {
    position: sticky;
    left: var(--comparison-key);
    z-index: 1;
    box-shadow: var(--_us-sides), var(--_sep);
}

.comparison__table thead .is-us {
    box-shadow: var(--_us-sides), inset 0 4px 0 var(--brand-blue-500), var(--_sep);
}

.comparison__table tbody:last-child tr:last-child .is-us {
    box-shadow: var(--_us-sides), inset 0 -4px 0 var(--brand-blue-500);
}

/* ---------- Cells and marks ---------- */

.comparison__cell {
    text-align: center;
}

/* Dates the whole comparison, 16 below the table. A table of claims about named products is only
   as good as the day it was checked, so this is part of the component rather than page furniture. */
.comparison__updated {
    margin: var(--space-16) 0 0;
    font-size: var(--type-body-xl-size);
    line-height: var(--type-body-xl-line);
    color: var(--black-48);
}

/* flex, not inline-flex: as an inline box the glyph would sit on the text baseline and drag the
   descender space of the cell's line box in with it, making every body row 4px taller than drawn.

   relative is not decoration. The "Yes"/"No" label inside is position:absolute, and an absolutely
   positioned box is only clipped by an ancestor that is itself positioned -- the scroller is not.
   Without this the labels escape the scroller at their static positions, out to the full width of
   the table, and the whole page gains several hundred pixels of horizontal scroll. */
.comparison__mark {
    display: flex;
    position: relative;
    justify-content: center;
}

.comparison__mark svg {
    width: 20px;
    height: 20px;
}

.comparison__mark--yes {
    color: var(--brand-teal-500);
}

.comparison__mark--no {
    color: var(--black-32);
}

/* The two in-between values keep the yes and no colours; the glyph tells them apart. A half-filled
   circle in teal reads as part of a yes, a question mark in grey as nothing either way. */
.comparison__mark--partial {
    color: var(--brand-teal-500);
}

.comparison__mark--unknown {
    color: var(--black-32);
}

/* Only appears when a row has fewer values than there are platforms. Deliberately unlike the other
   two marks: a hole in the data must not pass for a claim about a product. */
.comparison__mark--missing {
    color: var(--black-20);
}

/* Below 1200 the Avicenna column narrows to its mark, and the name goes to the accessibility tree
   rather than away: the column header still has to announce which platform it is. */
.comparison__logo {
    display: block;
    width: 24px;
    height: 24px;
    margin-inline: auto;
}

/* The whole column narrows, not just its header: a table column is as wide as its widest cell, so
   leaving the body cells at 16px of padding would hold the column at 52 however narrow the header
   got. */
.comparison__table .is-us {
    width: var(--comparison-us);
    min-width: var(--comparison-us);
    padding-inline: var(--space-8);
}

.comparison__platform.is-us .comparison__platform-name {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    border: 0;
    clip-path: inset(50%);
    white-space: nowrap;
}

@media (min-width: 1200px) {
    .comparison {
        --comparison-key: var(--grid-xl-col-4);
    }

    /* At full width the table fits its container, so the columns share the space equally rather
       than sizing to their labels. */
    .comparison__table {
        min-width: 0;
        table-layout: fixed;
    }

    .comparison__logo {
        display: none;
    }

    .comparison__table .is-us {
        width: auto;
        min-width: 0;
        padding-inline: var(--space-16);
    }

    .comparison__platform.is-us .comparison__platform-name {
        position: static;
        width: auto;
        height: auto;
        margin: 0;
        overflow: visible;
        clip-path: none;
    }
}

/* ---------- Boxed feature section ----------
   Figma: 27309:42815 on Why Avicenna (28799:80258).

   A tinted panel holding a centred head, a white card and an illustration. The panel is the same
   surface the card grid and the list panel use -- teal-50, 80px of padding, 1152 of content -- but
   it draws its own, which is the pattern this file already follows: .security__panel and
   .list-panel each own theirs too, and components that merely agree today should still be able to
   disagree later.

   The card's ringed list is partials/marker-list.html unchanged. It only needs three properties:
   violet instead of teal, a step down in text, and a ring offset derived from that text rather
   than fixed -- which keeps the ring centred at both type steps instead of only the larger one. */
.boxed-feature {
    padding: var(--space-40) var(--grid-xs-margin);
    /* Full-bleed below 834, as the card grid's panel and the list panel both go. */
    margin-inline: calc(var(--grid-xs-margin) * -1);
    background: var(--brand-teal-50);
}

.boxed-feature__items {
    display: grid;
    gap: var(--space-16);
}

/* The illustration sits above the card at narrow widths, which is what the 390 frame draws. Order,
   not DOM order: the card's text still comes first for anything reading the page linearly. */
.boxed-feature__visual {
    order: -1;
}

.boxed-feature__image {
    display: block;
    width: 100%;
    height: auto;
    border-radius: var(--radius-16);
}

.boxed-feature__card {
    padding: var(--space-20);
    border-radius: var(--radius-16);
    background: var(--white);
}

.boxed-feature__caption {
    margin: 0 0 var(--space-12);
    font-size: var(--type-caption-md-size);
    font-weight: var(--caption-weight);
    line-height: var(--caption-line-height-md);
    letter-spacing: var(--type-caption-md-track);
    color: var(--brand-teal-500);
    text-transform: uppercase;
}

.boxed-feature__title {
    margin: 0;
    font-family: var(--font-heading);
    font-size: var(--type-heading-xs-size);
    font-weight: var(--heading-weight-xs);
    line-height: var(--type-heading-xs-line);
    letter-spacing: var(--type-heading-xs-track);
    color: var(--black);
}

.boxed-feature__description {
    margin: var(--space-12) 0 0;
    font-size: var(--type-body-md-size);
    line-height: var(--type-body-md-line);
    color: var(--black-80);
}

.boxed-feature__card .marker-list {
    --marker-accent: var(--brand-violet-500);
    --marker-text-size: var(--type-body-md-size);
    --marker-text-line: var(--type-body-md-line);
    /* Derived, not fixed: 6 against a 24px line and 4 against the 20px one below 834. */
    --marker-offset: calc((var(--type-body-md-line) - 12px) / 2);

    margin-top: var(--space-32);
}

/* The SURFACE steps at 834, which is where the card grid's panel and the list panel's step too --
   inset with its radius back, and the card's padding up to 40. */
@media (min-width: 834px) {
    .boxed-feature {
        padding: var(--space-80);
        margin-inline: 0;
        border-radius: var(--radius-32);
    }

    .boxed-feature__card {
        padding: var(--space-40);
    }
}

/* The COLUMNS step at 1200, a breakpoint later than the surface. Two columns need 1152 of content
   to be the 560 each the frame draws; between 834 and 1199 the panel's 80px padding leaves closer
   to 600, and splitting that gives two columns too narrow for either the card's list or the
   illustration. So the stack holds until the width is actually there. */
@media (min-width: 1200px) {
    .boxed-feature__items {
        grid-template-columns: 1fr 1fr;
        gap: var(--space-32);
        align-items: stretch;
    }

    .boxed-feature__visual {
        order: 0;
    }

    /* The two columns are drawn the same height. cover rather than fill so a card that outgrows the
       illustration crops it instead of stretching it. */
    .boxed-feature__image {
        height: 100%;
        object-fit: cover;
    }
}

/* ---------- Reference list ----------
   Figma: 27286:35398 on About Us (28836:80261), with the narrow layout at 27286:43193.

   Ruled bibliographic rows, not cards: no surface, no padding-inline, and each row is as tall as
   its own text. The rule goes BELOW every row including the last, which is what a bottom border on
   every item gives for free -- the frames close the list with a line and the Items frame ends on it.

   Below 834 the year moves from the right of the row to above the title. That is one
   `flex-direction` away from the wide layout because the row has exactly two children, so
   `column-reverse` lifts the year without a second element and without touching the DOM order that
   a screen reader follows. */
.reference-list {
    --reference-list-measure: var(--grid-xl-col-8);

    padding-block: var(--space-48);
}

.reference-list .section-head {
    max-width: var(--reference-list-measure);
    /* 64, not the head's own 48: the frames set the items 64 below a head that ends at 120. */
    margin-inline: auto;
    margin-bottom: var(--space-64);
}

.reference-list__items {
    display: flex;
    flex-direction: column;
    gap: var(--space-32);
    max-width: var(--reference-list-measure);
    margin: 0 auto;
    padding: 0;
    list-style: none;
}

.reference-list__item {
    display: flex;
    flex-direction: column-reverse;
    align-items: flex-start;
    gap: var(--space-4);
    padding-bottom: var(--space-16);
    border-bottom: 1px solid var(--black-12);
}

.reference-list__title {
    margin: 0;
    font-family: var(--font-heading);
    font-size: var(--type-heading-xs-size);
    font-weight: var(--heading-weight-xs);
    line-height: var(--type-heading-xs-line);
    letter-spacing: var(--type-heading-xs-track);
    color: var(--black);
}

/* The title is black whether or not it links out: the frames draw no link colour and no rest-state
   underline, so only the global a:hover underline separates a linked title from a plain one. */
.reference-list__title a {
    color: inherit;
}

.reference-list__authors,
.reference-list__source {
    font-size: var(--type-body-sm-size);
    line-height: var(--type-body-sm-line);
    color: var(--black-64);
}

.reference-list__authors {
    margin: var(--space-8) 0 0;
}

.reference-list__source {
    margin: var(--space-16) 0 0;
}

.reference-list__year {
    margin: 0;
    font-size: var(--type-body-sm-size);
    line-height: var(--type-body-sm-line);
    color: var(--black-48);
}

/* ---------- Reference list: from Sm ---------- */
@media (min-width: 834px) {
    .reference-list {
        padding-block: var(--space-96);
    }

    .reference-list__item {
        flex-direction: row;
        gap: var(--space-32);
        padding-bottom: var(--space-32);
    }

    .reference-list__main {
        flex: 1;
        min-width: 0;
    }

    .reference-list__year {
        flex: none;
    }
}

/* ---------- Related publications ----------
   Figma: 27636:146492 on Mental Health and its four siblings, narrow at 27636:147447.

   The section that closes a research-area page: a caption, a link to /publications, and four
   compact cards in two columns. The cards arrive from the API, so this file styles shapes that
   static/js/related-publications.js creates; only the caption and the link exist server-side.

   Its padding is 48, not the 96 nearly every other section carries, at BOTH widths -- the frames
   draw 48 above and below at 1600 and at 390. It is a footer to the page rather than a section of
   it, which is what that shorter space says.

   At 834 the head comes apart: the caption centres and the link moves below the cards. Both stay
   one element, moved with `order` inside the section's own column, which is why the head is
   `display: contents` there rather than a box that would have to be emptied and refilled. */
.related-publications {
    display: flex;
    flex-direction: column;
    gap: var(--space-16);
    padding-block: var(--space-48);
}

.related-publications__head {
    display: flex;
    gap: var(--space-16);
    align-items: center;
    justify-content: space-between;
}

.related-publications__caption {
    margin: 0;
    font-size: var(--type-caption-md-size);
    font-weight: var(--caption-weight);
    line-height: var(--caption-line-height-md);
    letter-spacing: var(--type-caption-md-track);
    color: var(--brand-teal-500);
    text-transform: uppercase;
}

/* minmax(0, 1fr), not 1fr: the titles are clipped rather than wrapped, so a long one would push
   its own track past an even half and the page would scroll sideways. 1fr floors at min-content. */
.related-publications__items {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-20);
}

/* Empty, loading and error all render through one line, which is hidden once there is nothing to
   say. It is a live region because the list it describes arrives after the page does. */
.related-publications__status {
    margin: 0;
    font-size: var(--type-body-sm-size);
    line-height: var(--type-body-sm-line);
    color: var(--black-64);
}

.related-publication {
    position: relative;
    display: flex;
    gap: var(--space-12);
    align-items: center;
    padding: var(--space-20);
    border: 1px solid var(--black-12);
    border-radius: var(--radius-12);
}

.related-publication__text {
    flex: 1 1 auto;
    min-width: 0;
}

.related-publication__title {
    margin: 0;
    font-family: var(--font-heading);
    font-size: var(--type-heading-xs-size);
    font-weight: var(--heading-weight-xs);
    line-height: var(--type-heading-xs-line);
    letter-spacing: var(--type-heading-xs-track);
    color: var(--black);
}

/* One line each, because the API returns titles up to 226 characters and author lists longer
   still, and the frames draw a card of exactly two lines. */
.related-publication__title,
.related-publication__meta {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.related-publication__link {
    color: inherit;
}

/* Covers the card so the whole of it is clickable, while the accessible name stays the title. */
.related-publication__link::after {
    position: absolute;
    inset: 0;
    content: '';
}

.related-publication__meta {
    margin: var(--space-2) 0 0;
    font-size: var(--type-body-sm-size);
    line-height: var(--type-body-sm-line);
    color: var(--black-64);
}

.related-publication__icon {
    display: flex;
    flex: none;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    background: var(--brand-blue-50);
    border-radius: var(--radius-8);
}

.related-publication__glyph {
    width: 16px;
    height: 16px;
    color: var(--brand-blue-500);
}

/* ---------- Related publications: to Sm ---------- */
@media (max-width: 833.95px) {
    .related-publications__head {
        display: contents;
    }

    .related-publications__caption {
        order: 1;
        text-align: center;
    }

    .related-publications__items {
        order: 2;
        grid-template-columns: minmax(0, 1fr);
        gap: var(--space-12);
    }

    .related-publications__link {
        order: 3;
        align-self: center;
    }

    .related-publications__status {
        order: 4;
        text-align: center;
    }

    .related-publication {
        padding: var(--space-12);
    }
}

/* ---------- Field ----------
   Figma: `Search Field` 27273:47517 and `Filterby-Default` 27273:47513, both inside Search+Filter
   27273:48306 on the Publications list page, drawn at 864 and 350.

   The shell both controls wear: a 48px box with 16px of side padding, a hairline edge and one
   16px icon slot. It is a base rather than a component because the two things inside it have
   nothing else in common -- one is an <input>, the other a button owning a popup -- while the box
   itself is identical, down to the radius.

   The two heights the frames draw, 48 wide and 44 narrow, are not a media query: padding-block is
   12 at both, and Body Md steps its line height from 24 to 20 at 834, which is exactly the 4px.
   The one number the frames disagree on is the icon's right inset -- 12 on the search field, 16 on
   the dropdown -- and 16 is used for both, so a row of them lines up.

   The edge is an inset shadow rather than a border, which is what .tag already does and for the
   same reason: the box has no set height, so a real border would add its 2px on top of the 48 and
   the field would not match the button beside it. */
.field {
    display: flex;
    gap: var(--space-8);
    align-items: center;
    width: 100%;
    padding: var(--space-12) var(--space-16);
    font-size: var(--type-body-md-size);
    line-height: var(--type-body-md-line);
    letter-spacing: var(--body-letter-space);
    color: var(--black-80);
    text-align: left;
    background: var(--white);
    border: 0;
    border-radius: var(--radius-8);
    box-shadow: inset 0 0 0 1px var(--black-12);
    transition: box-shadow 150ms ease;
}

.field:hover {
    box-shadow: inset 0 0 0 1px var(--black-32);
}

/* On the wrapper, not the control: the input has no edge of its own, so the shell is what has to
   show focus. A button trigger matches :focus-within too. */
.field:focus-within {
    box-shadow: inset 0 0 0 2px var(--brand-blue-500);
}

/* The field inside the dropdown's panel sits on its own row rather than on the page, so it drops
   the edge and takes a tint instead -- two nested hairline boxes would read as a mistake. */
.field--compact {
    background: var(--gray-50);
    box-shadow: none;
}

.field__control {
    flex: 1 1 auto;
    min-width: 0;
    padding: 0;
    font: inherit;
    letter-spacing: inherit;
    color: inherit;
    background: transparent;
    border: 0;
}

.field__control:focus {
    outline: 0;
}

.field__control::placeholder {
    color: var(--black-48);
    opacity: 1;
}

/* ---------- Panel head ----------
   Figma: `Head` in Contact-Form 28955:277009 and in the GCalendar panel 28955:277011, both on
   Request a Demo.

   A heading with a line under it at the top of a card. The frame makes it 60 tall: a 28 heading,
   an 8 gap and a 24 line, so nothing here sets a height.

   Not .section-head: see partials/panel-head.html for why the two are separate. */
.panel-head {
    display: flex;
    flex-direction: column;
    gap: var(--space-8);
}

.panel-head__title {
    margin: 0;
    font-family: var(--font-heading);
    font-size: var(--type-heading-sm-size);
    font-weight: var(--heading-weight-sm);
    line-height: var(--type-heading-sm-line);
    letter-spacing: var(--type-heading-sm-track);
    color: var(--black);
}

.panel-head__description {
    margin: 0;
    font-size: var(--type-body-md-size);
    line-height: var(--type-body-md-line);
    letter-spacing: var(--body-letter-space);
    color: var(--black-80);
}

/* Same placement as the Publications page's: where the first card will appear, centred on the
   column, so the eye is already in the right place when the row arrives. */
.related-publications__loading {
    display: flex;
    justify-content: center;
    padding-block: var(--space-32);
}

.related-publications__loading[hidden] {
    display: none;
}

/* ---------- Loading bar ----------
   The indeterminate bar shown while a list is being fetched. See partials/loading-bar.html.

   Geometry and motion are the ones the site has used since the Gatsby build -- an 80x4 track, two
   segments on a 2.1s loop with the second trailing the first by 1.15s -- carried over so the wait
   looks the same as it always has. What changed is the palette: the old .loading further down this
   file paints with --color-logo-* and --color-primary, which are pre-redesign tokens. That rule
   stays where it is because /study still renders it and is still on the old design.

   More than one segment, because an indeterminate bar with a single travelling block reads as a
   thing moving rather than as work in progress; the ones trailing it are what make it read as a
   loop. There are four, in the brand's four accent hues -- the loader carried four colours before
   the bar replaced the dots, and the two it had after that were half a palette rather than a
   decision. */
.loading-bar {
    position: relative;
    display: inline-block;
    overflow: hidden;
    width: 80px;
    height: 4px;
    background: var(--black-08);
    border-radius: var(--radius-full);
}

.loading-bar__segment {
    position: absolute;
    top: 0;
    bottom: 0;
}

/* The two sweeps are the shapes the bar has always had -- a short block that accelerates away and
   a long one that eases in behind it. Four segments alternate between them, half a loop apart, so
   the pair that was there keeps its timing and the pair added to it fills the gap rather than
   crowding into the same moment. */
.loading-bar__segment:nth-child(odd) {
    animation: loading-bar-sweep-1 2100ms cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;
}

.loading-bar__segment:nth-child(even) {
    animation: loading-bar-sweep-2 2100ms cubic-bezier(0.165, 0.84, 0.44, 1) infinite;
}

/* Green, blue, orange and purple: the brand's four accent hues, in the order they pass. Teal is
   the palette's green -- it is the hue the logo and the old loader both used for it. */
.loading-bar__segment:nth-child(1) {
    background: var(--brand-teal-500);
    animation-delay: 0ms;
}

.loading-bar__segment:nth-child(2) {
    background: var(--brand-blue-500);
    animation-delay: 525ms;
}

.loading-bar__segment:nth-child(3) {
    background: var(--brand-coral-500);
    animation-delay: 1050ms;
}

.loading-bar__segment:nth-child(4) {
    background: var(--brand-violet-500);
    animation-delay: 1575ms;
}

@keyframes loading-bar-sweep-1 {
    0% { left: -35%; right: 100%; }
    60%, 100% { left: 100%; right: -90%; }
}

@keyframes loading-bar-sweep-2 {
    0% { left: -200%; right: 100%; }
    60%, 100% { left: 107%; right: -8%; }
}

/* Without the sweep there is nothing to see, so the track alone would read as an empty box. The
   bar fills instead, which still says "busy" without anything moving. */
@media (prefers-reduced-motion: reduce) {
    /* :nth-child(n) matches every segment and nothing else; it is here to carry the same
       specificity as the two rules that set the animation, which a bare class selector loses to
       however late it comes in the file. */
    .loading-bar__segment:nth-child(n) {
        animation: none;
    }

    /* The four lie side by side instead of chasing each other, so the palette still reads without
       anything moving. 25% each, placed from the left. */
    .loading-bar__segment:nth-child(1) {
        left: 0;
        right: 75%;
    }

    .loading-bar__segment:nth-child(2) {
        left: 25%;
        right: 50%;
    }

    .loading-bar__segment:nth-child(3) {
        left: 50%;
        right: 25%;
    }

    .loading-bar__segment:nth-child(4) {
        left: 75%;
        right: 0;
    }
}

/* ---------- Form field ----------
   Figma: `Input Field` and `Textarea Field` in Contact-Form 28955:277009, Request a Demo.

   A label over a .field, with the inline error slot under it. The frame makes the block 76 tall:
   a 24 label, 4, and the 48 the shared field already is. Nothing here sets a height; the numbers
   fall out of the type.

   See partials/form-field.html for why the error slot renders even though every frame hides it. */
.form-field {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
}

.form-field__label {
    font-size: var(--type-body-md-size);
    font-weight: var(--body-weight-bold);
    line-height: var(--type-body-md-line);
    letter-spacing: var(--body-letter-space);
    color: var(--black);
}

.form-field__required {
    color: var(--black);
}

.form-field__error {
    margin: 0;
    font-size: var(--body-size-sm-max);
    line-height: var(--body-line-height-sm-max);
    color: var(--semantic-danger-500);
}

.form-field__error[hidden] {
    display: none;
}

/* The textarea is the same box grown: 144 against the input's 48, which is the 172 the frame draws
   less the label and its 4. align-items goes to the start because the control no longer fills the
   box's height, and resize is left vertical -- dragging it sideways would break the two-column row
   above it. */
.field--textarea {
    align-items: flex-start;
    height: 144px;
}

.field--textarea .field__control {
    height: 100%;
    resize: vertical;
}

/* The error state is the frame's: the edge and the value both go red, and nothing is written under
   the field -- partials/form-notice.html carries the sentence. The edge is the same inset shadow
   the field already uses, so an invalid field is not 2px taller than a valid one beside it. */
.field[data-invalid='true'] {
    box-shadow: inset 0 0 0 1px var(--semantic-danger-500);
}

.field[data-invalid='true'] .field__control,
.field[data-invalid='true'] .field__control::placeholder {
    color: var(--semantic-danger-500);
}

/* The two-up row the frame draws for First and Last name: 393 either side of a 12 gutter in 798.
   One column below 834, where the card is narrow enough that two would crush the labels. */
.form-row {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-16);
}

@media (min-width: 834px) {
    .form-row {
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: var(--space-12);
    }
}

/* The frame draws the submit filling the card at 390 -- 347 of a 350 card -- and hugging its
   label at 1600, where it is 175 wide. Below 834 a button that only wraps its words looks stranded
   in a single column of full-width fields; above it, the fields are wide enough that a full-width
   button would read as a banner. The step is the site's own, and matches where .form-row goes
   two-up. */
.form-submit {
    width: 100%;
}

@media (min-width: 834px) {
    /* align-self as well as the width: the form is a flex column, and a flex item stretches across
       the cross axis unless it is told not to, so `width: auto` alone leaves it full width. */
    .form-submit {
        width: auto;
        align-self: flex-start;
    }
}

/* ---------- Form notice ----------
   Figma: `Notice/validation` in Contact-Form 28955:277009, drawn in the two result frames.

   One panel, two states, told apart by data-state. The tints are sampled off the frames and land
   on the semantic 50s; the text is the matching 500. 72 tall is 16 of padding around two 20px
   lines, so it follows the type rather than being set. */
.form-notice {
    padding: var(--space-16);
    border: 1px solid transparent;
    border-radius: var(--radius-8);
}

.form-notice[hidden] {
    display: none;
}

.form-notice__title,
.form-notice__message {
    margin: 0;
    font-size: var(--body-size-sm-max);
    line-height: var(--body-line-height-sm-max);
}

.form-notice__title {
    font-weight: var(--body-weight-bold);
}

.form-notice__title:empty {
    display: none;
}

/* Tint, edge and text are the 50, the 300 and the 500 of the same ramp. The edge is sampled off
   the frames: a 1px line a step darker than the fill, which the downscaled render blends but which
   is clearly there on both panels. The border is declared transparent above so that adding it
   cannot change the panel's height between states. */
.form-notice[data-state='success'] {
    color: var(--semantic-success-500);
    background: var(--semantic-success-50);
    border-color: var(--semantic-success-300);
}

.form-notice[data-state='error'] {
    color: var(--semantic-danger-500);
    background: var(--semantic-danger-50);
    border-color: var(--semantic-danger-300);
}

/* The browser's own clear affordance on type="search" is inconsistent between engines and sits
   where the magnifier goes, so it is removed and the icon is drawn here instead. */
.field__control::-webkit-search-cancel-button {
    appearance: none;
}

.field__icon {
    flex: none;
    width: 16px;
    height: 16px;
    color: var(--black-48);
}

/* ---------- Filter select ----------
   A multi-select listbox, not a <select>: the frames put a search field inside the panel and show
   several keywords active at once, and a native select can do neither. See
   partials/filter-select.html and static/js/filter-select.js. */
.filter-select {
    position: relative;
}

/* The resting label is a placeholder and takes the search field's placeholder colour, so the two
   controls in the row read as one set. The frames draw this one at black-64 and the search
   placeholder at black-48; matching them was asked for, and it also makes the pair say the same
   thing -- "nothing here yet" in the same grey.

   Once keywords are chosen the trigger stops being a placeholder and reports a value, so it takes
   the value colour the input's own text uses. The script flips data-filled. */
/* family and weight only, never the `font` shorthand: the shorthand resets font-size and
   line-height too, and .field's 16/24 is what holds the box at 48. With it the trigger inherits
   the body's 1.75 and stands 52 beside a 48 search field. */
.filter-select__trigger {
    justify-content: space-between;
    font-family: inherit;
    font-weight: inherit;
    color: var(--black-48);
    cursor: pointer;
}

.filter-select__trigger[data-filled='true'] {
    color: var(--black-80);
}

.filter-select__trigger[aria-expanded='true'] {
    box-shadow: inset 0 0 0 2px var(--brand-blue-500);
}

.filter-select__value {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.filter-select__chevron {
    transition: transform 150ms ease;
}

.filter-select__trigger[aria-expanded='true'] .filter-select__chevron {
    transform: rotate(180deg);
}

.filter-select__panel {
    position: absolute;
    z-index: 20;
    top: calc(100% + var(--space-4));
    right: 0;
    left: 0;
    overflow: hidden auto;
    /* 312 is the panel height the frames draw, and it is a max rather than a height so a short
       list closes around its own rows instead of leaving a void under them. */
    max-height: 312px;
    background: var(--white);
    border: 1px solid var(--black-12);
    border-radius: var(--radius-8);
    box-shadow: var(--shadow-dropdown);
    scrollbar-width: thin;
    scrollbar-color: var(--gray-200) var(--black-08);
}

/* The old design's keyword panel drew its own scrollbar rather than taking the platform's, and
   this keeps that: 6px, a faint track and a grey thumb. Its three literals turned out to be on
   the scale already -- #b0b0b0 is --gray-300 exactly, #d0d0d0 is --gray-200 but for one step, and
   the 6% track rounds to --black-08 -- so the look is the old one and the values are tokens.

   Stated twice because the two engines share nothing here: scrollbar-color above for Firefox,
   ::-webkit-scrollbar below for Chrome and Safari. The frames draw 8px; 6 is the old design's and
   what was asked for. */
.filter-select__panel::-webkit-scrollbar {
    width: 6px;
}

.filter-select__panel::-webkit-scrollbar-track {
    background: var(--black-08);
}

.filter-select__panel::-webkit-scrollbar-thumb {
    background: var(--gray-200);
    border-radius: var(--radius-2);
}

.filter-select__panel::-webkit-scrollbar-thumb:hover {
    background: var(--gray-300);
}

/* Sticky so the field stays reachable while the list scrolls under it -- the frames draw 14 rows
   in a panel that holds six. */
.filter-select__search {
    position: sticky;
    top: 0;
    z-index: 1;
    padding: var(--space-8);
    background: var(--white);
}

.filter-select__options {
    margin: 0;
    padding: 0;
    list-style: none;
}

.filter-select__option {
    display: flex;
    gap: var(--space-8);
    align-items: center;
    justify-content: space-between;
    padding: var(--space-12) var(--space-16);
    font-size: var(--type-body-md-size);
    line-height: var(--type-body-md-line);
    color: var(--black-80);
    cursor: pointer;
}

.filter-select__option:hover,
.filter-select__option[data-active='true'] {
    background: var(--gray-50);
}

/* A check rather than a colour change: the row has to say "selected" when it is scrolled past the
   pointer, and on a tinted hover row a colour alone would not. */
.filter-select__option[aria-selected='true'] {
    font-weight: var(--body-weight-bold);
    color: var(--brand-blue-500);
}

.filter-select__option[aria-selected='true']::after {
    content: '\2713';
    flex: none;
}

.filter-select__option[hidden] {
    display: none;
}

.filter-select__status {
    margin: 0;
    padding: var(--space-12) var(--space-16);
    font-size: var(--type-body-sm-size);
    line-height: var(--type-body-sm-line);
    color: var(--black-48);
}

@media (hover: none) {
    .field:hover {
        box-shadow: inset 0 0 0 1px var(--black-12);
    }
}

/* ---------- Filter bar ----------
   Figma: Search+Filter 27273:48306, at rest and with keywords chosen (27273:48124), plus 350 wide
   at 27273:49311. See partials/filter-bar.html.

   61 tall at rest and 103 with chips, which is the row's 48, 12 to the rule, the rule, 12 again
   and a 30 chip. Both fall out of the gaps rather than being set. */
.filter-bar__row {
    display: flex;
    gap: var(--space-12);
    align-items: stretch;
}

.filter-bar__search {
    flex: 1 1 auto;
    min-width: 0;
}

/* 304 is the grid's 3-of-12, which is what the frames draw; the search field takes the rest. */
.filter-bar__select {
    flex: none;
    width: var(--grid-xl-col-3);
}

.filter-bar__rule {
    height: 1px;
    margin-top: var(--space-12);
    background: var(--black-12);
}

.filter-bar__chips {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-4);
    margin-top: var(--space-12);
}

/* Same trap .tag[hidden] already carries: a class's own display beats the hidden attribute's, so
   the row stays laid out and its margin still holds the bar 12 taller than the frames draw. */
.filter-bar__chips[hidden] {
    display: none;
}

/* The chips are the shared tag in its action colours -- fill for a keyword, outline for the reset
   -- which is what the tag component was built expecting. Only the cross is new. */
.filter-chip {
    cursor: pointer;
}

.filter-chip__cross {
    width: 16px;
    height: 16px;
}

/* ---------- Filter bar: to Sm ---------- */
@media (max-width: 833.95px) {
    .filter-bar__row {
        flex-direction: column;
    }

    .filter-bar__select {
        width: 100%;
    }
}

/* ---------- Tooltip ----------
   Figma: Tooltip-Content 27406:77772, on the Publications list page's tag overflow.

   A black bubble with a caret, placed above whatever describes it and centred on it. Kept generic
   because nothing about it is about tags -- it is positioned by the element that owns it, which
   only has to be a positioned box.

   Hidden by default and shown by that owner; it carries role="tooltip" and is pointed at with
   aria-describedby, never a title attribute, which a keyboard cannot reach and touch ignores. */
.tooltip {
    position: absolute;
    bottom: calc(100% + var(--space-8));
    left: 50%;
    z-index: 10;
    display: none;
    flex-direction: column;
    width: max-content;
    /* The frames draw 128 wide for their short labels. Real tag names reach 62 characters, so this
       is a ceiling rather than a width and the lines wrap inside it. */
    max-width: 240px;
    padding: var(--space-8);
    font-size: var(--type-body-sm-size);
    font-weight: var(--body-weight-regular);
    line-height: var(--type-body-sm-line);
    color: var(--white);
    text-align: left;
    background: var(--black);
    border-radius: var(--radius-8);
    transform: translateX(-50%);
}

/* An 8px square turned 45 degrees, half of it below the bubble -- which is the 11.31 the frame
   records, that being 8 x sqrt(2). */
.tooltip::after {
    position: absolute;
    top: 100%;
    left: 50%;
    width: 8px;
    height: 8px;
    content: '';
    background: var(--black);
    transform: translate(-50%, -50%) rotate(45deg);
}

/* ---------- Tag row ----------
   Figma: the `Feature used` frame inside _Publication-Item -- 864x30 at 27270:36261 and 350x26 at
   27464:40000. See tagRow() in static/js/pages/publications.js and static/js/tag-row.js.

   One line, never two: nowrap is what makes the overflow real rather than decorative. Without the
   script every tag stays visible and the row wraps instead, which is the right failure. */
.tag-row {
    display: flex;
    flex-wrap: nowrap;
    gap: var(--space-4);
    align-items: center;
}

.tag-row__tag[hidden] {
    display: none;
}

/* The anchor the tooltip is positioned against. */
.tag-row__overflow {
    position: relative;
    display: inline-flex;
    flex: none;
}

.tag-row__overflow[hidden] {
    display: none;
}

/* One switch for all three ways in -- pointer, keyboard and a tap -- because the script has to
   place the bubble once it is visible, so every route goes through the same attribute rather than
   some of them through :hover alone. */
.tag-row__overflow[data-open='true'] .tooltip {
    display: flex;
}

/* Flipped below when there is no room above it. The caret moves with it. */
.tag-row__overflow[data-place='below'] .tooltip {
    top: calc(100% + var(--space-8));
    bottom: auto;
}

.tag-row__overflow[data-place='below'] .tooltip::after {
    top: 0;
    transform: translate(-50%, -50%) rotate(45deg);
}

/* ---------- Reference list: the Publications row ----------
   The list page draws the same row from the same Figma component (_Publication-Item 27270:36261),
   built at runtime from the API rather than from front matter -- so it wears these classes and
   static/js/pages/publications.js emits them. Two differences from the About Us rows: the venue
   line is off, and a row of keyword tags sits 16 under the authors.

   The left column narrows from 799 to 751 on its own, because the date beside it is a full date
   rather than a year and that column has always sized to its content. */
.reference-list__tags {
    margin-top: var(--space-16);
}

/* The page owns the measure and the list has no head of its own here, so the component's 96/96 and
   its centred head are both dropped. */
.publications__list .reference-list {
    padding-block: 0;
}
