/* The default shell: the look of the layouts this library ships, for both the signed-in and the
   anonymous shell, and of every screen rendered inside them. See docs\design-shell-styling.md.

   INVARIANT: everything here sits in the `shell` cascade layer, above meliora.css's `meliora` layer
   and below anything unlayered. An application's theme.css and app.css are unlayered, so either
   overrides any rule here by naming it once, with no specificity to reason about.

   INVARIANT: every custom property the shell reads is declared here, in :root and in both dark
   blocks, with its default value. theme.css may only override what this file declares, and a test in
   each application holds it to that; a token read here and declared nowhere is one no theme can tune.

   Plain CSS, no build step, no CDN, system fonts. */

@layer meliora, shell;

@layer shell {

/* ---- Tokens ------------------------------------------------------------------------------------

   WHY: the light palette is on bare :root and the dark blocks re-point names, so every rule below
   is written once against a name. The third theme state is the ABSENCE of data-theme, answered by
   the media query; :not([data-theme="light"]) keeps an explicit light choice from a dark system. */

:root {
    color-scheme: light dark;

    /* Type. System stacks; an application that ships a face declares it in app.css and points these at it. */
    --font: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
    /* Consolas first: Segoe's stablemate, so the labels sit with the body face rather than beside it. */
    --font-mono: Consolas, "SF Mono", Menlo, "Cascadia Mono", monospace;

    /*  INK AND GROUND. Every neutral here leans toward the accent rather than sitting on pure grey: the
        dark palette's neutrals are unmistakably blue-violet, and light's were within three points of
        achromatic, which is what made the two themes read as different degrees of finish rather than as
        one palette in two lights.

        WHY the ground is darker than the surface by more than a hair: a card is a white panel and the
        shadow is deliberately a whisper, so the step to the ground is what lifts it. Deepening the
        ground lifts every card at once; strengthening the shadow instead reads as embossing. */
    --ink: #191c28;
    --ink-2: #474c5e;
    --ink-3: #6c7183;
    --line: #c2c8da;
    --line-2: #d2d7e4;
    /* The chip's edge: the line in light, a step brighter in dark, where --line sinks into the surface. */
    --chip-line: var(--line);
    --bg: #e8ebf4;
    --surface: #ffffff;
    --surface-inset: #dde1ec;
    --field: #ffffff;
    --hover: #eef1f8;

    /* WHY: rows sit on --surface once a table is a panel, so a row hover is the same step down the
       chrome takes; it stays its own token because an application may want them apart. */
    --row-hover: var(--accent-tint);

    /* The accent and its relatives. --accent-ink is the text-safe one; --on-accent is what sits on it. */
    --accent: #1a5497;
    --accent-ink: #174a88;
    /* How far a closed rail group leans from the dim ink toward the accent. Light leans less: its
       accent and dim ink sit nearer in brightness, so the lit group needs the room. */
    --rail-group-lean: 40%;
    /* The Meliora mark, reduced: three filled triangles cut from the hub's brand/meliora. On a light ground the
       first is jade at 40%, the light reduced mark; on a dark ground the colours are the gallery's own. */
    --meliora-mark-faint: rgba(14, 107, 74, .4);
    --meliora-mark-jade: #0E6B4A;
    --meliora-mark-gold: #D9A441;
    --accent-hover: #154680;
    --accent-tint: #dde7f7;
    --on-accent: #ffffff;

    --danger: #a81b33;
    --danger-tint: #fbeaec;
    --warn: #9a5b00;
    --warn-tint: #fbf2e3;
    --ok: #17794a;
    --ok-tint: #e8f5ee;

    /* The chart series, in fixed order: a breakdown by anything but a tier takes them in vocabulary order,
       so a value keeps its colour whatever a filter does to its neighbours. */
    --series-1: #1a5497;
    --series-2: #0f7f86;
    --series-3: #b7791f;
    --series-4: #6b4c9a;
    --series-5: #b23a5e;
    --series-6: #4f7a2a;
    --series-7: #8a5a3c;
    --series-8: #5a6478;
    --series-9: #c2410c;
    --series-10: #3f3ea3;
    --series-11: #7c7a21;
    --series-12: #9b2d86;

    /* A list row's tones: aliases of the status colours, so each follows the theme it is read in, and
       retinted in theme.css like any token. See docs\design-list-decoration.md. */
    --tone-attention: var(--accent);
    --tone-attention-fill: var(--accent-tint);
    --tone-warning: var(--warn);
    --tone-warning-fill: var(--warn-tint);
    --tone-danger: var(--danger);
    --tone-danger-fill: var(--danger-tint);
    --tone-success: var(--ok);
    --tone-success-fill: var(--ok-tint);

    /* The header's chrome: a dark neutral, and a theme moves it to a brand colour in one line. */
    --chrome: #22262f;
    --chrome-hover: rgba(255, 255, 255, .24);
    --chrome-inset: rgba(255, 255, 255, .10);
    --chrome-line: rgba(255, 255, 255, .22);
    --on-chrome: rgba(255, 255, 255, .72);
    --on-chrome-strong: #ffffff;
    /* WHY: the chrome is the same dark neutral in both themes, so this is not tracking a background
       change. A hollow control reads as brighter in dark mode, where the eye is adapted to a dark
       page, so the dark value is the dimmer one. */
    --on-chrome-muted: rgba(255, 255, 255, .72);
    --chrome-notice: rgba(217, 161, 67, .24);
    --chrome-notice-ink: #f6dfae;
    --chrome-critical: rgba(220, 122, 135, .26);
    --chrome-critical-ink: #f8d2d7;
    --bell-attention: #6aa8f5;
    --bell-critical: #e5687a;
    --severity-notice: #f7e7c3;
    --severity-critical: #f8dbe0;
    --severity-notice-line: #e0c27f;
    --severity-critical-line: #e39aa6;
    --inbox-card-line: var(--line);
    --inbox-foot: rgba(0, 0, 0, .05);
    --about-surface: var(--surface);

    /* Tables. */
    --row-weight: 400;

    /* The agreement pane: how much of a text shows before it scrolls, on the Accept screen and the version page. */
    --agreement-pane-height: 24rem;

    /* How wide a page of rows may get. WHY: the main column is capped at reading width, which is right
       for a form and wrong for a grid — a nine-column list has nothing to do with how far the eye
       travels along a line of prose. A list takes this instead. */
    --list-w: 88rem;

    /* How narrow a page of rows may get. It is the strip that sets this rather than the rows: a list of
       three short columns is roomier than its content needs so that Search, the count and the live
       filter sit on one line. */
    --list-min-w: 30rem;

    /* The two boxes: the query's, and the live filter's. */
    --list-search-w: 12rem;
    --list-filter-w: 12rem;

    /* How much room a filter gives what is chosen, past which it ellipsizes. */
    --list-choice-value-w: 7rem;

    /* How wide a form's cards may get, the run and the tail alike. Narrower than a list on purpose: a
       field is read like a line of prose and a row of cells is not. */
    --form-w: 56rem;

    /* THE THREE HEIGHTS A TEXT BOX OPENS AT, and there are no others: a note (a few lines beside a
       record), a field (the frame's own text field), and a body (prose: a page's text, a message, the
       questions of a survey). Markup says which by class, never by rows. */
    --textarea-note: 4.25rem;
    --textarea-field: 5.4rem;
    --textarea-body: 14rem;

    /* The word in a card's own edge: DETAILS, STATUS, DELETE. It is a token because the tail row also
       computes a half of its rendered height, which only holds while all three are one size. */
    --card-label-size: .75rem;
    --footer-ink: #313648;
    --field-line: #a2a8ba;
    --ghost-fill: #ffffff;
    --ghost-fill-hover: #eef1f8;
    --issue-lit: #ffffff;
    --row-line: #e2e6ef;

    /* Shape and depth. */
    --radius: 6px;
    --radius-lg: 10px;
    --shadow: 0 1px 2px rgba(23, 28, 48, .08);
    --shadow-lg: 0 12px 32px rgba(23, 28, 48, .13);
    /* The veil a dialog lays over the page. Carries the ground's blue-violet bias rather than being
       neutral black, so the page reads as dimmed rather than as covered by something else. */
    --scrim: rgba(13, 16, 34, .74);
    --focus: #1a5497;
    --focus-halo: transparent;

    /* The border of the field that HAS focus, darker than the ring. WHY a token of its own: the ring sits
       outside a control against the page, this sits on the field's own fill, and the two want different
       weights to read as equally present. */
    --field-focus-line: #123f77;

    /* INVARIANT: the nav's top, the nav's height and any sticky table head derive from --pinned-h,
       which is --sub-h when the sub row exists and --header-h when it does not; never from either directly.
       Changing the sub row's height without this token detaches all three, silently. */
    --header-h: 3.5rem;
    --sub-h: 2.75rem;
    --footer-h: 1.75rem;
    --nav-w: 13.5rem;

    /* The front door: its ground sits a step below the page's, its fields a step below the card's. */
    --auth-bg: #d4d5dd;
    --auth-field: #ededf1;
    --auth-field-line: #d9d9e1;
    --auth-top: 13rem;
}

/* The card sits lower on a tall window than the mockup's fixed offset would allow a short one. */
@media (max-height: 48rem) {
    :root { --auth-top: 11rem; }
}

/* WHY: flat rather than nested. A browser without native nesting would silently render the light
   palette on a dark system, and nothing here would catch it. */
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        --ink: #e9ebf5;
        --ink-2: #b8bfd8;
        --ink-3: #848fb0;
        --line: #394573;
        --line-2: #1f2642;
        --chip-line: #46507a;
        --bg: #080915;
        --surface: #0f1226;
        --surface-inset: #191d38;
        --field: #13172f;
        --hover: #1a203b;
        --row-hover: #1a203b;
        --accent: #4f8fe0;
        --accent-ink: #8fbaf0;
    --rail-group-lean: 48%;
    --meliora-mark-faint: #93C4AC;
    --meliora-mark-jade: #0E6B4A;
    --meliora-mark-gold: #D9A441;
        --accent-hover: #6aa1e8;
        --accent-tint: rgba(79, 143, 224, .22);
        --on-accent: #06182f;
        --danger: #dc7a87;
        --danger-tint: rgba(196, 35, 64, .18);
        --warn: #d9a143;
        --warn-tint: rgba(178, 108, 0, .2);
        --ok: #4cb98a;
        --ok-tint: rgba(23, 121, 74, .2);

        /* The chart series, in fixed order: a breakdown by anything but a tier takes them in vocabulary order. */
        --series-1: #6fa3ea;
        --series-2: #4fc0c7;
        --series-3: #e0b25a;
        --series-4: #b094de;
        --series-5: #e07a99;
        --series-6: #9ccc65;
        --series-7: #d2a07c;
        --series-8: #a0a8bd;
        --series-9: #f0956a;
        --series-10: #9a9bee;
        --series-11: #c9c863;
        --series-12: #de8fd0;
        --chrome: #22262f;
        --chrome-hover: rgba(255, 255, 255, .24);
        --chrome-inset: rgba(255, 255, 255, .10);
        --chrome-line: rgba(255, 255, 255, .22);
        --on-chrome: rgba(255, 255, 255, .72);
        --on-chrome-strong: #ffffff;
        --on-chrome-muted: rgba(255, 255, 255, .58);
        --chrome-notice: rgba(217, 161, 67, .24);
        --chrome-notice-ink: #f6dfae;
        --chrome-critical: rgba(220, 122, 135, .26);
        --chrome-critical-ink: #f8d2d7;
        --bell-attention: #6aa8f5;
        --bell-critical: #e5687a;
        --severity-notice: rgba(217, 161, 67, .30);
        --severity-critical: rgba(220, 122, 135, .28);
        --severity-notice-line: rgba(217, 161, 67, .6);
        --severity-critical-line: rgba(220, 122, 135, .6);
        --inbox-card-line: #3b4770;
        --inbox-foot: rgba(0, 0, 0, .35);
        --about-surface: #1a2240;
        --row-weight: 300;
        --footer-ink: #b8bfd8;
        --field-line: #445184;
        --ghost-fill: #232848;
        --ghost-fill-hover: #2d3358;
        --issue-lit: rgba(255, 255, 255, .45);
        --row-line: #252c4b;
        --shadow: 0 1px 3px rgba(0, 0, 0, .4);
        --shadow-lg: 0 12px 36px rgba(0, 0, 0, .55);
        /* Deeper than light's: the page is already dark, so a veil that only dims it leaves the panel
           on a ground of much the same weight and nothing reads as lifted. */
        --scrim: rgba(2, 3, 9, .74);
        --focus: #8fbaf0;
        --focus-halo: transparent;
        --field-focus-line: #8fbaf0;
        --auth-bg: #080915;
        --auth-field: #13172f;
        --auth-field-line: #293152;
    }
}

/* INVARIANT: a pinned theme pins color-scheme too. meliora.css paints its controls with the system
   colours Canvas and CanvasText, which follow color-scheme and not data-theme; left at `light dark`
   they follow the operating system, and a page pinned to light on a dark machine gets dark
   controls. The unpinned case keeps `light dark`, which is what "system" means. */
:root[data-theme="light"] {
    color-scheme: light;
}

:root[data-theme="dark"] {
    color-scheme: dark;

    --ink: #e9ebf5;
    --ink-2: #b8bfd8;
    --ink-3: #848fb0;
    --line: #394573;
    --line-2: #1f2642;
    --chip-line: #46507a;
    --bg: #080915;
    --surface: #0f1226;
    --surface-inset: #191d38;
    --field: #13172f;
    --hover: #1a203b;
    --row-hover: #1a203b;
    --accent: #4f8fe0;
    --accent-ink: #8fbaf0;
    --rail-group-lean: 48%;
    --meliora-mark-faint: #93C4AC;
    --meliora-mark-jade: #0E6B4A;
    --meliora-mark-gold: #D9A441;
    --accent-hover: #6aa1e8;
    --accent-tint: rgba(79, 143, 224, .22);
    --on-accent: #06182f;
    --danger: #dc7a87;
    --danger-tint: rgba(196, 35, 64, .18);
    --warn: #d9a143;
    --warn-tint: rgba(178, 108, 0, .2);
    --ok: #4cb98a;
    --ok-tint: rgba(23, 121, 74, .2);

    /* The chart series, in fixed order: a breakdown by anything but a tier takes them in vocabulary order. */
    --series-1: #6fa3ea;
    --series-2: #4fc0c7;
    --series-3: #e0b25a;
    --series-4: #b094de;
    --series-5: #e07a99;
    --series-6: #9ccc65;
    --series-7: #d2a07c;
    --series-8: #a0a8bd;
    --series-9: #f0956a;
    --series-10: #9a9bee;
    --series-11: #c9c863;
    --series-12: #de8fd0;
    --chrome: #22262f;
    --chrome-hover: rgba(255, 255, 255, .24);
    --chrome-inset: rgba(255, 255, 255, .10);
    --chrome-line: rgba(255, 255, 255, .22);
    --on-chrome: rgba(255, 255, 255, .72);
    --on-chrome-strong: #ffffff;
    --on-chrome-muted: rgba(255, 255, 255, .58);
    --chrome-notice: rgba(217, 161, 67, .24);
    --chrome-notice-ink: #f6dfae;
    --chrome-critical: rgba(220, 122, 135, .26);
    --chrome-critical-ink: #f8d2d7;
    --bell-attention: #6aa8f5;
    --bell-critical: #e5687a;
    --severity-notice: rgba(217, 161, 67, .30);
    --severity-critical: rgba(220, 122, 135, .28);
    --severity-notice-line: rgba(217, 161, 67, .6);
    --severity-critical-line: rgba(220, 122, 135, .6);
    --inbox-card-line: #3b4770;
    --inbox-foot: rgba(0, 0, 0, .35);
    --about-surface: #1a2240;
    --row-weight: 300;
    --footer-ink: #b8bfd8;
    --field-line: #445184;
    --ghost-fill: #232848;
    --ghost-fill-hover: #2d3358;
    --issue-lit: rgba(255, 255, 255, .45);
    --row-line: #252c4b;
    --shadow: 0 1px 3px rgba(0, 0, 0, .4);
    --shadow-lg: 0 12px 36px rgba(0, 0, 0, .55);
    --scrim: rgba(2, 3, 9, .74);
    --focus: #8fbaf0;
    --focus-halo: transparent;
    --field-focus-line: #8fbaf0;
    --auth-bg: #080915;
    --auth-field: #13172f;
    --auth-field-line: #293152;
}

/* ---- The page ---------------------------------------------------------------------------------- */

body {
    font-family: var(--font);
    font-size: .9375rem;
    margin: 0;
    line-height: 1.5;
    background: var(--bg);
    color: var(--ink);
    -webkit-font-smoothing: antialiased;
}

a { color: var(--accent-ink); }

:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }

/* The first focusable thing on the page, off-screen until focused. */
.skip {
    position: absolute;
    left: -100vw;
    top: 0;
    z-index: 100;
    background: var(--surface);
    color: var(--ink);
    padding: .5rem .8rem;
    border: 1px solid var(--line);
    border-radius: var(--radius);
}
.skip:focus { left: .5rem; top: .5rem; }

/* ---- Header: brand chrome; pinned only when there is no sub row ---------------------------------

   The lowest bar pins. With a sub row the header rolls off, because everything in it is a deliberate,
   infrequent action and the sub row holds what the page needs; without one the header is that bar.
   See docs\design-shell-bars.md. */

    /* The space a stuck table header keeps between itself and the bar above it. */
html { --sticky-gap: .5rem; }
html { --pinned-h: var(--sub-h); }
html:not([data-subheader]) { --pinned-h: var(--header-h); }
html:not([data-subheader]) .shell-header { position: sticky; top: 0; z-index: 55; }

.shell-header {
    display: flex;
    align-items: center;
    gap: .75rem;
    height: var(--header-h);
    box-sizing: border-box;
    padding: 0 1.1rem;
    background: var(--chrome);
    color: var(--on-chrome);
}

/* WHY: the margin sets the brand apart from the workspace buttons by more than the header's own gap;
   at the gap alone the first button reads as part of the name. */
.brand {
    font-weight: 600;
    font-size: 1.15rem;
    letter-spacing: .01em;
    text-decoration: none;
    color: var(--on-chrome-strong);
    margin-right: 1.5rem;
    padding: .2rem .4rem;
    margin-left: -.4rem;
    border-radius: var(--radius);
}
.brand:hover { background: var(--chrome-hover); }

/* The brand as a mark: an application's dark-chrome image at the header's height, or the lettermark tile
   on the chrome's inset with the short name beside it. */
.shell-header .brand { display: inline-flex; align-items: center; gap: .55rem; }
.shell-header .brand img { display: block; height: 1.75rem; width: auto; }
.shell-header .brand .lettermark { width: 1.75rem; height: 1.75rem; font-size: .95rem; border-color: var(--chrome-line); background: var(--chrome-inset); color: var(--on-chrome-strong); }

.workspaces { display: flex; gap: .7rem; }
.workspaces .workspace {
    text-decoration: none;
    color: var(--on-chrome);
    font-size: .8rem;
    font-weight: 600;
    letter-spacing: .02em;
    padding: .3rem .75rem;
    border-radius: var(--radius);
    background: var(--chrome-inset);
}
.workspaces .workspace:hover { background: var(--chrome-hover); color: var(--on-chrome-strong); }
.workspaces .workspace[aria-current] { color: var(--on-accent); background: var(--accent); }

/* WHY: the one element that grows, so the controls to its right stay pinned as announcements rotate
   past. Flush right inside it, so the strip sits beside the controls it belongs with. */
.announcements {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    justify-content: flex-end;
}

/* ---- The announcement strip: a fixed pill on the chrome, tinted by the message's severity ---------
   Structure, rotation and the ellipsis are meliora.css's; this is the look. The pill exists only while
   an announcement is showing: with every one hidden the strip is its restore control alone, and that
   is an ordinary header button. The tint follows the announcement showing, read off the root with
   :has(). Width is fixed so the strip never pushes the controls beside it. */

[data-meliora-announcements]:has([data-meliora-announcement-item]) {
    width: 28rem;
    max-width: 28rem;
    box-sizing: border-box;
    min-height: 1.95rem;
    padding: 0 .35rem 0 .7rem;
    background: var(--chrome-inset);
    color: var(--on-chrome-strong);
    border-radius: var(--radius);
    font-size: .8rem;
}
[data-meliora-announcements] a { color: inherit; }

[data-meliora-announcements]:has([data-current="true"][data-severity="Notice"]) {
    background: var(--chrome-notice);
    color: var(--chrome-notice-ink);
}
[data-meliora-announcements]:has([data-current="true"][data-severity="Critical"]) {
    background: var(--chrome-critical);
    color: var(--chrome-critical-ink);
    font-weight: 600;
}

[data-meliora-announcements] [data-meliora-announcement-count] {
    font-family: var(--font-mono);
    font-size: .66rem;
    letter-spacing: .04em;
    opacity: .7;
}

.shell-header [data-meliora-announcements]:has([data-meliora-announcement-item]) button {
    font-size: .68rem;
    padding: .15rem .45rem;
    color: inherit;
    border-color: currentColor;
    opacity: .75;
}
.shell-header [data-meliora-announcements]:has([data-meliora-announcement-item]) button:hover { opacity: 1; color: inherit; background: transparent; }

.shell-header form { margin: 0; }
.shell-header button {
    font: inherit;
    font-size: .8rem;
    font-weight: 500;
    background: var(--chrome-inset);
    border: 1px solid var(--chrome-line);
    border-radius: var(--radius);
    color: var(--on-chrome);
    padding: .3rem .65rem;
    cursor: pointer;
}
.shell-header button:hover { background: var(--chrome-hover); color: var(--on-chrome-strong); }

/* The user menu is a native <details>: keyboard-operable, closes on Escape, no script required. */
.who { position: relative; font-size: .85rem; margin-left: .25rem; }
.who > summary {
    cursor: pointer;
    list-style: none;
    padding: .3rem .6rem;
    border-radius: var(--radius);
    color: var(--on-chrome-strong);
    font-weight: 500;
    white-space: nowrap;
}
.who > summary::-webkit-details-marker { display: none; }
.who > summary:hover, .who[open] > summary { background: var(--chrome-hover); }

.who-menu {
    position: absolute;
    right: 0;
    top: calc(100% + .45rem);
    min-width: 13rem;
    background: var(--surface);
    color: var(--ink);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
    padding: .35rem;
    display: flex;
    flex-direction: column;
    gap: .1rem;
    z-index: 60;
}
.who-menu form { margin: 0; }
.who-menu button, .who-menu a {
    display: block;
    width: 100%;
    box-sizing: border-box;
    text-align: left;
    font: inherit;
    background: none;
    border: 0;
    color: var(--ink-2);
    text-decoration: none;
    padding: .45rem .65rem;
    border-radius: var(--radius);
    cursor: pointer;
}
.who-menu button:hover, .who-menu a:hover { background: var(--hover); color: var(--ink); }

/* INVARIANT: the script hides the dialog's fallback link with the hidden attribute, and a display rule on
   .who-menu a would outrank the user agent's [hidden]. This keeps hidden meaning hidden. */
.who-menu [hidden] { display: none; }

/* ---- The settings dialog: a rounded panel over a dimmed page, a tinted rail beside the section ------
   Size, the fixed shape and the scrolling are meliora.css's; this is the paint. */

/* Every dialog carries data-meliora-dialog-panel as well as its own name, so one rule veils them all. */
[data-meliora-dialog-panel]::backdrop { background: var(--scrim); }

[data-meliora-dialog-panel][open] {
    padding: 0;
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    background: var(--surface);
    color: var(--ink);
    box-shadow: var(--shadow-lg);
}

[data-meliora-dialog-panel] [data-meliora-dialog-header] {
    margin: 0;
    padding: .4rem .6rem .4rem 1.1rem;
    align-items: center;
    border-bottom: 1px solid var(--line);
}
[data-meliora-dialog-panel] [data-meliora-dialog-header] h2 { margin: 0; font-size: 1.15rem; font-weight: 600; }

[data-meliora-dialog-panel] [data-meliora-dialog-close] {
    display: grid;
    place-items: center;
    width: 1.9rem;
    height: 1.9rem;
    padding: 0;
    border: 0;
    border-radius: var(--radius);
    background: transparent;
    color: var(--ink-3);
    cursor: pointer;
}
[data-meliora-dialog-panel] [data-meliora-dialog-close]:hover { background: var(--hover); color: var(--ink); }

/* The confirm dialog: a title, the question, and Confirm beside Cancel at the foot. */
[data-meliora-confirm-dialog] [data-meliora-confirm-body] { padding: 1.1rem 1.25rem 1rem; }
[data-meliora-confirm-dialog] h2 { margin: 0 0 .45rem; font-size: 1.1rem; font-weight: 600; }
[data-meliora-confirm-dialog] p { margin: 0; color: var(--ink-2); line-height: 1.5; }
[data-meliora-confirm-dialog] .actions { justify-content: flex-end; margin-top: 1.1rem; }

/* The /Settings page frames the same panel the dialog holds, so every rule below names both frames. */
[data-meliora-settings-page] {
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    background: var(--surface);
    overflow: hidden;
    max-width: 56rem;
}
[data-meliora-settings-page] > [data-meliora-settings-layout] { min-height: 22rem; }

[data-meliora-dialog-panel] [data-meliora-settings-layout], [data-meliora-settings-page] [data-meliora-settings-layout] { gap: 0; flex-wrap: nowrap; }

[data-meliora-dialog-panel] [data-meliora-settings-nav], [data-meliora-settings-page] [data-meliora-settings-nav] {
    gap: .1rem;
    padding: .6rem .5rem;
    border-right: 1px solid var(--line);
    background: var(--bg);
}

[data-meliora-settings-link] {
    display: block;
    padding: .4rem .65rem;
    border-radius: var(--radius);
    font-size: .9rem;
    color: var(--ink-2);
    text-decoration: none;
}
[data-meliora-settings-link]:hover { background: var(--hover); color: var(--ink); }
[data-meliora-settings-link][data-current="true"] { background: var(--accent-tint); color: var(--accent-ink); font-weight: 600; }

/* Content starts on one line in every section: the Agreements list's, which is the pane's top padding plus
   a table's own margin. Whatever a section opens with carries no top margin of its own, so a form's first
   label, a heading and a table all begin there. */
[data-meliora-dialog-panel] [data-meliora-settings-body], [data-meliora-settings-page] [data-meliora-settings-body] { padding: 1.55rem 1.4rem 1.1rem; flex: 1; }
[data-meliora-dialog-panel] [data-meliora-settings-body] > * > :first-child, [data-meliora-settings-page] [data-meliora-settings-body] > * > :first-child { margin-top: 0; }

/* The Defaults section holds a form per choice, each with its own Save; the gap is what keeps the next
   label from reading as part of the Save above it. */
[data-meliora-defaults] > form + form { margin-top: 1.25rem; }

/* The pane's controls and the Configuration screen's are the list toolbar's size: they sit inside a task, not on a page. The reveal toggle keeps its own. */
[data-meliora-settings-body] button:not(.reveal-toggle), [data-meliora-settings-body] .btn, .settings-table button, .settings-table .btn { padding: .22rem .6rem; font-size: .8rem; line-height: 1.4; }

/* The pane's labels read a step larger than a page form's: the pane is the whole task. */
[data-meliora-settings-body] .field label { font-size: .95rem; }

/* The preferences table: a group row reads as a heading across the columns, a purpose row is a plain line, and a channel a purpose does not go by is quiet. */
[data-meliora-preferences] th[scope="row"] { white-space: normal; font-weight: 400; font-family: inherit; font-size: inherit; letter-spacing: 0; text-transform: none; color: var(--ink); background: transparent; position: static; }
[data-meliora-preferences] th[scope="row"]::before { content: none; }
[data-meliora-preferences] td[data-channel] { width: 5.5rem; }
[data-meliora-preferences] [data-meliora-preference-group] th { font-family: inherit; font-size: .8rem; letter-spacing: .04em; text-transform: uppercase; color: var(--ink-3); background: var(--bg); position: static; padding-top: .7rem; }
[data-meliora-preferences] [data-meliora-preference-group] th::before { content: none; }
[data-meliora-preferences] td[data-state="unavailable"] button { opacity: .45; cursor: not-allowed; }

/* The Token Registry: a comment's row heads its tokens as prose, a colour carries a sample, and an alias shows what it resolves to beneath it. */
[data-meliora-token-registry] [data-meliora-token-group] td { background: var(--bg); color: var(--ink-2); font-size: .875rem; padding-top: .9rem; }
[data-meliora-token-registry] [data-meliora-token-group] p { margin: 0 0 .35rem; max-width: 72ch; }
[data-meliora-token-registry] td { vertical-align: top; }
[data-meliora-token-registry] td[data-cell="reads"] { text-align: right; width: 4rem; }
.token-swatch { display: inline-block; width: 1rem; height: 1rem; margin-right: .4rem; vertical-align: -.2rem; border: 1px solid var(--line); border-radius: 3px; }
.token-resolved, .token-theme, .token-flag { display: block; margin-top: .15rem; }
.token-flag { color: var(--danger); font-size: .8rem; }

/* INVARIANT: a table header inside the dialog sticks at the pane's own top. The page offset below is for
   the page's pinned bars, which are not above the pane, and a header stuck at that offset shifts down
   over the first row of a table that sits at the top of the pane, which read as an empty list. */
[data-meliora-dialog-panel] th { top: 0; }
[data-meliora-dialog-panel] th::before { content: none; }

/* ---- The About box: a mark, a name, two versions, one line for the Suite, and a contact ------------ */

[data-meliora-about-panel] { padding: 1.1rem 1.4rem 1.25rem; font-size: .875rem; }
[data-meliora-about-app] { display: flex; align-items: center; gap: 1.5rem; }
/* The card sits on its own surface: the page's in light, a brighter blue in dark so it does not sink into the page. */
[data-meliora-about-dialog][open] { background: var(--about-surface); }
[data-meliora-about-app] h3 { margin: 0; font-size: 1.15rem; font-weight: 650; }
[data-meliora-about-version] { margin: .15rem 0 0; color: var(--ink-3); font-size: .8rem; font-variant-numeric: tabular-nums; }

/* The mark: an application's own image in the variant the theme calls for, or a bordered letter. */
[data-meliora-about-mark] .brand { position: static; margin: 0; padding: 0; }
/* An image mark stands a little further from the name than the lettermark tile does. */
[data-meliora-about-mark] img { display: block; height: 3rem; width: auto; }
[data-meliora-about-mark]:has(img) { margin-right: .6rem; }
[data-meliora-about-mark] .mark-dark { display: none; }
.lettermark {
    display: grid;
    place-items: center;
    width: 5rem;
    height: 5rem;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--bg);
    color: var(--accent-ink);
    font-size: 2.6rem;
    font-weight: 700;
}

[data-meliora-about-suite] { margin-top: 1rem; padding-top: .9rem; border-top: 1px solid var(--line); }
[data-meliora-about-suite] p { margin: 0; }
/* The mark beside the Suite's line, the reduced mark at about the line's own height. */
[data-meliora-about-suite] [data-meliora-about-suite-mark] { display: flex; align-items: center; gap: .5rem; }
[data-meliora-about-suite] [data-meliora-about-suite-mark] svg { height: 1.05em; width: auto; flex: none; }
[data-meliora-about-contact] { margin-top: 1rem; padding-top: .9rem; border-top: 1px solid var(--line); }
[data-meliora-about-contact] h4 { margin: 0 0 .35rem; font-size: .7rem; font-family: var(--font-mono); letter-spacing: .1em; text-transform: uppercase; color: var(--ink-3); }
[data-meliora-about-contact] ul { list-style: none; margin: 0; padding: 0; display: grid; gap: .2rem; }
[data-meliora-about-extra] { margin-top: 1rem; padding-top: .9rem; border-top: 1px solid var(--line); }

/* The footer's About sits beside the copyright as a plain footer link. */
.shell-foot [data-meliora-about] { display: inline; margin-left: .75rem; }
.shell-foot [data-meliora-dialog-toggle] { background: none; border: 0; padding: 0; font: inherit; color: var(--ink-3); cursor: pointer; }
.shell-foot [data-meliora-dialog-toggle]:hover { color: var(--ink); }
.shell-foot [data-meliora-dialog-fallback] { color: var(--ink-3); text-decoration: none; }

/* ---- Sub: pinned to the top wherever it exists -------------------------------------------------- */

.shell-sub {
    position: sticky;
    top: 0;
    z-index: 55;
    height: var(--sub-h);
    box-sizing: border-box;
    display: flex;
    align-items: center;
    gap: .75rem;
    padding: 0 1.1rem;
    background: var(--surface);
    border-bottom: 1px solid var(--line);
}

/* Left takes the width even when empty, so the controls stay right. */
.sub-context { flex: 1 1 auto; min-width: 0; font-size: .85rem; color: var(--ink-2);
               display: flex; align-items: center; gap: .6rem; }
.sub-controls { flex: 0 0 auto; display: flex; align-items: center; gap: .6rem; }

/* ---- Context chips: what the reader is carrying, in the sub row before the application's controls --
   A chip is a rail entry the room placed here: its label is the kind, its resolved title the value.
   Lit as a rail entry is when its page is the one shown. The value is capped and clipped, because
   three names beside a search box must fit one row. */

.context-chip {
    display: inline-flex;
    align-items: baseline;
    gap: .4rem;
    max-width: 16rem;
    padding: .22rem .6rem;
    border: 1px solid var(--chip-line);
    border-radius: 999px;
    background: var(--surface);
    color: var(--ink-2);
    font-size: .8rem;
    text-decoration: none;
    white-space: nowrap;
}
.context-chip:hover { background: var(--hover); color: var(--ink); }
.context-chip[aria-current] { background: var(--accent-tint); border-color: transparent; color: var(--accent-ink); }
.context-chip .chip-kind {
    font-family: var(--font-mono);
    font-size: .62rem;
    font-weight: 600;
    letter-spacing: .12em;
    text-transform: uppercase;
    color: var(--ink-3);
}
.context-chip[aria-current] .chip-kind { color: inherit; opacity: .75; }
.context-chip .chip-value { font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis; }

/* Between the chips and the application's controls, only when both are present. */
.sub-divider { display: inline-block; width: 1px; height: 1.4rem; margin: 0 .2rem; background: var(--line); }

/* ---- Body: nav rail beside scrolling content ---------------------------------------------------- */

.shell-body { display: flex; align-items: flex-start; }

/* Sticky below whichever bar pins, its own scrollbar, stopping above the fixed footer; every number is a token. */
.shell-nav {
    position: sticky;
    top: var(--pinned-h);
    flex: 0 0 var(--nav-w);
    height: calc(100vh - var(--pinned-h) - var(--footer-h));
    overflow-y: auto;
    box-sizing: border-box;
    padding: .85rem 0 1.5rem;
    background: var(--surface);
    border-right: 1px solid var(--line);
}
/* Inset and rounded, as the settings rail and the workspace pills are: the text still starts at the
   rail's 1.1rem, split between the margin and the padding. */
.shell-nav a {
    display: block;
    margin: 0 .5rem;
    padding: .36rem .6rem;
    border-radius: var(--radius);
    text-decoration: none;
    color: var(--ink-2);
    font-size: calc(.875rem - 1pt);
}
.shell-nav a:hover { background: var(--hover); color: var(--ink); }
/* The settings rail's treatment: the tint and the weight, no edge. */
.shell-nav a[aria-current] {
    background: var(--accent-tint);
    color: var(--accent-ink);
    font-weight: 600;
}

/* ---- Nav groups: a <details> heading with its entries inside it ---------------------------------

   WHY: membership is structural, so the indent is a descendant selector rather than a sibling one
   that would also catch every entry after the group; collapsing is native, so no script. An untitled
   group renders as bare links and gets none of this. */

.rail-group { margin: .6rem 0 .2rem; }

.rail-title {
    /* WHY: the marker is replaced rather than display:none, which takes the click affordance with it
       in some engines. */
    list-style: none;
    cursor: pointer;
    display: flex;
    align-items: center;
    gap: .45rem;
    padding: .3rem 1.1rem;
    /* A step toward the accent from the dim ink, so a closed group reads as kin to the lit one. */
    color: color-mix(in srgb, var(--accent-ink) var(--rail-group-lean), var(--ink-3));
    font-family: var(--font-mono);
    font-size: .775rem;
    font-weight: 600;
    letter-spacing: .13em;
    text-transform: uppercase;
    user-select: none;
}

.rail-title::-webkit-details-marker { display: none; }

/* Drawn rather than left to the engine, whose default caret differs and sits on the wrong side in some. */
.rail-title::before {
    content: "";
    width: 0;
    height: 0;
    /* In em rather than px, so the caret grows with the title. */
    border-left: .4em solid currentColor;
    border-top: .4em solid transparent;
    border-bottom: .4em solid transparent;
    opacity: .7;
    transition: transform .12s ease;
}

.rail-group[open] > .rail-title::before { transform: rotate(90deg); }

.rail-title:hover { color: var(--ink); }

/* The group's entries keep the rail's full width for hover and current; only the text steps in. */
.shell-nav .rail-group > a { padding-left: 1.1rem; }

/* INVARIANT: the selector names the value. Razor emits data-current="" rather than omitting the
   attribute, so a bare [data-current] lights every heading. Lighter than the entry's own highlight:
   it says "the current page is in here", which matters most when the group is collapsed. */
.rail-group[data-current="true"] > .rail-title { color: var(--accent-ink); }

/* Pads itself clear of the fixed footer, or the last row of a table hides under it. */
.shell-main {
    flex: 1 1 auto;
    min-width: 0;
    padding: 1.5rem 1.9rem;
    padding-bottom: calc(3rem + var(--footer-h));
    max-width: 72rem;
}

/* INVARIANT: a list widens the main column rather than escaping it, because a child cannot outgrow an
   ancestor's max-width. Everything a form draws — the h1 rule, the danger card, the footer — is sized
   by .shell-main, so the cap has to move here or those would stay at reading width while the grid grew
   past them. Keyed on the frame, so a page with no list is untouched. */
.shell-main:has(.list-frame) { max-width: var(--list-w); }

/* --list-w IS A CEILING, NOT A WIDTH, and the frame takes only what it holds. WHY: a table stretched to
   its container spreads the slack across every column, so a one-column list of place names became a
   hairline of text in a band of empty panel — and the primary action and the live-filter box, both
   pushed to the frame's right edge, ended up a screen away from what they act on. One cause, three
   symptoms. The frame shrink-wraps its widest row instead, so a narrow list is narrow and a wide one
   still reaches the ceiling. */
/*  A LIST IS NEVER NARROWER THAN THE FORM IT OPENS. A table shrink-wraps to its columns, so a
    four-column vocabulary came out at about half the width of the form behind its rows and the content
    column jumped on every click — and jumped the other way on a nine-column grid, so the direction was
    not even learnable. The floor makes the common case no jump at all; a genuinely wide grid still
    exceeds it, up to --list-w.

    min() rather than the bare token so a viewport narrower than a form still gets the whole of it. */
.list-frame { width: fit-content; max-width: 100%; min-width: min(var(--form-w), 100%); }

/* INVARIANT: a table is never narrower than the toolbar above it. The frame is already sized to its
   widest row, so filling the frame is what says that — and it costs nothing when the table is the
   widest thing, because then the frame is the table. The floor is the last resort beneath both, for a
   list whose toolbar is a search box and nothing else. */
.list-frame table { width: 100%; min-width: var(--list-min-w); }

/* ---- Footer: fixed, full width ------------------------------------------------------------------ */

.shell-foot {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    height: var(--footer-h);
    box-sizing: border-box;
    display: flex;
    align-items: center;
    gap: .75rem;
    padding: 0 1.1rem;
    background: var(--surface);
    border-top: 1px solid var(--line);
    font-size: .72rem;
    color: var(--ink-3);
    z-index: 58;
}

/* ---- Back to top ---------------------------------------------------------------------------------
   The anchor is the feature and works with no script; meliora.js only decides when it is worth
   showing, and it starts hidden so a blocked script leaves it absent rather than always present. */
.to-top {
    position: fixed;
    right: 1.1rem;
    bottom: calc(var(--footer-h) + .85rem);
    z-index: 59;
    padding: .55rem 1rem;
    font-size: .75rem;
    font-weight: 600;
    letter-spacing: .02em;
    text-decoration: none;
    color: var(--on-accent);
    background: var(--accent);
    border-radius: var(--radius);
    box-shadow: 0 3px 12px rgba(0, 0, 0, .22);
}

.to-top:hover { background: var(--accent-hover); }
.to-top[hidden] { display: none; }

/* ---- Content ------------------------------------------------------------------------------------ */

h1 { font-size: 1.5rem; font-weight: 600; letter-spacing: -.01em; line-height: 1.2; margin: 0 0 1rem; }

/* A key or a code takes the shell's monospace stack, not the browser's, so it reads the same on every platform. */
code { font-family: var(--font-mono); font-size: .92em; }
h2 { font-size: 1.1rem; font-weight: 600; margin: 1.5rem 0 .6rem; }

/* A heading followed by its subtitle sits tighter than a heading followed by content. */
h1 + .muted { margin-top: -.6rem; margin-bottom: 1.25rem; }

hr { margin: 1.5rem 0; border: 0; border-top: 1px solid var(--line); }

/* A table is a panel: its own surface, a hairline, rounded corners on the edge cells. WHY: the
   corners are on the cells rather than on an overflow:hidden wrapper, which would make the table a
   scroll container and pin a sticky head to it rather than to the viewport. */
table {
    border-collapse: separate;
    border-spacing: 0;
    width: 100%;
    margin: 1rem 0;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow);
}
th, td { text-align: left; padding: .4rem .8rem; border-bottom: 1px solid var(--row-line); }
/* The line under the header row is a step bolder than the lines between rows. */
thead th { border-bottom-color: var(--line); }

/*  THE HEADER ROW IS THE CARD'S TOP EDGE, NOT A BAR LAID ON IT — and it is the same species of thing
    as a card's legend, so it is lettered the same way: the mono face, the card label's size and
    tracking, and the accent ink. A filled grey cap was the heaviest element on a list page and the one
    colour with no relation to anything else on it, which is most of why a list and a form read as two
    places rather than two views.

    INVARIANT: the fill stays OPAQUE, whatever colour it is. The row is sticky, so rows scroll behind
    it; a transparent header would let them read through the words. --surface is the card's own fill,
    which is why it can carry that job and still disappear. */
th {
    font-family: var(--font-mono);
    font-size: var(--card-label-size);
    line-height: 1.5;
    font-weight: 600;
    letter-spacing: .13em;
    text-transform: uppercase;
    color: var(--accent-ink);
    background: var(--surface);
    white-space: nowrap;
    position: sticky;
    top: calc(var(--pinned-h) + var(--sticky-gap));
    z-index: 3;
}
/* The gap above a stuck header is painted in the page colour by a strip that scrolls with the header,
   so the rows passing beneath it do not show through the space. */
th::before {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    top: calc(-1 * var(--sticky-gap) - 1px);
    height: calc(var(--sticky-gap) + 1px);
    background: var(--bg);
}
th a { color: inherit; text-decoration: none; }
th a:hover { color: var(--accent-ink); }
thead tr:first-child th:first-child { border-top-left-radius: var(--radius-lg); }
thead tr:first-child th:last-child { border-top-right-radius: var(--radius-lg); }
tbody tr:last-child td { border-bottom: 0; }
tbody tr:last-child td:first-child { border-bottom-left-radius: var(--radius-lg); }
tbody tr:last-child td:last-child { border-bottom-right-radius: var(--radius-lg); }
td { font-size: .8125rem; line-height: 1.41; font-weight: var(--row-weight); }

/* A CELL HOLDING A QUANTITY, put there by the screen. WHY a class rather than a rule keyed on the type:
   an identifier that happens to be digits — a student code, a year — reads as a label and stays left, so
   what decides this is what the number MEANS, which only the screen knows. Tabular figures are half the
   point: without them the digits are proportional and a right-aligned column still wobbles. */
th.num, td.num { text-align: right; font-variant-numeric: tabular-nums; }

/* A CELL HOLDING A SHORT MARK, a rank pill or a flag, centred for the same reason and by the same hand:
   a left-aligned pill under a short header reads as ragged. */
th.center, td.center { text-align: center; }

/* An abbreviated header carries its full name as an <abbr>; the browser's dotted underline would read
   as a second link inside the sort link, so the title is the hover and the mark is not drawn. */
th abbr[title] { text-decoration: none; }

/* The list toolbar: search and the entity's filter controls on the left, Search and Clear beside them, and
   the primary action alone at the right. The filters slot is display: contents so its controls sit in the
   row as if the screen had written them here. */
.list-toolbar { display: flex; gap: .5rem; flex-wrap: wrap; align-items: center; margin-bottom: .5rem; }
.list-toolbar input[type=search] { width: var(--list-search-w); }
.list-toolbar .list-filters { display: contents; }

/* A second row of filters, started on purpose rather than by wrapping: it takes the whole width and
   is indented past the search box, so its first control stands directly under the first control of
   the row above. The screen decides which controls go below. */
.list-toolbar .list-filter-row { flex-basis: 100%; display: flex; gap: .5rem; flex-wrap: wrap; align-items: center; padding-left: calc(var(--list-search-w) + .5rem); }

/* Row 1: what you can do here. The primary action, then the live filter, set apart from it: the two
   are unrelated acts that happen to share a row.

   THE SEPARATION FROM THE QUERY IS RHYTHM, NOT A RULE. Rows 2 and 3 are one thing said in two lines —
   what you asked for, and what came back — so they sit close, and the distance from row 1 is what says
   it is something else. A hairline here would be a third horizontal line within a centimetre of the
   table's own top edge; spacing also costs nothing on a list that has no query row.

   INVARIANT: the extra space is there to separate row 1 from the query and only means that. A list whose
   filter box is followed by its table takes the ordinary gap, which is why this is keyed on the row that
   follows rather than set on row 1 outright. */
/* .6rem is the results row's gap between Search and Clear: two small buttons side by side sit the same
   distance apart wherever they are on this screen. */
.list-actions { display: flex; gap: .6rem; flex-wrap: wrap; align-items: center; margin-bottom: .5rem; }

/* The filter leads the row, lined up with the search box beneath it, and the acts sit apart from it:
   one side narrows what is already on screen, the other changes or extracts the list. */
.list-actions .list-filter { margin-right: .4rem; }
.list-actions:has(+ .list-toolbar) { margin-bottom: 1.15rem; }

/* A choice filter: closed, it reads as a field beside the search box; open, a small surface of boxes. */
.list-choice { position: relative; }
.list-choice > summary {
    display: flex;
    align-items: baseline;
    gap: .35em;
    list-style: none;
    cursor: pointer;
    padding: .45rem 2rem .45rem .75rem;
    font-size: .9rem;
    color: var(--ink);
    background: var(--field);
    border: 1px solid var(--field-line);
    border-radius: var(--radius);
    white-space: nowrap;
    user-select: none;
}

/* INVARIANT: the label is free and the value is not. The control's width is the label plus a fixed
   allowance, so it is the same whatever is picked — the whole text is in the title, and the count
   sentence on the results row names every narrowing in full. */
.list-choice-label { flex: 0 0 auto; }
.list-choice-value { flex: 0 0 var(--list-choice-value-w); overflow: hidden; text-overflow: ellipsis; }
.list-choice > summary::-webkit-details-marker { display: none; }
.list-choice > summary::after { content: "\25BE"; position: absolute; right: .7rem; top: 50%; transform: translateY(-50%); color: var(--ink-3); font-size: .8rem; }
.list-choice[open] > summary { border-color: var(--accent); }
.list-choice-panel {
    position: absolute;
    top: calc(100% + .25rem);
    left: 0;
    z-index: 20;
    min-width: 100%;
    max-height: 18rem;
    overflow-y: auto;
    padding: .3rem;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    box-shadow: var(--shadow-lg);
}
.list-choice-panel label { display: flex; align-items: center; gap: .5rem; padding: .3rem .6rem; border-radius: var(--radius); font-size: .9rem; white-space: nowrap; cursor: pointer; }
.list-choice-panel label:hover { background: var(--hover); }

/* A flag filter: a checkbox drawn as a field beside the search box, the choice control's shape without its panel. */
.list-flag {
    display: inline-flex;
    align-items: center;
    gap: .45em;
    padding: .45rem .75rem;
    font-size: .9rem;
    color: var(--ink);
    background: var(--field);
    border: 1px solid var(--field-line);
    border-radius: var(--radius);
    white-space: nowrap;
    cursor: pointer;
    user-select: none;
}
.list-flag input { margin: 0; }

/* The export menu borrows the choice control's MECHANIC — a details that opens, closes and needs no
   script — and the primary action's SHAPE, because it acts on the list rather than holding a value.

   GHOST RATHER THAN FILLED, and the size is what makes them a set: two solid buttons side by side are
   two primaries, so neither leads, and the one that creates the record this screen is for should. Open
   is drawn like hover, so a menu standing open reads as pressed. Its summary carries no value, so it
   takes no allowance and no caret column. */
.list-export > summary {
    font-weight: 600;
    font-size: .8rem;
    line-height: 1.4;
    color: var(--ink-2);
    background: var(--ghost-fill);
    border-color: var(--field-line);
    padding: .22rem .6rem;
}
.list-export[open] > summary,
.list-export > summary:hover { background: var(--ghost-fill-hover); color: var(--ink); }
.list-export > summary::after { content: none; }
.list-export-head { margin: .5rem 0 .15rem; padding: 0 .6rem; font-family: var(--font-mono); font-size: .62rem; font-weight: 600; letter-spacing: .12em; text-transform: uppercase; color: var(--ink-3); }
.list-export-head:first-child { margin-top: .15rem; }

/* A choice is a LINK, drawn like the panel's labels above it so the two menus read as one control
   with two jobs. The underline comes off because a row in a menu is not prose. */
.list-export-choice {
    display: block;
    padding: .2rem .6rem;
    color: var(--ink);
    text-decoration: none;
    white-space: nowrap;
}
.list-export-choice:hover { background: var(--hover); text-decoration: none; }

/* Where the application writes PDFs the panel is a form: the format as the forms' segmented pair, then
   the rows choices as submit buttons drawn exactly as the links they replace, so the two menus read
   alike. The panel's own label rule would round each half and pad it as a menu row; the pair keeps
   the segmented control's square inner edges. */
.list-export-formats.list-scope { margin: .15rem .6rem .3rem; }
.list-export-note { margin: 0 .6rem .3rem; max-width: 14rem; font-size: .75rem; white-space: normal; }
.list-choice-panel .list-export-formats label { display: block; border-radius: 0; padding: .2rem .7rem; font-size: .8rem; }
.list-export-form button.list-export-choice {
    display: block;
    width: 100%;
    margin: 0;
    border: 0;
    border-radius: 0;
    background: none;
    box-shadow: none;
    font: inherit;
    font-weight: inherit;
    text-align: left;
    cursor: pointer;
}
.list-export-form button.list-export-choice:hover { background: var(--hover); }

/* An unpaged list asks nothing, so its export is one button on the row beside the primary action. */
.list-export-only { padding: .22rem .6rem; font-size: .8rem; line-height: 1.4; }

/* The results row: Search and Clear at the search box's left edge, narrow so they read as the row's controls
   and not the page's; the sentence beside them always starts at the same place, because Clear is drawn
   disabled rather than left out. */
.list-query { display: flex; gap: .6rem; margin: 0; }
.list-query button, .list-query .btn { padding: .22rem .6rem; font-size: .8rem; line-height: 1.4; }
.list-actions .list-primary { padding: .22rem .6rem; font-size: .8rem; line-height: 1.4; flex: 0 0 auto; }
.list-query [data-disabled="true"] { opacity: .45; }

/* ---- Controls ----------------------------------------------------------------------------------- */

input:not([type=checkbox]):not([type=radio]):not([type=submit]):not([type=button]),
select,
textarea {
    font: inherit;
    font-size: .9rem;
    color: var(--ink);
    background: var(--field);
    border: 1px solid var(--field-line);
    border-radius: var(--radius);
    padding: .4rem .6rem;
    box-sizing: border-box;
}
input::placeholder { color: var(--ink-3); }

/* A locked control keeps its words legible and loses the field's own ground, so it reads as read. */
input:disabled:not([type=checkbox]):not([type=radio]), select:disabled, textarea:disabled { background: transparent; color: var(--ink-2); cursor: default; }

textarea { min-height: var(--textarea-field); }
textarea.note { min-height: var(--textarea-note); }
textarea.body { min-height: var(--textarea-body); }
input:not([type=checkbox]):not([type=radio]):not([type=submit]):not([type=button]):focus, select:focus, textarea:focus {
    outline: none;
    box-shadow: 0 0 0 3px var(--focus-halo);
    border-color: var(--field-focus-line);
    background: var(--surface);
}

/* ---- The select's list, drawn by the page where the browser allows it ------------------------------
   appearance: base-select makes the open list ordinary elements this stylesheet paints, so the hover
   and the current choice take the accent family instead of the operating system's highlight, and the
   list itself takes the shell's surface. A browser without it ignores every rule here and draws its own
   list in the palette color-scheme asks for; nothing else changes. */
select, ::picker(select) { appearance: base-select; }

select::picker-icon { color: var(--ink-3); }

::picker(select) {
    margin-top: .25rem;
    padding: .3rem;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--surface);
    color: var(--ink);
    box-shadow: var(--shadow-lg);
}

select option {
    padding: .35rem .6rem;
    border-radius: var(--radius);
    font-size: .9rem;
}
select option:hover, select option:focus-visible { background: var(--accent-tint); color: var(--accent-ink); outline: none; }
select option:checked { background: var(--accent); color: var(--on-accent); }
select option::checkmark { display: none; }

/* A group's label is bold in the accent ink, and its options step in beneath it: on the dark surface
   two weights of the same ink read as one, so the label owes its difference to colour and indent. */
select optgroup { font-size: .82rem; font-weight: 700; color: var(--accent-ink); padding: .4rem .6rem .15rem; }
select optgroup option { font-size: .9rem; font-weight: 400; color: var(--ink); padding-left: 1.3rem; }

input[type=checkbox], input[type=radio] { accent-color: var(--accent); }
button, .btn {
    font: inherit;
    font-size: .875rem;
    font-weight: 600;
    color: var(--on-accent);
    background: var(--accent);
    border: 1px solid var(--accent);
    border-radius: var(--radius);
    padding: .45rem .95rem;
    cursor: pointer;
    text-decoration: none;
}
button:hover, .btn:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
.btn.ghost, button.ghost { background: transparent; color: var(--ink-2); border-color: var(--line); }
.btn.ghost:hover, button.ghost:hover { background: var(--hover); color: var(--ink); }
button:disabled { opacity: .5; cursor: not-allowed; }

/* ---- The class vocabulary the library's screens render with ------------------------------------- */

.field { margin-bottom: .95rem; max-width: 26rem; }
.field label { display: block; font-weight: 600; font-size: .85rem; color: var(--ink-2); margin-bottom: .3rem; }
.field input, .field select { width: 100%; }

.flash, .errors {
    padding: .65rem .85rem;
    border-radius: var(--radius);
    margin-bottom: 1rem;
    font-size: .9rem;
    line-height: 1.45;
}
.flash { background: var(--accent-tint); color: var(--accent-ink); }
.errors { background: var(--danger-tint); color: var(--danger); }
.errors ul { margin: 0; padding-left: 1.2rem; }
.field-error { color: var(--danger); font-size: .85rem; margin-top: .25rem; }
.muted { color: var(--ink-3); font-size: .875rem; }
.actions { display: flex; gap: .6rem; align-items: center; margin-top: 1.25rem; }
/* A thin actions row: the form frame's button size, for a row that stands outside a frame. */
.actions.thin button, .actions.thin .btn { padding: .22rem .6rem; font-size: .8rem; line-height: 1.4; }
/* Two frames in a column keep a breath between the first's actions and the second's card. */
.form-frame + .form-frame { margin-top: 1.25rem; }
/* Who signed a version, under a hairline beneath its actions. */
.version-acceptances { margin-top: 1.25rem; padding-top: .9rem; border-top: 1px solid var(--line); }
.version-acceptances h2 { margin: 0 0 .5rem; font-size: 1rem; }

/* WHY: read off a screen to be typed, so monospace is what keeps 0/O and 1/l apart. */
.recovery-codes { font-family: var(--font-mono); letter-spacing: .06em; }

/* ---- Status tables: a titled group, a table of rows, a pill naming each row's state -------------
   Every pill carries its own words; the colour is the second signal and never the only one. */
.status-group { margin-bottom: 2.5rem; }
.status-group h2 { display: flex; align-items: baseline; gap: .6rem; }
.status-group h3 { margin-top: 1.25rem; font-size: .8rem; text-transform: uppercase; letter-spacing: .08em; color: var(--ink-3); }
.status-group table { margin-top: .4rem; }
.status-group td { font-variant-numeric: tabular-nums; }

/* No leading bar: a note reads as a paragraph, not a callout. */
/* TWO CLASSES, TWO JOBS, and they were one word apart from being the same mistake. .lede is a page's
   one-line description, directly under its heading and nowhere else; .note is secondary text anywhere
   — a description inside a cell, a qualifier beside a control, a subtitle of a record's own data.

   .lede had NO RULE AT ALL until the pages that carry prose were gathered: it was written on one page
   and styled nowhere, so that paragraph rendered as body text beside neighbours that did not. What
   used to follow it now lives in Page Information, so the lede is a sentence rather than a paragraph. */
.lede { margin: 0 0 .9rem; max-width: 62ch; color: var(--ink-2); font-size: .95rem; line-height: 1.5; }

.note { padding: .35rem 0; color: var(--ink-2); font-size: .9rem; }

.pill { display: inline-block; padding: .1rem .6rem; border-radius: 999px; font-size: .75rem; font-weight: 600; white-space: nowrap; background: var(--line-2); color: var(--ink-2); }
.pill.ok   { background: var(--ok-tint); color: var(--ok); }
.pill.warn { background: var(--warn-tint); color: var(--warn); }
.pill.bad  { background: var(--danger-tint); color: var(--danger); }
.flash.bad { background: var(--danger-tint); color: var(--danger); }

/* A POST inside a table cell, styled to sit beside a neighbouring anchor. */
.inline-form { display: inline; }
.link-button { background: none; border: 0; padding: 0; font: inherit; font-weight: inherit; color: var(--accent-ink); text-decoration: underline; cursor: pointer; }
.link-button:hover { background: none; opacity: .8; }

/* ---- The inbox bell: an icon, a count badge, and the bell filled for critical -----------------------
   The toggle carries data-unread, data-critical and data-new. Severity is the bell's fill and the
   badge's colour; whether the reader has looked is the badge's fill: filled says something arrived
   since they last opened the panel, hollow says they have seen it and not acted. Every state holds
   with colour removed. WHY: --bell-attention rather than --ok, because green means "all clear". */

.shell-header [data-meliora-popover-toggle] {
    display: inline-flex;
    align-items: center;
    padding: .3rem .5rem;
    line-height: 1;
}
.shell-header [data-meliora-popover-toggle] svg { display: block; }

/* ---- Page Information: a text button beside Theme, and the panel it opens --------------------------
   IT IS DRAWN ON EVERY PAGE AND SAYS WHICH ONES HAVE SOMETHING. Filled when this page has
   information, hollow when it does not -- the bell's own vocabulary, reused by its neighbour rather
   than invented. Hiding the control on pages with nothing was the alternative and it reflows the
   whole cluster on every navigation; a permanently dimmed one reads as broken. Both states hold with
   colour removed, because the fill is a background rather than a hue.

   SPACING IS INHERITED, NOT DECLARED. The header's own gap sets it apart from Theme, and .lock's
   margin below sets the lock apart from it -- the same pair of distances the cluster already had. */
.shell-header .page-info { position: relative; }
.shell-header .page-info > summary {
    cursor: pointer;
    list-style: none;
    font-size: .8rem;
    font-weight: 500;
    padding: .3rem .65rem;
    border: 1px solid var(--chrome-line);
    border-radius: var(--radius);
    color: var(--on-chrome);
    white-space: nowrap;
}
.shell-header .page-info > summary::-webkit-details-marker { display: none; }
.shell-header .page-info[data-page-info-has="true"] > summary { background: var(--chrome-inset); }
.shell-header .page-info[data-page-info-has="false"] > summary { background: transparent; color: var(--on-chrome-muted); opacity: .62; }
.shell-header .page-info > summary:hover,
.shell-header .page-info[open] > summary { background: var(--chrome-hover); color: var(--on-chrome-strong); opacity: 1; }

/* A PAGE WITH NOTHING TO OPEN GETS A LABEL, and it is styled like the hollow summary and behaves like
   neither: no pointer, no hover, no focus ring, because responding to a hover is the same lie as
   taking the pressed look on a click that opens nothing. It keeps the border so the header's spacing
   does not change between a page that has information and one that does not. */
.shell-header .page-info-inert {
    display: inline-block;
    font-size: .8rem;
    font-weight: 500;
    padding: .3rem .65rem;
    border: 1px solid var(--chrome-line);
    border-radius: var(--radius);
    background: transparent;
    color: var(--on-chrome-muted);
    opacity: .62;
    white-space: nowrap;
    cursor: default;
    user-select: none;
}

/* The panel hangs from the control rather than opening a dialog: page information is consulted WHILE
   looking at the page, and a scrim says the opposite. Length is bounded by the scroll, not by a cap
   on what an operator may write -- see docs\design-page-information.md section 6. */
.page-info-panel {
    position: absolute;
    top: calc(100% + .35rem);
    right: 0;
    z-index: 60;
    width: min(30rem, 80vw);
    max-height: min(26rem, 60vh);
    overflow-y: auto;
    padding: .75rem .9rem;
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    background: var(--surface);
    color: var(--ink);
    box-shadow: var(--shadow-lg);
    font-size: .88rem;
    line-height: 1.5;
    text-align: left;
    white-space: normal;
}
.page-info-panel p { margin: 0 0 .6rem; }
.page-info-panel p:last-child { margin-bottom: 0; }

/* When it was written, set below the words by a rule: it is provenance rather than part of the text,
   and a reader who wants the words should not have to sort them from the metadata. */
.page-info-recorded {
    margin-top: .7rem;
    padding-top: .5rem;
    border-top: 1px solid var(--line);
    font-size: .82rem;
}

/* A page's printout is the page; help about how to read it is not part of the record. This is the
   shell's only print rule, so it is scoped to this control rather than opening a print stylesheet
   that would then have to decide about every other piece of chrome. */
@media print {
    .shell-header .page-info { display: none; }
}

/* The padlock is an icon button on the bell's pattern, set a hair apart from the word before it so the two icons group. */
.shell-header .lock { margin-left: .55rem; }
.shell-header .lock button { display: inline-flex; align-items: center; padding: .3rem .5rem; line-height: 1; }
.shell-header .lock button svg { display: block; }

/* Solid and red: the only time the bell is solid is an unread critical, so the fill may carry the colour too. */
[data-meliora-popover-toggle][data-critical="true"] svg { color: var(--bell-critical); }
[data-meliora-popover-toggle][data-critical="true"] [data-meliora-inbox-bell] { fill: currentColor; }

[data-meliora-inbox-count] {
    box-sizing: border-box;
    min-width: 1.3rem;
    height: 1.3rem;
    padding: 0 .32rem;
    border-radius: 999px;
    border: 1.5px solid var(--bell-attention);
    background: var(--chrome);
    color: var(--bell-attention);
    font-size: .72rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
[data-meliora-popover-toggle][data-critical="true"] [data-meliora-inbox-count] {
    border-color: var(--bell-critical);
    color: var(--bell-critical);
}
[data-meliora-popover-toggle][data-new="true"] [data-meliora-inbox-count] {
    background: var(--bell-attention);
    color: #ffffff;
}
[data-meliora-popover-toggle][data-critical="true"][data-new="true"] [data-meliora-inbox-count] {
    background: var(--bell-critical);
}

/* One swing when a page loads with something new; the badge is the signal and this only draws the eye. */
@media (prefers-reduced-motion: no-preference) {
    [data-meliora-popover-toggle][data-new="true"] svg {
        transform-origin: 50% 0;
        animation: bell-swing .9s ease-in-out 1;
    }
}

@keyframes bell-swing {
    0%, 100% { transform: rotate(0); }
    20%      { transform: rotate(14deg); }
    40%      { transform: rotate(-10deg); }
    60%      { transform: rotate(6deg); }
    80%      { transform: rotate(-3deg); }
}

/* ---- The inbox panel: the dialog's vocabulary, a wide list of headlines ---------------------------- */

/* As tall as its rows up to a share of the viewport, the list scrolling inside and the footer pinned;
   not([hidden]) because a display here would otherwise outrank the closed state. */
[data-meliora-inbox] [data-meliora-popover-panel]:not([hidden]) {
    display: flex;
    flex-direction: column;
    overflow: hidden;
}
[data-meliora-inbox] [data-meliora-popover-panel] {
    width: 40rem;
    max-width: 92vw;
    max-height: 85vh;
    margin-top: .45rem;
    padding: 0;
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    background: var(--surface);
    color: var(--ink);
    box-shadow: var(--shadow-lg);
    font-size: .85rem;
}

[data-meliora-inbox-panel] { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
:is([data-meliora-inbox], [data-meliora-inbox-page]) [data-meliora-inbox-list] { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; }
[data-meliora-inbox-foot] { flex: 0 0 auto; }

/* Severity is the row's ground: each row is a card on the page colour, the gap between them showing
   it, with a line a step bolder than a hairline. Information keeps the surface; Notice and Critical
   take the severity tokens, which default to the page's warn and danger tints. */
:is([data-meliora-inbox], [data-meliora-inbox-page]) [data-meliora-inbox-list] { padding: .5rem .6rem; background: var(--bg); }
:is([data-meliora-inbox], [data-meliora-inbox-page]) [data-meliora-inbox-item] {
    padding: .6rem .9rem;
    margin: 0 0 .45rem;
    border: 1px solid var(--inbox-card-line);
    border-radius: var(--radius);
    background: var(--surface);
    font-weight: 400;
}
:is([data-meliora-inbox], [data-meliora-inbox-page]) [data-meliora-inbox-item]:last-child { margin-bottom: 0; }
:is([data-meliora-inbox], [data-meliora-inbox-page]) [data-meliora-inbox-item] + [data-meliora-inbox-item] { border-top-color: var(--inbox-card-line); }
:is([data-meliora-inbox], [data-meliora-inbox-page]) [data-meliora-inbox-item][data-severity="Notice"] { background: var(--severity-notice); border-color: var(--severity-notice-line); }
:is([data-meliora-inbox], [data-meliora-inbox-page]) [data-meliora-inbox-item][data-severity="Critical"] { background: var(--severity-critical); border-color: var(--severity-critical-line); }
/* Unread is told by weight: the subject heavy while unread, ordinary once read, and a read card sits
   back a step. WHY: the shell layer outranks the structural layer, so the weight has to be said here
   or the structural rule for unread is silently lost. */
:is([data-meliora-inbox], [data-meliora-inbox-page]) [data-meliora-inbox-item] h3 { font-size: .875rem; font-weight: 700; margin: 0; }
:is([data-meliora-inbox], [data-meliora-inbox-page]) [data-meliora-inbox-item][data-read="true"] h3 { font-weight: 500; }
:is([data-meliora-inbox], [data-meliora-inbox-page]) [data-meliora-inbox-item][data-read="true"] { opacity: .78; }
:is([data-meliora-inbox], [data-meliora-inbox-page]) [data-meliora-inbox-item][data-read="true"]:hover { opacity: 1; }
/* The subject's link is the subject's ink, underlined in the accent, so it reads on every card ground. */
:is([data-meliora-inbox], [data-meliora-inbox-page]) [data-meliora-inbox-item] h3 a { color: inherit; text-decoration: underline; text-decoration-color: var(--accent); text-decoration-thickness: 1px; text-underline-offset: 3px; }
:is([data-meliora-inbox], [data-meliora-inbox-page]) [data-meliora-inbox-item] h3 a:hover { color: var(--accent-ink); text-decoration-color: var(--accent-ink); }
:is([data-meliora-inbox], [data-meliora-inbox-page]) [data-meliora-inbox-item] time { display: block; font-size: .7rem; color: var(--ink-2); margin: .1rem 0 .25rem; }

/* Two lines, then the page: a headline panel, not a reading pane. */
:is([data-meliora-inbox], [data-meliora-inbox-page]) [data-meliora-inbox-item] p {
    margin: 0 0 .4rem;
    color: var(--ink-2);
    font-weight: 400;
    font-size: .8rem;
    line-height: 1.4;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* The row's actions are quiet: text-sized, hairline, and they take the page's ink rather than the chrome's. */
/* The row's actions and the footer rail's two controls, one rule: text-sized, a lighter ground, a hairline. */
.shell-header [data-meliora-inbox] [data-meliora-popover-panel] button,
[data-meliora-inbox-page] [data-meliora-inbox-item] button,
[data-meliora-inbox-page] [data-meliora-inbox-read-all],
[data-meliora-inbox-all] {
    font-size: .72rem;
    padding: .18rem .5rem;
    background: var(--surface-inset);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    color: var(--ink-2);
    text-decoration: none;
    font-weight: 500;
    line-height: 1.4;
}
.shell-header [data-meliora-inbox] [data-meliora-popover-panel] button:hover,
[data-meliora-inbox-page] [data-meliora-inbox-item] button:hover,
[data-meliora-inbox-page] [data-meliora-inbox-read-all]:hover,
[data-meliora-inbox-all]:hover { background: var(--hover); color: var(--ink); }
.shell-header [data-meliora-inbox] [data-meliora-popover-panel] button:disabled { opacity: .45; }

:is([data-meliora-inbox], [data-meliora-inbox-page]) [data-meliora-inbox-empty] { margin: 0; padding: 1rem; color: var(--ink-3); }

/* ---- The full page: the panel's cards at reading width, the body unclamped, and the two views as a
   small rail beside the page's one bulk action. ---- */
[data-meliora-inbox-page] { max-width: 48rem; }
[data-meliora-inbox-page] [data-meliora-inbox-list] { padding: 0; background: transparent; }
[data-meliora-inbox-page] [data-meliora-inbox-item] { margin-bottom: .55rem; }
[data-meliora-inbox-page] [data-meliora-inbox-item] p {
    margin: 0 0 .45rem;
    color: var(--ink-2);
    font-size: .85rem;
    line-height: 1.45;
    display: block;
    -webkit-line-clamp: none;
    overflow: visible;
}
[data-meliora-inbox-page] [data-meliora-inbox-item] form + form { margin-left: .35rem; }
[data-meliora-inbox-page] [data-meliora-inbox-empty] { padding: .5rem 0; }
[data-meliora-inbox-bar] { display: flex; justify-content: space-between; align-items: center; gap: 1rem; margin: 0 0 .75rem; }
[data-meliora-inbox-views] { display: flex; gap: .3rem; }
[data-meliora-inbox-view] {
    padding: .32rem .75rem;
    border-radius: var(--radius);
    color: var(--ink-2);
    font-size: .9rem;
    font-weight: 500;
    text-decoration: none;
}
[data-meliora-inbox-view]:hover { background: var(--hover); color: var(--ink); }
[data-meliora-inbox-view][data-current="true"] { background: var(--accent-tint); color: var(--accent-ink); font-weight: 600; }

[data-meliora-inbox-foot] {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: .6rem;
    padding: .55rem 1rem;
    border-top: 1px solid var(--line);
    background: var(--inbox-foot);
    border-radius: 0 0 var(--radius-lg) var(--radius-lg);
}
[data-meliora-inbox-foot] form { margin: 0; }
/* The link alone sits at the right when there is nothing to mark. */
[data-meliora-inbox-foot] > [data-meliora-inbox-all]:only-child { margin-left: auto; }

/* ---- The list pager, above and below every grid -------------------------------------------------
   One row that wraps rather than scrolls; the size choices are links, because a select that applies
   itself needs script. */
/* INVARIANT: a strip fills the frame but never widens it. WHY: the count sentence names every
   narrowing in full, so it is as long as the values a reader picked — and with the frame sized to its
   widest row and the table sized to the frame, that sentence would set the width of the grid and move
   every column as the reader filtered. width: 0 keeps it out of the frame's intrinsic size and
   min-width puts it back afterwards, so prose wraps, which is what prose should do. */
.list-pager {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: .35rem 1.25rem;
    margin: .5rem 0;
    font-size: .85rem;
    color: var(--ink-2);
    width: 0;
    min-width: 100%;
}

.list-pager > p, .list-pager > form { margin: 0; }

.list-sizes { display: flex; align-items: baseline; gap: .5rem; }

/* THE PAGING ROW READS AS TWO QUESTIONS: how many rows, at the left; which page, at the right. The
   sizes lead the row whatever order the partial wrote them in, and everything after them is pushed
   over, so the steps and the jump box sit together rather than trailing the sizes. */
.list-pager[data-position="bottom"] .list-sizes { order: -1; margin-right: auto; }

/* Row 3 sits tight under row 2: the query and its answer are one unit. */
.list-pager[data-position="top"] { margin-top: 0; }

.list-size { font-variant-numeric: tabular-nums; }
.list-size.current { font-weight: 600; color: var(--ink); opacity: 1; }

.list-steps { display: flex; gap: .75rem; }

/* The live filter, rendered hidden by the server and revealed by meliora.js. It sits in row 1 beside
   the primary action, and its "n of N" follows the box rather than preceding it: the count appears
   only once something is typed, and a count that pushed the box sideways as you typed would be the
   toolbar-width problem again in miniature. */
.list-filter { display: flex; align-items: baseline; gap: 0; margin: 0; }
.list-filter-count:not(:empty) { margin-left: .6rem; }
.list-page { margin-left: .75rem; }
.list-filter input[type=search] { width: var(--list-filter-w); padding: .22rem .55rem; font-size: .85rem; }
.list-filter-count { font-variant-numeric: tabular-nums; color: var(--ink-3); font-size: .85rem; }
/* The filter's scope, where a page offers one: a run of radios drawn as one segmented control. */
.list-scope { display: inline-flex; margin-left: .6rem; border: 1px solid var(--field-line); border-radius: var(--radius); overflow: hidden; align-self: center; }
.list-scope label { position: relative; padding: .2rem .6rem; font-size: .8rem; line-height: 1.4; color: var(--ink-2); background: var(--ghost-fill); cursor: pointer; user-select: none; }
.list-scope label + label { border-left: 1px solid var(--field-line); }
.list-scope label:hover { background: var(--ghost-fill-hover); color: var(--ink); }
.list-scope input[type=radio] { position: absolute; width: 1px; height: 1px; margin: 0; opacity: 0; }
.list-scope label:has(> input:checked) { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.list-scope label:has(> input:focus-visible) { outline: 2px solid var(--accent); outline-offset: -2px; }

.list-jump { display: flex; align-items: baseline; gap: .4rem; }
.list-jump input[type=number] { width: 4.5rem; padding: .25rem .4rem; }
.list-jump button { padding: .25rem .6rem; font-size: .8rem; }

/* ---- A list row that opens its record --------------------------------------------------------------
   A real <a> in the first cell, stretched over the row by its ::after; ctrl-click, middle-click, the
   context menu and keyboard access all survive. The cost: the overlay swallows text selection. */
.row-select tbody tr.selectable { position: relative; cursor: pointer; }

.row-select tbody tr.selectable:hover td,
.row-select tbody tr.selectable:focus-within td { background: var(--row-hover); }

.row-link { color: inherit; text-decoration: none; font-weight: inherit; }
.row-link::after { content: ''; position: absolute; inset: 0; }

/* INVARIANT: every interactive element in a row is hoisted above the overlay, and :not(.row-link)
   is load-bearing. Hoisting the link itself makes it its own containing block, so its ::after
   shrinks from the row to the width of the name and the row-click silently stops working. */
.row-select tbody :is(a, button, input, select, [role="button"]):not(.row-link) {
    position: relative;
    z-index: 1;
}

/* A hinted cell's text is hoisted the same way, or its title never receives the pointer; the script
   hands its plain click back to the row. */
.row-select tbody [data-meliora-hint] { position: relative; z-index: 1; }

/* ---- A row's tone, from its data --------------------------------------------------------------------
   Never colour alone: a coloured tone also draws a bar at the row's leading edge, and muted and struck
   change the text's style as well as its ink. An application's named tone sets --tone-bar and a fill
   in its own stylesheet and draws the same way. See docs\design-list-decoration.md. */
tbody tr[data-tone] > :first-child { box-shadow: inset 3px 0 0 var(--tone-bar, transparent); }
tbody tr[data-tone="attention"] { --tone-bar: var(--tone-attention); }
tbody tr[data-tone="attention"] > :is(td, th) { background: var(--tone-attention-fill); }
tbody tr[data-tone="warning"] { --tone-bar: var(--tone-warning); }
tbody tr[data-tone="warning"] > :is(td, th) { background: var(--tone-warning-fill); }
tbody tr[data-tone="danger"] { --tone-bar: var(--tone-danger); }
tbody tr[data-tone="danger"] > :is(td, th) { background: var(--tone-danger-fill); }
tbody tr[data-tone="success"] { --tone-bar: var(--tone-success); }
tbody tr[data-tone="success"] > :is(td, th) { background: var(--tone-success-fill); }
tbody tr[data-tone="muted"] > :is(td, th) { color: var(--ink-3); font-style: italic; }
tbody tr[data-tone="strong"] > :is(td, th) { font-weight: 600; }
tbody tr[data-tone="struck"] > :is(td, th) { color: var(--ink-3); text-decoration: line-through; }

/* ---- The scope bar, inside .sub-context --------------------------------------------------------- */

/* What is being chosen, not which one: a label's relationship to its value. */
.scope-kind {
    font-family: var(--font-mono);
    font-size: .66rem;
    font-weight: 600;
    letter-spacing: .12em;
    text-transform: uppercase;
    color: var(--ink-3);
}

.scope-current { font-weight: 600; color: var(--ink); }

.scope-pick { display: flex; align-items: center; gap: .4rem; margin: 0; }

/* INVARIANT: the Go button hides only on data-auto, which meliora.js sets before it binds a
   listener. Keyed on .scope-pick alone it would hide the only control a script-less picker has. */
.scope-pick[data-auto="true"] button[type="submit"] { display: none; }
.scope-pick select { max-width: 20rem; font-weight: 600; padding: .3rem .5rem; }
.scope-pick button { padding: .3rem .6rem; font-size: .8rem; }

.scope-ancestry { color: var(--ink-3); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.scope-ancestry b { font-weight: 600; color: var(--ink-2); }
.scope-ancestry .sep { opacity: .5; }

/* The chain is the only element whose absence costs context rather than function. */
@media (max-width: 56rem) {
    .scope-ancestry { display: none; }
}

/* ---- Definition lists of fields, as a detail page renders them ---------------------------------- */

dl.fields {
    display: grid;
    grid-template-columns: max-content 1fr;
    align-items: baseline;
    gap: .6rem 1.5rem;
    margin: 1rem 0;
    padding: 1.1rem 1.25rem;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow);
}
dl.fields dt { font-size: .8rem; color: var(--ink-3); }
dl.fields dd { margin: 0; font-weight: 500; min-width: 0; }

/* ---- The anonymous shell: a form column beside an art panel --------------------------------------
   Scoped on the body class the anonymous layout writes, so the two shells share one token set and
   one vocabulary. The card holds the screen; the art and the brand come from two partials an
   application overrides on their own. */

.anonymous {
    display: flex;
    min-height: 100vh;
    background: var(--auth-bg);
}

.anonymous main {
    flex: 1 1 auto;
    min-width: 0;
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: var(--auth-top) 2rem 4rem;
    box-sizing: border-box;
}

.anonymous .brand {
    position: absolute;
    top: 2.25rem;
    left: 2.75rem;
    margin: 0;
    padding: 0;
    color: var(--ink);
    font-size: 1.5rem;
    font-weight: 600;
}
.anonymous .brand:hover { background: none; }
.anonymous .brand img { display: block; height: 3rem; width: auto; }

.auth-card {
    width: 100%;
    max-width: 27rem;
    box-sizing: border-box;
    padding: 1.9rem 2rem 2rem;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 14px;
    box-shadow: var(--shadow-lg);
}
.auth-card h1 { font-size: 1.45rem; margin-bottom: .3rem; }
.auth-card .field { max-width: none; }
.auth-card .actions button, .auth-card .actions .btn { width: 100%; padding: .65rem 1rem; }
.auth-card .actions { flex-direction: column; align-items: stretch; }

/* An error page in the card: the reference reads as something to quote, the help line as small print. */
.error-page .error-actions .btn { box-sizing: border-box; text-align: center; }
.error-page .error-ref { font-family: var(--font-mono); font-size: .8rem; letter-spacing: .02em; }
.error-page .error-help { margin-top: 1.1rem; font-size: .85rem; }
.error-page .error-help > * { margin-left: .4rem; }

/* WHY: on the card's white the page-level field ground reads flat, so a field here sits inset and a
   step darker, and focus lifts it to the card's surface: the active field is the one plain white thing. */
.auth-card input:not([type=checkbox]):not([type=radio]):not([type=submit]):not([type=button]) {
    background: var(--auth-field);
    border-color: var(--auth-field-line);
    box-shadow: inset 0 1px 2px rgba(13, 13, 18, .07);
}
.auth-card input:not([type=checkbox]):not([type=radio]):not([type=submit]):not([type=button]):hover { border-color: var(--line); }
.auth-card input:not([type=checkbox]):not([type=radio]):not([type=submit]):not([type=button]):focus { background: var(--surface); box-shadow: 0 0 0 3px var(--focus-halo); border-color: var(--focus); }

/* Under the card rather than inside it: chrome for the page, not part of the form. */
.auth-foot {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: .6rem;
    margin: 1.1rem 0 0;
    font-size: .75rem;
    color: var(--ink-3);
}
.auth-foot a { color: inherit; text-decoration: none; }
.auth-foot a:hover { color: var(--accent-ink); }
.auth-foot .sep { opacity: .5; }

/* The art panel: the library's is a gradient on the chrome; an application replaces the partial. */
/* An application with no art overrides _AuthArt with an empty file, and the panel goes with it. */
.auth-art:not(:has(*)) { display: none; }

.auth-art {
    flex: 0 0 44%;
    max-width: 38rem;
    position: relative;
    overflow: hidden;
    background: linear-gradient(160deg, var(--accent) -60%, var(--chrome) 45%, #000 160%);
    color: var(--on-chrome-strong);
}
.auth-art-copy {
    position: absolute;
    left: 2.75rem;
    right: 2.75rem;
    top: 2.5rem;
    font-size: 1.1rem;
    letter-spacing: .12em;
    text-transform: uppercase;
    line-height: 1.5;
    color: var(--on-chrome);
}
.auth-art-copy strong {
    display: block;
    color: var(--on-chrome-strong);
    font-weight: 600;
    margin-top: .7rem;
    padding-top: .8rem;
    border-top: 2px solid var(--accent);
}

@media (max-width: 56rem) {
    .auth-art { display: none; }
    .anonymous main { padding-top: 9rem; }
}

/* ---- The account screens' mechanics: the look of the reveal, the code boxes and the meter -------- */

.reveal-toggle, [data-meliora-reveal-toggle] {
    font-size: .72rem;
    font-weight: 600;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--ink-3);
    background: none;
    border: 0;
    padding: .3rem .5rem;
    border-radius: var(--radius);
    cursor: pointer;
}
.reveal-toggle:hover, [data-meliora-reveal-toggle]:hover { background: var(--line-2); color: var(--ink); }
[data-meliora-reveal-toggle][aria-pressed="true"] { color: var(--accent-ink); }

/* The single code field, before the script draws boxes or when it never does. */
input.code { font-family: var(--font-mono); font-size: 1.15rem; letter-spacing: .3em; text-align: center; }

[data-meliora-code-box] {
    font-family: var(--font-mono);
    font-size: 1.3rem;
    font-weight: 600;
    color: var(--ink);
    background: var(--field);
    border: 1px solid var(--line);
    border-radius: var(--radius);
}
[data-meliora-code-box]:not(:placeholder-shown), [data-meliora-code-box]:not([value=""]) { background: var(--surface); }

[data-meliora-strength-bars] i { background: var(--line); }
[data-meliora-strength-bars] i[data-on="weak"] { background: var(--danger); }
[data-meliora-strength-bars] i[data-on="fair"] { background: var(--warn); }
[data-meliora-strength-bars] i[data-on="good"], [data-meliora-strength-bars] i[data-on="strong"] { background: var(--accent); }
.strength .muted, [data-meliora-strength-hint] { margin: .35rem 0 0; font-size: .8rem; }

/* ---- The lock scene: the unlock screen on the chrome, full-bleed, and nothing else --------------- */

[data-scene="lock"] .anonymous {
    background: linear-gradient(160deg, var(--accent) -60%, var(--chrome) 45%, #000 160%);
}
[data-scene="lock"] .anonymous .auth-art { display: none; }
[data-scene="lock"] .anonymous .brand { color: var(--on-chrome-strong); }
[data-scene="lock"] .anonymous main { justify-content: center; padding-top: 2rem; }
[data-scene="lock"] .anonymous .auth-card { text-align: center; }
[data-scene="lock"] .anonymous .auth-foot { color: var(--on-chrome); }
[data-scene="lock"] .anonymous .auth-foot a:hover { color: var(--on-chrome-strong); }


/* ---- The user menu's shape: a head, the entries, a rule, and sign out set apart ----------------- */


.who-name {
    display: inline-block;
    max-width: 14rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    vertical-align: bottom;
}
.who-head strong { overflow-wrap: anywhere; }

.who-head {
    padding: .5rem .65rem .6rem;
    border-bottom: 1px solid var(--line-2);
    margin-bottom: .25rem;
    line-height: 1.3;
}
.who-head strong { display: block; font-size: .9rem; color: var(--ink); }
.who-head span { display: block; font-size: .75rem; color: var(--ink-3); margin-top: .15rem; }

/* The shortcut sits at the right edge of its entry, in the mono face and a step quieter. */
.who-menu button kbd {
    float: right;
    margin-left: 1.5rem;
    font-family: var(--font-mono);
    font-size: .68rem;
    letter-spacing: .04em;
    color: var(--ink-3);
}

.who-rule { margin: .3rem .35rem; border: 0; border-top: 1px solid var(--line-2); }

.who-menu .who-danger { color: var(--danger); }
.who-menu .who-danger:hover { background: var(--danger-tint); color: var(--danger); }

}

/* The mark's dark variant, where an application has one, under both ways of being dark. */
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) [data-meliora-about-mark] .mark-light:not(:only-child) { display: none; }
    :root:not([data-theme="light"]) [data-meliora-about-mark] .mark-dark { display: block; }
}
:root[data-theme="dark"] [data-meliora-about-mark] .mark-light:not(:only-child) { display: none; }
:root[data-theme="dark"] [data-meliora-about-mark] .mark-dark { display: block; }

/* ==== live search: a box whose popover fills as you type. The rows are anchors; the chosen one
   carries aria-selected. Above the sticky bars, which sit at 55 and 60. ==== */
[data-meliora-livesearch] { position: relative; }

[data-meliora-livesearch-panel] {
    position: absolute;
    top: calc(100% + .35rem);
    right: 0;
    z-index: 70;
    width: 28rem;
    max-width: 92vw;
    max-height: 60vh;
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: .3rem;
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    background: var(--surface);
    color: var(--ink);
    box-shadow: var(--shadow-lg);
    font-size: .85rem;
    text-align: left;
}

[data-meliora-livesearch-item] {
    display: flex;
    align-items: baseline;
    gap: .6rem;
    padding: .4rem .6rem;
    border-radius: var(--radius);
    color: var(--ink);
    text-decoration: none;
}

[data-meliora-livesearch-item]:hover,
[data-meliora-livesearch-item][aria-selected="true"] { background: var(--hover); }

[data-meliora-livesearch-item] .muted { margin-left: auto; }

[data-meliora-livesearch-empty],
[data-meliora-livesearch-more] { display: block; padding: .45rem .6rem; color: var(--ink-3); }

[data-meliora-livesearch-more] { text-decoration: none; }
[data-meliora-livesearch-more]:hover { color: var(--ink); }

/* ==== form frame: one column of fields at a readable width; the issues panel beside them, out of the
   flow so nothing moves when it appears or goes; an invalid field takes a border and its label the
   danger colour, and nothing else. ==== */
.form-frame { position: relative; }

/*  A TITLE IS SEPARATED BY SPACE, HERE AS EVERYWHERE ELSE. A form was the only page type drawing a rule
    under its heading, and what follows one is a card with its own border — a second horizontal line a
    few millimetres above an edge that is already doing the work.

    The line under a TABBED form's heading is the tab strip's floor, not the title's, and stays: a row of
    pills does need something to sit on. */
.form-frame > h1 { margin-bottom: 1.1rem; }
h1:has(+ .page-tabs) { margin-bottom: .6rem; }

/* Two fields of 26rem, the gap between them and the card's own padding. The form is still a column —
   this is well short of the main column — but it stops being one field wide. */
.form-frame .form { max-width: var(--form-w); }

/* THE AGREEMENT TEXT, AS TYPED: every line break kept, long lines wrapped, and a fixed height that scrolls, so
   the whole text is in front of the person before their name is asked. The evidence is the bytes, and the
   pane shows them rather than a reading of them. */
.agreement-text {
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    max-height: var(--agreement-pane-height);
    overflow-y: auto;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    padding: .9rem 1.2rem;
    margin: 0 0 1rem;
    font-family: var(--font);
}

/* A SECTION IS A CARD: a surface lifted off the ground by a hairline and a shadow, its title inside at the
   top. The leading unsectioned fields are the Details card, so every form is cards, and a one-field form is
   one quiet card. The legend floats so it renders inside the card rather than on its border. */
/*  TWO FIELDS TO A ROW. A form is mostly short values — a name, a code, a chosen row — and one per line
    made a card as tall as its field count for no reason. What cannot share a row says so through its
    kind, which is the only thing in the markup that knows a line from a paragraph.

    The last field of an odd section keeps its column rather than stretching: a field's width should not
    depend on how many siblings it happens to have. */
.form-frame fieldset[data-meliora-form-section] {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow);
    padding: .9rem 1.2rem 1.1rem;
    margin: 0 0 1rem;
    min-width: 0;
}

/*  THE SECTION'S NAME SITS IN THE CARD'S OWN EDGE, the border stopping either side of it. This is a
    <legend> doing what a <legend> natively does — the browser cuts the notch and places the word in it
    — so the work here is the padding that sets the gap, not the placement.

    INVARIANT: do not give this a float or a width. Both pull it back inside the card, which is what it
    used to do; that is also why the fields are gridded in a div below rather than by the fieldset, a
    legend being no ordinary child. */
.form-frame fieldset[data-meliora-form-section] > legend {
    margin-inline-start: .35rem;
    padding: 0 .45rem;
    font-family: var(--font-mono);
    font-size: var(--card-label-size);
    line-height: 1.5;
    font-weight: 600;
    letter-spacing: .13em;
    text-transform: uppercase;
    color: var(--accent-ink);
}

.form-frame .form-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: .95rem 1.2rem;
    align-items: start;
}

/* The gap is the spacing now; a margin as well would double it between rows and not apply across one. */
.form-frame .form-grid .field { margin-bottom: 0; }

/*  A paragraph takes the row: half a card is not enough to write one in. So does a line that declared
    itself wide — a URL, a path — which the kind cannot tell from a name, because both are one line and
    the column's length says nothing about how much of it gets used. */
.form-frame .form-grid > .field[data-meliora-form-kind="text"],
.form-frame .form-grid > .field[data-meliora-form-wide] { grid-column: 1 / -1; max-width: none; }
/* A field alone on its row spans both columns so the next starts a row, and keeps a column's width. */
.form-frame .form-grid > .field[data-meliora-form-alone] { grid-column: 1 / -1; max-width: calc(50% - .6rem); }

/*  THE STATE CARD IS ONE LINE, NOT A GRID. Its contents are a run of toggles and at most a short
    number, which together are narrower than one column — laid out on the two-column grid the toggles
    took a full row and the number dropped beneath them, leaving the card mostly empty.

    Bottom-aligned because the number carries a label above it and the toggles carry theirs inside, so
    it is the controls' feet that should line up, not their tops. */
.form-tail .form-grid {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: .55rem .9rem;
}

/*  A NUMBER IN THE STATE CARD IS NARROW. The one that goes there is a position in a short list, so it
    holds two digits and its stepper and nothing longer would belong. Scoped to the tail because a
    number in the run above — a score, a threshold — is not this. */
.form-tail .field[data-meliora-form-kind="number"] { max-width: max-content; }

/*  OUR STEPPER, NOT THE BROWSER'S. Chrome draws its spinner on hover and Firefox always, and neither
    lets the box be as narrow as a position wants — so the native one goes and two buttons sit beside
    the field instead. They are an enhancement: the input still takes typing and the arrow keys with no
    script at all. tabindex="-1" keeps them out of the tab order, where they would double the number of
    stops on every form for a gesture the keyboard already has. */
.form-frame .field[data-meliora-form-kind="number"] input[type="number"] { appearance: textfield; -moz-appearance: textfield; }
.form-frame .field[data-meliora-form-kind="number"] input[type="number"]::-webkit-outer-spin-button,
.form-frame .field[data-meliora-form-kind="number"] input[type="number"]::-webkit-inner-spin-button { appearance: none; margin: 0; }

/*  STACKED, + ABOVE -, IN ONE COLUMN BESIDE THE FIELD. Side by side the pair spends two buttons'
    width on a row that has a label above it, and neither button sits on the side its value moves.
    justify-content keeps the field at the width it takes for itself; a 1fr column would stretch it. */
.form-frame .stepper {
    display: grid;
    grid-template-columns: auto auto;
    grid-template-rows: 1fr 1fr;
    justify-content: start;
    gap: .15rem .25rem;
}
.form-frame .stepper > input { grid-row: 1 / span 2; min-width: 0; }

.form-frame .stepper .step {
    width: 1.45rem;
    padding: 0;
    font-size: .72rem;
    line-height: 1;
    font-weight: 600;
    color: var(--ink-2);
    background: var(--ghost-fill);
    border: 1px solid var(--field-line);
    border-radius: var(--radius);
    cursor: pointer;
}
.form-frame .stepper .step:hover { background: var(--ghost-fill-hover); color: var(--ink); }

/*  THE ARMS REST DIM and come up while their field has focus or the pointer: on a form with a column of
    number fields the arms would otherwise outweigh the fields they nudge. */
.form-frame .stepper .step { opacity: .4; transition: opacity .12s ease; }
.form-frame .stepper:focus-within .step, .form-frame .stepper:hover .step { opacity: 1; }

/*  The side padding comes off with the width. A field this narrow spends a third of itself on padding
    otherwise, and the digits would sit against its edges. */
.form-tail .field[data-meliora-form-kind="number"] input[type="number"] { width: 3rem; padding-inline: .3rem; text-align: center; }

/* A run of toggles is one row of buttons, each as wide as its own word. */
.form-frame .field-toggles { grid-column: 1 / -1; display: flex; flex-wrap: wrap; gap: .6rem; }
.form-frame .field-toggles .field { max-width: none; }

/*  A RUN OF ONE IS NOT A RUN. A single toggle needs a fraction of a column, so taking the whole row
    for it puts a lone button on a line of its own and pushes whatever follows down. It keeps its cell
    and pairs with the field beside it.

    Aligned to the row's end because a toggle carries its label INSIDE the button while its neighbour
    carries one above: it is the control's feet that should meet the neighbouring input's, not its top
    the neighbour's label. */
.form-frame .form-grid > .field-toggles:has(> .field:only-child) { grid-column: auto; align-self: end; }

/* One column before the card would have to shrink its fields to keep two. */
@media (max-width: 58rem) {
    .form-frame .form-grid { grid-template-columns: minmax(0, 1fr); }
}

.form-frame .form-subtitle { margin: 0 0 1rem; font-size: 1.1rem; font-weight: 600; color: var(--ink); }

/* The actions row sits below every card, the tail row included; the buttons are the list toolbar's size. */
.form-frame .form-actions { margin: 1.2rem 0 0; max-width: var(--form-w); }

/*  WHAT THE RECORD CARRIES, AT THE ROW'S OTHER END. Notes, addresses and contacts each open a dialog
    that writes on its own, so they are not things Save commits — the gap says so, where sitting in the
    run beside Cancel would have said the opposite. Only some entities declare any of them; History is
    every form's and is the footer's, below the hairline. */
.form-carried { display: inline-flex; gap: .6rem; align-items: center; margin-left: auto; }
/* On the grants form the row runs the frame's full width, so the Preview sits beside Cancel rather than at the far end. */
[data-meliora-grants] .form-carried { margin-left: .4rem; }
/* The three dialog forms and the confirm dialog take the same size on their buttons, so they read as the main form's Save and Cancel do. */
:is(.form-frame, .note-form, .address-form, .contact-form, [data-meliora-confirm-dialog]) .actions button, :is(.form-frame, .note-form, .address-form, .contact-form, [data-meliora-confirm-dialog]) .actions .btn { padding: .22rem .6rem; font-size: .8rem; line-height: 1.4; }

/*  THE TAIL: STATUS AND DELETE, SIDE BY SIDE, as the last row of the form. Two columns whether or not
    both are filled, so the state card is the same width on a record that cannot be deleted as on one
    that can. The empty half says there is no delete here; a state card stretched across the row would
    say nothing, and every form would end a different shape.

    Both cards are inside the one form: Delete redirects that form with formaction rather than carrying
    a form of its own, so the state fields are ordinary fields and Save sits below the row. */
.form-tail {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 1.2rem;
    align-items: stretch;
    margin-top: .6rem;
}

/* The gap is the spacing; the cards' own bottom margin would hang below the row. */
.form-tail > fieldset[data-meliora-form-section], .form-tail > .form-danger { margin: 0; }

/*  DELETE TAKES THE STATE CARD'S PLACE WHEN THERE IS NO STATE CARD. Auto placement does it: after a
    state card Delete lands in the right-hand half, and alone it lands in the left, so a form without an
    Active flag does not end with a hole beside its Delete.

    WHY the offset: a <legend> straddles its fieldset's top border, so the fieldset's box begins half a
    legend ABOVE the line it draws. The danger card's box IS its line — its heading is lifted out of
    flow — so without this the two cards' borders would sit a few pixels apart. Half a legend is half of
    the label's size times its line height, which is why the legend sets that line height explicitly. */
.form-tail > .form-danger { margin-top: calc(var(--card-label-size) * 1.5 / 2); }

/* One column where two cards would be narrower than the buttons inside them. */
@media (max-width: 58rem) {
    .form-tail { grid-template-columns: minmax(0, 1fr); }
}

/*  A TABLE INSIDE A FORM: rows of fields in a card, drawn by <form-table>. It sheds the shell's table
    chrome, a card inside a card otherwise, keeps the label face on its headers, hugs its columns, and
    gives each row after the first the same air above as below and the last none beneath. A number cell
    is narrow, its header centred over the box and its arms. See docs\design-composite-screens.md §4. */
.form-frame .form-table { width: auto; margin: 0; background: transparent; border: 0; border-radius: 0; box-shadow: none; border-collapse: collapse; }
.form-frame .form-table th { background: transparent; position: static; padding: 0 .5rem .25rem 0; border-bottom: 0; }
.form-frame .form-table th::before { content: none; }
.form-frame .form-table th[data-kind="number"] { text-align: center; }
.form-frame .form-table td { padding: 0 .5rem .3rem 0; border-bottom: 0; }
.form-frame .form-table td + td { padding-left: .5rem; }
.form-frame .form-table tbody tr + tr td { padding-top: .3rem; }
.form-frame .form-table tbody tr:last-child td { padding-bottom: 0; }
.form-frame .form-table .field { margin: 0; }
.form-frame .form-table .field[data-meliora-form-kind="number"] input[type="number"] { width: 2.4rem; min-width: 0; padding-left: .3rem; padding-right: .3rem; }
.form-frame .form-table .field[data-meliora-form-kind="line"] input { width: 12rem; }
/* A fact cell names the row and is not typed into: it reads as a label beside the row's controls. */
.form-frame .form-table .field[data-meliora-form-kind="fact"] { display: block; padding: .3rem 0; white-space: nowrap; color: var(--ink-2); }
.form-frame fieldset[data-meliora-form-table] { max-width: max-content; }

/*  A NARROW FORM IS HALF A FORM WIDE, declared by the screen: every card, the tab strip and the footer
    take the tail row's half, so a record with one field, a short table and a delete ends as one column
    of one width. box-sizing, because a fieldset sizes its content and would come out a padding wider. */
.form-frame[data-meliora-form-width="half"] fieldset[data-meliora-form-section],
.form-frame[data-meliora-form-width="half"] .page-tabs,
.form-frame[data-meliora-form-width="half"] .form-footer { max-width: calc((min(var(--form-w), 100%) - 1.2rem) / 2); box-sizing: border-box; }

/*  A TAB STRIP OVER A LIST ends where the list does: the wrapper hugs its widest child, and the strip,
    a block, takes that width. */
.tabbed-list { width: fit-content; max-width: 100%; }
.tabbed-list > .page-tabs { max-width: none; }

/* DELETE IN A CARD OF ITS OWN, tinted with the danger colour rather than painted in it. */
.form-danger {
    padding: .9rem 1.2rem 1.1rem;
    border: 1px solid color-mix(in srgb, var(--danger) 35%, var(--line));
    border-radius: var(--radius-lg);
    background: color-mix(in srgb, var(--danger) 6%, var(--surface));
}

/*  In the card's edge, like every section's name — but reached the other way. This card is a <section>
    and its title an <h2>, not a fieldset and a legend, because it holds a form of its own and a form
    cannot nest inside the one above it. So the notch is made rather than native: the heading is lifted
    onto the border and masks it with the card's own fill.

    WHY background: inherit — the fill is a color-mix of the danger colour and the surface, and naming
    it again here would be a second place to change it. */
.form-danger > h2 {
    position: absolute;
    top: 0;
    left: 1.55rem;
    transform: translateY(-50%);
    margin: 0;
    padding: 0 .45rem;
    background: inherit;
    font-family: var(--font-mono);
    font-size: var(--card-label-size);
    line-height: 1.5;
    font-weight: 600;
    letter-spacing: .13em;
    text-transform: uppercase;
    color: var(--danger);
}

.form-danger .actions { margin: 0; }

/* THE FOOTER: when the row was last written and by whom, with History beside it, apart from the actions. */
.form-footer { display: flex; flex-wrap: wrap; gap: .8rem; align-items: center; max-width: var(--form-w); margin-top: 1.2rem; padding-top: .8rem; border-top: 1px solid var(--line); font-size: .85rem; color: var(--footer-ink); }

/* The card's own heading is positioned against it. */
.form-danger { position: relative; }

/* The last write sits in a well; History stands beside it on the ground, a button like the rest. */
.form-footer-text { display: inline-block; padding: .3rem .7rem; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface-inset); }
.form-footer button.ghost, .form-footer .btn.ghost, .notes-bar .btn.ghost, .addresses-bar .btn.ghost, .contacts-bar .btn.ghost { padding: .22rem .6rem; font-size: .8rem; line-height: 1.4; border-color: var(--field-line); background: var(--ghost-fill); color: var(--ink); }
.form-footer button.ghost:hover, .form-footer .btn.ghost:hover, .notes-bar .btn.ghost:hover, .addresses-bar .btn.ghost:hover, .contacts-bar .btn.ghost:hover { background: var(--ghost-fill-hover); }

/* The issues recall is a button in the danger colour, the size of Save and Cancel. */
.form-frame .actions .btn.ghost.form-errors-recall { color: var(--danger); border-color: var(--danger); background: var(--ghost-fill); text-decoration: none; }
.form-frame .actions .btn.ghost.form-errors-recall:hover { color: var(--danger); background: var(--danger-tint); }

/* THE ROW OF PAGES under a heading: the picker's pill row, the current page solid. */
.page-tabs { display: flex; flex-wrap: wrap; gap: .5rem; margin: 0 0 1.1rem; padding: 0 0 .75rem; border-bottom: 1px solid var(--line); }

/* Its rule ends where the form does, like the footer's. A hairline running past the cards it divides
   reads as the page's, not the form's. */
.form-frame .page-tabs { max-width: var(--form-w); }
.page-tabs .btn { padding: .22rem .6rem; font-size: .8rem; line-height: 1.4; }
.page-tabs .btn.ghost { background: var(--ghost-fill); }
.page-tabs .btn.ghost:hover { background: var(--ghost-fill-hover); }
.page-tabs a[aria-current="page"] { cursor: default; }

[data-meliora-page-tab-dialog][open] { width: min(64rem, 94vw); height: fit-content; max-height: 88vh; }
[data-meliora-page-tab-dialog] > [data-meliora-dialog-body] { padding: .6rem 1.1rem 1.1rem; overflow: auto; }

.form-frame .field textarea { width: 100%; }
/* A wide text field holds paragraphs: it is a body. */
.form-frame .field[data-meliora-form-wide] textarea { min-height: var(--textarea-body); }
/* A field that names its size takes that token, wide or not: FormField.TextSize on the declaration. */
.form-frame .field[data-meliora-form-text="note"] textarea { min-height: var(--textarea-note); }
.form-frame .field[data-meliora-form-text="field"] textarea { min-height: var(--textarea-field); }
.form-frame .field[data-meliora-form-text="body"] textarea { min-height: var(--textarea-body); }

/*  A CHECKBOX FIELD IS A TOGGLE BUTTON: ghost while off, filled with a tick while on.

    THE TICK IS WHY THIS PASSES THE RULE THAT COLOUR IS NEVER THE ONLY SIGNAL. It is reserved rather
    than inserted — hidden by visibility, not display — so the button is the same width in both states
    and does not resize under the pointer that just pressed it.

    The box itself is taken out of the flow rather than hidden: display:none or visibility:hidden would
    drop it out of the tab order, and the whole point of keeping a real input is that it still works
    from the keyboard. Focus is drawn on the label, since the box has no size to draw it on. */
:is(.form-frame, .address-form, .contact-form) .field .toggle {
    display: inline-flex;
    align-items: baseline;
    gap: .4rem;
    padding: .3rem .75rem;
    border: 1px solid var(--field-line);
    border-radius: var(--radius);
    background: var(--ghost-fill);
    color: var(--ink-2);
    font-size: .85rem;
    font-weight: 600;
    cursor: pointer;
    user-select: none;
}

:is(.form-frame, .address-form, .contact-form) .field .toggle > input[type=checkbox] { position: absolute; width: 1px; height: 1px; margin: 0; opacity: 0; }

:is(.form-frame, .address-form, .contact-form) .field .toggle::before { content: "\2713"; visibility: hidden; font-size: .9em; }

:is(.form-frame, .address-form, .contact-form) .field .toggle:hover { background: var(--ghost-fill-hover); color: var(--ink); }

:is(.form-frame, .address-form, .contact-form) .field .toggle:has(> input:checked) { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
:is(.form-frame, .address-form, .contact-form) .field .toggle:has(> input:checked)::before { visibility: visible; }
:is(.form-frame, .address-form, .contact-form) .field .toggle:has(> input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }

:is(.form-frame, .address-form, .contact-form) .field.invalid .toggle { border-color: var(--danger); }

/* A locked toggle keeps its state on show and stops answering the pointer. */
:is(.form-frame, .address-form, .contact-form) .field .toggle:has(> input:disabled) { cursor: default; opacity: .7; }
:is(.form-frame, .address-form, .contact-form) .field .toggle:has(> input:disabled):hover { background: var(--ghost-fill); color: var(--ink-2); }
:is(.form-frame, .address-form, .contact-form) .field .toggle:has(> input:checked:disabled):hover { background: var(--accent); color: var(--on-accent); }

/* The same three rules reach the dialog forms, which write the frame's field markup by hand. */
:is(.form-frame, .note-form, .address-form, .contact-form) .field.invalid label { color: var(--danger); }
:is(.form-frame, .note-form, .address-form, .contact-form) .field.invalid :is(input, select, textarea) { border-color: var(--danger); }

/* The field's own message, under its control. The panel keeps the overview and the count; this is the
   copy a reader reaches by focus, and the one that survives the panel being put away. */
:is(.form-frame, .note-form, .address-form, .contact-form) .field-error { margin: .3rem 0 0; font-size: .78rem; line-height: 1.4; color: var(--danger); }

.form-frame [data-meliora-form-readonly] .field .label {
    display: block;
    font-weight: 600;
    font-size: .85rem;
    color: var(--ink-2);
    margin-bottom: .3rem;
}

.form-errors {
    position: fixed;
    top: calc(var(--pinned-h) + 1rem);
    right: 1.5rem;
    z-index: 20;
    width: 22rem;
    max-width: calc(100vw - 3rem);
    max-height: calc(100vh - var(--pinned-h) - 2rem);
    overflow-y: auto;
    padding: .65rem .85rem;
    border: 1px solid var(--danger);
    border-radius: var(--radius-lg);
    background: var(--danger-tint);
    color: var(--danger);
    box-shadow: var(--shadow-lg);
    font-size: .9rem;
    line-height: 1.45;
}

/*  WITH A SUB ROW the header has not rolled off at the top of the page, so the sub row sits a header
    below where --pinned-h says; the panel clears both bars there and keeps that offset while scrolled,
    since a fixed panel cannot follow the header off. */
html[data-subheader] .form-errors { top: calc(var(--header-h) + var(--pinned-h) + 1rem); max-height: calc(100vh - var(--header-h) - var(--pinned-h) - 2rem); }

.form-errors[data-open="false"] { display: none; }

.form-errors-head { display: flex; align-items: center; justify-content: space-between; gap: .6rem; margin-bottom: .35rem; }
.form-errors-head button { padding: .15rem .5rem; font-size: .8rem; color: var(--danger); border-color: var(--danger); }

.form-errors ol { margin: 0; padding-left: 1.2rem; }
.form-errors li { margin: .2rem 0; }
.form-errors a { color: inherit; text-decoration: none; }
.form-errors a:hover, .form-errors [data-meliora-form-errors-item].lit { text-decoration: underline; }
.form-errors [data-meliora-form-errors-item].lit { background: var(--issue-lit); box-shadow: inset 0 0 0 1px var(--danger); border-radius: var(--radius); padding: .1rem .4rem; margin: 0 -.4rem; text-decoration: none; }

/*  IN THE ROW, NOT PUSHED OFF THE END OF IT. It is a button you press, so it reads with the others at
    the row's normal gap; floated right it read as a notice that happened to be clickable. It keeps the
    danger colour, which is the panel's — that pairing is what says where pressing it leads. */
.form-errors-recall { color: var(--danger); font-size: .85rem; }
.form-frame .actions .btn.ghost.form-errors-recall[aria-expanded="true"] { background: var(--danger); color: var(--surface); }
.form-errors:focus { outline: none; }

.form-delete { margin: 0; }
/* A second irreversible action stands off the first by most of a row, so the two questions never read as one. */
.form-delete + .form-delete { margin-top: .9rem; }

/* ---- The history dialog: a card of writes, newest first, each a small table of labelled changes ---- */
.form-history { display: inline-flex; }

/* A card, not a task: sized to what it holds and capped, so a long trail scrolls inside it. */
[data-meliora-history-dialog][open] {
    width: min(44rem, 92vw);
    height: fit-content;
    max-height: 85vh;
}

[data-meliora-history-dialog] > [data-meliora-dialog-body] { padding: .6rem 1.1rem 1.1rem; overflow: auto; }

.history-entry { padding: .7rem 0; border-top: 1px solid var(--line); }
.history-entry:first-child { border-top: 0; }
.history-entry > header { display: flex; gap: .6rem; align-items: baseline; flex-wrap: wrap; }
.history-entry > header time { margin-left: auto; font-variant-numeric: tabular-nums; }

.history-changes { width: 100%; border-collapse: collapse; margin-top: .35rem; font-size: .92em; }
.history-changes th { text-align: left; font-weight: 500; color: var(--ink-2); padding: .15rem .6rem .15rem 0; white-space: nowrap; width: 1%; }
.history-changes td { padding: .15rem .3rem .15rem 0; overflow-wrap: anywhere; }
.history-changes .history-before { color: var(--ink-3); text-decoration: line-through; }
.history-changes .history-arrow { width: 1%; color: var(--ink-3); padding-right: .5rem; }
.form-delete .muted { color: var(--ink-2); }

/* Cancel and Delete are ghosts; on a form they take the field border rather than the hairline, so they read as controls. */
:is(.form-frame, .note-form, .address-form, .contact-form) .actions .btn.ghost, :is(.form-frame, .note-form, .address-form, .contact-form) .actions button.ghost { border-color: var(--field-line); background: var(--ghost-fill); color: var(--ink); }
:is(.form-frame, .note-form, .address-form, .contact-form) .actions .btn.ghost:hover, :is(.form-frame, .note-form, .address-form, .contact-form) .actions button.ghost:hover { background: var(--ghost-fill-hover); }

/* ---- The notes dialog: the record's notes newest first, the add or edit form at the top ------------ */
.form-notes { display: inline-flex; margin-left: .5rem; }
.form-notes .notes-count { display: inline-block; min-width: 1.4em; margin-left: .4em; padding: 0 .4em; border-radius: 999px; background: var(--ghost-fill-hover); color: var(--ink-2); font-size: .8em; text-align: center; font-variant-numeric: tabular-nums; }
[data-meliora-notes][data-count="0"] .notes-count { opacity: .6; }

/* The file control: the file and its actions on one line that wraps, the upload form after it. See docs\design-files.md. */
.file-control { display: flex; flex-wrap: wrap; align-items: center; gap: .4rem .75rem; }
.file-control .file-on-file { display: inline-flex; flex-wrap: wrap; align-items: center; gap: .3rem .6rem; }
.file-control form { display: inline-flex; align-items: center; gap: .4rem; margin: 0; }
.file-control input[type="file"] { max-width: 16rem; font-size: .85rem; }
.file-control .file-issue { color: var(--danger); }

/* A list: its files stacked, each a name and its type over the description and who uploaded it, then the add form. */
.file-list { flex-direction: column; align-items: stretch; }
.file-list .file-items { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: .5rem; }
.file-list .file-items > li { display: flex; flex-direction: column; gap: .15rem; padding: .45rem .6rem; border: 1px solid var(--line); border-radius: var(--radius); }
.file-list .file-item-head, .file-list .file-item-foot { display: flex; flex-wrap: wrap; align-items: center; gap: .3rem .6rem; }
.file-list .file-item-description { color: var(--ink-2); }
.file-list .file-upload-list { flex-wrap: wrap; }
.file-list .file-upload-list input[type="text"] { min-width: 14rem; flex: 1; }
[data-meliora-notes-dialog][open] { width: min(48rem, 94vw); height: fit-content; max-height: 88vh; }
[data-meliora-notes-dialog] > [data-meliora-dialog-body] { padding: .6rem 1.1rem 1.1rem; overflow: auto; }
/*  THE WORDING PREVIEW: one rendering per channel, the text kept as typed and wrapped, never running past
    the panel; the shipped wording last, set off by a hairline. */
[data-meliora-wording-preview-dialog][open] { width: min(44rem, 94vw); height: fit-content; max-height: 88vh; }
[data-meliora-wording-preview-dialog] > [data-meliora-dialog-body] { padding: .6rem 1.1rem 1.1rem; overflow: auto; }
.wording-preview section + section { margin-top: 1rem; }
.wording-preview h3 { margin: 0 0 .3rem; font-family: var(--font-mono); font-size: var(--card-label-size); letter-spacing: .13em; text-transform: uppercase; color: var(--accent-ink); }
.wording-preview .wording-subject { margin: 0 0 .3rem; font-weight: 600; }
.wording-preview pre { margin: 0; padding: .6rem .8rem; border: 1px solid var(--line); border-radius: var(--radius); background: var(--field); font: inherit; white-space: pre-wrap; overflow-wrap: anywhere; }
.wording-preview [data-meliora-wording-default] { padding-top: 1rem; border-top: 1px solid var(--line); }

/* The text message is one segment of 160 characters, so its box is the note's height and never the body's. */
[data-meliora-wording-form] .field[data-meliora-form-wide] textarea[name="SmsBody"] { min-height: var(--textarea-note); }

.notes-bar { display: flex; justify-content: flex-start; padding: .4rem 0 .6rem; }
.note-form { padding: .4rem 0 .8rem; border-bottom: 1px solid var(--line); margin-bottom: .4rem; }
.note-form-row { display: flex; gap: 1rem; flex-wrap: wrap; }
.note-form-row .field { flex: 1 1 12rem; }
/* The body runs the dialog's width and takes its height from the token, never from the browser's default box. */
.note-form .field[data-meliora-form-field="Body"] { max-width: none; }
.note-form textarea.note { width: 100%; min-height: var(--textarea-note); }
/* An application's placeholder is read before writing, so a box carrying one grows to show it whole; the field height is the floor where field-sizing is unsupported. */
.note-form textarea.note[placeholder] { min-height: var(--textarea-field); field-sizing: content; }
/* Scoped to a note row: the bare class is the shell's note paragraph, and the two must not share a rule. */
[data-meliora-note] { padding: .7rem 0; border-top: 1px solid var(--line); }
[data-meliora-note]:first-of-type { border-top: 0; }
.note > header { display: flex; gap: .6rem; align-items: baseline; flex-wrap: wrap; }
.note > header .entered { margin-left: auto; font-variant-numeric: tabular-nums; }
.note-body { margin: .35rem 0 0; white-space: pre-wrap; overflow-wrap: anywhere; }
.note-actions { display: flex; gap: .8rem; margin-top: .3rem; font-size: .9em; }
.note-actions form { display: inline; }
.note-actions .link { background: none; border: 0; padding: 0; font: inherit; cursor: pointer; text-decoration: underline; color: var(--accent); }
.note-actions .link.danger { color: var(--danger); }

/* ---- The addresses dialog: the record's addresses, active first, the form at the top when open ---- */
.form-addresses { display: inline-flex; margin-left: .5rem; }
.form-addresses .addresses-count { display: inline-block; min-width: 1.4em; margin-left: .4em; padding: 0 .4em; border-radius: 999px; background: var(--ghost-fill-hover); color: var(--ink-2); font-size: .8em; text-align: center; font-variant-numeric: tabular-nums; }
[data-meliora-addresses][data-count="0"] .addresses-count { opacity: .6; }
[data-meliora-addresses-dialog][open] { width: min(48rem, 94vw); height: fit-content; max-height: 88vh; }
[data-meliora-addresses-dialog] > [data-meliora-dialog-body] { padding: .6rem 1.1rem 1.1rem; overflow: auto; }
.addresses-bar { display: flex; justify-content: flex-start; padding: .4rem 0 .6rem; }
.address-form { padding: .4rem 0 .8rem; border-bottom: 1px solid var(--line); margin-bottom: .4rem; }
.address-form-title { margin: 0 0 .5rem; font-size: 1rem; }
.address-form-row { display: flex; gap: 1rem; flex-wrap: wrap; }
.address-form-row .field { flex: 1 1 10rem; }
.address { padding: .7rem 0; border-top: 1px solid var(--line); }
.address:first-of-type { border-top: 0; }
.address > header { display: flex; gap: .6rem; align-items: baseline; flex-wrap: wrap; }
.address > header .entered { margin-left: auto; font-variant-numeric: tabular-nums; }
.address > header .pill { font-size: .72rem; padding: .1em .55em; border-radius: 999px; background: var(--accent-tint); color: var(--accent-ink); font-weight: 600; }
.address-lines { margin: .35rem 0 0; white-space: pre-line; }
.address.retired .address-lines { color: var(--ink-3); }
.address-retired-note { margin: .25rem 0 0; font-size: .85em; }
.address-actions { display: flex; gap: .8rem; margin-top: .3rem; font-size: .9em; }
.address-actions form { display: inline; }
.address-actions .link { background: none; border: 0; padding: 0; font: inherit; cursor: pointer; text-decoration: underline; color: var(--accent); }
.address-actions .link.danger { color: var(--danger); }
.addresses-retired { margin-top: .8rem; }
.addresses-retired > summary { cursor: pointer; color: var(--ink-3); font-size: .9em; }

/* ---- The contacts dialog: the record's contacts grouped by kind, the form at the top when open ---- */
.form-contacts { display: inline-flex; margin-left: .5rem; }
.form-contacts .contacts-count { display: inline-block; min-width: 1.4em; margin-left: .4em; padding: 0 .4em; border-radius: 999px; background: var(--ghost-fill-hover); color: var(--ink-2); font-size: .8em; text-align: center; font-variant-numeric: tabular-nums; }
[data-meliora-contacts][data-count="0"] .contacts-count { opacity: .6; }
[data-meliora-contacts-dialog][open] { width: min(44rem, 94vw); height: fit-content; max-height: 88vh; }
[data-meliora-contacts-dialog] > [data-meliora-dialog-body] { padding: .6rem 1.1rem 1.1rem; overflow: auto; }
.contacts-bar { display: flex; justify-content: flex-start; padding: .4rem 0 .6rem; }
.contact-form { padding: .4rem 0 .8rem; border-bottom: 1px solid var(--line); margin-bottom: .4rem; }
.contact-form-title { margin: 0 0 .5rem; font-size: 1rem; }
.contact-form-row { display: flex; gap: 1rem; flex-wrap: wrap; }
.contact-form-row .field { flex: 1 1 10rem; }
.contact-form-row .field.narrow { flex: 0 1 7rem; }
.contact-kind { margin: .8rem 0 0; }
.contact-kind > h3 { margin: 0; padding-bottom: .2rem; font-size: .8rem; text-transform: uppercase; letter-spacing: .04em; color: var(--ink-3); border-bottom: 1px solid var(--line); }
.contact { padding: .55rem 0; border-top: 1px solid var(--line); }
.contact:first-of-type { border-top: 0; }
.contact > header { display: flex; gap: .6rem; align-items: baseline; flex-wrap: wrap; }
.contact > header .entered { margin-left: auto; font-variant-numeric: tabular-nums; }
.contact > header .pill { font-size: .72rem; padding: .1em .55em; border-radius: 999px; background: var(--accent-tint); color: var(--accent-ink); font-weight: 600; }
.contact-value { margin: .2rem 0 0; font-variant-numeric: tabular-nums; }
.contact.retired .contact-value { color: var(--ink-3); }
.contact-retired-note { margin: .25rem 0 0; font-size: .85em; }
.contact-actions { display: flex; gap: .8rem; margin-top: .3rem; font-size: .9em; }
.contact-actions form { display: inline; }
.contact-actions .link { background: none; border: 0; padding: 0; font: inherit; cursor: pointer; text-decoration: underline; color: var(--accent); }
.contact-actions .link.danger { color: var(--danger); }
.contacts-retired { margin-top: .8rem; }
.contacts-retired > summary { cursor: pointer; color: var(--ink-3); font-size: .9em; }

/* ---- Folds: a screen's own <details> headings with a count on the summary, and the filter above them ---- */
.fold-filter { display: flex; gap: .6rem; align-items: center; margin: .6rem 0 .4rem; }
.fold-filter input { flex: 0 1 22rem; }
.fold { margin: .8rem 0 0; border-top: 1px solid var(--line); }
/* A fold's title is a group label, in the face the rail's groups and the cards' legends settled on. */
.fold-title { display: flex; gap: .8rem; align-items: baseline; padding: .55rem 0; cursor: pointer; list-style: none; font-family: var(--font-mono); font-size: var(--card-label-size); font-weight: 600; letter-spacing: .13em; text-transform: uppercase; color: var(--accent-ink); }
.fold-title::-webkit-details-marker { display: none; }
.fold-title::before { content: ''; display: inline-block; width: .45em; height: .45em; border-right: 2px solid currentColor; border-bottom: 2px solid currentColor; transform: rotate(-45deg); transition: transform .12s; margin-right: .2rem; }
.fold[open] > .fold-title::before { transform: rotate(45deg); }
.fold-count { font-family: var(--font); font-weight: 400; font-size: .85rem; letter-spacing: 0; text-transform: none; font-variant-numeric: tabular-nums; }
.fold > table { margin: 0 0 .6rem; }
/*  A ROLE'S CODES AND A PERSON'S GRANTS SHARE ONE LAYOUT, keyed on data-meliora-codes: the cards flow in
    two columns at the frame's full width so a screen shows many codes at once, each row one line, a
    switch, the code in a fixed column and its description, with the row's state as a pill after the
    words. A code no room may grant is marked Admin only and dimmed rather than explained on every row. */
[data-meliora-codes] .list-filter { margin-bottom: .9rem; }
.grant-rows { display: flex; flex-direction: column; gap: .45rem; }
/*  The row wraps only for a panel, its second line: the words take no basis of their own, so they keep
    the first line however long they run. */
.form-frame .grant-row { display: flex; flex-wrap: wrap; gap: .8rem; align-items: baseline; max-width: none; }
/*  INVARIANT: a display rule beats the hidden attribute, so the live filter needs this to hide a row. */
.form-frame .grant-row[hidden] { display: none; }
/*  A row a switch has moved off the filtered view, kept in place and dimmed until the next change so the click can be undone. */
.form-frame .grant-row[data-meliora-list-leaving="true"] { opacity: .45; }
.form-frame .grant-row > .muted { font-size: .85rem; }
.form-frame .grant-row > span.muted { flex: 1 1 0; min-width: 0; }
.form-frame:has([data-meliora-codes]) .form, [data-meliora-codes] .form-frame .form { max-width: none; }
.form-frame:has([data-meliora-codes]) .form-footer, [data-meliora-codes] .form-frame .form-footer { max-width: none; }
[data-meliora-codes] .codes-body { column-count: 2; column-gap: 1.25rem; }
[data-meliora-codes] .codes-body > p, [data-meliora-codes] .codes-body > .list-filter { column-span: all; }
[data-meliora-codes] fieldset[data-meliora-form-section] { break-inside: avoid; margin-bottom: 1rem; }
[data-meliora-codes] .grant-rows { gap: .3rem; }
[data-meliora-codes] .grant-row { display: flex; flex-wrap: wrap; gap: .3rem .6rem; align-items: baseline; }
[data-meliora-codes] .grant-row[hidden] { display: none; }
/* An Admin-only row dims its switch and its words, never its mark, so the mark reads at full strength
   in both themes; the mark's ink leans toward the page's ink, which darkens it on a light page. */
[data-meliora-codes] .grant-row[data-outside="true"] > .switch,
[data-meliora-codes] .grant-row[data-outside="true"] > .muted { opacity: .6; }
[data-meliora-codes] .grant-row > span.muted { flex: 1 1 14rem; font-size: .75rem; line-height: 1.35; }
/* A pill ends the words, so every description starts at the same column and a marked row reads like its neighbours. */
[data-meliora-codes] .grant-row .pill { margin-left: .4rem; font-size: .65rem; padding: 0 .45rem; vertical-align: baseline; }
[data-meliora-codes] .grant-row .pill.warn { color: color-mix(in srgb, var(--warn) 70%, var(--ink)); }
/* The source of a held code: a via pill names the role, in the accent's tint; a count pill is an outline. */
[data-meliora-codes] .grant-row .pill.via { background: var(--accent-tint); color: var(--accent-ink); }
[data-meliora-codes] .grant-row .pill.via.is-losing { text-decoration: line-through; opacity: .7; }
[data-meliora-codes] .switch { display: inline-flex; align-items: baseline; gap: .5rem; cursor: pointer; user-select: none; }
[data-meliora-codes] .switch > input[type=checkbox] { position: absolute; width: 1px; height: 1px; margin: 0; opacity: 0; }
/* A fixed code column, sized to the longest code, so every description starts at the same place. */
[data-meliora-codes] .switch > .code { display: inline-block; width: 8.5rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-family: var(--font-mono); font-size: .75rem; color: var(--ink); }
[data-meliora-codes] .switch-track { display: inline-block; min-width: 3.6rem; padding: .05rem .35rem; border: 1px solid var(--field-line); border-radius: var(--radius); background: var(--ghost-fill); font-size: .65rem; font-weight: 600; letter-spacing: .08em; text-transform: uppercase; text-align: center; color: var(--ink-3); }
/* THE TRACK SAYS THE SOURCE, since it decides what switching off does: Direct and Role are the two ways a
   code is held, in one solid styling, Kept is locked here, Off is neither. */
[data-meliora-codes] .switch-on, [data-meliora-codes] .switch-role, [data-meliora-codes] .switch-revoke, [data-meliora-codes] .switch-kept { display: none; }
[data-meliora-codes] .switch:hover > .switch-track { background: var(--ghost-fill-hover); color: var(--ink); }
[data-meliora-codes] .switch:has(> input:checked) > .switch-track { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
[data-meliora-codes] .switch:has(> input:checked) .switch-on { display: inline; }
[data-meliora-codes] .switch:has(> input:checked) .switch-off { display: none; }
[data-meliora-codes] .switch:has(> input:focus-visible) > .switch-track { outline: 2px solid var(--accent); outline-offset: 2px; }
[data-meliora-codes] .grant-row[data-live="gain"] .switch > .switch-track { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
[data-meliora-codes] .grant-row[data-via="true"] .switch:has(> input:checked) .switch-on,
[data-meliora-codes] .grant-row[data-live="cover"] .switch:has(> input:checked) .switch-on,
[data-meliora-codes] .grant-row[data-live="gain"] .switch .switch-off,
[data-meliora-codes] .grant-row[data-live="gain"] .switch .switch-on { display: none; }
[data-meliora-codes] .grant-row[data-via="true"] .switch:has(> input:checked) .switch-role,
[data-meliora-codes] .grant-row[data-live="cover"] .switch:has(> input:checked) .switch-role,
[data-meliora-codes] .grant-row[data-live="gain"] .switch .switch-role { display: inline; }
/* REVOKED FROM A ROLE THAT STANDS: a covered code switched off reads Revoke in red, and switches back to Role. */
[data-meliora-codes] .grant-row[data-via="true"]:not([data-live]) .switch:has(> input:not(:checked)) > .switch-track,
[data-meliora-codes] .grant-row[data-live="gain"][data-revoke="true"] .switch > .switch-track { background: var(--danger-tint); border-color: var(--danger); color: var(--danger); }
[data-meliora-codes] .grant-row[data-via="true"]:not([data-live]) .switch:has(> input:not(:checked)) .switch-off,
[data-meliora-codes] .grant-row[data-live="gain"][data-revoke="true"] .switch .switch-role { display: none; }
[data-meliora-codes] .grant-row[data-via="true"]:not([data-live]) .switch:has(> input:not(:checked)) .switch-revoke,
[data-meliora-codes] .grant-row[data-live="gain"][data-revoke="true"] .switch .switch-revoke { display: inline; }
/* A role switched off takes its codes with it on Save: the track reads Off ahead of it and the via pill is struck. */
[data-meliora-codes] .grant-row[data-live="lose"] .switch > .switch-track { background: var(--ghost-fill); border: 1px dashed var(--field-line); color: var(--ink-3); }
[data-meliora-codes] .grant-row[data-live="lose"] .switch .switch-on,
[data-meliora-codes] .grant-row[data-live="lose"] .switch .switch-revoke,
[data-meliora-codes] .grant-row[data-live="lose"] .switch .switch-role { display: none; }
[data-meliora-codes] .grant-row[data-live="lose"] .switch .switch-off { display: inline; }
[data-meliora-codes] .grant-row[data-locked="true"] .switch { cursor: default; }
[data-meliora-codes] .grant-row[data-locked="true"] .switch > .switch-track { background: var(--ghost-fill); border-color: var(--field-line); color: var(--ink-2); }
[data-meliora-codes] .grant-row[data-locked="true"] .switch .switch-on,
[data-meliora-codes] .grant-row[data-locked="true"] .switch .switch-role,
[data-meliora-codes] .grant-row[data-locked="true"] .switch .switch-off { display: none; }
[data-meliora-codes] .grant-row[data-locked="true"] .switch .switch-kept { display: inline; }
@media (max-width: 60rem) { [data-meliora-codes] .codes-body { column-count: 1; } }
.form-frame fieldset[data-meliora-form-section] > legend .fold-count { margin-left: .5rem; color: var(--ink-3); }
/*  THE ROLES CARD on a person's grants: the same rows in two columns with a role as the subject, always
    open above the codes fold; the toggle at the end of a row opens the role's window. */
/* A grid rather than columns, so the roles read across: Admin's first, then Client, each by name. A rule on
   the left-hand row separates two roles on one line. A row never wraps: its words are cut with an ellipsis instead. */
[data-meliora-roles] .grant-rows { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: .3rem 1rem; }
[data-meliora-roles] .grant-row { flex-wrap: nowrap; min-width: 0; }
[data-meliora-roles] .grant-row:nth-child(odd) { padding-right: 1rem; border-right: 1px solid var(--line); }
[data-meliora-roles] .grant-row > span.muted { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
[data-meliora-roles] .switch > .code { width: 6.5rem; font-family: var(--font); font-weight: 600; }
/* The count in a column of its own after the name, a plain figure in the legends' face, so the counts line up and the descriptions start together. */
[data-meliora-roles] .role-count { flex: 0 0 1.6rem; text-align: right; font-size: .75rem; font-variant-numeric: tabular-nums; color: var(--ink-3); cursor: default; }
.role-window { display: inline-flex; align-self: center; }
/* An application's Org Access rows on the same layout: a place's name is prose, in a column wide enough for
   most, and a row never wraps; the words after it are cut with an ellipsis and carry themselves as a title. */
[data-meliora-org-access] .switch > .code { width: 12rem; font-family: var(--font); font-weight: 600; font-size: .8rem; }
[data-meliora-org-access] .grant-row { flex-wrap: nowrap; }
[data-meliora-org-access] .grant-row > span.muted { flex: 1 1 0; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
[data-meliora-org-access] .grant-row > span.muted > .pill { margin-left: 0; margin-right: .4rem; }
@media (max-width: 60rem) { [data-meliora-roles] .grant-rows { grid-template-columns: minmax(0, 1fr); } [data-meliora-roles] .grant-row:nth-child(odd) { padding-right: 0; border-right: 0; } }
/*  A ROLE'S WINDOW: the shell's dialog, sized for two columns of codes, its tabs on the page-tab strip
    and the codes under the page's own headings. */
[data-meliora-role-dialog][open] { width: min(52rem, 92vw); }
[data-meliora-role-dialog] [data-meliora-dialog-body] { padding: 1rem 1.2rem 1.1rem; font-size: .85rem; line-height: 1.5; }
.role-panel .page-tabs { margin: 0 0 .9rem; padding-bottom: .6rem; }
.role-panel-short { margin: 0 0 .6rem; font-weight: 600; }
.role-panel > p { margin: 0 0 .6rem; }
.role-panel-group { margin: .8rem 0 .35rem; font-family: var(--font-mono); font-size: var(--card-label-size); font-weight: 600; letter-spacing: .13em; text-transform: uppercase; color: var(--accent-ink); }
.role-panel-group:first-of-type { margin-top: 0; }
.role-codes-list { margin: 0; padding: 0; list-style: none; column-count: 2; column-gap: 1.25rem; font-size: .75rem; line-height: 1.4; }
.role-codes-list > li { display: flex; gap: .6rem; align-items: baseline; break-inside: avoid; margin-bottom: .2rem; }
.role-codes-list > li > code { flex: 0 0 8.5rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: .75rem; }
.role-codes-list > li > span { color: var(--ink-2); }
@media (max-width: 60rem) { .role-codes-list { column-count: 1; } }
/*  THE PREVIEW: what Save would leave, the changes first and the effective set after, each row's state in
    the switch's words and a changed row marked by a word with a tint behind it. */
[data-meliora-grants-preview-dialog][open] { width: min(52rem, 92vw); max-height: 88vh; }
[data-meliora-grants-preview-dialog] [data-meliora-dialog-body] { padding: 1rem 1.2rem 1.1rem; font-size: .85rem; line-height: 1.5; overflow: auto; }
.grants-preview-line { margin: 0 0 .8rem; }
.grants-preview-list { margin: 0 0 .6rem; padding-left: 1.2rem; font-size: .8rem; }
.grants-preview-list > li { margin-bottom: .15rem; }
.grants-preview-warnings > li { color: color-mix(in srgb, var(--warn) 70%, var(--ink)); }
.grants-preview-refusals > li { color: var(--danger); }
.grants-preview-heading { margin: .6rem 0 .25rem; font-size: .8rem; font-weight: 600; color: var(--ink-2); }
.grants-preview-heading .fold-count { font-size: .75rem; }
.grants-preview-row { padding: .05rem .3rem; border-radius: var(--radius); }
.grants-preview-row[data-change="gained"] { background: var(--ok-tint); }
.grants-preview-row[data-change="lost"] { background: var(--warn-tint); }
.grants-preview-row[data-change="changed"] { background: var(--accent-tint); }
.grants-preview-state { color: var(--ink-2); }
.grants-preview-row[data-state="excluded"] .grants-preview-state, .grants-preview-row[data-state="idle"] .grants-preview-state { color: color-mix(in srgb, var(--warn) 70%, var(--ink)); }
.grants-preview-row[data-state="role"] .grants-preview-state { color: var(--accent-ink); }
.grants-preview-row .pill.grants-preview-mark { margin-left: .3rem; font-size: .6rem; padding: 0 .4rem; }
/*  THE CODES SECTION'S FOLD: one control over the whole section, drawn as this sign-in left it. Its
    summary carries the held count and a switch reading Hide or Show by the fold's state. */
[data-meliora-codes] .codes-fold > summary { display: flex; gap: .8rem; align-items: baseline; margin: 0 0 .7rem; list-style: none; cursor: pointer; }
[data-meliora-codes] .codes-fold > summary::-webkit-details-marker { display: none; }
.codes-fold-switch { flex: 0 0 auto; padding: .22rem .6rem; border: 1px solid var(--field-line); border-radius: var(--radius); background: var(--ghost-fill); color: var(--ink); font-size: .8rem; line-height: 1.4; }
[data-meliora-codes] .codes-fold > summary:hover .codes-fold-switch { background: var(--ghost-fill-hover); }
[data-meliora-codes] .codes-fold > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.codes-fold > summary .when-open { display: none; }
.codes-fold[open] > summary .when-open { display: inline; }
.codes-fold[open] > summary .when-closed { display: none; }
/*  A PERMISSION'S FULLER TEXT: the toggle ends the row and the panel is the row's second line, full width
    under it. The panel names its own display rule, since a display rule beats the hidden attribute. */
.grant-row .explain-toggle { flex: 0 0 auto; align-self: center; display: inline-flex; align-items: center; justify-content: center; width: 1.05rem; height: 1.05rem; padding: 0; border: 1px solid var(--field-line); border-radius: 50%; background: var(--ghost-fill); color: var(--ink-3); font-family: var(--font-mono); font-size: .65rem; font-weight: 600; line-height: 1; cursor: pointer; }
.grant-row .explain-toggle:hover { background: var(--ghost-fill-hover); color: var(--ink); }
.grant-row .explain-toggle[aria-expanded="true"] { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.grant-row .explain-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.grant-row > .explain-panel { flex: 1 1 100%; margin: .1rem 0 .3rem; padding: .45rem .7rem; border-left: 2px solid var(--accent); border-radius: 0 var(--radius) var(--radius) 0; background: var(--ghost-fill); color: var(--ink-2); font-size: .8rem; line-height: 1.45; }
.grant-row > .explain-panel[hidden] { display: none; }
.grant-row > .explain-panel p { margin: 0 0 .4rem; }
.grant-row > .explain-panel p:last-child { margin-bottom: 0; }
/* A weaker setting is a warning tone, drawn by the tone rules; its own fill must win over being set. */
.settings-table tr.is-set:not([data-tone]) > th, .settings-table tr.is-set:not([data-tone]) > td { background: var(--ok-tint); }
/* The row header is the setting itself and wraps like a cell: the table's nowrap and sort marker are for column heads. */
.settings-table th[scope="row"] { white-space: normal; font-weight: 400; max-width: 24rem; }
.settings-table th[scope="row"]::before { content: none; }
.settings-table th[scope="row"] > code { font-weight: 600; }
.settings-description, .settings-hint, .settings-caveat, .settings-clear-hint, .settings-problem { display: block; margin-top: .2rem; font-size: .9em; }

/* INVARIANT: on a list whose first column carries a description under the key, only the description
   wraps. The short cells are nowrap, so both themes break the same column at the same width; the row
   weight differs by theme, and a wrap that depended on it wrapped in one theme and not the other. */
[data-meliora-configuration] td, [data-meliora-channel-policy] td { white-space: nowrap; }
[data-meliora-configuration] .settings-description, [data-meliora-channel-policy] .note { white-space: normal; }
/* A long value, such as a paragraph setting, stops at a width and ellipsizes; the full text is on hover and in the row's form. */
[data-meliora-configuration] .settings-value strong, [data-meliora-configuration] .settings-default span { display: inline-block; max-width: 22rem; overflow: hidden; text-overflow: ellipsis; vertical-align: bottom; }

/* The wording list: Channel, Owner and Wording are short and stay on one line; Message, Purpose and Says may wrap. */
[data-meliora-wording] td:nth-child(3), [data-meliora-wording] td:nth-child(4), [data-meliora-wording] td:nth-child(6) { white-space: nowrap; }

.settings-hint, .settings-clear-hint { color: var(--ink-3); }
/* Nowrap so the table gives the action column the room its input and button need and squeezes the setting column instead. */
.settings-form { display: flex; gap: .5rem; align-items: center; flex-wrap: nowrap; }
.settings-form input[type="text"] { min-width: 8rem; max-width: 16rem; }

/* ---- Charts: a card titled on its edge, a donut beside its legend ----------------------------------
   The card is the form section's: the same surface, hairline, radius and shadow, and a <legend> in the
   notch of its own border, in the legends' face but in the title's own case. It is as wide as its
   figure, so the padding either side is the padding, not the leftover of a reading width.
   The drawing is inline SVG the server writes; its fills are tokens the application names, so the
   chart follows the theme as the pills do. Text in the drawing wears the ink tokens, never a series
   colour. The gap between slices is a stroke in the surface colour. */
.chart-card { width: max-content; max-width: 100%; margin: 0 0 2.5rem; padding: .525rem .69rem .63rem; background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-lg); box-shadow: var(--shadow); min-width: 0; }
.chart-card > legend { margin-inline-start: .35rem; padding: 0 .45rem; font-family: var(--font-mono); font-size: var(--card-label-size); line-height: 1.5; font-weight: 600; letter-spacing: .13em; text-transform: uppercase; color: var(--accent-ink); }
.chart-row { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 1rem 1.5rem; margin-bottom: 2.5rem; }
.chart-row > .chart-card { margin: 0; }
.chart-swatch { width: .69rem; height: .69rem; flex: 0 0 auto; }
.donut-figure { display: flex; flex-wrap: wrap; align-items: flex-start; gap: .58rem 1.15rem; margin: 0; }
.donut-figure figcaption { min-width: 0; }
svg.donut { width: 5.78rem; max-width: 100%; height: auto; flex: 0 0 auto; font-family: var(--font); }
svg.donut path { stroke: var(--surface); }
svg.donut .donut-empty { stroke: var(--line-2); }
svg.donut .donut-total { fill: var(--ink); font-size: 1.58rem; font-weight: 600; font-variant-numeric: tabular-nums; }
svg.donut .donut-label { fill: var(--ink-3); font-size: .74rem; }
.chart-legend { list-style: none; margin: 0; padding: 0; display: grid; gap: .23rem; font-size: .92rem; }
.chart-legend li { display: flex; align-items: center; gap: .46rem; font-variant-numeric: tabular-nums; }
.chart-legend-name { color: var(--ink-2); }
.chart-legend b { font-weight: 600; }
.chart-legend-share { color: var(--ink-3); font-size: .81rem; }
/* The hover pair: a slice and its legend row share a key; while one is under the pointer the script
   marks the card, lifts the pair and dims the rest. Opacity, so every fill keeps its own colour. */
svg.donut path, svg.donut circle, .chart-legend li { transition: opacity .15s ease; }
[data-meliora-chart-lit] svg.donut [data-key]:not(.is-lit), [data-meliora-chart-lit] .chart-legend li[data-key]:not(.is-lit) { opacity: .35; }
.chart-legend li[data-key] { cursor: default; }
/* A long legend in columns, filled down each before the next; the card sets the grid's columns and rows inline, since only it knows the counts. */
.chart-legend[data-meliora-legend-columns]:not([data-meliora-legend-columns="1"]) { grid-auto-flow: column; column-gap: 1.1rem; }
.chart-legend li.is-lit .chart-legend-name { color: var(--ink); }

/* The dashboard's sections, each headed; the rows inside them keep their own wrap. */
.dashboard-section { margin: 0 0 1.75rem; }
.dashboard-section > h2 { font-size: 1rem; font-weight: 600; color: var(--ink-2); margin: 0 0 .8rem; padding-bottom: .3rem; border-bottom: 1px solid var(--line-2); }
.dashboard-section .chart-row { margin-bottom: 0; }
.dashboard-section .chart-row + .chart-row { margin-top: 1.5rem; }

/* A status box: the number open, large, and the whole of it the way to the list when it links. */
.status-card { min-width: 10.5rem; }
.status-link { display: inline-flex; align-items: baseline; gap: .4rem; color: inherit; text-decoration: none; }
.status-open { font-size: 1.9rem; font-weight: 600; line-height: 1.1; font-variant-numeric: tabular-nums; color: var(--ink); }
.status-word { color: var(--ink-3); font-size: .85rem; }
a.status-link:hover .status-open, a.status-link:focus-visible .status-open { color: var(--accent); }
.status-detail { margin: .35rem 0 0; font-size: .85rem; color: var(--ink-2); font-variant-numeric: tabular-nums; }
.status-detail a { color: inherit; }
.status-detail b { font-weight: 600; margin-inline-start: .25rem; }

/* The line chart: the same card, a span picker above the drawing, the drawing beside its legend. */
.trend-ranges { display: flex; flex-wrap: wrap; gap: .25rem 1rem; margin: 0 0 .6rem; font-size: .85rem; }
.trend-ranges a { color: var(--ink-2); text-decoration: none; padding-bottom: .1rem; border-bottom: 2px solid transparent; }
.trend-ranges a:hover { color: var(--ink); }
.trend-ranges a[aria-current="true"] { color: var(--ink); font-weight: 600; border-bottom-color: var(--accent); }
svg.linechart { width: 23rem; max-width: 100%; height: auto; flex: 0 0 auto; font-family: var(--font); }
/* Two trend cards share their row (Todd, 2026-10-30): equal shares, the drawing giving way before the legend does. */
.chart-row > .trend-card { flex: 1 1 26rem; width: auto; min-width: 0; }
.trend-card svg.linechart { flex: 1 1 14rem; width: auto; max-width: 23rem; }
svg.linechart .linechart-base { stroke: var(--line); stroke-width: 1; }
svg.linechart .linechart-grid { stroke: var(--line-2); stroke-width: 1; stroke-dasharray: 2 3; }
svg.linechart .linechart-axis { fill: var(--ink-3); font-size: 8px; }
svg.linechart .linechart-line { stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
svg.linechart .linechart-dot { stroke: var(--surface); stroke-width: 1; }
/* The hover pair, as on the donut: a line, its dots and its legend row share a key; while one is under the pointer the rest dim. */
svg.linechart path, svg.linechart circle { transition: opacity .15s ease; }
[data-meliora-chart-lit] svg.linechart [data-key]:not(.is-lit) { opacity: .25; }
.trend-note { margin: .4rem 0 0; font-size: .81rem; color: var(--ink-3); }
