/*!
 * phil-ux — the CSS framework of PHIL. MIT License.
 *
 * Real CSS, mostly classless, in cascade layers: a site's own styles, written outside any layer, win over all of them
 * without !important. The tokens come first, each family in its own layer, so a site can replace the colours, the
 * dimensions or both.
 */
@layer phil.palette, phil.colors, phil.dimensions, phil.base, phil.layout, phil.components, phil.utilities;

@layer phil.palette {
    /*
     * phil-ux — the palette.
     *
     * Seven hues in OKLCH, each tone numbered by its lightness: `--phil-brand-40` is `oklch(0.40 …)`. Because the number
     * is the lightness, it also gives the contrast: two tones whose numbers differ by 50 or more reach 4.5:1 (text), and
     * by 40 or more reach 3:1 (large text, borders, icons), whatever their hues. The tests of the package check that rule
     * on every pair, and that every tone fits in sRGB.
     *
     * Components never read the palette: they read the colour roles of colors.css. A site changes a hue here, or a role
     * there, in its own stylesheet.
     */
    :root {
        --phil-neutral-05: oklch(0.05 0.012 265);
        --phil-neutral-10: oklch(0.10 0.012 265);
        --phil-neutral-20: oklch(0.20 0.012 265);
        --phil-neutral-30: oklch(0.30 0.012 265);
        --phil-neutral-40: oklch(0.40 0.012 265);
        --phil-neutral-50: oklch(0.50 0.012 265);
        --phil-neutral-60: oklch(0.60 0.012 265);
        --phil-neutral-70: oklch(0.70 0.012 265);
        --phil-neutral-80: oklch(0.80 0.012 265);
        --phil-neutral-90: oklch(0.90 0.012 265);
        --phil-neutral-95: oklch(0.95 0.012 265);
        --phil-neutral-99: oklch(0.99 0.004 265);

        --phil-brand-05: oklch(0.05 0.014 195);
        --phil-brand-10: oklch(0.10 0.016 195);
        --phil-brand-20: oklch(0.20 0.029 195);
        --phil-brand-30: oklch(0.30 0.044 195);
        --phil-brand-40: oklch(0.40 0.058 195);
        --phil-brand-50: oklch(0.50 0.073 195);
        --phil-brand-60: oklch(0.60 0.087 195);
        --phil-brand-70: oklch(0.70 0.102 195);
        --phil-brand-80: oklch(0.80 0.116 195);
        --phil-brand-90: oklch(0.90 0.120 195);
        --phil-brand-95: oklch(0.95 0.064 195);

        --phil-red-05: oklch(0.05 0.028 25);
        --phil-red-10: oklch(0.10 0.037 25);
        --phil-red-20: oklch(0.20 0.070 25);
        --phil-red-30: oklch(0.30 0.104 25);
        --phil-red-40: oklch(0.40 0.138 25);
        --phil-red-50: oklch(0.50 0.172 25);
        --phil-red-60: oklch(0.60 0.180 25);
        --phil-red-70: oklch(0.70 0.162 25);
        --phil-red-80: oklch(0.80 0.097 25);
        --phil-red-90: oklch(0.90 0.044 25);
        --phil-red-95: oklch(0.95 0.021 25);

        --phil-green-05: oklch(0.05 0.022 150);
        --phil-green-10: oklch(0.10 0.026 150);
        --phil-green-20: oklch(0.20 0.047 150);
        --phil-green-30: oklch(0.30 0.071 150);
        --phil-green-40: oklch(0.40 0.094 150);
        --phil-green-50: oklch(0.50 0.117 150);
        --phil-green-60: oklch(0.60 0.141 150);
        --phil-green-70: oklch(0.70 0.150 150);
        --phil-green-80: oklch(0.80 0.150 150);
        --phil-green-90: oklch(0.90 0.150 150);
        --phil-green-95: oklch(0.95 0.071 150);

        --phil-amber-05: oklch(0.05 0.038 70);
        --phil-amber-10: oklch(0.10 0.021 70);
        --phil-amber-20: oklch(0.20 0.037 70);
        --phil-amber-30: oklch(0.30 0.056 70);
        --phil-amber-40: oklch(0.40 0.074 70);
        --phil-amber-50: oklch(0.50 0.092 70);
        --phil-amber-60: oklch(0.60 0.111 70);
        --phil-amber-70: oklch(0.70 0.129 70);
        --phil-amber-80: oklch(0.80 0.138 70);
        --phil-amber-90: oklch(0.90 0.064 70);
        --phil-amber-95: oklch(0.95 0.031 70);

        --phil-blue-05: oklch(0.05 0.041 255);
        --phil-blue-10: oklch(0.10 0.032 255);
        --phil-blue-20: oklch(0.20 0.057 255);
        --phil-blue-30: oklch(0.30 0.084 255);
        --phil-blue-40: oklch(0.40 0.112 255);
        --phil-blue-50: oklch(0.50 0.140 255);
        --phil-blue-60: oklch(0.60 0.168 255);
        --phil-blue-70: oklch(0.70 0.136 255);
        --phil-blue-80: oklch(0.80 0.087 255);
        --phil-blue-90: oklch(0.90 0.042 255);
        --phil-blue-95: oklch(0.95 0.021 255);

        --phil-violet-05: oklch(0.05 0.033 300);
        --phil-violet-10: oklch(0.10 0.048 300);
        --phil-violet-20: oklch(0.20 0.092 300);
        --phil-violet-30: oklch(0.30 0.136 300);
        --phil-violet-40: oklch(0.40 0.180 300);
        --phil-violet-50: oklch(0.50 0.180 300);
        --phil-violet-60: oklch(0.60 0.180 300);
        --phil-violet-70: oklch(0.70 0.158 300);
        --phil-violet-80: oklch(0.80 0.101 300);
        --phil-violet-90: oklch(0.90 0.048 300);
        --phil-violet-95: oklch(0.95 0.024 300);
    }
}
@layer phil.colors {
    /*
     * phil-ux — the colour roles.
     *
     * One theme, light and dark in the same declarations: each role is `light-dark(<light tone>, <dark tone>)`, so the
     * tone follows the `color-scheme` of the element that uses it. The page follows the system unless `data-theme`
     * chooses: `light` (or `default`, the same) and `dark`, on the root or on any element, which then flips its subtree.
     *
     * The grid: for each role, a fill, a border and the colour "on" that fill, each quiet (a tint) or loud (solid).
     * `on-quiet` is the text on `fill-quiet`, `on-loud` the text on `fill-loud`; both pairs reach 4.5:1 in both schemes,
     * and `border-loud` reaches 3:1 on the surface. The tests check every pair named in the package's contrast rule.
     *
     * This layer holds colour only; sizes live in dimensions.css, so a site can override one without the other.
     */
    :root {
        color-scheme: light dark;

        /* The page */
        --phil-color-surface: light-dark(var(--phil-neutral-99), var(--phil-neutral-10));
        --phil-color-surface-raised: light-dark(var(--phil-neutral-95), var(--phil-neutral-20));
        --phil-color-text: light-dark(var(--phil-neutral-10), var(--phil-neutral-95));
        --phil-color-text-muted: light-dark(var(--phil-neutral-40), var(--phil-neutral-70));
        --phil-color-link: light-dark(var(--phil-brand-40), var(--phil-brand-70));
        --phil-color-focus: light-dark(var(--phil-blue-50), var(--phil-blue-70));
        --phil-color-border: light-dark(var(--phil-neutral-80), var(--phil-neutral-30));
        --phil-color-shadow: light-dark(oklch(0 0 0 / 0.12), oklch(0 0 0 / 0.5));
        --phil-color-backdrop: light-dark(oklch(0 0 0 / 0.4), oklch(0 0 0 / 0.6));

        /* The levels of an activity calendar, from nothing to the most */
        --phil-color-activity-0: light-dark(var(--phil-neutral-95), var(--phil-neutral-20));
        --phil-color-activity-1: light-dark(var(--phil-brand-90), var(--phil-brand-30));
        --phil-color-activity-2: light-dark(var(--phil-brand-70), var(--phil-brand-50));
        --phil-color-activity-3: light-dark(var(--phil-brand-50), var(--phil-brand-70));
        --phil-color-activity-4: light-dark(var(--phil-brand-30), var(--phil-brand-90));

        /* The quiet tones an image's box is painted in while it loads, near the dominant colour of the picture */
        --phil-color-placeholder-brand: light-dark(var(--phil-brand-90), var(--phil-brand-20));
        --phil-color-placeholder-red: light-dark(var(--phil-red-90), var(--phil-red-20));
        --phil-color-placeholder-green: light-dark(var(--phil-green-90), var(--phil-green-20));
        --phil-color-placeholder-amber: light-dark(var(--phil-amber-90), var(--phil-amber-20));
        --phil-color-placeholder-blue: light-dark(var(--phil-blue-90), var(--phil-blue-20));
        --phil-color-placeholder-violet: light-dark(var(--phil-violet-90), var(--phil-violet-20));

        /* The veil under a text laid over a picture, dark enough in both schemes for its light text to reach 4.5:1 whatever
           the picture (9:1 over white), and that text */
        --phil-color-veil: oklch(0 0 0 / 0.72);
        --phil-color-on-veil: oklch(0.99 0 0);

        /* brand */
        --phil-color-brand-fill-quiet: light-dark(var(--phil-brand-95), var(--phil-brand-20));
        --phil-color-brand-fill-loud: light-dark(var(--phil-brand-40), var(--phil-brand-70));
        --phil-color-brand-border-quiet: light-dark(var(--phil-brand-80), var(--phil-brand-30));
        --phil-color-brand-border-loud: light-dark(var(--phil-brand-50), var(--phil-brand-60));
        --phil-color-brand-on-quiet: light-dark(var(--phil-brand-30), var(--phil-brand-90));
        --phil-color-brand-on-loud: light-dark(var(--phil-brand-95), var(--phil-brand-10));

        /* neutral */
        --phil-color-neutral-fill-quiet: light-dark(var(--phil-neutral-95), var(--phil-neutral-20));
        --phil-color-neutral-fill-loud: light-dark(var(--phil-neutral-40), var(--phil-neutral-70));
        --phil-color-neutral-border-quiet: light-dark(var(--phil-neutral-80), var(--phil-neutral-30));
        --phil-color-neutral-border-loud: light-dark(var(--phil-neutral-50), var(--phil-neutral-60));
        --phil-color-neutral-on-quiet: light-dark(var(--phil-neutral-30), var(--phil-neutral-90));
        --phil-color-neutral-on-loud: light-dark(var(--phil-neutral-95), var(--phil-neutral-10));

        /* danger */
        --phil-color-danger-fill-quiet: light-dark(var(--phil-red-95), var(--phil-red-20));
        --phil-color-danger-fill-loud: light-dark(var(--phil-red-40), var(--phil-red-70));
        --phil-color-danger-border-quiet: light-dark(var(--phil-red-80), var(--phil-red-30));
        --phil-color-danger-border-loud: light-dark(var(--phil-red-50), var(--phil-red-60));
        --phil-color-danger-on-quiet: light-dark(var(--phil-red-30), var(--phil-red-90));
        --phil-color-danger-on-loud: light-dark(var(--phil-red-95), var(--phil-red-10));

        /* success */
        --phil-color-success-fill-quiet: light-dark(var(--phil-green-95), var(--phil-green-20));
        --phil-color-success-fill-loud: light-dark(var(--phil-green-40), var(--phil-green-70));
        --phil-color-success-border-quiet: light-dark(var(--phil-green-80), var(--phil-green-30));
        --phil-color-success-border-loud: light-dark(var(--phil-green-50), var(--phil-green-60));
        --phil-color-success-on-quiet: light-dark(var(--phil-green-30), var(--phil-green-90));
        --phil-color-success-on-loud: light-dark(var(--phil-green-95), var(--phil-green-10));

        /* warning */
        --phil-color-warning-fill-quiet: light-dark(var(--phil-amber-95), var(--phil-amber-20));
        --phil-color-warning-fill-loud: light-dark(var(--phil-amber-40), var(--phil-amber-70));
        --phil-color-warning-border-quiet: light-dark(var(--phil-amber-80), var(--phil-amber-30));
        --phil-color-warning-border-loud: light-dark(var(--phil-amber-50), var(--phil-amber-60));
        --phil-color-warning-on-quiet: light-dark(var(--phil-amber-30), var(--phil-amber-90));
        --phil-color-warning-on-loud: light-dark(var(--phil-amber-95), var(--phil-amber-10));

        /* info */
        --phil-color-info-fill-quiet: light-dark(var(--phil-blue-95), var(--phil-blue-20));
        --phil-color-info-fill-loud: light-dark(var(--phil-blue-40), var(--phil-blue-70));
        --phil-color-info-border-quiet: light-dark(var(--phil-blue-80), var(--phil-blue-30));
        --phil-color-info-border-loud: light-dark(var(--phil-blue-50), var(--phil-blue-60));
        --phil-color-info-on-quiet: light-dark(var(--phil-blue-30), var(--phil-blue-90));
        --phil-color-info-on-loud: light-dark(var(--phil-blue-95), var(--phil-blue-10));
    }

    [data-theme="light"],
    [data-theme="default"] {
        color-scheme: light;
    }

    [data-theme="dark"] {
        color-scheme: dark;
    }
}
@layer phil.dimensions {
    /*
     * phil-ux — the dimensions.
     *
     * Every scale is named in t-shirt sizes, from `3xs` to `4xl`, and computed from one unit and a global multiplier:
     * `--phil-scale-space`, `--phil-scale-text` and `--phil-scale-radius` change a whole scale at once, and
     * `--phil-scale` changes the spaces and the radii together. A site sets them in its own `:root`.
     *
     * This layer holds sizes, durations and fonts only; colours live in colors.css. The shadows take their geometry from
     * here and their colour from `--phil-color-shadow`.
     */
    :root {
        /* Global multipliers */
        --phil-scale: 1;
        --phil-scale-space: var(--phil-scale);
        --phil-scale-text: 1;
        --phil-scale-radius: var(--phil-scale);

        /* Space */
        --phil-space-unit: calc(1rem * var(--phil-scale-space));
        --phil-space-3xs: calc(var(--phil-space-unit) * 0.125);
        --phil-space-2xs: calc(var(--phil-space-unit) * 0.25);
        --phil-space-xs: calc(var(--phil-space-unit) * 0.5);
        --phil-space-sm: calc(var(--phil-space-unit) * 0.75);
        --phil-space-md: var(--phil-space-unit);
        --phil-space-lg: calc(var(--phil-space-unit) * 1.5);
        --phil-space-xl: calc(var(--phil-space-unit) * 2);
        --phil-space-2xl: calc(var(--phil-space-unit) * 3);
        --phil-space-3xl: calc(var(--phil-space-unit) * 4);
        --phil-space-4xl: calc(var(--phil-space-unit) * 6);

        /* Text: a modular scale around the body size */
        --phil-text-ratio: 1.25;
        --phil-text-md: calc(1rem * var(--phil-scale-text));
        --phil-text-sm: calc(var(--phil-text-md) / var(--phil-text-ratio));
        --phil-text-xs: calc(var(--phil-text-sm) / var(--phil-text-ratio));
        --phil-text-lg: calc(var(--phil-text-md) * var(--phil-text-ratio));
        --phil-text-xl: calc(var(--phil-text-lg) * var(--phil-text-ratio));
        --phil-text-2xl: calc(var(--phil-text-xl) * var(--phil-text-ratio));
        --phil-text-3xl: calc(var(--phil-text-2xl) * var(--phil-text-ratio));
        --phil-text-4xl: calc(var(--phil-text-3xl) * var(--phil-text-ratio));
        --phil-leading-tight: 1.15;
        --phil-leading-md: 1.6;
        --phil-measure: 65ch;
        --phil-measure-narrow: 36ch;

        --phil-weight-regular: 400;
        --phil-weight-medium: 500;
        --phil-weight-bold: 700;

        /* Fonts: the system's own, nothing downloaded */
        --phil-font-sans: system-ui, -apple-system, "Segoe UI", Roboto, "Noto Sans", Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
        --phil-font-serif: ui-serif, Georgia, Cambria, "Times New Roman", serif;
        --phil-font-mono: ui-monospace, "SFMono-Regular", Menlo, Consolas, "Liberation Mono", monospace;

        /* Radius */
        --phil-radius-unit: calc(0.25rem * var(--phil-scale-radius));
        --phil-radius-none: 0;
        --phil-radius-sm: var(--phil-radius-unit);
        --phil-radius-md: calc(var(--phil-radius-unit) * 2);
        --phil-radius-lg: calc(var(--phil-radius-unit) * 4);
        --phil-radius-xl: calc(var(--phil-radius-unit) * 6);
        --phil-radius-full: 9999px;

        /* Shadow */
        --phil-shadow-sm: 0 1px 2px var(--phil-color-shadow);
        --phil-shadow-md: 0 2px 4px var(--phil-color-shadow), 0 4px 12px var(--phil-color-shadow);
        --phil-shadow-lg: 0 4px 8px var(--phil-color-shadow), 0 12px 32px var(--phil-color-shadow);

        /* Focus */
        --phil-focus-width: 2px;
        --phil-focus-offset: 2px;

        /* Lines */
        --phil-border-sm: 1px;
        --phil-border-md: 2px;
        --phil-border-lg: 0.25rem;

        /* Controls: the medium height is a touch target of 44 CSS pixels; a check box or a radio is 24 */
        --phil-control-sm: 2rem;
        --phil-control-md: 2.75rem;
        --phil-control-lg: 3.25rem;
        --phil-check-size: 1.5rem;

        /* Panels: the width of a dialog, a sheet or a menu */
        --phil-panel-sm: 20rem;
        --phil-panel-md: 32rem;
        --phil-panel-lg: 48rem;

        /* Stacking, inside the page; dialogs and popovers live in the browser's top layer */
        --phil-z-raised: 1;
        --phil-z-overlay: 10;

        /* States: how far a hover or a press mixes a fill towards the text colour, which only raises its contrast */
        --phil-opacity-disabled: 0.55;
        --phil-mix-hover: 12%;
        --phil-mix-active: 24%;

        /* Motion: every animation of phil-ux stops under prefers-reduced-motion */
        --phil-duration-sm: 150ms;
        --phil-duration-md: 300ms;
        --phil-duration-lg: 600ms;
        --phil-duration-loop: 1s;
        --phil-ease-out: cubic-bezier(0.22, 1, 0.36, 1);
        --phil-ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
    }
}
@layer phil.base {
    /*
     * phil-ux — the classless base.
     *
     * The HTML elements dressed without a class: the page, its typography, links, media, code, quotes, the focus ring,
     * forms, buttons, tables, dialogs, disclosures, navigation, gauges and popovers. Every value is a token, so a theme or
     * a scale changes them all, and every direction is logical, so a right-to-left page needs nothing more.
     */
    *,
    *::before,
    *::after {
        box-sizing: border-box;
    }

    html {
        font-family: var(--phil-font-sans);
        font-size: 100%;
        line-height: var(--phil-leading-md);
        color: var(--phil-color-text);
        background-color: var(--phil-color-surface);
        accent-color: var(--phil-color-brand-fill-loud);
        text-size-adjust: 100%;
        -webkit-text-size-adjust: 100%;
    }

    body {
        margin: 0;
        font-size: var(--phil-text-md);
    }

    h1,
    h2,
    h3,
    h4,
    h5,
    h6 {
        margin-block: 0 var(--phil-space-sm);
        font-weight: var(--phil-weight-bold);
        line-height: var(--phil-leading-tight);
        text-wrap: balance;
    }

    h1 { font-size: var(--phil-text-3xl); }
    h2 { font-size: var(--phil-text-2xl); }
    h3 { font-size: var(--phil-text-xl); }
    h4 { font-size: var(--phil-text-lg); }
    h5 { font-size: var(--phil-text-md); }
    h6 { font-size: var(--phil-text-sm); }

    p,
    ul,
    ol,
    dl,
    blockquote,
    pre,
    figure,
    table {
        margin-block: 0 var(--phil-space-md);
    }

    p {
        text-wrap: pretty;
    }

    ul,
    ol {
        padding-inline-start: var(--phil-space-lg);
    }

    hgroup {
        margin-block-end: var(--phil-space-md);
    }

    hgroup > * {
        margin-block: 0;
    }

    a {
        color: var(--phil-color-link);
        text-decoration-thickness: from-font;
        text-underline-offset: 0.15em;
    }

    a:hover {
        text-decoration-thickness: 0.125em;
    }

    :focus-visible {
        outline: var(--phil-focus-width) solid var(--phil-color-focus);
        outline-offset: var(--phil-focus-offset);
    }

    img,
    svg,
    video,
    canvas {
        display: block;
        max-inline-size: 100%;
        block-size: auto;
    }

    picture {
        display: block;
    }

    figure {
        margin-inline: 0;
    }

    figcaption {
        margin-block-start: var(--phil-space-xs);
        font-size: var(--phil-text-sm);
        color: var(--phil-color-text-muted);
    }

    small {
        font-size: var(--phil-text-sm);
    }

    hr {
        margin-block: var(--phil-space-lg);
        border: 0;
        border-block-start: var(--phil-border-sm) solid var(--phil-color-border);
    }

    code,
    kbd,
    samp,
    pre {
        font-family: var(--phil-font-mono);
        font-size: 0.9em;
    }

    pre {
        overflow-x: auto;
        padding: var(--phil-space-sm) var(--phil-space-md);
        background-color: var(--phil-color-surface-raised);
        border-radius: var(--phil-radius-md);
    }

    blockquote {
        margin-inline: 0;
        padding-inline-start: var(--phil-space-md);
        border-inline-start: var(--phil-border-lg) solid var(--phil-color-brand-border-loud);
    }

    mark {
        color: var(--phil-color-warning-on-quiet);
        background-color: var(--phil-color-warning-fill-quiet);
    }

    dt {
        font-weight: var(--phil-weight-medium);
    }

    dd {
        margin-inline-start: 0;
        margin-block-end: var(--phil-space-xs);
    }

    kbd {
        padding-block: var(--phil-space-3xs);
        padding-inline: var(--phil-space-2xs);
        color: var(--phil-color-text);
        background-color: var(--phil-color-surface-raised);
        border: var(--phil-border-sm) solid var(--phil-color-border);
        border-block-end-width: var(--phil-border-md);
        border-radius: var(--phil-radius-sm);
    }

    abbr[title] {
        text-decoration: underline dotted;
        text-underline-offset: 0.15em;
        cursor: help;
    }

    /* An important declaration of this first layer wins over every later one: `hidden` hides, whatever the display a
       class sets. `until-found` is left to the browser, which needs the box. */
    [hidden]:not([hidden="until-found"]) {
        display: none !important;
    }

    /*
     * Forms. A label over its control, a control the height of a touch target, the border of the loud neutral (3:1 on the
     * surface) and the validity read from the browser or from aria-invalid. The native check boxes, radios, sliders and
     * pickers are kept and take the brand through `accent-color` and the scheme through `color-scheme`.
     */
    fieldset {
        min-inline-size: 0;
        margin-inline: 0;
        margin-block: 0 var(--phil-space-md);
        padding: var(--phil-space-md);
        border: var(--phil-border-sm) solid var(--phil-color-border);
        border-radius: var(--phil-radius-md);
    }

    legend {
        padding-inline: var(--phil-space-2xs);
        font-weight: var(--phil-weight-medium);
    }

    label {
        display: block;
        margin-block-end: var(--phil-space-2xs);
        font-weight: var(--phil-weight-medium);
    }

    /* A control inside its label: the box first, the text beside it, the whole line a touch target. */
    label:has(> input:is([type="checkbox"], [type="radio"])) {
        display: flex;
        align-items: center;
        gap: var(--phil-space-xs);
        min-block-size: var(--phil-control-md);
        margin-block-end: 0;
        font-weight: var(--phil-weight-regular);
    }

    label:has(> :disabled) {
        cursor: not-allowed;
    }

    input,
    select,
    textarea,
    button {
        font: inherit;
    }

    input:not([type="checkbox"], [type="radio"], [type="range"], [type="color"], [type="file"], [type="submit"], [type="button"], [type="reset"], [type="image"], [type="hidden"]),
    select,
    textarea {
        --phil-control-border: var(--phil-color-neutral-border-loud);

        display: block;
        inline-size: 100%;
        min-block-size: var(--phil-control-size, var(--phil-control-md));
        padding-block: var(--phil-space-2xs);
        padding-inline: var(--phil-control-padding, var(--phil-space-sm));
        color: var(--phil-color-text);
        background-color: var(--phil-color-surface);
        border: var(--phil-border-sm) solid var(--phil-control-border);
        border-radius: var(--phil-radius-md);
        transition: border-color var(--phil-duration-sm) var(--phil-ease-out);
    }

    textarea {
        min-block-size: calc(var(--phil-control-md) * 2);
        resize: block;
    }

    select[multiple] {
        padding-block: var(--phil-space-2xs);
    }

    ::placeholder {
        color: var(--phil-color-text-muted);
        opacity: 1;
    }

    input:is([type="checkbox"], [type="radio"]) {
        flex: none;
        inline-size: var(--phil-check-size);
        block-size: var(--phil-check-size);
        margin: 0;
        cursor: pointer;
    }

    input[type="range"] {
        display: block;
        inline-size: 100%;
        min-block-size: var(--phil-control-md);
        margin: 0;
        cursor: pointer;
    }

    input[type="color"] {
        display: block;
        inline-size: calc(var(--phil-control-md) * 1.5);
        block-size: var(--phil-control-md);
        padding: var(--phil-space-2xs);
        background-color: var(--phil-color-surface);
        border: var(--phil-border-sm) solid var(--phil-color-neutral-border-loud);
        border-radius: var(--phil-radius-md);
        cursor: pointer;
    }

    input[type="file"] {
        display: block;
        inline-size: 100%;
        min-block-size: var(--phil-control-md);
    }

    ::file-selector-button {
        min-block-size: var(--phil-control-md);
        margin-inline-end: var(--phil-space-sm);
        padding-inline: var(--phil-space-md);
        font: inherit;
        font-weight: var(--phil-weight-medium);
        color: var(--phil-color-neutral-on-quiet);
        background-color: var(--phil-color-neutral-fill-quiet);
        border: var(--phil-border-sm) solid var(--phil-color-neutral-border-quiet);
        border-radius: var(--phil-radius-md);
        cursor: pointer;
    }

    :is(input, select, textarea):user-valid {
        --phil-control-border: var(--phil-color-success-border-loud);
    }

    /* The wider ring says it without the colour; the message goes beside the control. */
    :is(input, select, textarea):user-invalid,
    :is(input, select, textarea)[aria-invalid="true"] {
        --phil-control-border: var(--phil-color-danger-border-loud);

        box-shadow: inset 0 0 0 var(--phil-border-sm) var(--phil-control-border);
    }

    :is(input, select, textarea):disabled {
        opacity: var(--phil-opacity-disabled);
        cursor: not-allowed;
    }

    /*
     * The markup of Symfony's form_row: a div holding the label, the errors as a plain list, the widget and the
     * `help-text` paragraph; `label.required` for a required field. Rows are spaced, a single check box comes before its
     * label, and expanded choices, siblings in a div, line up as box and text.
     */
    form div:has(> label, > button, > input[type="submit"]) {
        margin-block-end: var(--phil-space-md);
    }

    form div:has(> input:is([type="checkbox"], [type="radio"])) {
        display: grid;
        grid-template-columns: auto minmax(0, 1fr);
        grid-auto-flow: dense;
        align-items: center;
        gap: var(--phil-space-2xs) var(--phil-space-xs);
    }

    form div:has(> input:is([type="checkbox"], [type="radio"])) > input {
        grid-column: 1;
    }

    form div:has(> input:is([type="checkbox"], [type="radio"])) > label {
        grid-column: 2;
        margin-block-end: 0;
        font-weight: var(--phil-weight-regular);
    }

    form div:has(> input:is([type="checkbox"], [type="radio"])) > :not(input, label) {
        grid-column: 1 / -1;
    }

    label.required::after {
        content: " *" / "";
        color: var(--phil-color-danger-on-quiet);
    }

    form div > label + ul:not([class], [role]),
    form div > ul:first-child:not([class], [role]) {
        margin-block: 0 var(--phil-space-2xs);
        padding-inline-start: 0;
        list-style: none;
        font-size: var(--phil-text-sm);
        color: var(--phil-color-danger-on-quiet);
    }

    .help-text {
        margin-block: var(--phil-space-2xs) 0;
        font-size: var(--phil-text-sm);
        color: var(--phil-color-text-muted);
    }

    /*
     * Buttons. A button is solid and in the brand until a class says otherwise. It reads the six colours of its tone and
     * sets them on itself, so one inside a coloured notice keeps its own; `isDanger` and the other tones replace them.
     */
    button,
    input:is([type="submit"], [type="button"], [type="reset"]),
    [role="button"],
    .isButton {
        --phil-tone-fill-quiet: var(--phil-color-brand-fill-quiet);
        --phil-tone-fill-loud: var(--phil-color-brand-fill-loud);
        --phil-tone-border-quiet: var(--phil-color-brand-border-quiet);
        --phil-tone-border-loud: var(--phil-color-brand-border-loud);
        --phil-tone-on-quiet: var(--phil-color-brand-on-quiet);
        --phil-tone-on-loud: var(--phil-color-brand-on-loud);
        --phil-button-fill: var(--phil-tone-fill-loud);
        --phil-button-fill-hover: color-mix(in oklch, var(--phil-button-fill), var(--phil-color-text) var(--phil-mix-hover));
        --phil-button-fill-active: color-mix(in oklch, var(--phil-button-fill), var(--phil-color-text) var(--phil-mix-active));
        --phil-button-on: var(--phil-tone-on-loud);
        --phil-button-border: transparent;

        display: inline-flex;
        align-items: center;
        justify-content: center;
        gap: var(--phil-space-xs);
        min-block-size: var(--phil-control-size, var(--phil-control-md));
        padding-block: var(--phil-space-2xs);
        padding-inline: var(--phil-control-padding, var(--phil-space-md));
        font: inherit;
        font-weight: var(--phil-weight-medium);
        line-height: var(--phil-leading-tight);
        color: var(--phil-button-on);
        text-align: center;
        text-decoration: none;
        background-color: var(--phil-button-fill);
        border: var(--phil-border-sm) solid var(--phil-button-border);
        border-radius: var(--phil-radius-md);
        cursor: pointer;
        touch-action: manipulation;
        transition:
            background-color var(--phil-duration-sm) var(--phil-ease-out),
            border-color var(--phil-duration-sm) var(--phil-ease-out);
    }

    :is(button, input:is([type="submit"], [type="button"], [type="reset"]), [role="button"], .isButton):hover:not(:disabled, [aria-disabled="true"]) {
        background-color: var(--phil-button-fill-hover);
    }

    :is(button, input:is([type="submit"], [type="button"], [type="reset"]), [role="button"], .isButton):active:not(:disabled, [aria-disabled="true"]) {
        background-color: var(--phil-button-fill-active);
    }

    :is(button, input:is([type="submit"], [type="button"], [type="reset"]), [role="button"], .isButton):is(:disabled, [aria-disabled="true"]) {
        opacity: var(--phil-opacity-disabled);
        cursor: not-allowed;
    }

    /* Tables: a line under each row, start-aligned cells, tabular figures. A table wider than its box scrolls inside a
       figure. */
    table {
        inline-size: 100%;
        border-collapse: collapse;
        font-variant-numeric: tabular-nums;
    }

    caption {
        margin-block-end: var(--phil-space-xs);
        font-size: var(--phil-text-sm);
        color: var(--phil-color-text-muted);
        text-align: start;
    }

    th,
    td {
        padding-block: var(--phil-space-xs);
        padding-inline: var(--phil-space-sm);
        text-align: start;
        vertical-align: top;
        border-block-end: var(--phil-border-sm) solid var(--phil-color-border);
    }

    th {
        font-weight: var(--phil-weight-medium);
    }

    thead th {
        border-block-end: var(--phil-border-md) solid var(--phil-color-neutral-border-loud);
    }

    tfoot :is(th, td) {
        font-weight: var(--phil-weight-medium);
        border-block-start: var(--phil-border-md) solid var(--phil-color-neutral-border-loud);
        border-block-end: 0;
    }

    figure:has(> table) {
        overflow-x: auto;
    }

    /*
     * Dialogs. Modal or not, centred by the browser, at most the height of the screen and scrolling inside. They and the
     * backdrop fade in and out where the browser can animate the top layer; elsewhere they simply appear. A modal dialog
     * holds the page still behind it.
     */
    dialog {
        max-inline-size: min(var(--phil-dialog-size, var(--phil-panel-md)), calc(100vw - var(--phil-space-xl)));
        max-block-size: calc(100dvh - var(--phil-space-xl));
        padding: var(--phil-space-lg);
        overflow: auto;
        overscroll-behavior: contain;
        color: var(--phil-color-text);
        background-color: var(--phil-color-surface);
        border: var(--phil-border-sm) solid var(--phil-color-border);
        border-radius: var(--phil-radius-lg);
        box-shadow: var(--phil-shadow-lg);
        opacity: 0;
        transition:
            opacity var(--phil-duration-sm) var(--phil-ease-out),
            translate var(--phil-duration-sm) var(--phil-ease-out),
            overlay var(--phil-duration-sm) allow-discrete,
            display var(--phil-duration-sm) allow-discrete;
    }

    dialog[open] {
        opacity: 1;
    }

    @starting-style {
        dialog[open] {
            opacity: 0;
        }
    }

    dialog:not(:modal) {
        z-index: var(--phil-z-overlay);
    }

    dialog > :last-child {
        margin-block-end: 0;
    }

    dialog > footer {
        display: flex;
        flex-wrap: wrap;
        justify-content: flex-end;
        gap: var(--phil-space-xs);
        margin-block-start: var(--phil-space-lg);
    }

    dialog::backdrop {
        background-color: var(--phil-color-backdrop);
        opacity: 0;
        transition:
            opacity var(--phil-duration-sm) var(--phil-ease-out),
            overlay var(--phil-duration-sm) allow-discrete,
            display var(--phil-duration-sm) allow-discrete;
    }

    dialog[open]::backdrop {
        opacity: 1;
    }

    @starting-style {
        dialog[open]::backdrop {
            opacity: 0;
        }
    }

    html:has(dialog:modal) {
        overflow: hidden;
        scrollbar-gutter: stable;
    }

    /* Popovers: a panel over the page, centred by the browser until a controller places it and sets data-placed. */
    [popover] {
        max-inline-size: min(var(--phil-panel-md), calc(100vw - var(--phil-space-xl)));
        padding: var(--phil-space-md);
        color: var(--phil-color-text);
        background-color: var(--phil-color-surface);
        border: var(--phil-border-sm) solid var(--phil-color-border);
        border-radius: var(--phil-radius-lg);
        box-shadow: var(--phil-shadow-lg);
    }

    [popover][data-placed] {
        position: fixed;
        inset: auto;
        margin: 0;
    }

    /* Disclosures. A summary is a touch target; where the browser can animate to height: auto, the content opens. */
    details {
        margin-block-end: var(--phil-space-md);
    }

    summary {
        padding-block: var(--phil-space-xs);
        font-weight: var(--phil-weight-medium);
        cursor: pointer;
    }

    @supports selector(::details-content) {
        @media (prefers-reduced-motion: no-preference) {
            details {
                interpolate-size: allow-keywords;
            }

            details::details-content {
                block-size: 0;
                overflow: clip;
                transition:
                    block-size var(--phil-duration-md) var(--phil-ease-out),
                    content-visibility var(--phil-duration-md) allow-discrete;
            }

            details[open]::details-content {
                block-size: auto;
            }
        }

        [data-motion="reduce"] details::details-content {
            transition-duration: 0.01ms !important;
        }
    }

    /* Navigation: the lists of a nav lose their bullets and lie in a row, its links are touch targets, and the current
       page is said in weight as well as in colour. */
    nav :is(ul, ol) {
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        gap: var(--phil-space-2xs) var(--phil-space-md);
        margin-block: 0;
        padding-inline-start: 0;
        list-style: none;
    }

    nav a {
        display: inline-flex;
        align-items: center;
        min-block-size: var(--phil-control-md);
    }

    nav a[aria-current]:not([aria-current="false"]) {
        font-weight: var(--phil-weight-bold);
        color: var(--phil-color-text);
    }

    /* Gauges: the vendor pseudo-elements each in a rule of their own, since a browser drops a whole list for one it does
       not know. */
    progress,
    meter {
        display: block;
        inline-size: 100%;
        block-size: var(--phil-space-xs);
        overflow: hidden;
        background: var(--phil-color-surface-raised);
        border: 0;
        border-radius: var(--phil-radius-full);
        appearance: none;
    }

    progress {
        color: var(--phil-color-brand-fill-loud);
    }

    ::-webkit-progress-bar,
    ::-webkit-meter-bar {
        background: var(--phil-color-surface-raised);
        border: 0;
        border-radius: var(--phil-radius-full);
    }

    ::-webkit-progress-value {
        background: var(--phil-color-brand-fill-loud);
        border-radius: var(--phil-radius-full);
    }

    ::-moz-progress-bar {
        background: var(--phil-color-brand-fill-loud);
        border-radius: var(--phil-radius-full);
    }

    ::-webkit-meter-optimum-value {
        background: var(--phil-color-success-fill-loud);
    }

    ::-webkit-meter-suboptimum-value {
        background: var(--phil-color-warning-fill-loud);
    }

    ::-webkit-meter-even-less-good-value {
        background: var(--phil-color-danger-fill-loud);
    }

    :-moz-meter-optimum::-moz-meter-bar {
        background: var(--phil-color-success-fill-loud);
    }

    :-moz-meter-sub-optimum::-moz-meter-bar {
        background: var(--phil-color-warning-fill-loud);
    }

    :-moz-meter-sub-sub-optimum::-moz-meter-bar {
        background: var(--phil-color-danger-fill-loud);
    }

    /* An unknown progress is a bar that travels, and a still bar under reduced motion. */
    progress:indeterminate {
        background-image: linear-gradient(to right, var(--phil-color-surface-raised) 30%, var(--phil-color-brand-fill-loud) 50%, var(--phil-color-surface-raised) 70%);
        background-size: 200% 100%;
        animation: phil-progress calc(var(--phil-duration-loop) * 2) linear infinite;
    }

    progress:indeterminate::-webkit-progress-bar {
        background: transparent;
    }

    progress:indeterminate::-moz-progress-bar {
        background: transparent;
    }

    @keyframes phil-progress {
        from {
            background-position: 200% 0;
        }

        to {
            background-position: -200% 0;
        }
    }

    /* Motion is a choice of the reader: under prefers-reduced-motion, or when the root carries data-motion="reduce",
       animations end at once. Important declarations of an earlier layer win over every later layer and over unlayered
       styles, so a site cannot bring motion back by accident. */
    @media (prefers-reduced-motion: reduce) {
        *,
        *::before,
        *::after,
        ::backdrop {
            animation-duration: 0.01ms !important;
            animation-iteration-count: 1 !important;
            transition-duration: 0.01ms !important;
            scroll-behavior: auto !important;
        }
    }

    [data-motion="reduce"],
    [data-motion="reduce"] *,
    [data-motion="reduce"] *::before,
    [data-motion="reduce"] *::after,
    [data-motion="reduce"] ::backdrop {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }

    /* Contrast is a choice of the reader too: under prefers-contrast: more, or when the root carries
       data-contrast="more", muted text takes the text colour and borders the loud neutral one. */
    @media (prefers-contrast: more) {
        :root {
            --phil-color-text-muted: var(--phil-color-text);
            --phil-color-border: var(--phil-color-neutral-border-loud);
        }
    }

    [data-contrast="more"] {
        --phil-color-text-muted: var(--phil-color-text);
        --phil-color-border: var(--phil-color-neutral-border-loud);
    }

    /* The text size the reader chose, on the root: every size of the scale is in rem, so the whole page follows. */
    html[data-text-size="large"] {
        font-size: 112.5%;
    }

    html[data-text-size="larger"] {
        font-size: 125%;
    }
}
@layer phil.layout {
    /*
     * phil-ux — the layout utilities.
     *
     * Each class says what the element is: `isStack`, `isCluster`, `isCenter`, `isSplit`, `isCover`. They hold no
     * colour and read their gaps from tokens, each with a custom property of its own to change one element.
     */

    /* A column of children with the same gap between them. */
    .isStack {
        display: flex;
        flex-direction: column;
        gap: var(--phil-stack-gap, var(--phil-space-md));
    }

    .isStack > * {
        margin-block: 0;
    }

    /* A row of children that wraps, with the same gap both ways. */
    .isCluster {
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        gap: var(--phil-cluster-gap, var(--phil-space-sm));
    }

    /* As many columns as fit, each at least `--phil-grid-min` wide. */
    .isGrid {
        display: grid;
        grid-template-columns: repeat(auto-fill, minmax(min(var(--phil-grid-min, var(--phil-panel-sm)), 100%), 1fr));
        gap: var(--phil-grid-gap, var(--phil-space-md));
    }

    /* A block centred in its container, no wider than a readable line. */
    .isCenter {
        box-sizing: content-box;
        max-inline-size: var(--phil-center-measure, var(--phil-measure));
        margin-inline: auto;
        padding-inline: var(--phil-center-gutter, 0);
    }

    .isCenter.isNarrow,
    .isNarrow {
        max-inline-size: var(--phil-center-measure, var(--phil-measure-narrow));
    }

    /* Two columns side by side from 48rem, stacked below; the first child comes first in both. */
    .isSplit {
        display: grid;
        grid-template-columns: minmax(0, 1fr);
        gap: var(--phil-split-gap, 0);
    }

    @media (min-width: 48rem) {
        .isSplit {
            grid-template-columns: repeat(2, minmax(0, 1fr));
        }
    }

    /* At least the height of the screen; dvh follows the browser bars on phones. */
    .isFullHeight {
        min-block-size: 100vh;
        min-block-size: 100dvh;
    }

    /* A column whose first and last children sit at its edges and whose `isCoverBody` takes the space left, its content
       centred both ways. */
    .isCover {
        display: flex;
        flex-direction: column;
        gap: var(--phil-cover-gap, var(--phil-space-md));
        padding: var(--phil-cover-padding, var(--phil-space-lg));
    }

    .isCover > .isCoverBody {
        display: grid;
        flex: 1 1 auto;
        place-items: center;
    }

    /* Media that fills its cell: an image, or a picture's image, covers the box and keeps its ratio. Below 48rem, the box
       takes 40 % of the screen's height. */
    .isBleed {
        position: relative;
        min-block-size: var(--phil-bleed-min-size, 40vh);
        margin: 0;
        overflow: hidden;
    }

    .isBleed img,
    .isBleed video {
        position: absolute;
        inset: 0;
        inline-size: 100%;
        block-size: 100%;
        max-inline-size: none;
        object-fit: cover;
    }

    @media (min-width: 48rem) {
        .isSplit > .isBleed {
            min-block-size: auto;
        }
    }

    /* The media enters with a slanted clip, once, beside the other column; never under prefers-reduced-motion. */
    @media (min-width: 48rem) and (prefers-reduced-motion: no-preference) {
        .isBleed.hasEntrance {
            clip-path: polygon(100% 0%, 100% 0%, 100% 100%, 100% 100%);
            animation: phil-entrance var(--phil-duration-lg) var(--phil-ease-out) forwards;
        }
    }

    @keyframes phil-entrance {
        to {
            clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 10% 100%);
        }
    }
}
@layer phil.components {
    /*
     * phil-ux — the components the classless base cannot guess. Each class says what the element is or has. The tones and
     * the sizes come last, so `isDanger` or `isSmall` apply to any component that reads them.
     */

    /* A short label above a heading: "Coming soon", a category, a date. Muted and small, never the heading itself. */
    .isEyebrow {
        margin-block: 0 var(--phil-space-xs);
        font-size: var(--phil-text-sm);
        font-weight: var(--phil-weight-medium);
        letter-spacing: 0.02em;
        color: var(--phil-color-text-muted);
    }

    /* A display headline, larger than h1 and fluid between the 2xl and 4xl text sizes. */
    .isHeadline {
        font-size: clamp(var(--phil-text-2xl), 1rem + 4vw, var(--phil-text-4xl));
        line-height: var(--phil-leading-tight);
    }

    /*
     * The clip reveal: the words of the headline stacked in one box, the current one shown. Without JavaScript, the
     * current word stands still. The animated-headline controller adds `isRevealing`, then sets `--phil-reveal-size`
     * to close the box to its caret and open it on the next word; `isPulsing` blinks the caret between two words. Once
     * the words have played through, the controller removes both and the box is back to its natural width.
     */
    .isReveal {
        --phil-reveal-caret: 0.06em;

        display: inline-grid;
        vertical-align: bottom;
        inline-size: var(--phil-reveal-size, auto);
        overflow: hidden;
        border-inline-end: var(--phil-reveal-caret) solid transparent;
        transition: inline-size var(--phil-reveal-duration, var(--phil-duration-lg)) var(--phil-ease-in-out);
    }

    .isReveal > * {
        grid-area: 1 / 1;
        justify-self: start;
        white-space: nowrap;
        visibility: hidden;
    }

    .isReveal > .isCurrent {
        visibility: visible;
    }

    .isReveal.isRevealing {
        border-inline-end-color: currentcolor;
    }

    .isReveal.isPulsing {
        animation: phil-caret 1s steps(1, end) infinite;
    }

    @keyframes phil-caret {
        50% {
            border-inline-end-color: transparent;
        }
    }

    /*
     * The pause/play button of a cycling headline, rendered `hidden` and shown by the controller: small text in the
     * heading's line, its name the visible label.
     */
    .isRevealToggle {
        margin-inline-start: var(--phil-space-sm);
        min-block-size: 0;
        padding: var(--phil-space-2xs) var(--phil-space-xs);
        vertical-align: middle;
        font: inherit;
        font-size: var(--phil-text-sm);
        font-weight: var(--phil-weight-medium);
        letter-spacing: normal;
        line-height: var(--phil-leading-tight);
        color: var(--phil-color-text);
        background: var(--phil-color-surface-raised);
        border: var(--phil-border-sm) solid var(--phil-color-border);
        border-radius: var(--phil-radius-md);
        cursor: pointer;
    }

    .isRevealToggle[hidden] {
        display: none;
    }

    /*
     * A reaction: a target wide and tall enough for a finger, whatever it holds, and what it says when it is the viewer's.
     */
    .isReaction {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        gap: var(--phil-space-2xs);
        min-block-size: var(--phil-control-md);
        min-inline-size: var(--phil-control-md);
        padding-block: 0;
        padding-inline: var(--phil-space-xs);
        font: inherit;
        font-size: var(--phil-text-sm);
        color: var(--phil-color-text);
        background: var(--phil-color-surface-raised);
        border: var(--phil-border-sm) solid var(--phil-color-border);
        border-radius: var(--phil-radius-md);
    }

    button.isReaction,
    summary.isReaction {
        cursor: pointer;
    }

    .isReaction[aria-pressed="true"] {
        color: var(--phil-color-brand-on-quiet);
        background: var(--phil-color-brand-fill-quiet);
        border-color: var(--phil-color-brand-border-loud);
    }

    .isReaction[hidden] {
        display: none;
    }

    .isReaction img {
        inline-size: 1.25em;
        block-size: 1.25em;
    }

    /* The other reactions, laid over what follows. */
    .isReactionPicker {
        position: relative;
        margin-block-end: 0;
    }

    .isReactionPickerPanel {
        position: absolute;
        z-index: var(--phil-z-overlay);
        inline-size: min(var(--phil-panel-sm), calc(100vw - var(--phil-space-xl)));
        padding: var(--phil-space-xs);
        background: var(--phil-color-surface-raised);
        border: var(--phil-border-sm) solid var(--phil-color-border);
        border-radius: var(--phil-radius-md);
        box-shadow: var(--phil-shadow-md);
    }

    @media (prefers-reduced-motion: reduce) {
        .isReveal,
        .isReveal.isRevealing {
            inline-size: auto;
            border-inline-end-color: transparent;
            transition: none;
            animation: none;
        }
    }

    /* A logo: an image at the logo's height, its width from its ratio. */
    .isLogo {
        display: inline-block;
    }

    .isLogo img {
        inline-size: auto;
        block-size: var(--phil-logo-size, 3rem);
    }

    /* An image of the content: a block as wide as its own width allows, never wider than its container, its height from
       its ratio once width and height are given. */
    .isImage {
        display: block;
        max-inline-size: 100%;
        block-size: auto;
    }

    /* Its layout: at its width whatever the container, or across the whole container; constrained by default, never wider
       than its width. */
    .isImage.hasLayoutFixed {
        max-inline-size: none;
    }

    .isImage.hasLayoutFullWidth {
        inline-size: 100%;
    }

    /* The ratio of its box, the picture fitted into it; hasRatio reads --phil-image-ratio, which a class of the site sets. */
    .isImage.hasRatio16x9 {
        aspect-ratio: 16 / 9;
    }

    .isImage.hasRatio4x3 {
        aspect-ratio: 4 / 3;
    }

    .isImage.hasRatio3x2 {
        aspect-ratio: 3 / 2;
    }

    .isImage.hasRatio1x1 {
        aspect-ratio: 1;
    }

    .isImage.hasRatio21x9 {
        aspect-ratio: 21 / 9;
    }

    .isImage.hasRatio3x4 {
        aspect-ratio: 3 / 4;
    }

    .isImage.hasRatio2x3 {
        aspect-ratio: 2 / 3;
    }

    .isImage.hasRatio9x16 {
        aspect-ratio: 9 / 16;
    }

    .isImage.hasRatio {
        aspect-ratio: var(--phil-image-ratio, auto);
    }

    /* How the picture fills its box, and where it is cropped. */
    .isImage.hasFitCover {
        object-fit: cover;
    }

    .isImage.hasFitContain {
        object-fit: contain;
    }

    .isImage.hasPositionCenter {
        object-position: center;
    }

    .isImage.hasPositionTop {
        object-position: top;
    }

    .isImage.hasPositionBottom {
        object-position: bottom;
    }

    .isImage.hasPositionLeft {
        object-position: left;
    }

    .isImage.hasPositionRight {
        object-position: right;
    }

    /* Under the caption of an image, each on its own line: who made it, its license and how it was made. */
    .isCredit,
    .isLicense,
    .isCreation {
        display: block;
    }

    /* The colour an image's box is painted while it loads, a quiet tone of the palette in clear and in dark; hasPlaceholder
       reads --phil-image-placeholder, which a class of the site sets. It stays behind a transparent picture once loaded:
       for photographs, not for logos. */
    .isImage.hasPlaceholderNeutral {
        background-color: var(--phil-color-surface-raised);
    }

    .isImage.hasPlaceholderBrand {
        background-color: var(--phil-color-placeholder-brand);
    }

    .isImage.hasPlaceholderRed {
        background-color: var(--phil-color-placeholder-red);
    }

    .isImage.hasPlaceholderGreen {
        background-color: var(--phil-color-placeholder-green);
    }

    .isImage.hasPlaceholderAmber {
        background-color: var(--phil-color-placeholder-amber);
    }

    .isImage.hasPlaceholderBlue {
        background-color: var(--phil-color-placeholder-blue);
    }

    .isImage.hasPlaceholderViolet {
        background-color: var(--phil-color-placeholder-violet);
    }

    .isImage.hasPlaceholder {
        background-color: var(--phil-image-placeholder, var(--phil-color-surface-raised));
    }

    /* The blurred preview the image-preview controller paints behind an image while it loads, through the CSSOM, never a
       style attribute: laid out as the picture will be. */
    .isImage.hasPreview {
        background-repeat: no-repeat;
        background-position: center;
        background-size: cover;
    }

    .isImage.hasPreview.hasFitContain {
        background-size: contain;
    }

    /* A frame drawn from the tokens, on the image itself, never on its caption: hasRounded rounds its corners by
       --phil-image-radius, the medium radius by default; hasFramed draws a line --phil-image-frame-width wide around it, of
       --phil-image-frame-color, the colour of the borders by default; hasRaised lifts it from the page by
       --phil-image-shadow, the medium shadow by default. They go together. */
    .isImage.hasRounded {
        border-radius: var(--phil-image-radius, var(--phil-radius-md));
    }

    .isImage.hasFramed {
        border: var(--phil-image-frame-width, var(--phil-border-sm)) solid var(--phil-image-frame-color, var(--phil-color-border));
    }

    .isImage.hasRaised {
        box-shadow: var(--phil-image-shadow, var(--phil-shadow-md));
    }

    /* A token, a pawn or an avatar: the picture covering a square box cropped to a disc, inside a ring
       --phil-image-token-width wide, of --phil-image-token-color, the loud line of the brand by default. Where colours are
       forced, the ring takes the colour of the text. */
    .isImage.hasToken {
        aspect-ratio: 1;
        object-fit: cover;
        border: var(--phil-image-token-width, var(--phil-border-lg)) solid var(--phil-image-token-color, var(--phil-color-brand-border-loud));
        border-radius: 50%;
    }

    /* Pixel art, enlarged without blurring its pixels: each drawn as a square of its own colour. */
    .isImage.hasPixelated {
        image-rendering: pixelated;
    }

    /* The name of the image in a view transition, so that it moves from one page to the next as one: --phil-image-transition,
       set by a class of the site, or, where the browser types attr(), Chromium 133 and later, the data-transition of the
       image. A name stands once in a page. */
    .isImage.hasTransition {
        view-transition-name: var(--phil-image-transition, none);
    }

    @supports (view-transition-name: attr(data-transition type(<custom-ident>), none)) {
        .isImage.hasTransition[data-transition] {
            view-transition-name: attr(data-transition type(<custom-ident>), var(--phil-image-transition, none));
        }
    }

    /* The long description of an image: a disclosure under it, before its caption. */
    .isDescription {
        margin-block: var(--phil-space-xs) 0;
    }

    .isDescription > p {
        margin: 0;
    }

    /* A gallery: its figures in a grid of columns as wide as its container allows, --phil-gallery-column at least, the
       gap growing with the container; each image fills its cell. */
    .isGallery {
        container-type: inline-size;
    }

    .isGallery > ul {
        display: grid;
        grid-template-columns: repeat(auto-fill, minmax(min(100%, var(--phil-gallery-column, 12rem)), 1fr));
        gap: var(--phil-gallery-gap, var(--phil-space-sm));
        margin: 0;
        padding: 0;
        list-style: none;
    }

    .isGallery > ul > li {
        min-inline-size: 0;
        margin: 0;
    }

    .isGallery figure {
        margin: 0;
    }

    .isGallery .isImage {
        inline-size: 100%;
    }

    @container (min-width: 48rem) {
        .isGallery > ul {
            gap: var(--phil-gallery-gap, var(--phil-space-md));
        }
    }

    /* A deferred gallery: the browser skips the rendering of the figures off screen, each kept meanwhile at the ratio of
       its data-width and data-height, read by attr() where the browser types it, else a square cell; once rendered, its
       size is remembered. The cell reaches into the gap by the width of the focus ring, which its paint containment would
       otherwise clip. */
    .isGallery.isDeferred > ul > li {
        --phil-gallery-ring: calc(var(--phil-focus-width) + var(--phil-focus-offset));

        box-sizing: content-box;
        margin: calc(-1 * var(--phil-gallery-ring));
        padding: var(--phil-gallery-ring);
        content-visibility: auto;
        contain-intrinsic-size: auto var(--phil-gallery-column, 12rem);
    }

    @supports (aspect-ratio: attr(data-width type(<number>), 1) / 1) {
        .isGallery.isDeferred > ul > li[data-width][data-height] {
            aspect-ratio: attr(data-width type(<number>), 1) / attr(data-height type(<number>), 1);
            contain-intrinsic-size: auto none;
        }
    }

    /* A lightbox: its thumbnail, a button without a fill that keeps the focus ring, and its image shown whole in a dialog as
       wide as the screen allows, the close button in its corner. */
    .isLightbox {
        display: block;
    }

    .isLightboxTrigger {
        --phil-button-fill: transparent;
        --phil-button-fill-hover: transparent;
        --phil-button-fill-active: transparent;

        display: block;
        min-block-size: 0;
        padding: 0;
        color: inherit;
        border: 0;
        border-radius: var(--phil-radius-md);
        cursor: zoom-in;
    }

    /* Where colours are forced, the thumbnail's button keeps the frame of a button of the system. */
    @media (forced-colors: active) {
        .isLightboxTrigger {
            border: var(--phil-border-sm) solid ButtonBorder;
        }
    }

    .isLightboxDialog {
        --phil-dialog-size: 100vw;

        padding: var(--phil-space-md);
    }

    .isLightboxDialog .isImage {
        inline-size: auto;
        max-block-size: calc(100dvh - var(--phil-space-4xl));
        margin-inline: auto;
    }

    .isLightboxClose {
        --phil-tone-fill-quiet: var(--phil-color-neutral-fill-quiet);
        --phil-tone-on-quiet: var(--phil-color-neutral-on-quiet);
        --phil-button-fill: var(--phil-color-surface);
        --phil-button-fill-hover: var(--phil-tone-fill-quiet);
        --phil-button-fill-active: var(--phil-tone-fill-quiet);
        --phil-button-on: var(--phil-tone-on-quiet);

        position: absolute;
        inset-block-start: var(--phil-space-xs);
        inset-inline-end: var(--phil-space-xs);
        min-inline-size: var(--phil-control-md);
        padding-inline: 0;
    }

    /* A caption laid over the foot of its image, on a veil dark enough for its light text whatever the picture: the figure
       stacks the image and the caption in one cell, as wide as the image; a long description stays under them. */
    .hasOverlayCaption {
        display: grid;
        inline-size: fit-content;
        max-inline-size: 100%;
    }

    .hasOverlayCaption:has(> .hasLayoutFullWidth, > picture > .hasLayoutFullWidth) {
        inline-size: auto;
    }

    .hasOverlayCaption > :first-child,
    .hasOverlayCaption > figcaption {
        grid-area: 1 / 1;
    }

    .hasOverlayCaption > figcaption {
        align-self: end;
        z-index: 1;
        margin: 0;
        padding: var(--phil-space-xs) var(--phil-space-sm);
        color: var(--phil-color-on-veil);
        background-color: var(--phil-color-veil);
    }

    .hasOverlayCaption > figcaption a {
        color: inherit;
    }

    /* A gallery laid out as a carousel: its figures in a row that scrolls and snaps, the keyboard scrolling it once it has
       the focus; where the browser draws them, a marker per figure under the row and a button on each side, half way down
       the row, named by the attributes of the list. Elsewhere, the row scrolls alone. */
    .isGallery.hasLayoutCarousel {
        --phil-gallery-markers: calc(var(--phil-space-sm) + 2 * var(--phil-space-xs));

        position: relative;
    }

    .isGallery.hasLayoutCarousel > ul {
        grid-template-columns: none;
        grid-auto-columns: minmax(min(85%, var(--phil-gallery-column, 12rem)), 1fr);
        grid-auto-flow: column;
        padding-block-end: var(--phil-space-xs);
        overflow-x: auto;
        overscroll-behavior-x: contain;
        scroll-snap-type: x mandatory;
        scroll-marker-group: after;
    }

    .isGallery.hasLayoutCarousel > ul > li {
        scroll-snap-align: start;
    }

    .isGallery.hasLayoutCarousel > ul > li::scroll-marker {
        content: "" / attr(data-marker);
        inline-size: var(--phil-space-sm);
        block-size: var(--phil-space-sm);
        border: var(--phil-border-sm) solid var(--phil-color-neutral-border-loud);
        border-radius: 50%;
    }

    .isGallery.hasLayoutCarousel > ul > li::scroll-marker:target-current {
        background-color: var(--phil-color-text);
    }

    .isGallery.hasLayoutCarousel > ul::scroll-marker-group {
        display: flex;
        gap: var(--phil-space-xs);
        justify-content: center;
        block-size: var(--phil-gallery-markers);
        padding-block: var(--phil-space-xs);
        box-sizing: border-box;
    }

    .isGallery.hasLayoutCarousel > ul::scroll-button(*) {
        position: absolute;
        inset-block-start: calc(50% - var(--phil-control-md) / 2 - var(--phil-gallery-markers) / 2);
        inline-size: var(--phil-control-md);
        block-size: var(--phil-control-md);
        color: var(--phil-color-on-veil);
        background-color: var(--phil-color-veil);
        border: 0;
        border-radius: 50%;
        cursor: pointer;
    }

    .isGallery.hasLayoutCarousel > ul::scroll-button(*):disabled {
        opacity: 0.4;
        cursor: default;
    }

    .isGallery.hasLayoutCarousel > ul::scroll-button(inline-start) {
        content: "\2039" / attr(data-previous);
        inset-inline-start: var(--phil-space-xs);
    }

    .isGallery.hasLayoutCarousel > ul::scroll-button(inline-end) {
        content: "\203A" / attr(data-next);
        inset-inline-end: var(--phil-space-xs);
    }

    /* Right to left, the button at the start stands on the right and points to it, that at the end on the left. */
    .isGallery.hasLayoutCarousel > ul:dir(rtl)::scroll-button(inline-start) {
        content: "\203A" / attr(data-previous);
    }

    .isGallery.hasLayoutCarousel > ul:dir(rtl)::scroll-button(inline-end) {
        content: "\2039" / attr(data-next);
    }

    /* An image with points of interest: each link stands over the image at its point, its place a class to the nearest five
       per cent, or exactly the typed attr() of its data-x and data-y where the browser reads one. The picture is not
       mirrored right to left, so the list is laid out left to right and each link reads its words in their own direction.
       On a narrow screen, the links read as a list under the image, in the direction of the page. */
    .isHotspots {
        position: relative;
        inline-size: fit-content;
        max-inline-size: 100%;
    }

    .isHotspotsList {
        margin: 0;
        padding: 0;
        list-style: none;
        direction: ltr;
    }

    .isHotspot {
        position: absolute;
        inset-block-start: var(--phil-hotspot-y, 50%);
        inset-inline-start: var(--phil-hotspot-x, 50%);
        translate: -50% -50%;
    }

    .isHotspot > a {
        display: inline-flex;
        gap: var(--phil-space-2xs);
        align-items: center;
        min-block-size: var(--phil-check-size);
        padding-inline: var(--phil-space-xs);
        font-size: var(--phil-text-sm);
        color: var(--phil-color-on-veil);
        white-space: nowrap;
        text-decoration: none;
        unicode-bidi: plaintext;
        background-color: var(--phil-color-veil);
        border-radius: var(--phil-radius-full);
    }

    .isHotspot > a::before {
        content: "";
        inline-size: 0.5em;
        block-size: 0.5em;
        background-color: currentcolor;
        border-radius: 50%;
    }

    .isHotspot > a:hover {
        text-decoration: underline;
    }

    .isHotspot.hasHotspotX0 {
        --phil-hotspot-x: 0%;
    }

    .isHotspot.hasHotspotX5 {
        --phil-hotspot-x: 5%;
    }

    .isHotspot.hasHotspotX10 {
        --phil-hotspot-x: 10%;
    }

    .isHotspot.hasHotspotX15 {
        --phil-hotspot-x: 15%;
    }

    .isHotspot.hasHotspotX20 {
        --phil-hotspot-x: 20%;
    }

    .isHotspot.hasHotspotX25 {
        --phil-hotspot-x: 25%;
    }

    .isHotspot.hasHotspotX30 {
        --phil-hotspot-x: 30%;
    }

    .isHotspot.hasHotspotX35 {
        --phil-hotspot-x: 35%;
    }

    .isHotspot.hasHotspotX40 {
        --phil-hotspot-x: 40%;
    }

    .isHotspot.hasHotspotX45 {
        --phil-hotspot-x: 45%;
    }

    .isHotspot.hasHotspotX50 {
        --phil-hotspot-x: 50%;
    }

    .isHotspot.hasHotspotX55 {
        --phil-hotspot-x: 55%;
    }

    .isHotspot.hasHotspotX60 {
        --phil-hotspot-x: 60%;
    }

    .isHotspot.hasHotspotX65 {
        --phil-hotspot-x: 65%;
    }

    .isHotspot.hasHotspotX70 {
        --phil-hotspot-x: 70%;
    }

    .isHotspot.hasHotspotX75 {
        --phil-hotspot-x: 75%;
    }

    .isHotspot.hasHotspotX80 {
        --phil-hotspot-x: 80%;
    }

    .isHotspot.hasHotspotX85 {
        --phil-hotspot-x: 85%;
    }

    .isHotspot.hasHotspotX90 {
        --phil-hotspot-x: 90%;
    }

    .isHotspot.hasHotspotX95 {
        --phil-hotspot-x: 95%;
    }

    .isHotspot.hasHotspotX100 {
        --phil-hotspot-x: 100%;
    }

    .isHotspot.hasHotspotY0 {
        --phil-hotspot-y: 0%;
    }

    .isHotspot.hasHotspotY5 {
        --phil-hotspot-y: 5%;
    }

    .isHotspot.hasHotspotY10 {
        --phil-hotspot-y: 10%;
    }

    .isHotspot.hasHotspotY15 {
        --phil-hotspot-y: 15%;
    }

    .isHotspot.hasHotspotY20 {
        --phil-hotspot-y: 20%;
    }

    .isHotspot.hasHotspotY25 {
        --phil-hotspot-y: 25%;
    }

    .isHotspot.hasHotspotY30 {
        --phil-hotspot-y: 30%;
    }

    .isHotspot.hasHotspotY35 {
        --phil-hotspot-y: 35%;
    }

    .isHotspot.hasHotspotY40 {
        --phil-hotspot-y: 40%;
    }

    .isHotspot.hasHotspotY45 {
        --phil-hotspot-y: 45%;
    }

    .isHotspot.hasHotspotY50 {
        --phil-hotspot-y: 50%;
    }

    .isHotspot.hasHotspotY55 {
        --phil-hotspot-y: 55%;
    }

    .isHotspot.hasHotspotY60 {
        --phil-hotspot-y: 60%;
    }

    .isHotspot.hasHotspotY65 {
        --phil-hotspot-y: 65%;
    }

    .isHotspot.hasHotspotY70 {
        --phil-hotspot-y: 70%;
    }

    .isHotspot.hasHotspotY75 {
        --phil-hotspot-y: 75%;
    }

    .isHotspot.hasHotspotY80 {
        --phil-hotspot-y: 80%;
    }

    .isHotspot.hasHotspotY85 {
        --phil-hotspot-y: 85%;
    }

    .isHotspot.hasHotspotY90 {
        --phil-hotspot-y: 90%;
    }

    .isHotspot.hasHotspotY95 {
        --phil-hotspot-y: 95%;
    }

    .isHotspot.hasHotspotY100 {
        --phil-hotspot-y: 100%;
    }

    @supports (left: calc(attr(data-x type(<number>), 0) * 1%)) {
        .isHotspot[data-x] {
            --phil-hotspot-x: calc(attr(data-x type(<number>), 50) * 1%);
        }

        .isHotspot[data-y] {
            --phil-hotspot-y: calc(attr(data-y type(<number>), 50) * 1%);
        }
    }

    @media (max-width: 36rem) {
        .isHotspotsList {
            direction: inherit;
            display: flex;
            flex-wrap: wrap;
            gap: var(--phil-space-xs);
            margin-block-start: var(--phil-space-xs);
        }

        .isHotspot {
            position: static;
            translate: none;
        }
    }

    /* A hero: its image covers the whole box, which stands the height of the screen, most of it, or that of its content;
       the content lies over it on a veil dark enough for its light text, at the end of the height by default. */
    .isHero {
        position: relative;
        display: grid;
        align-content: end;
        min-block-size: 20rem;
        padding: var(--phil-space-md);
        overflow: hidden;
    }

    .isHero.hasHeroScreen {
        min-block-size: 100svh;
    }

    .isHero.hasHeroLarge {
        min-block-size: 70svh;
    }

    .isHero.hasHeroStart {
        align-content: start;
    }

    .isHero.hasHeroCenter {
        align-content: center;
    }

    .isHeroMedia {
        position: absolute;
        inset: 0;
    }

    .isHeroMedia .isImage {
        inline-size: 100%;
        max-inline-size: none;
        block-size: 100%;
    }

    .isHeroContent {
        position: relative;
        max-inline-size: 40rem;
        padding: var(--phil-space-md);
        color: var(--phil-color-on-veil);
        background-color: var(--phil-color-veil);
        border-radius: var(--phil-radius-md);
    }

    .isHeroContent a {
        color: inherit;
    }

    .isHeroContent:empty {
        display: none;
    }

    /* A video: as wide as its width allows, never wider than its container, its height from the ratio of its width and
       height while nothing is loaded. */
    .isVideo {
        display: block;
        max-inline-size: 100%;
        block-size: auto;
    }

    @supports (aspect-ratio: attr(width type(<number>), 16) / 9) {
        .isVideo[width][height] {
            aspect-ratio: attr(width type(<number>), 16) / attr(height type(<number>), 9);
        }
    }

    /* Several images under one caption: a grid of columns as wide as the container allows, --phil-figure-column at least,
       or two, three or four on every screen; each image fills its cell. */
    .isFigureGroup {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--phil-figure-column, 10rem)), 1fr));
        gap: var(--phil-space-sm);
        align-items: start;
    }

    .isFigureGroup.hasTwoColumns {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }

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

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

    .isFigureGroup .isImage {
        inline-size: 100%;
    }

    /* The images on paper: a figure never cut between two pages, its caption kept with its image, an image never taller
       than --phil-image-print-height, most of the page, and shown whole unless it covers its box; what lies over an image,
       a caption, the content of a hero or a point, printed with its veil, which a printer would otherwise drop under light
       text; a carousel laid out as the grid of a gallery, every figure printed, without markers or buttons; a deferred
       gallery rendered whole; a lightbox open printed without its close button or backdrop; the canvas of a celebration
       left out. */
    @media print {
        figure,
        .isGallery > ul > li,
        .isLightbox,
        .isHotspots,
        .isHero {
            break-inside: avoid;
        }

        figcaption {
            break-before: avoid;
        }

        .isImage,
        .isLightboxDialog .isImage {
            max-block-size: var(--phil-image-print-height, 85vh);
        }

        .isImage:not(.hasFitCover, .hasToken) {
            object-fit: contain;
        }

        .hasOverlayCaption > figcaption,
        .isHeroContent,
        .isHotspot > a {
            print-color-adjust: exact;
        }

        .isGallery.hasLayoutCarousel > ul {
            grid-template-columns: repeat(auto-fill, minmax(min(100%, var(--phil-gallery-column, 12rem)), 1fr));
            grid-auto-columns: auto;
            grid-auto-flow: row;
            overflow: visible;
            scroll-marker-group: none;
        }

        .isGallery.hasLayoutCarousel > ul::scroll-button(*) {
            content: none;
        }

        .isGallery.isDeferred > ul > li {
            content-visibility: visible;
        }

        .isLightboxClose,
        .isCelebrationCanvas {
            display: none;
        }

        .isLightboxDialog::backdrop {
            background: none;
        }
    }

    /* Long text written by people: a readable measure, a steady rhythm between blocks, headings that breathe. */
    .isProse {
        max-inline-size: var(--phil-measure);
    }

    .isProse > * {
        margin-block: 0;
    }

    .isProse > * + * {
        margin-block-start: var(--phil-prose-gap, var(--phil-space-md));
    }

    .isProse > :is(h2, h3, h4) {
        margin-block-start: var(--phil-space-xl);
    }

    .isProse > :is(h2, h3, h4) + * {
        margin-block-start: var(--phil-space-xs);
    }

    .isProse li + li {
        margin-block-start: var(--phil-space-2xs);
    }

    .isProse img,
    .isProse video {
        border-radius: var(--phil-radius-md);
    }

    /*
     * The neutral tone, the default of the components that are not calls to action; a tone class further down replaces it.
     * Buttons take theirs from the base layer.
     */
    .isBadge,
    .isAlert,
    .isTag,
    .isSecondary,
    .isGhost,
    .isNumberInputStep,
    .isFileInputClear,
    .isDatePickerTrigger,
    .isDatePickerMonth {
        --phil-tone-fill-quiet: var(--phil-color-neutral-fill-quiet);
        --phil-tone-fill-loud: var(--phil-color-neutral-fill-loud);
        --phil-tone-border-quiet: var(--phil-color-neutral-border-quiet);
        --phil-tone-border-loud: var(--phil-color-neutral-border-loud);
        --phil-tone-on-quiet: var(--phil-color-neutral-on-quiet);
        --phil-tone-on-loud: var(--phil-color-neutral-on-loud);
    }

    /* The looks of a button: the solid brand is the default, `isPrimary` says so aloud. */
    .isSecondary {
        --phil-button-fill: var(--phil-tone-fill-quiet);
        --phil-button-on: var(--phil-tone-on-quiet);
        --phil-button-border: var(--phil-tone-border-quiet);
    }

    .isGhost {
        --phil-button-fill: transparent;
        --phil-button-fill-hover: var(--phil-tone-fill-quiet);
        --phil-button-fill-active: var(--phil-tone-fill-quiet);
        --phil-button-on: var(--phil-tone-on-quiet);
    }

    /* A button that reads as a link: no fill, no border, underlined, in the colour of links. */
    .isLink {
        --phil-button-fill: transparent;
        --phil-button-fill-hover: transparent;
        --phil-button-fill-active: transparent;
        --phil-button-on: var(--phil-color-link);

        text-decoration: underline;
    }

    .isOutline {
        --phil-button-fill: transparent;
        --phil-button-fill-hover: var(--phil-tone-fill-quiet);
        --phil-button-fill-active: var(--phil-tone-fill-quiet);
        --phil-button-on: var(--phil-tone-on-quiet);
        --phil-button-border: var(--phil-tone-border-loud);
    }

    /* A button that is on, or that names the current page: after the looks, so it holds for all of them. */
    :is(button, [role="button"], .isButton):is([aria-pressed="true"], [aria-current]:not([aria-current="false"])) {
        --phil-button-fill: var(--phil-tone-fill-quiet);
        --phil-button-on: var(--phil-tone-on-quiet);
        --phil-button-border: var(--phil-tone-border-loud);
    }

    /* An icon beside a label; alone, with `isIconOnly`, a square target whose name is an aria-label. */
    .hasIcon > :is(svg, img) {
        flex: none;
        inline-size: 1.25em;
        block-size: 1.25em;
    }

    .isIconOnly {
        min-inline-size: var(--phil-control-size, var(--phil-control-md));
        padding-inline: 0;
    }

    /* Waiting: the content stays in place, so nothing jumps, under a spinner of the colour it would have had. */
    .isLoading {
        position: relative;
        cursor: progress;
        pointer-events: none;
    }

    .isLoading::after {
        content: "";
        position: absolute;
        inset: 0;
        inline-size: 1.25em;
        block-size: 1.25em;
        margin: auto;
        border: var(--phil-border-md) solid var(--phil-spinner-color, currentcolor);
        border-inline-end-color: transparent;
        border-radius: var(--phil-radius-full);
        animation: phil-spin var(--phil-duration-loop) linear infinite;
    }

    :is(button, [role="button"], .isButton).isLoading {
        --phil-spinner-color: var(--phil-button-on);

        color: transparent;
    }

    :is(button, [role="button"], .isButton).isLoading > * {
        visibility: hidden;
    }

    @keyframes phil-spin {
        to {
            rotate: 1turn;
        }
    }

    .isSpinner {
        display: inline-block;
        inline-size: 1.5em;
        block-size: 1.5em;
        vertical-align: middle;
        border: var(--phil-border-md) solid currentcolor;
        border-inline-end-color: transparent;
        border-radius: var(--phil-radius-full);
        animation: phil-spin var(--phil-duration-loop) linear infinite;
    }

    /* Buttons side by side, sharing their borders; the focused or pressed one is drawn above its neighbours. */
    .isButtonGroup {
        display: inline-flex;
        flex-wrap: wrap;
    }

    .isButtonGroup > * {
        position: relative;
    }

    .isButtonGroup > :not(:first-child) {
        margin-inline-start: calc(var(--phil-border-sm) * -1);
        border-start-start-radius: 0;
        border-end-start-radius: 0;
    }

    .isButtonGroup > :not(:last-child) {
        border-start-end-radius: 0;
        border-end-end-radius: 0;
    }

    .isButtonGroup.isVertical {
        flex-direction: column;
        align-items: stretch;
    }

    .isButtonGroup.isVertical > * {
        border-radius: var(--phil-radius-md);
    }

    .isButtonGroup.isVertical > :not(:first-child) {
        margin-inline-start: 0;
        margin-block-start: calc(var(--phil-border-sm) * -1);
        border-start-start-radius: 0;
        border-start-end-radius: 0;
    }

    .isButtonGroup.isVertical > :not(:last-child) {
        border-end-start-radius: 0;
        border-end-end-radius: 0;
    }

    /* Plain text among the buttons of a group: their height, their border, the quiet surface. */
    .isButtonGroupText {
        display: inline-flex;
        align-items: center;
        gap: var(--phil-space-2xs);
        min-block-size: var(--phil-control-size, var(--phil-control-md));
        padding-inline: var(--phil-control-padding, var(--phil-space-sm));
        font-size: var(--phil-text-sm);
        background-color: var(--phil-color-surface-raised);
        border: var(--phil-border-sm) solid var(--phil-color-border);
        border-radius: var(--phil-radius-md);
    }

    .isButtonGroup > :is(:focus-visible, [aria-pressed="true"], [aria-current]:not([aria-current="false"])) {
        z-index: var(--phil-z-raised);
    }

    /* A short status: quiet by default, solid with `isLoud`, a border only with `isOutline`. */
    .isBadge {
        display: inline-flex;
        align-items: center;
        gap: var(--phil-space-2xs);
        padding-block: var(--phil-space-3xs);
        padding-inline: var(--phil-space-xs);
        font-size: var(--phil-text-xs);
        font-weight: var(--phil-weight-medium);
        line-height: var(--phil-leading-tight);
        white-space: nowrap;
        color: var(--phil-badge-color, var(--phil-tone-on-quiet));
        background-color: var(--phil-tone-fill-quiet);
        border: var(--phil-border-sm) solid var(--phil-badge-color, var(--phil-tone-border-quiet));
        border-radius: var(--phil-radius-full);
    }

    /* A badge of a colour of its own: the colour of its data-color, read by a typed attr() where the browser reads one,
       Chromium 133 and later, the colour of its variant elsewhere; --phil-badge-color, set by a class of the site, works
       everywhere. Never an inline style, so that it holds under a strict Content Security Policy. */
    @supports (color: attr(data-color type(<color>), red)) {
        .isBadge[data-color] {
            --phil-badge-color: attr(data-color type(<color>));
        }
    }

    .isBadge.isLoud {
        color: var(--phil-tone-on-loud);
        background-color: var(--phil-tone-fill-loud);
        border-color: transparent;
    }

    .isBadge.isOutline {
        background-color: transparent;
        border-color: var(--phil-tone-border-loud);
    }

    .isBadge:is(.isGhost, .isLink) {
        background-color: transparent;
        border-color: transparent;
    }

    .isBadge.isLink {
        color: var(--phil-color-link);
        text-decoration: underline;
    }

    /* A notice in the page: quiet, a loud rule on its start side. The `role` is the template's: `status` or `alert`. */
    .isAlert {
        margin-block-end: var(--phil-space-md);
        padding: var(--phil-space-md);
        color: var(--phil-tone-on-quiet);
        background-color: var(--phil-tone-fill-quiet);
        border: var(--phil-border-sm) solid var(--phil-tone-border-quiet);
        border-inline-start: var(--phil-border-lg) solid var(--phil-tone-border-loud);
        border-radius: var(--phil-radius-md);
    }

    .isAlert.isLoud {
        color: var(--phil-tone-on-loud);
        background-color: var(--phil-tone-fill-loud);
        border-color: transparent;
    }

    .isAlert > :last-child {
        margin-block-end: 0;
    }

    .isAlert a {
        color: inherit;
    }

    .isAlertTitle {
        margin-block: 0 var(--phil-space-2xs);
    }

    .isAlertDescription > :last-child {
        margin-block-end: 0;
    }

    .isAlertAction {
        margin-block-start: var(--phil-space-sm);
    }

    .isAlert.hasIcon {
        display: grid;
        grid-template-columns: auto minmax(0, 1fr);
        gap: var(--phil-space-2xs) var(--phil-space-sm);
    }

    .isAlert.hasIcon > :is(svg, img) {
        grid-row: span 2;
    }

    .isAlert.hasIcon > :not(svg, img) {
        grid-column: 2;
    }

    /* A surface of its own: media first, a body, a header and a footer. An `isCardLink` covers the whole card. */
    .isCard {
        position: relative;
        display: flex;
        flex-direction: column;
        overflow: hidden;
        margin-block-end: var(--phil-space-md);
        background-color: var(--phil-color-surface);
        border: var(--phil-border-sm) solid var(--phil-color-border);
        border-radius: var(--phil-radius-lg);
        box-shadow: var(--phil-shadow-sm);
        transition: box-shadow var(--phil-duration-sm) var(--phil-ease-out);
    }

    .isCardMedia {
        inline-size: 100%;
        aspect-ratio: var(--phil-card-media-ratio, 4 / 3);
        object-fit: cover;
        background-color: var(--phil-color-surface-raised);
    }

    .isCardBody {
        display: flex;
        flex: 1;
        flex-direction: column;
        gap: var(--phil-space-xs);
        padding: var(--phil-space-md);
    }

    .isCardBody > * {
        margin-block: 0;
    }

    .isCardTitle {
        margin-block: 0;
        font-size: var(--phil-text-lg);
        line-height: var(--phil-leading-tight);
    }

    .isCard > header,
    .isCardHeader {
        padding: var(--phil-space-md);
        border-block-end: var(--phil-border-sm) solid var(--phil-color-border);
    }

    .isCardHeader:has(> .isCardAction) {
        display: grid;
        grid-template-columns: minmax(0, 1fr) auto;
        gap: 0 var(--phil-space-sm);
    }

    .isCardAction {
        grid-row: 1 / span 2;
        grid-column: 2;
    }

    .isCardDescription {
        margin-block: var(--phil-space-2xs) 0;
        font-size: var(--phil-text-sm);
        color: var(--phil-color-text-muted);
    }

    .isCard > footer,
    .isCardFooter {
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        gap: var(--phil-space-xs);
        padding-block: var(--phil-space-sm);
        padding-inline: var(--phil-space-md);
        font-size: var(--phil-text-sm);
        color: var(--phil-color-text-muted);
        border-block-start: var(--phil-border-sm) solid var(--phil-color-border);
    }

    .isCard.isSmall > :is(header, footer, .isCardHeader, .isCardBody, .isCardFooter) {
        padding: var(--phil-space-sm);
    }

    .isCardLink {
        color: inherit;
        text-decoration: none;
    }

    .isCardLink::after {
        content: "";
        position: absolute;
        inset: 0;
    }

    .isCardLink:focus-visible {
        outline: none;
    }

    .isCard:has(.isCardLink:hover) {
        box-shadow: var(--phil-shadow-md);
    }

    .isCard:has(.isCardLink:focus-visible) {
        outline: var(--phil-focus-width) solid var(--phil-color-focus);
        outline-offset: var(--phil-focus-offset);
    }

    /* Nothing to show yet: a dashed frame, a title and what to do about it. */
    .isEmptyState {
        display: flex;
        flex-direction: column;
        align-items: center;
        gap: var(--phil-space-xs);
        padding-block: var(--phil-space-2xl);
        padding-inline: var(--phil-space-md);
        color: var(--phil-color-text-muted);
        text-align: center;
        border: var(--phil-border-sm) dashed var(--phil-color-neutral-border-loud);
        border-radius: var(--phil-radius-lg);
    }

    .isEmptyState > * {
        margin-block: 0;
        max-inline-size: var(--phil-measure-narrow);
    }

    .isEmptyState > :is(h2, h3, h4, h5, h6),
    .isEmptyTitle {
        margin-block: 0;
        font-size: var(--phil-text-lg);
        color: var(--phil-color-text);
    }

    .isEmptyHeader {
        display: flex;
        flex-direction: column;
        align-items: center;
        gap: var(--phil-space-2xs);
    }

    .isEmptyMedia {
        font-size: var(--phil-text-2xl);
    }

    .isEmptyDescription {
        margin-block: 0;
    }

    .isEmptyContent {
        display: flex;
        flex-wrap: wrap;
        justify-content: center;
        gap: var(--phil-space-xs);
    }

    /* Tabs: the ARIA roles carry the structure, the tab bar scrolls when it does not fit. */
    .isTabs > [role="tablist"],
    .isTabList {
        display: flex;
        gap: var(--phil-space-2xs);
        overflow-x: auto;
        border-block-end: var(--phil-border-sm) solid var(--phil-color-border);
    }

    .isTabs [role="tab"],
    .isTab {
        --phil-button-fill: transparent;
        --phil-button-fill-hover: var(--phil-color-neutral-fill-quiet);
        --phil-button-fill-active: var(--phil-color-neutral-fill-quiet);
        --phil-button-on: var(--phil-color-text-muted);

        border: 0;
        border-block-end: var(--phil-border-md) solid transparent;
        border-radius: 0;
        white-space: nowrap;
    }

    .isTabs [role="tab"][aria-selected="true"],
    .isTab[aria-selected="true"] {
        --phil-button-on: var(--phil-color-text);

        border-block-end-color: var(--phil-color-brand-border-loud);
    }

    .isTabs [role="tabpanel"],
    .isTabPanel {
        padding-block: var(--phil-space-md);
    }

    /* The panels a script will hide stay out of sight until it runs; without one, every panel shows. */
    @media (scripting: enabled) {
        .isTabs:has(> .isTabPanel.isCurrent) > .isTabPanel:not(.isCurrent) {
            display: none;
        }
    }

    .isTabs.isVertical {
        display: flex;
        gap: var(--phil-space-md);
    }

    .isTabs.isVertical > :is([role="tablist"], .isTabList) {
        flex-direction: column;
        overflow-x: visible;
        border-block-end: 0;
        border-inline-end: var(--phil-border-sm) solid var(--phil-color-border);
    }

    .isTabs.isVertical :is([role="tab"], .isTab) {
        justify-content: flex-start;
        border-block-end: 0;
        border-inline-end: var(--phil-border-md) solid transparent;
    }

    .isTabs.isVertical :is([role="tab"], .isTab)[aria-selected="true"] {
        border-inline-end-color: var(--phil-color-brand-border-loud);
    }

    .isTabs.isVertical > :is([role="tabpanel"], .isTabPanel) {
        flex: 1;
        padding-block: 0;
    }

    /* What opens a dialog or a popover sits in the line; the panel itself is in the top layer. */
    .hasDialog,
    .hasPopover {
        display: inline-block;
    }

    /* A modal as wide as its panel size allows, its close button in the corner, its parts spaced. */
    .isDialog {
        inline-size: 100%;
    }

    :is(.isDialogHeader, .isSheetHeader) {
        display: flex;
        flex-direction: column;
        gap: var(--phil-space-2xs);
        padding-inline-end: var(--phil-control-lg);
    }

    :is(.isDialogTitle, .isSheetTitle) {
        margin-block: 0;
        font-size: var(--phil-text-lg);
        line-height: var(--phil-leading-tight);
    }

    :is(.isDialogDescription, .isSheetDescription) {
        margin-block: 0;
        font-size: var(--phil-text-sm);
        color: var(--phil-color-text-muted);
    }

    :is(.isDialogFooter, .isSheetFooter) {
        display: flex;
        flex-wrap: wrap;
        justify-content: flex-end;
        gap: var(--phil-space-xs);
        margin-block-start: var(--phil-space-lg);
    }

    :is(.isDialogClose, .isSheetClose) {
        position: absolute;
        inset-block-start: var(--phil-space-xs);
        inset-inline-end: var(--phil-space-xs);
    }

    /* A popover a controller places against what opens it. */
    .isPopover {
        min-inline-size: min(var(--phil-panel-sm), calc(100vw - var(--phil-space-xl)));
    }

    /* A dialog as a panel against an edge: the end side by default, `isStart`, `isTop` or `isBottom` otherwise. */
    dialog.isSheet {
        inline-size: 100%;
        max-inline-size: min(var(--phil-dialog-size, var(--phil-panel-md)), 100vw);
        block-size: 100dvh;
        max-block-size: 100dvh;
        margin-block: 0;
        margin-inline: auto 0;
        border-block: 0;
        border-inline-end: 0;
        border-radius: 0;
        translate: 0 0;
    }

    @starting-style {
        dialog.isSheet[open] {
            translate: var(--phil-sheet-offset, 100% 0);
        }
    }

    dialog.isSheet:not([open]) {
        translate: var(--phil-sheet-offset, 100% 0);
    }

    dialog.isSheet:dir(rtl) {
        --phil-sheet-offset: -100% 0;
    }

    dialog.isSheet.isStart {
        --phil-sheet-offset: -100% 0;

        margin-inline: 0 auto;
        border-inline-start: 0;
        border-inline-end: var(--phil-border-sm) solid var(--phil-color-border);
    }

    dialog.isSheet.isStart:dir(rtl) {
        --phil-sheet-offset: 100% 0;
    }

    dialog.isSheet:is(.isTop, .isBottom) {
        inline-size: 100%;
        max-inline-size: 100vw;
        block-size: auto;
        max-block-size: 100dvh;
        margin-inline: 0;
        border-inline: 0;
    }

    dialog.isSheet.isTop {
        --phil-sheet-offset: 0 -100%;

        margin-block: 0 auto;
        border-block-end: var(--phil-border-sm) solid var(--phil-color-border);
    }

    dialog.isSheet.isBottom {
        --phil-sheet-offset: 0 100%;

        margin-block: auto 0;
        border-block-start: var(--phil-border-sm) solid var(--phil-color-border);
    }

    /* A check box that reads as a switch: `role="switch"` on the input, with or without the class. */
    input:is(.isSwitch, [role="switch"]) {
        --phil-switch-inset: var(--phil-space-3xs);
        --phil-switch-track: var(--phil-color-neutral-fill-loud);
        --phil-switch-thumb: var(--phil-color-neutral-on-loud);

        display: inline-block;
        inline-size: calc(var(--phil-check-size) * 2);
        block-size: var(--phil-check-size);
        padding: var(--phil-switch-inset);
        vertical-align: middle;
        background-color: var(--phil-switch-track);
        border: 0;
        border-radius: var(--phil-radius-full);
        transition: background-color var(--phil-duration-sm) var(--phil-ease-out);
        appearance: none;
    }

    input:is(.isSwitch, [role="switch"])::before {
        content: "";
        display: block;
        inline-size: calc(var(--phil-check-size) - var(--phil-switch-inset) * 2);
        block-size: 100%;
        background-color: var(--phil-switch-thumb);
        border-radius: var(--phil-radius-full);
        transition: margin-inline-start var(--phil-duration-sm) var(--phil-ease-out);
    }

    .isSwitch.isSmall {
        --phil-check-size: var(--phil-space-md);
    }

    input:is(.isSwitch, [role="switch"]):checked {
        --phil-switch-track: var(--phil-color-brand-fill-loud);
        --phil-switch-thumb: var(--phil-color-brand-on-loud);
    }

    input:is(.isSwitch, [role="switch"]):checked::before {
        margin-inline-start: var(--phil-check-size);
    }

    /* A tooltip: a popover its controller places, or, in a `hasTooltip` box, shown on hover and on focus without one. */
    .isTooltip {
        max-inline-size: var(--phil-panel-sm);
        padding-block: var(--phil-space-2xs);
        padding-inline: var(--phil-space-xs);
        font-size: var(--phil-text-sm);
        font-weight: var(--phil-weight-regular);
        line-height: var(--phil-leading-tight);
        color: var(--phil-color-surface);
        background-color: var(--phil-color-text);
        border: 0;
        border-radius: var(--phil-radius-sm);
        box-shadow: var(--phil-shadow-sm);
    }

    .hasTooltip {
        position: relative;
        display: inline-block;
    }

    /* The popover a controller places holds the tooltip and is not a panel of its own. */
    .isTooltipWrapper {
        overflow: visible;
        padding: 0;
        background: transparent;
        border: 0;
        box-shadow: none;
    }

    /*
     * The arrow shows once the controller has written the side the bubble went to, on the edge that faces the trigger:
     * `isStart` and `isEnd` being logical, the edges are too, and right to left needs nothing more.
     */
    .isTooltip:is(.isTop, .isBottom, .isStart, .isEnd) {
        position: relative;
    }

    .isTooltipArrow {
        --phil-tooltip-arrow: var(--phil-space-xs);

        position: absolute;
        display: none;
        inline-size: var(--phil-tooltip-arrow);
        block-size: var(--phil-tooltip-arrow);
        background-color: inherit;
        translate: var(--phil-tooltip-arrow-x, 0) var(--phil-tooltip-arrow-y, 0);
        rotate: 45deg;
    }

    .isTooltip:is(.isTop, .isBottom, .isStart, .isEnd) > .isTooltipArrow {
        display: block;
    }

    .isTooltip:is(.isTop, .isBottom) > .isTooltipArrow {
        inset-inline-start: calc(50% - var(--phil-tooltip-arrow) / 2);
    }

    .isTooltip.isTop > .isTooltipArrow {
        inset-block-end: calc(var(--phil-tooltip-arrow) / -2);
    }

    .isTooltip.isBottom > .isTooltipArrow {
        inset-block-start: calc(var(--phil-tooltip-arrow) / -2);
    }

    .isTooltip:is(.isStart, .isEnd) > .isTooltipArrow {
        inset-block-start: calc(50% - var(--phil-tooltip-arrow) / 2);
    }

    .isTooltip.isStart > .isTooltipArrow {
        inset-inline-end: calc(var(--phil-tooltip-arrow) / -2);
    }

    .isTooltip.isEnd > .isTooltipArrow {
        inset-inline-start: calc(var(--phil-tooltip-arrow) / -2);
    }

    .hasTooltip > .isTooltip:not([popover]) {
        position: absolute;
        inset-block-end: 100%;
        inset-inline-start: 50%;
        z-index: var(--phil-z-overlay);
        inline-size: max-content;
        margin-block-end: var(--phil-space-2xs);
        visibility: hidden;
        translate: -50% 0;
    }

    .hasTooltip > .isTooltip:not([popover]):dir(rtl) {
        translate: 50% 0;
    }

    .hasTooltip:is(:hover, :focus-within) > .isTooltip:not([popover]) {
        visibility: visible;
    }

    /* A menu that opens under its button: on a `details`, or a wrapper with an `isDropdownMenu`, popover or not. */
    .isDropdown {
        position: relative;
        display: inline-block;
        margin-block-end: 0;
    }

    .isDropdown > summary {
        list-style: none;
    }

    .isDropdown > summary::-webkit-details-marker {
        display: none;
    }

    .isDropdownMenu {
        min-inline-size: var(--phil-panel-sm);
        margin: 0;
        padding: var(--phil-space-2xs);
        list-style: none;
        background-color: var(--phil-color-surface);
        border: var(--phil-border-sm) solid var(--phil-color-border);
        border-radius: var(--phil-radius-md);
        box-shadow: var(--phil-shadow-md);
    }

    .isDropdownMenu:not([popover]) {
        position: absolute;
        inset-block-start: 100%;
        inset-inline-start: 0;
        z-index: var(--phil-z-overlay);
        margin-block-start: var(--phil-space-2xs);
    }

    .isDropdownMenu :is(a, button, [role="menuitem"]),
    .isDropdownItem {
        --phil-button-fill: transparent;
        --phil-button-fill-hover: var(--phil-color-neutral-fill-quiet);
        --phil-button-fill-active: var(--phil-color-neutral-fill-quiet);
        --phil-button-on: var(--phil-color-text);

        display: flex;
        justify-content: flex-start;
        align-items: center;
        gap: var(--phil-space-xs);
        inline-size: 100%;
        min-block-size: var(--phil-control-md);
        padding-inline: var(--phil-space-sm);
        font-weight: var(--phil-weight-regular);
        color: var(--phil-color-text);
        text-align: start;
        text-decoration: none;
        border: 0;
        border-radius: var(--phil-radius-sm);
    }

    .isDropdownMenu a:hover {
        background-color: var(--phil-color-neutral-fill-quiet);
    }

    .isDropdownMenu hr {
        margin-block: var(--phil-space-2xs);
    }

    .isDropdownItem.isDanger {
        --phil-button-on: var(--phil-tone-on-quiet);
        --phil-button-fill-hover: var(--phil-tone-fill-quiet);
        --phil-button-fill-active: var(--phil-tone-fill-quiet);

        color: var(--phil-tone-on-quiet);
    }

    .isDropdownItem.isInset,
    .isDropdownLabel.isInset {
        padding-inline-start: var(--phil-space-xl);
    }

    .isDropdownItem[aria-disabled="true"] {
        opacity: var(--phil-opacity-disabled);
        pointer-events: none;
    }

    .isDropdownIndicator,
    .isSelectIndicator {
        flex: none;
        color: var(--phil-color-brand-on-quiet);
    }

    /* What names a group of items: small and muted, never an item itself. */
    .isDropdownLabel,
    .isSelectLabel {
        padding-block: var(--phil-space-2xs);
        padding-inline: var(--phil-space-sm);
        font-size: var(--phil-text-sm);
        font-weight: var(--phil-weight-medium);
        color: var(--phil-color-text-muted);
    }

    .isDropdownSeparator,
    .isSelectSeparator {
        margin-block: var(--phil-space-2xs);
        border-block-start: var(--phil-border-sm) solid var(--phil-color-border);
    }

    .isDropdownGroup + .isDropdownGroup,
    .isSelectGroup + .isSelectGroup {
        margin-block-start: var(--phil-space-2xs);
        padding-block-start: var(--phil-space-2xs);
        border-block-start: var(--phil-border-sm) solid var(--phil-color-border);
    }

    .isDropdownShortcut {
        margin-inline-start: auto;
        font-size: var(--phil-text-xs);
        color: var(--phil-color-text-muted);
    }

    .isDropdownSub {
        position: relative;
    }

    /* The arrow of a submenu, at the end of its item; the browser mirrors the character right to left. */
    .isDropdownSubIcon {
        margin-inline-start: auto;
    }

    /* A select made of a popover: the trigger reads as a field, the items as a list. */
    .isSelectTrigger {
        --phil-button-fill: var(--phil-color-surface);
        --phil-button-fill-hover: var(--phil-color-surface-raised);
        --phil-button-fill-active: var(--phil-color-surface-raised);
        --phil-button-on: var(--phil-color-text);
        --phil-button-border: var(--phil-color-neutral-border-loud);

        justify-content: space-between;
        min-inline-size: min(var(--phil-panel-sm), calc(100vw - var(--phil-space-4xl)));
        font-weight: var(--phil-weight-regular);
    }

    .isSelectValue {
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }

    .isSelectIcon {
        flex: none;
        color: var(--phil-color-text-muted);
    }

    /* The panel scrolls in its viewport alone, so the buttons at its ends stay put. */
    .isSelectContent {
        overflow: hidden;
    }

    .isSelectViewport {
        max-block-size: min(20rem, calc(100dvh - var(--phil-space-4xl)));
        overflow-y: auto;
    }

    .isSelectScroll {
        display: flex;
        justify-content: center;
        color: var(--phil-color-text-muted);
    }

    .isSelectItem {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: var(--phil-space-xs);
        min-block-size: var(--phil-control-md);
        padding-inline: var(--phil-space-sm);
        border-radius: var(--phil-radius-sm);
        cursor: pointer;
    }

    .isSelectItem:is(:hover, :focus) {
        background-color: var(--phil-color-neutral-fill-quiet);
    }

    .isSelectItem[aria-selected="true"] {
        font-weight: var(--phil-weight-medium);
    }

    .isSelectItem[aria-disabled="true"] {
        opacity: var(--phil-opacity-disabled);
        pointer-events: none;
    }

    .isSelectItemText {
        flex: 1;
    }

    /* A menu is laid over what follows, so its disclosure must not clip it. */
    @supports selector(::details-content) {
        :is(.isDropdown, .isReactionPicker)::details-content {
            block-size: auto;
            overflow: visible;
            transition: none;
        }
    }

    /* A set of disclosures that read as one; a shared `name` on the `details` keeps one open at a time. */
    .isAccordion {
        margin-block-end: var(--phil-space-md);
        border-block: var(--phil-border-sm) solid var(--phil-color-border);
    }

    .isAccordion > details,
    .isAccordionItem {
        margin-block-end: 0;
    }

    .isAccordion > details + details,
    .isAccordionItem + .isAccordionItem {
        border-block-start: var(--phil-border-sm) solid var(--phil-color-border);
    }

    .isAccordion > details > summary,
    .isAccordionTrigger {
        padding-block: var(--phil-space-sm);
    }

    .isAccordion > details > :not(summary),
    .isAccordionContent {
        margin-block: 0 var(--phil-space-sm);
    }

    /* The way down: a `nav` holding an ordered list, a separator drawn between the items. */
    .isBreadcrumb :is(ol, ul),
    .isBreadcrumbList {
        gap: var(--phil-space-2xs);
        font-size: var(--phil-text-sm);
    }

    .isBreadcrumbItem {
        display: inline-flex;
        align-items: center;
        gap: var(--phil-space-2xs);
    }

    .isBreadcrumbLink {
        color: var(--phil-color-text-muted);
    }

    .isBreadcrumbPage {
        font-weight: var(--phil-weight-medium);
    }

    .isBreadcrumbEllipsis {
        display: inline-flex;
        align-items: center;
    }

    /* A separator written as an item takes the place of the drawn one. */
    .isBreadcrumbSeparator {
        color: var(--phil-color-text-muted);
    }

    .isBreadcrumb li + li:not(.isBreadcrumbSeparator, .isBreadcrumbSeparator + li)::before {
        content: "/" / "";
        margin-inline-end: var(--phil-space-2xs);
        color: var(--phil-color-text-muted);
    }

    /* Pages: a `nav` holding a list of links, the current one with `aria-current`, each a touch target. */
    .isPagination :is(ol, ul),
    .isPaginationList {
        gap: var(--phil-space-2xs);
    }

    .isPaginationItem {
        display: inline-flex;
    }

    .isPagination :is(a, button, li > span),
    .isPaginationLink {
        --phil-button-fill: transparent;
        --phil-button-fill-hover: var(--phil-color-neutral-fill-quiet);
        --phil-button-fill-active: var(--phil-color-neutral-fill-quiet);
        --phil-button-on: var(--phil-color-text);

        display: inline-flex;
        align-items: center;
        justify-content: center;
        min-block-size: var(--phil-control-md);
        min-inline-size: var(--phil-control-md);
        padding-inline: var(--phil-space-xs);
        color: var(--phil-color-text);
        text-decoration: none;
        border: 0;
        border-radius: var(--phil-radius-md);
    }

    .isPagination a:hover {
        background-color: var(--phil-color-neutral-fill-quiet);
    }

    .isPagination :is(a, button)[aria-current]:not([aria-current="false"]) {
        font-weight: var(--phil-weight-bold);
        color: var(--phil-color-brand-on-loud);
        background-color: var(--phil-color-brand-fill-loud);
    }

    .isPaginationEllipsis {
        --phil-button-on: var(--phil-color-text-muted);
    }

    .isPagination [aria-disabled="true"] {
        color: var(--phil-color-text-muted);
        pointer-events: none;
    }

    /* Slides in a row, snapped; `--phil-carousel-item` is the width of one. The template makes it focusable. */
    .isCarousel {
        display: flex;
        gap: var(--phil-carousel-gap, var(--phil-space-md));
        margin-block: 0 var(--phil-space-md);
        padding-inline-start: 0;
        overflow-x: auto;
        overscroll-behavior-x: contain;
        list-style: none;
        scroll-behavior: smooth;
        scroll-snap-type: inline mandatory;
    }

    .isCarousel > * {
        flex: 0 0 var(--phil-carousel-item, 100%);
        scroll-snap-align: start;
    }

    /* A carousel with its buttons: the frame holds the viewport that scrolls, and in it the track of slides. */
    .isCarouselFrame {
        display: grid;
        grid-template-columns: auto minmax(0, 1fr) auto;
        align-items: center;
        gap: var(--phil-space-xs);
        margin-block-end: var(--phil-space-md);
    }

    .isCarouselViewport {
        grid-row: 1;
        grid-column: 2;
        overflow-x: auto;
        overscroll-behavior-x: contain;
        scrollbar-width: thin;
        scroll-snap-type: inline mandatory;
    }

    .isCarouselTrack {
        display: flex;
        gap: var(--phil-carousel-gap, var(--phil-space-md));
    }

    .isCarouselSlide {
        flex: 0 0 var(--phil-carousel-item, 100%);
        scroll-snap-align: start;
    }

    .hasCenteredSlides .isCarouselSlide {
        scroll-snap-align: center;
    }

    .hasEndSlides .isCarouselSlide {
        scroll-snap-align: end;
    }

    .isCarouselPrevious {
        grid-row: 1;
        grid-column: 1;
    }

    .isCarouselNext {
        grid-row: 1;
        grid-column: 3;
    }

    .isCarouselToggle {
        grid-column: 1 / -1;
        justify-self: center;
    }

    .isCarouselFrame.isVertical .isCarouselViewport {
        max-block-size: var(--phil-carousel-size, var(--phil-panel-sm));
        overflow: hidden auto;
        scroll-snap-type: block mandatory;
    }

    .isCarouselFrame.isVertical .isCarouselTrack {
        flex-direction: column;
    }

    /* The place of something still loading. */
    .isSkeleton {
        display: block;
        min-block-size: 1em;
        color: transparent;
        background-color: var(--phil-color-surface-raised);
        border-radius: var(--phil-radius-sm);
        animation: phil-pulse calc(var(--phil-duration-loop) * 2) var(--phil-ease-in-out) infinite;
    }

    @keyframes phil-pulse {
        50% {
            opacity: var(--phil-opacity-disabled);
        }
    }

    /* Someone's picture, or their initials on the quiet neutral. */
    .isAvatar {
        display: inline-grid;
        place-items: center;
        flex: none;
        inline-size: var(--phil-control-size, var(--phil-control-md));
        block-size: var(--phil-control-size, var(--phil-control-md));
        overflow: hidden;
        font-weight: var(--phil-weight-medium);
        color: var(--phil-color-neutral-on-quiet);
        background-color: var(--phil-color-neutral-fill-quiet);
        border-radius: var(--phil-radius-full);
    }

    .isAvatar > img,
    .isAvatarImage {
        inline-size: 100%;
        block-size: 100%;
        object-fit: cover;
        border-radius: inherit;
    }

    .isAvatarFallback {
        display: grid;
        place-items: center;
        inline-size: 100%;
        block-size: 100%;
    }

    /* The badge sits on the edge of the picture, so the frame stops clipping. */
    .isAvatar:has(> .isAvatarBadge) {
        position: relative;
        overflow: visible;
    }

    .isAvatarBadge {
        position: absolute;
        inset-block-end: 0;
        inset-inline-end: 0;
        display: grid;
        place-items: center;
        min-inline-size: 1em;
        block-size: 1em;
        font-size: var(--phil-text-xs);
        color: var(--phil-color-brand-on-loud);
        background-color: var(--phil-color-brand-fill-loud);
        border-radius: var(--phil-radius-full);
        box-shadow: 0 0 0 var(--phil-border-md) var(--phil-color-surface);
    }

    /* Avatars overlapping, each ringed with the surface; the count of those left out is one more. */
    .isAvatarGroup {
        display: inline-flex;
    }

    .isAvatarGroup > * {
        box-shadow: 0 0 0 var(--phil-border-md) var(--phil-color-surface);
    }

    .isAvatarGroup > * + * {
        margin-inline-start: calc(var(--phil-space-xs) * -1);
    }

    .isAvatarGroupCount {
        display: inline-grid;
        place-items: center;
        inline-size: var(--phil-control-size, var(--phil-control-md));
        block-size: var(--phil-control-size, var(--phil-control-md));
        font-size: var(--phil-text-sm);
        color: var(--phil-color-neutral-on-quiet);
        background-color: var(--phil-color-neutral-fill-quiet);
        border-radius: var(--phil-radius-full);
    }

    /* A rule: on an `hr`, or around a short label on a `div`; `isVertical` stands up inside a row. */
    .isSeparator:not(hr) {
        display: flex;
        align-items: center;
        gap: var(--phil-space-sm);
        margin-block: var(--phil-space-md);
        font-size: var(--phil-text-sm);
        color: var(--phil-color-text-muted);
    }

    .isSeparator:not(hr)::before,
    .isSeparator:not(hr)::after {
        content: "";
        flex: 1;
        border-block-start: var(--phil-border-sm) solid var(--phil-color-border);
    }

    .isSeparator:not(hr):empty::after {
        display: none;
    }

    hr.isSeparator {
        margin-block: var(--phil-space-md);
        margin-inline: 0;
    }

    .isSeparator.isVertical {
        align-self: stretch;
        margin-block: 0;
        margin-inline: var(--phil-space-xs);
        border-block-start: 0;
        border-inline-start: var(--phil-border-sm) solid var(--phil-color-border);
    }

    .isSeparator.isVertical::before,
    .isSeparator.isVertical::after {
        display: none;
    }

    /* The steps of a process: an ordered list, the current one with `aria-current="step"`, the finished ones `isDone`. */
    .isStepper {
        --phil-step-size: var(--phil-control-sm);

        display: flex;
        margin-block: 0 var(--phil-space-md);
        padding-inline-start: 0;
        list-style: none;
        counter-reset: phil-step;
    }

    .isStepper > li {
        position: relative;
        display: flex;
        flex: 1;
        flex-direction: column;
        align-items: center;
        gap: var(--phil-space-2xs);
        font-size: var(--phil-text-sm);
        color: var(--phil-color-text-muted);
        text-align: center;
        counter-increment: phil-step;
    }

    .isStepper > li::before {
        content: counter(phil-step);
        display: grid;
        place-items: center;
        inline-size: var(--phil-step-size);
        block-size: var(--phil-step-size);
        font-weight: var(--phil-weight-medium);
        color: var(--phil-color-neutral-on-quiet);
        background-color: var(--phil-color-neutral-fill-quiet);
        border: var(--phil-border-sm) solid var(--phil-color-neutral-border-loud);
        border-radius: var(--phil-radius-full);
    }

    .isStepper > li + li::after {
        content: "";
        position: absolute;
        inset-block-start: calc(var(--phil-step-size) / 2);
        inset-inline: calc(-50% + var(--phil-step-size) / 2) calc(50% + var(--phil-step-size) / 2);
        border-block-start: var(--phil-border-sm) solid var(--phil-color-neutral-border-loud);
    }

    .isStepper > li[aria-current="step"] {
        font-weight: var(--phil-weight-medium);
        color: var(--phil-color-text);
    }

    .isStepper > li[aria-current="step"]::before {
        color: var(--phil-color-brand-on-loud);
        background-color: var(--phil-color-brand-fill-loud);
        border-color: transparent;
    }

    .isStepper > li.isDone {
        color: var(--phil-color-text);
    }

    .isStepper > li.isDone::before {
        color: var(--phil-color-brand-on-quiet);
        background-color: var(--phil-color-brand-fill-quiet);
        border-color: var(--phil-color-brand-border-loud);
    }

    .isStepperItem,
    .isStepperLabel {
        min-inline-size: 0;
        overflow-wrap: anywhere;
    }

    /* On a phone the steps read downwards, each counter beside its label. */
    @media (max-width: 30rem) {
        .isStepper {
            flex-direction: column;
            gap: var(--phil-space-xs);
        }

        .isStepper > li {
            flex-direction: row;
            text-align: start;
        }

        .isStepper > li + li::after {
            display: none;
        }
    }

    /* Activity over time: a cell per day, seven rows, as many columns as there are weeks. Each cell says its level with
       `data-level` (0 to 4) and its meaning with a title or a label: two levels apart reach 3:1, the colour does not
       stand alone. */
    .isActivityCalendar {
        --phil-activity-cell: var(--phil-space-sm);

        display: grid;
        grid-auto-columns: var(--phil-activity-cell);
        grid-auto-flow: column;
        grid-template-rows: repeat(7, var(--phil-activity-cell));
        gap: var(--phil-space-3xs);
        margin-block: 0 var(--phil-space-md);
        padding-inline-start: 0;
        overflow-x: auto;
        list-style: none;
    }

    .isActivityCalendar > * {
        background-color: var(--phil-color-activity-0);
        border-radius: var(--phil-radius-sm);
    }

    .isActivityCalendar > [data-level="1"],
    .isActivityLegend > [data-level="1"] { background-color: var(--phil-color-activity-1); }
    .isActivityCalendar > [data-level="2"],
    .isActivityLegend > [data-level="2"] { background-color: var(--phil-color-activity-2); }
    .isActivityCalendar > [data-level="3"],
    .isActivityLegend > [data-level="3"] { background-color: var(--phil-color-activity-3); }
    .isActivityCalendar > [data-level="4"],
    .isActivityLegend > [data-level="4"] { background-color: var(--phil-color-activity-4); }

    .isActivityCalendar > .isActivityCalendarBlank {
        background-color: transparent;
    }

    .isActivityCalendar > .isActivityCalendarDay {
        min-inline-size: 0;
    }

    .hasActivityCalendar {
        max-inline-size: 100%;
    }

    .isActivityLegend {
        display: flex;
        align-items: center;
        gap: var(--phil-space-3xs);
        margin: 0;
        font-size: var(--phil-text-xs);
        color: var(--phil-color-text-muted);
    }

    .isActivityLegend > .isActivityCalendarDay {
        inline-size: var(--phil-space-sm);
        block-size: var(--phil-space-sm);
        border-radius: var(--phil-radius-sm);
    }

    .isActivityLegend > [data-level="0"] { background-color: var(--phil-color-activity-0); }

    /* A table that scrolls in a box of its own, its rows striped or lit under the pointer. */
    .isScrollable {
        overflow-x: auto;
    }

    .isStriped tbody tr:nth-child(even) {
        background-color: var(--phil-color-surface-raised);
    }

    .hasHover tbody tr:hover {
        background-color: var(--phil-color-brand-fill-quiet);
    }

    .hasHover tbody tr:hover :is(td, th) {
        color: var(--phil-color-brand-on-quiet);
    }

    /* A table in the kit's markup: its box scrolls, the caption reads under it, the last row closes without a rule. */
    .isTable {
        margin-block: 0;
    }

    .isTableCaption {
        margin-block: var(--phil-space-xs) 0;
        caption-side: bottom;
    }

    .isTableHeader {
        white-space: nowrap;
    }

    .isTableBody > :last-child > * {
        border-block-end: 0;
    }

    .isTableFooter {
        background-color: var(--phil-color-surface-raised);
    }

    .isTableRow[aria-selected="true"] {
        color: var(--phil-color-brand-on-quiet);
        background-color: var(--phil-color-brand-fill-quiet);
    }

    .isTableHead {
        vertical-align: bottom;
    }

    .isTableCell {
        vertical-align: middle;
    }

    /* A button that copies a text: it keeps its place while its words change. */
    .isCopyButton {
        display: inline-flex;
        align-items: center;
    }

    .isCopyButtonTrigger {
        min-inline-size: 7ch;
    }

    /*
     * A celebration: what plays it, its message once said, and the canvas its controller draws on over the whole page,
     * which never takes a click and is never drawn under reduced motion.
     */
    .isCelebration {
        display: flex;
        flex-direction: column;
        align-items: flex-start;
        gap: var(--phil-space-sm);
    }

    .isCelebrationMessage {
        margin: 0;
        font-size: var(--phil-text-lg);
        font-weight: 600;
    }

    .isCelebrationCanvas {
        position: fixed;
        inset: 0;
        z-index: var(--phil-z-overlay);
        inline-size: 100%;
        block-size: 100%;
        pointer-events: none;
    }

    @media (prefers-reduced-motion: reduce) {
        .isCelebrationCanvas {
            display: none;
        }
    }

    [data-motion="reduce"] .isCelebrationCanvas {
        display: none;
    }

    /*
     * A button that moves under the pointer and the focus, and stands still under reduced motion: `hasShine` sweeps a
     * light across it, `hasLift` raises it, `hasArrow` pushes its arrow forward.
     */
    .isAnimatedButton {
        position: relative;
        overflow: hidden;
    }

    .isAnimatedButtonLabel {
        position: relative;
    }

    .isAnimatedButton.hasShine::before {
        content: "";
        position: absolute;
        inset-block: 0;
        inset-inline-start: -60%;
        inline-size: 50%;
        background: linear-gradient(90deg, transparent, color-mix(in oklch, var(--phil-button-on) 35%, transparent), transparent);
        transition: inset-inline-start var(--phil-duration-lg) var(--phil-ease-out);
        pointer-events: none;
    }

    .isAnimatedButton.hasShine:is(:hover, :focus-visible)::before {
        inset-inline-start: 110%;
    }

    .isAnimatedButton.hasLift {
        transition:
            translate var(--phil-duration-sm) var(--phil-ease-out),
            box-shadow var(--phil-duration-sm) var(--phil-ease-out);
    }

    .isAnimatedButton.hasLift:is(:hover, :focus-visible) {
        translate: 0 calc(var(--phil-space-3xs) * -1);
        box-shadow: var(--phil-shadow-md);
    }

    .isAnimatedButtonArrow {
        display: inline-block;
        transition: translate var(--phil-duration-sm) var(--phil-ease-out);
    }

    .isAnimatedButtonArrow:dir(rtl) {
        scale: -1 1;
    }

    .isAnimatedButton.hasArrow:is(:hover, :focus-visible) > .isAnimatedButtonArrow {
        translate: var(--phil-space-2xs) 0;
    }

    .isAnimatedButton.hasArrow:is(:hover, :focus-visible):dir(rtl) > .isAnimatedButtonArrow {
        translate: calc(var(--phil-space-2xs) * -1) 0;
    }

    @media (prefers-reduced-motion: reduce) {
        .isAnimatedButton,
        .isAnimatedButton::before,
        .isAnimatedButtonArrow {
            transition: none;
        }

        .isAnimatedButton.hasLift:is(:hover, :focus-visible) {
            translate: none;
        }

        .isAnimatedButton.hasShine::before {
            display: none;
        }

        .isAnimatedButton.hasArrow:is(:hover, :focus-visible) > .isAnimatedButtonArrow,
        .isAnimatedButton.hasArrow:is(:hover, :focus-visible):dir(rtl) > .isAnimatedButtonArrow {
            translate: none;
        }
    }

    /* A tree: nested lists, each level set in, the row of an item the only part that lights up. */
    .isTree,
    .isTreeGroup {
        margin: 0;
        padding-inline-start: 0;
        list-style: none;
    }

    .isTreeGroup {
        padding-inline-start: var(--phil-space-md);
    }

    .isTreeItem {
        outline: none;
    }

    .isTreeRow {
        display: flex;
        align-items: center;
        gap: var(--phil-space-2xs);
        min-block-size: var(--phil-control-sm);
        padding-inline: var(--phil-space-2xs);
        border-radius: var(--phil-radius-sm);
        cursor: pointer;
    }

    .isTreeRow:hover {
        background-color: var(--phil-color-neutral-fill-quiet);
    }

    .isTreeItem:focus-visible > .isTreeRow {
        outline: var(--phil-focus-width) solid var(--phil-color-focus);
        outline-offset: calc(var(--phil-focus-width) * -1);
    }

    .isTreeItem[aria-selected="true"] > .isTreeRow {
        color: var(--phil-color-brand-on-quiet);
        background-color: var(--phil-color-brand-fill-quiet);
    }

    .isTreeToggle {
        flex: none;
        inline-size: 1em;
        text-align: center;
        transition: rotate var(--phil-duration-sm) var(--phil-ease-out);
    }

    .isTreeToggle::before {
        content: "\25B8";
    }

    .isTreeToggle:dir(rtl)::before {
        content: "\25C2";
    }

    .isTreeItem[aria-expanded="true"] > .isTreeRow > .isTreeToggle {
        rotate: 90deg;
    }

    .isTreeItem[aria-expanded="true"] > .isTreeRow > .isTreeToggle:dir(rtl) {
        rotate: -90deg;
    }

    .isTreeLabel {
        color: inherit;
        overflow-wrap: anywhere;
    }

    .isTreeRow > .isTreeLabel:first-child {
        margin-inline-start: calc(1em + var(--phil-space-2xs));
    }

    /* Before its script runs a tree shows everything, so that nothing is out of reach. */
    @media (scripting: enabled) {
        .isTreeItem[aria-expanded="false"] > .isTreeGroup {
            display: none;
        }
    }

    @media (pointer: coarse) {
        .isTreeRow {
            min-block-size: var(--phil-control-md);
        }
    }

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

    /*
     * Stars. In a field, each is the label of a radio button: what comes before the one checked is lit by `:has()`, so the
     * order of the markup is the order read, in both directions.
     */
    .isRating {
        --phil-rating-on: var(--phil-color-brand-fill-loud);
        --phil-rating-off: var(--phil-color-neutral-border-loud);

        min-inline-size: 0;
        margin: 0;
        padding: 0;
        border: 0;
    }

    .isRating.isReadonly {
        display: inline-flex;
        align-items: center;
    }

    .isRatingLegend {
        padding: 0;
        font-weight: var(--phil-weight-medium);
    }

    .isRatingStars {
        display: inline-flex;
        flex-wrap: wrap;
        align-items: center;
    }

    .isRatingStar {
        display: inline-grid;
        place-items: center;
        min-inline-size: var(--phil-control-md);
        min-block-size: var(--phil-control-md);
        margin: 0;
        font-size: var(--phil-text-xl);
        line-height: 1;
        color: var(--phil-rating-off);
        cursor: pointer;
    }

    .isRating.isReadonly > .isRatingStar {
        min-inline-size: 0;
        min-block-size: 0;
        font-size: inherit;
        cursor: default;
    }

    .isRatingIcon {
        display: block;
    }

    .isRatingStar.isFilled,
    .isRatingStar:has(input:checked),
    .isRatingStar:has(~ .isRatingStar input:checked) {
        color: var(--phil-rating-on);
    }

    .isRatingStar.isHalf {
        color: transparent;
        background: linear-gradient(90deg, var(--phil-rating-on) 50%, var(--phil-rating-off) 50%);
        background-clip: text;
    }

    .isRatingStar.isHalf:dir(rtl) {
        background: linear-gradient(270deg, var(--phil-rating-on) 50%, var(--phil-rating-off) 50%);
        background-clip: text;
    }

    .isRatingStar:has(input:focus-visible),
    .isRatingClear:has(input:focus-visible) {
        outline: var(--phil-focus-width) solid var(--phil-color-focus);
        outline-offset: calc(var(--phil-focus-width) * -1);
        border-radius: var(--phil-radius-sm);
    }

    @media (hover: hover) {
        .isRating:not(:disabled) .isRatingStar:hover,
        .isRating:not(:disabled) .isRatingStar:has(~ .isRatingStar:hover) {
            color: var(--phil-rating-on);
        }

        .isRating:not(:disabled) .isRatingStar:hover ~ .isRatingStar {
            color: var(--phil-rating-off);
        }
    }

    .isRating:disabled .isRatingStar {
        cursor: not-allowed;
        opacity: var(--phil-opacity-disabled);
    }

    .isRatingClear {
        display: inline-flex;
        align-items: center;
        min-block-size: var(--phil-control-md);
        margin: 0;
        padding-inline: var(--phil-space-xs);
        font-size: var(--phil-text-sm);
        cursor: pointer;
    }

    /* A number field between its two buttons, which share its borders once they show. */
    .isNumberInput {
        display: inline-flex;
        align-items: stretch;
        max-inline-size: 100%;
    }

    .isNumberInputField {
        inline-size: 6rem;
        text-align: center;
        appearance: textfield;
    }

    .isNumberInputField::-webkit-inner-spin-button,
    .isNumberInputField::-webkit-outer-spin-button {
        margin: 0;
        appearance: none;
    }

    .isNumberInput:has(.isNumberInputStep:not([hidden])) .isNumberInputField {
        border-radius: 0;
    }

    .isNumberInputStep {
        --phil-button-fill: var(--phil-tone-fill-quiet);
        --phil-button-on: var(--phil-tone-on-quiet);
        --phil-button-border: var(--phil-tone-border-loud);

        flex: none;
        inline-size: var(--phil-control-md);
        padding-inline: 0;
    }

    .isNumberInputStep:first-child {
        border-start-end-radius: 0;
        border-end-end-radius: 0;
    }

    .isNumberInputStep:last-child {
        border-start-start-radius: 0;
        border-end-start-radius: 0;
    }

    /* A file field whose label is a zone to drop on; the script takes the bare input out of sight. */
    .isFileInput {
        display: grid;
        gap: var(--phil-space-xs);
        max-inline-size: 100%;
    }

    .isFileInputZone {
        position: relative;
        display: grid;
        gap: var(--phil-space-2xs);
        justify-items: center;
        margin: 0;
        padding: var(--phil-space-lg);
        text-align: center;
        border: var(--phil-border-md) dashed var(--phil-color-neutral-border-loud);
        border-radius: var(--phil-radius-md);
        cursor: pointer;
    }

    .isFileInputZone.isDragging {
        background-color: var(--phil-color-brand-fill-quiet);
        border-color: var(--phil-color-brand-border-loud);
    }

    .isFileInputZone:has(input:focus-visible) {
        outline: var(--phil-focus-width) solid var(--phil-color-focus);
        outline-offset: var(--phil-focus-offset);
    }

    @media (scripting: enabled) {
        .isFileInputField {
            position: absolute;
            inline-size: 1px;
            block-size: 1px;
            overflow: hidden;
            opacity: 0;
        }
    }

    .isFileInputLabel {
        font-weight: var(--phil-weight-medium);
    }

    .isFileInputHint {
        font-size: var(--phil-text-sm);
        color: var(--phil-color-text-muted);
    }

    .isFileInputList {
        margin: 0;
        padding-inline-start: 0;
        font-size: var(--phil-text-sm);
        list-style: none;
    }

    .isFileInputList:empty,
    .isFileInputError:empty {
        display: none;
    }

    .isFileInputItem {
        overflow-wrap: anywhere;
    }

    .isFileInputError {
        margin: 0;
        font-size: var(--phil-text-sm);
        color: var(--phil-color-danger-on-quiet);
    }

    .isFileInputClear {
        --phil-button-fill: var(--phil-tone-fill-quiet);
        --phil-button-on: var(--phil-tone-on-quiet);
        --phil-button-border: var(--phil-tone-border-quiet);

        justify-self: start;
    }

    /* A short label that can be taken away; a tone on it colours it. */
    .isTag {
        display: inline-flex;
        align-items: center;
        gap: var(--phil-space-2xs);
        max-inline-size: 100%;
        padding-block: var(--phil-space-3xs);
        padding-inline: var(--phil-space-xs);
        font-size: var(--phil-text-sm);
        line-height: var(--phil-leading-tight);
        color: var(--phil-tone-on-quiet);
        background-color: var(--phil-tone-fill-quiet);
        border: var(--phil-border-sm) solid var(--phil-tone-border-quiet);
        border-radius: var(--phil-radius-full);
    }

    .isTagLabel {
        overflow-wrap: anywhere;
    }

    .isTagRemove {
        flex: none;
        inline-size: var(--phil-space-lg);
        min-block-size: var(--phil-space-lg);
        padding: 0;
        color: inherit;
        background-color: transparent;
        border: 0;
        border-radius: var(--phil-radius-full);
    }

    .isTagRemove:hover {
        background-color: color-mix(in oklch, currentcolor 15%, transparent);
    }

    /* A moment from now: its digits keep their width, so the line does not shake as they change. */
    .isRelativeTime {
        font-variant-numeric: tabular-nums;
        white-space: nowrap;
    }

    /* Progress as a ring, its text in the centre; `isSmall` and `isLarge` size it through the height of a control. */
    .isProgressRing {
        position: relative;
        display: inline-grid;
        place-items: center;
        inline-size: calc(var(--phil-control-size, var(--phil-control-md)) * 1.5);
        block-size: calc(var(--phil-control-size, var(--phil-control-md)) * 1.5);
        font-size: var(--phil-text-sm);
    }

    .isProgressRingImage {
        position: absolute;
        inset: 0;
        inline-size: 100%;
        block-size: 100%;
        rotate: -90deg;
    }

    .isProgressRingImage:dir(rtl) {
        scale: 1 -1;
    }

    .isProgressRingTrack,
    .isProgressRingValue {
        fill: none;
        stroke-width: 4;
    }

    .isProgressRingTrack {
        stroke: var(--phil-color-neutral-fill-quiet);
    }

    .isProgressRingValue {
        stroke: var(--phil-color-brand-fill-loud);
        stroke-linecap: round;
        transition: stroke-dasharray var(--phil-duration-md) var(--phil-ease-out);
    }

    .isProgressRingText {
        position: relative;
        font-weight: var(--phil-weight-medium);
    }

    .isProgressRing.isIndeterminate > .isProgressRingImage {
        animation: phil-spin var(--phil-duration-loop) linear infinite;
    }

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

        .isProgressRing.isIndeterminate > .isProgressRingImage {
            animation: none;
        }
    }

    /* A small line of values, in the line of the text. */
    .isSparkline {
        display: inline-block;
        max-inline-size: 100%;
        vertical-align: middle;
    }

    .isSparklineLine {
        fill: none;
        stroke: var(--phil-color-brand-fill-loud);
        stroke-width: 1.5;
        stroke-linecap: round;
        stroke-linejoin: round;
    }

    .isSparklineArea {
        fill: var(--phil-color-brand-fill-quiet);
    }

    .isSparklineDot {
        fill: var(--phil-color-brand-fill-loud);
    }

    /*
     * Two pictures, one above the other, the first cut where the slider stands. The slider is a transparent range over
     * the whole picture: it takes the pointer and the keyboard, and the handle follows its position.
     */
    .isBeforeAfter {
        --phil-before-after-position: 50%;
        --phil-before-after-handle: var(--phil-control-sm);

        position: relative;
        display: block;
        max-inline-size: 100%;
        overflow: hidden;
        border-radius: var(--phil-radius-md);
    }

    .isBeforeAfterAfter img,
    .isBeforeAfterBefore img {
        display: block;
        inline-size: 100%;
        block-size: 100%;
        object-fit: cover;
    }

    .isBeforeAfterBefore {
        position: absolute;
        inset: 0;
        clip-path: inset(0 calc(100% - var(--phil-before-after-position)) 0 0);
    }

    .isBeforeAfterBefore:dir(rtl) {
        clip-path: inset(0 0 0 calc(100% - var(--phil-before-after-position)));
    }

    .isBeforeAfter::after {
        content: "";
        position: absolute;
        inset-block: 0;
        inset-inline-start: var(--phil-before-after-position);
        margin-inline-start: calc(var(--phil-border-md) / -2);
        border-inline-start: var(--phil-border-md) solid var(--phil-color-surface);
        pointer-events: none;
    }

    .isBeforeAfter::before {
        content: "";
        position: absolute;
        inset-block-start: 50%;
        inset-inline-start: var(--phil-before-after-position);
        z-index: var(--phil-z-raised);
        inline-size: var(--phil-before-after-handle);
        block-size: var(--phil-before-after-handle);
        margin-block-start: calc(var(--phil-before-after-handle) / -2);
        margin-inline-start: calc(var(--phil-before-after-handle) / -2);
        background-color: var(--phil-color-surface);
        border: var(--phil-border-md) solid var(--phil-color-neutral-border-loud);
        border-radius: var(--phil-radius-full);
        pointer-events: none;
    }

    .isBeforeAfter:has(.isBeforeAfterRange:focus-visible)::before {
        outline: var(--phil-focus-width) solid var(--phil-color-focus);
        outline-offset: var(--phil-focus-offset);
    }

    .isBeforeAfterRange {
        position: absolute;
        inset: 0;
        z-index: var(--phil-z-raised);
        inline-size: 100%;
        block-size: 100%;
        margin: 0;
        cursor: ew-resize;
        opacity: 0;
    }

    @media (scripting: none) {
        .isBeforeAfterBefore {
            position: static;
            clip-path: none;
        }

        .isBeforeAfter::before,
        .isBeforeAfter::after,
        .isBeforeAfterRange {
            display: none;
        }
    }

    /* A colour well, its value and the swatches to choose from, wrapping on a narrow screen. */
    .isColorPicker {
        display: inline-flex;
        flex-wrap: wrap;
        gap: var(--phil-space-xs);
        align-items: center;
        max-inline-size: 100%;
    }

    .isColorPickerWell {
        flex: none;
        inline-size: var(--phil-control-md);
        block-size: var(--phil-control-md);
        padding: 0;
        cursor: pointer;
    }

    .isColorPickerValue {
        inline-size: 12ch;
        font-family: var(--phil-font-mono);
    }

    .isColorPickerSwatches {
        display: flex;
        flex-wrap: wrap;
        gap: var(--phil-space-2xs);
    }

    .isColorPickerSwatch {
        inline-size: var(--phil-control-sm);
        min-block-size: var(--phil-control-sm);
        padding: 0;
        border: var(--phil-border-sm) solid var(--phil-color-neutral-border-loud);
        border-radius: var(--phil-radius-full);
    }

    .isColorPickerSwatch[aria-pressed="true"] {
        outline: var(--phil-border-md) solid var(--phil-color-text);
        outline-offset: var(--phil-border-sm);
    }

    /* A date field and its button; the month opens in a popover the controller places. */
    .isDatePicker {
        display: inline-flex;
        gap: var(--phil-space-2xs);
        align-items: center;
        max-inline-size: 100%;
    }

    .isDatePickerField {
        flex: 1;
        inline-size: auto;
        min-inline-size: 0;
    }

    .isDatePicker:has(.isDatePickerTrigger:not([hidden])) .isDatePickerField::-webkit-calendar-picker-indicator {
        display: none;
    }

    .isDatePickerTrigger,
    .isDatePickerMonth {
        --phil-button-fill: var(--phil-tone-fill-quiet);
        --phil-button-on: var(--phil-tone-on-quiet);
        --phil-button-border: var(--phil-tone-border-quiet);

        flex: none;
        inline-size: var(--phil-control-md);
        padding-inline: 0;
    }

    .isDatePickerPanel {
        position: fixed;
        inline-size: min(var(--phil-panel-sm), calc(100vw - var(--phil-space-xl)));
        margin: 0;
        padding: var(--phil-space-xs);
        background-color: var(--phil-color-surface-raised);
        border: var(--phil-border-sm) solid var(--phil-color-border);
        border-radius: var(--phil-radius-md);
        box-shadow: var(--phil-shadow-md);
    }

    .isDatePickerHeader {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: var(--phil-space-2xs);
        margin-block-end: var(--phil-space-2xs);
    }

    .isDatePickerHeading {
        font-weight: var(--phil-weight-medium);
        text-align: center;
    }

    .isDatePickerGrid {
        inline-size: 100%;
        margin: 0;
        table-layout: fixed;
        border-collapse: collapse;
    }

    .isDatePickerGrid :is(th, td) {
        padding: 0;
        text-align: center;
        border: 0;
    }

    .isDatePickerWeekday {
        font-size: var(--phil-text-xs);
        font-weight: var(--phil-weight-regular);
        color: var(--phil-color-text-muted);
    }

    .isDatePickerDay {
        --phil-button-fill: transparent;
        --phil-button-on: var(--phil-color-text);

        inline-size: 100%;
        min-block-size: var(--phil-control-md);
        padding: 0;
        font-size: var(--phil-text-sm);
    }

    .isDatePickerDay[aria-current="date"] {
        --phil-button-border: var(--phil-color-brand-border-loud);
    }

    .isDatePickerDay[aria-pressed="true"] {
        --phil-button-fill: var(--phil-color-brand-fill-loud);
        --phil-button-on: var(--phil-color-brand-on-loud);
    }

    .isDatePickerDay[data-outside]:not([aria-pressed="true"]) {
        --phil-button-on: var(--phil-color-text-muted);
    }

    @media (forced-colors: active) {
        .isRatingStar {
            color: GrayText;
            forced-color-adjust: none;
        }

        .isRatingStar.isFilled,
        .isRatingStar:has(input:checked),
        .isRatingStar:has(~ .isRatingStar input:checked) {
            color: CanvasText;
        }

        .isRatingStar.isHalf {
            background: linear-gradient(90deg, CanvasText 50%, GrayText 50%);
            background-clip: text;
        }

        .isDatePickerDay[aria-pressed="true"] {
            forced-color-adjust: none;
            color: HighlightText;
            background-color: Highlight;
        }
    }

    /* A QR code and its caption. */
    .isQrCode {
        display: inline-flex;
        flex-direction: column;
        align-items: center;
        gap: var(--phil-space-xs);
        max-inline-size: 100%;
        margin: 0;
    }

    .isQrCode > figcaption,
    .isQrCodeCaption {
        max-inline-size: 100%;
        font-size: var(--phil-text-sm);
        color: var(--phil-color-text-muted);
        text-align: center;
        overflow-wrap: anywhere;
    }

    /*
     * Highlighted code: the hl-* classes that a highlighter writes, in the browser or on the server. Each kind of token
     * takes the colour role meant to be read on a surface, so the code follows light and dark with the rest.
     */
    pre,
    code {
        --phil-hl-keyword: var(--phil-color-brand-on-quiet);
        --phil-hl-string: var(--phil-color-success-on-quiet);
        --phil-hl-number: var(--phil-color-warning-on-quiet);
        --phil-hl-comment: var(--phil-color-text-muted);
        --phil-hl-name: var(--phil-color-info-on-quiet);
        --phil-hl-markup: var(--phil-color-danger-on-quiet);
    }

    .hl-keyword,
    .hl-selector,
    .hl-heading,
    .hl-section {
        color: var(--phil-hl-keyword);
    }

    .hl-string,
    .hl-regex,
    .hl-inserted,
    .hl-link,
    .hl-code {
        color: var(--phil-hl-string);
    }

    .hl-number,
    .hl-constant,
    .hl-entity,
    .hl-variable {
        color: var(--phil-hl-number);
    }

    .hl-comment,
    .hl-punctuation,
    .hl-meta {
        color: var(--phil-hl-comment);
    }

    .hl-comment,
    .hl-emphasis {
        font-style: italic;
    }

    .hl-function,
    .hl-type,
    .hl-builtin,
    .hl-property,
    .hl-attr-name {
        color: var(--phil-hl-name);
    }

    .hl-tag,
    .hl-deleted {
        color: var(--phil-hl-markup);
    }

    .hl-strong,
    .hl-heading {
        font-weight: var(--phil-weight-bold);
    }

    /*
     * The fallback of UJO (Universal JSON Output) media on paper: a frame, a video and a sound cannot play there, so the
     * link, the poster and the QR code that follow each one show in its place, and only on paper.
     */
    @media not print {
        .ujo-print-fallback {
            display: none;
        }
    }

    @media print {
        :is(iframe, video, audio):has(+ .ujo-print-fallback) {
            display: none;
        }

        a.ujo-print-fallback {
            display: flex;
            flex-direction: column;
            gap: var(--phil-space-2xs);
            color: inherit;
            text-decoration: none;
        }

        .ujo-print-fallback__title {
            font-weight: var(--phil-weight-bold);
        }

        .ujo-print-fallback__url {
            font-size: var(--phil-text-sm);
            overflow-wrap: anywhere;
        }
    }

    /* Under forced colours the browser repaints the backgrounds: what a background alone draws takes a system colour. */
    @media (forced-colors: active) {
        input:is(.isSwitch, [role="switch"]) {
            border: var(--phil-border-sm) solid ButtonText;
        }

        input:is(.isSwitch, [role="switch"])::before {
            background-color: ButtonText;
            forced-color-adjust: none;
        }

        input:is(.isSwitch, [role="switch"]):checked {
            background-color: Highlight;
            forced-color-adjust: none;
        }

        .isActivityCalendar > * {
            border: var(--phil-border-sm) solid CanvasText;
        }
    }

    @media (prefers-reduced-motion: reduce) {
        .isCarousel {
            scroll-behavior: auto;
        }
    }

    /*
     * A chart drawn in SVG, its caption above it and its legend below. Each series takes a tone — `isPrimary`, `isInfo`,
     * `isSuccess`, `isWarning`, `isDanger`, `isNeutral` — and the line of each tone but the first a dash of its own, so
     * that the colour never stands alone.
     */
    .isChart {
        display: grid;
        gap: var(--phil-space-sm);
        max-inline-size: 100%;
        margin: 0;
    }

    .isChartLine.isInfo {
        stroke-dasharray: 8 4;
    }

    .isChartLine.isSuccess {
        stroke-dasharray: 2 4;
    }

    .isChartLine.isWarning {
        stroke-dasharray: 12 4 2 4;
    }

    .isChartLine.isDanger {
        stroke-dasharray: 4 4;
    }

    .isChartLine.isNeutral {
        stroke-dasharray: 16 4;
    }

    .isChartCaption {
        font-weight: var(--phil-weight-medium);
    }

    .isChartImage,
    .isChartCanvas {
        inline-size: 100%;
        max-inline-size: 100%;
        block-size: auto;
    }

    .isChartGrid {
        stroke: var(--phil-color-neutral-border-quiet);
        stroke-width: 1;
    }

    .isChartAxis {
        stroke: var(--phil-color-neutral-border-loud);
        stroke-width: 1;
    }

    .isChartTick {
        font-size: var(--phil-text-xs);
        font-variant-numeric: tabular-nums;
        fill: var(--phil-color-text-muted);
    }

    .isChartSeries,
    .isChartBar {
        fill: var(--phil-tone-fill-loud);
    }

    .isChartLine {
        fill: none;
        stroke: var(--phil-tone-fill-loud);
        stroke-width: 2;
        stroke-linecap: round;
        stroke-linejoin: round;
    }

    .isChartLegend {
        display: flex;
        flex-wrap: wrap;
        gap: var(--phil-space-xs) var(--phil-space-md);
        padding: 0;
        margin: 0;
        font-size: var(--phil-text-sm);
        list-style: none;
    }

    .isChartLegend > li {
        display: inline-flex;
        align-items: center;
        gap: var(--phil-space-2xs);
    }

    .isChartSwatch {
        inline-size: var(--phil-space-md);
        block-size: var(--phil-space-2xs);
        background: var(--phil-tone-fill-loud);
        border-radius: var(--phil-radius-full);
    }

    .isChartLegend > li:not(.isPrimary) .isChartSwatch {
        background: repeating-linear-gradient(to right, var(--phil-tone-fill-loud) 0 60%, transparent 60% 100%);
    }

    .isChartTable {
        font-variant-numeric: tabular-nums;
    }

    .isChartData {
        font-size: var(--phil-text-sm);
    }

    .hasChartJs .isChartCanvas {
        position: relative;
    }

    /* A box kept to a ratio, its picture or video covering it: a common ratio by its class, hasRatio16x9 and the others of
       an image; any other by the typed attr() of its data-ratio-width and data-ratio-height where the browser reads one,
       Chromium 133 and later, else --phil-aspect-ratio, which a class of the site sets, else a square. Never an inline
       style, so that it holds under a strict Content Security Policy. */
    .isAspectRatio {
        position: relative;
        overflow: hidden;
        aspect-ratio: var(--phil-aspect-ratio, 1);
    }

    @supports (aspect-ratio: attr(data-ratio-width type(<number>), 1) / 1) {
        .isAspectRatio[data-ratio-width] {
            aspect-ratio: attr(data-ratio-width type(<number>), 1) / attr(data-ratio-height type(<number>), 1);
        }
    }

    .isAspectRatio.hasRatio16x9 {
        aspect-ratio: 16 / 9;
    }

    .isAspectRatio.hasRatio4x3 {
        aspect-ratio: 4 / 3;
    }

    .isAspectRatio.hasRatio3x2 {
        aspect-ratio: 3 / 2;
    }

    .isAspectRatio.hasRatio1x1 {
        aspect-ratio: 1;
    }

    .isAspectRatio.hasRatio21x9 {
        aspect-ratio: 21 / 9;
    }

    .isAspectRatio.hasRatio3x4 {
        aspect-ratio: 3 / 4;
    }

    .isAspectRatio.hasRatio2x3 {
        aspect-ratio: 2 / 3;
    }

    .isAspectRatio.hasRatio9x16 {
        aspect-ratio: 9 / 16;
    }

    .isAspectRatio > :is(img, video, iframe, picture, svg) {
        display: block;
        inline-size: 100%;
        block-size: 100%;
        object-fit: cover;
    }

    /* A disclosure without the frame of an accordion: its summary and its content alone. */
    .isCollapsible {
        padding: 0;
        border: 0;
    }

    .isCollapsibleTrigger {
        inline-size: fit-content;
        cursor: pointer;
    }

    .isCollapsibleContent {
        padding-block-start: var(--phil-space-xs);
    }

    /* The keys of one shortcut, side by side. */
    .isKbdGroup {
        display: inline-flex;
        gap: var(--phil-space-3xs);
        align-items: center;
        padding: 0;
        font-size: inherit;
        background: none;
        border: 0;
        box-shadow: none;
    }

    /* A form field: its label, its control, its help and its error, stacked or side by side. */
    .isField {
        display: grid;
        gap: var(--phil-space-2xs);
        align-content: start;
    }

    .isField.isHorizontal {
        grid-template-columns: minmax(0, 1fr) auto;
        align-items: center;
    }

    @container (min-width: 40rem) {
        .isField.isResponsive {
            grid-template-columns: minmax(0, 1fr) auto;
            align-items: center;
        }
    }

    .isFieldLabel,
    .isFieldTitle {
        font-weight: var(--phil-weight-medium);
    }

    .isFieldDescription {
        margin: 0;
        font-size: var(--phil-text-sm);
        color: var(--phil-color-text-muted);
    }

    .isFieldError {
        padding: 0;
        margin: 0;
        font-size: var(--phil-text-sm);
        color: var(--phil-color-danger-on-quiet);
        list-style: none;
    }

    .isFieldError > p {
        margin: 0;
    }

    .isField.isDanger :is(input, select, textarea) {
        border-color: var(--phil-color-danger-border-loud);
    }

    .isFieldSet {
        display: grid;
        gap: var(--phil-space-md);
        container-type: inline-size;
    }

    .isFieldLegend {
        font-weight: var(--phil-weight-medium);
    }

    .isFieldGroup {
        display: grid;
        gap: var(--phil-space-lg);
        container-type: inline-size;
    }

    .isFieldContent {
        display: grid;
        gap: var(--phil-space-3xs);
    }

    .isFieldSeparator {
        display: flex;
        gap: var(--phil-space-sm);
        align-items: center;
        font-size: var(--phil-text-sm);
        color: var(--phil-color-text-muted);
    }

    .isFieldSeparator::before,
    .isFieldSeparator::after {
        flex: 1;
        content: "";
        border-block-start: var(--phil-border-sm) solid var(--phil-color-border);
    }

    /* One entry of a list: its media, its title and description, its actions. */
    .isItem {
        display: flex;
        flex-wrap: wrap;
        gap: var(--phil-space-md);
        align-items: center;
        padding: var(--phil-space-md);
        color: inherit;
        text-decoration: none;
        border: var(--phil-border-sm) solid transparent;
        border-radius: var(--phil-radius-md);
    }

    .isItem.isOutline {
        border-color: var(--phil-color-border);
    }

    .isItem.isMuted {
        color: inherit;
        background-color: var(--phil-color-surface-raised);
    }

    .isItem.isSmall {
        gap: var(--phil-space-sm);
        padding: var(--phil-space-sm);
    }

    a.isItem:hover {
        background-color: var(--phil-color-neutral-fill-quiet);
    }

    .isItemMedia {
        display: flex;
        flex: none;
        align-items: center;
        justify-content: center;
    }

    .isItemMedia.hasIcon {
        inline-size: var(--phil-control-sm);
        block-size: var(--phil-control-sm);
        background-color: var(--phil-color-neutral-fill-quiet);
        border-radius: var(--phil-radius-sm);
    }

    .isItemMedia.hasImage {
        inline-size: var(--phil-control-lg);
        block-size: var(--phil-control-lg);
        overflow: hidden;
        border-radius: var(--phil-radius-sm);
    }

    .isItemMedia.hasImage > img {
        inline-size: 100%;
        block-size: 100%;
        object-fit: cover;
    }

    .isItemContent {
        display: grid;
        flex: 1;
        gap: var(--phil-space-3xs);
        min-inline-size: 0;
    }

    .isItemTitle {
        font-weight: var(--phil-weight-medium);
    }

    .isItemDescription {
        margin: 0;
        font-size: var(--phil-text-sm);
        color: var(--phil-color-text-muted);
    }

    .isItemActions {
        display: flex;
        gap: var(--phil-space-xs);
        align-items: center;
    }

    .isItemHeader,
    .isItemFooter {
        display: flex;
        flex-basis: 100%;
        gap: var(--phil-space-xs);
        align-items: center;
        justify-content: space-between;
    }

    .isItemGroup {
        display: grid;
        padding: 0;
        margin: 0;
        list-style: none;
    }

    .isItemSeparator {
        border-block-start: var(--phil-border-sm) solid var(--phil-color-border);
    }

    /* A box whose content scrolls inside it, with thin scroll bars; it shows its focus when the keyboard scrolls it. */
    .isScrollArea {
        overflow: auto;
        overscroll-behavior: contain;
        scrollbar-width: thin;
        scrollbar-color: var(--phil-color-neutral-border-loud) transparent;
    }

    .isScrollArea.isVertical {
        overflow-x: hidden;
    }

    .isScrollArea.isHorizontal {
        overflow-y: hidden;
    }

    .isScrollArea:focus-visible {
        outline: var(--phil-focus-width) solid var(--phil-color-focus);
        outline-offset: var(--phil-focus-width);
    }

    /*
     * A toggle: a label wrapping its checkbox or radio button, which is hidden but keeps the focus, the keyboard and the
     * form. Pressed, it takes the quiet fill of its tone; the focus ring goes around the whole label.
     */
    .isToggle {
        position: relative;
        display: inline-flex;
        gap: var(--phil-space-2xs);
        align-items: center;
        justify-content: center;
        min-inline-size: var(--phil-control-size, var(--phil-control-md));
        min-block-size: var(--phil-control-size, var(--phil-control-md));
        padding-inline: var(--phil-control-padding, var(--phil-space-sm));
        font-weight: var(--phil-weight-medium);
        color: var(--phil-color-text);
        cursor: pointer;
        user-select: none;
        background-color: transparent;
        border: var(--phil-border-sm) solid transparent;
        border-radius: var(--phil-radius-md);
        transition: background-color var(--phil-duration-sm) var(--phil-ease-out);
    }

    .isToggle.isOutline {
        border-color: var(--phil-color-border);
    }

    .isToggle:hover {
        background-color: var(--phil-color-neutral-fill-quiet);
    }

    .isToggle:has(> .isToggleInput:checked) {
        color: var(--phil-color-brand-on-quiet);
        background-color: var(--phil-color-brand-fill-quiet);
    }

    .isToggle:has(> .isToggleInput:focus-visible) {
        outline: var(--phil-focus-width) solid var(--phil-color-focus);
        outline-offset: var(--phil-focus-width);
    }

    .isToggle:has(> .isToggleInput:disabled) {
        cursor: not-allowed;
        opacity: var(--phil-opacity-disabled);
    }

    .isToggleInput {
        position: absolute;
        inset: 0;
        margin: 0;
        pointer-events: none;
        appearance: none;
        opacity: 0;
    }

    .isToggleGroup {
        display: inline-flex;
        flex-wrap: wrap;
        gap: var(--phil-space-3xs);
    }

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

    /* Radio buttons laid out in a column, or in a row that wraps. */
    .isRadioGroup {
        display: grid;
        grid-template-columns: auto minmax(0, 1fr);
        gap: var(--phil-space-xs) var(--phil-space-sm);
        align-items: center;
    }

    .isRadioGroup.isHorizontal {
        display: flex;
        flex-wrap: wrap;
    }

    /* The blocks of sign-in and sign-up: a card of a reading width, its form stacked. */
    .isLoginForm,
    .isSignupForm {
        inline-size: 100%;
        max-inline-size: 26rem;
        margin-inline: auto;
    }

    :is(.isLoginForm, .isSignupForm) form > button[type="submit"] {
        inline-size: 100%;
    }

    .isLoginFormForgot {
        justify-self: end;
        font-size: var(--phil-text-sm);
    }

    /*
     * The tones, on any component that reads them; last, so they win over a component's own default.
     */
    .isPrimary {
        --phil-tone-fill-quiet: var(--phil-color-brand-fill-quiet);
        --phil-tone-fill-loud: var(--phil-color-brand-fill-loud);
        --phil-tone-border-quiet: var(--phil-color-brand-border-quiet);
        --phil-tone-border-loud: var(--phil-color-brand-border-loud);
        --phil-tone-on-quiet: var(--phil-color-brand-on-quiet);
        --phil-tone-on-loud: var(--phil-color-brand-on-loud);
    }

    .isNeutral {
        --phil-tone-fill-quiet: var(--phil-color-neutral-fill-quiet);
        --phil-tone-fill-loud: var(--phil-color-neutral-fill-loud);
        --phil-tone-border-quiet: var(--phil-color-neutral-border-quiet);
        --phil-tone-border-loud: var(--phil-color-neutral-border-loud);
        --phil-tone-on-quiet: var(--phil-color-neutral-on-quiet);
        --phil-tone-on-loud: var(--phil-color-neutral-on-loud);
    }

    .isDanger {
        --phil-tone-fill-quiet: var(--phil-color-danger-fill-quiet);
        --phil-tone-fill-loud: var(--phil-color-danger-fill-loud);
        --phil-tone-border-quiet: var(--phil-color-danger-border-quiet);
        --phil-tone-border-loud: var(--phil-color-danger-border-loud);
        --phil-tone-on-quiet: var(--phil-color-danger-on-quiet);
        --phil-tone-on-loud: var(--phil-color-danger-on-loud);
    }

    .isSuccess {
        --phil-tone-fill-quiet: var(--phil-color-success-fill-quiet);
        --phil-tone-fill-loud: var(--phil-color-success-fill-loud);
        --phil-tone-border-quiet: var(--phil-color-success-border-quiet);
        --phil-tone-border-loud: var(--phil-color-success-border-loud);
        --phil-tone-on-quiet: var(--phil-color-success-on-quiet);
        --phil-tone-on-loud: var(--phil-color-success-on-loud);
    }

    .isWarning {
        --phil-tone-fill-quiet: var(--phil-color-warning-fill-quiet);
        --phil-tone-fill-loud: var(--phil-color-warning-fill-loud);
        --phil-tone-border-quiet: var(--phil-color-warning-border-quiet);
        --phil-tone-border-loud: var(--phil-color-warning-border-loud);
        --phil-tone-on-quiet: var(--phil-color-warning-on-quiet);
        --phil-tone-on-loud: var(--phil-color-warning-on-loud);
    }

    .isInfo {
        --phil-tone-fill-quiet: var(--phil-color-info-fill-quiet);
        --phil-tone-fill-loud: var(--phil-color-info-fill-loud);
        --phil-tone-border-quiet: var(--phil-color-info-border-quiet);
        --phil-tone-border-loud: var(--phil-color-info-border-loud);
        --phil-tone-on-quiet: var(--phil-color-info-on-quiet);
        --phil-tone-on-loud: var(--phil-color-info-on-loud);
    }

    /* The sizes, on a control or on a group of them, since they are inherited. */
    .isSmall {
        --phil-control-size: var(--phil-control-sm);
        --phil-control-padding: var(--phil-space-sm);

        font-size: var(--phil-text-sm);
    }

    .isLarge {
        --phil-control-size: var(--phil-control-lg);
        --phil-control-padding: var(--phil-space-lg);

        font-size: var(--phil-text-lg);
    }
}
@layer phil.utilities {
    /*
     * phil-ux — the utilities, last of the layers so they win over the components.
     */

    /* Read by screen readers, not shown. */
    .isVisuallyHidden {
        position: absolute !important;
        inline-size: 1px !important;
        block-size: 1px !important;
        margin: -1px !important;
        padding: 0 !important;
        overflow: hidden !important;
        clip-path: inset(50%) !important;
        white-space: nowrap !important;
        border: 0 !important;
    }

    /* A link that jumps past the navigation, shown when it has the focus. */
    .isSkipLink:not(:focus-visible) {
        position: absolute;
        inline-size: 1px;
        block-size: 1px;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
    }

    .isMuted {
        color: var(--phil-color-text-muted);
    }

    .isGrow {
        flex-grow: 1;
    }
}
