/*
    Switchboard's copy of Pillarbox's stylesheet, recoloured in operator blue (spec section 16).
    Kept close to Pillarbox's so the two can be compared line by line; what Switchboard does
    differently is in docs/design.md, and its own rules are under "Switchboard additions" at the end.
    Everything below this paragraph is Pillarbox's own account of the system.

    Pillarbox admin UI — Nocturne.

    Ported from the Nocturne design system and the "Pillarbox v3 Nocturne" screen design
    (claude.ai/design project 47b7c4f2). Two rules from that system are load-bearing here and
    are the ones to check a change against:

      1. NOTHING IS HARD-CODED. Every colour, size, radius and shadow comes from a token below.
         The whole look retunes from the :root block and nowhere else.

      2. THE ACCENT IS A LINE AND A GLOW, NEVER A FLOOD. The primary button is an accent
         OUTLINE on transparent, not a filled block. Large saturated fills are the one thing
         this system does not do.

    WHAT CHANGED FROM THE PILLAR-BOX-RED BUILD, AND WHY IT MATTERS

    The old sheet carried a rule: --brand was pillar box red, --danger was a *different* red, and
    a status was never allowed to reach for the brand colour — otherwise every screen looks like
    it is on fire next to the logo.

    Nocturne is a mono scheme: one accent, no second hue. So that rule inverts rather than
    survives. Here the accent IS the alarm colour, and the brand mark is deliberately quiet — a
    thin outlined glyph in the same accent, carrying no weight of its own. The separation the old
    rule protected is now carried by WEIGHT rather than by hue:

        filled accent   = something is wrong and someone should act   (.badge-critical)
        accent outline  = worth knowing, not yet actionable           (.badge-warning)
        filled neutral  = measured and fine                           (.badge-healthy)
        hairline only   = not measured — absence of data, not health  (.badge-neutral)

    That last distinction is the one to protect. "We did not look" must never render like "we
    looked and it was fine"; DisplayTests pins the mapping, and the four treatments below are
    what makes it visible.
*/

/* ══════════════════════════════════════════════════════════════════════════════════════════
   Tokens — from Nocturne's styles.css. Retune the look here.
   ══════════════════════════════════════════════════════════════════════════════════════ */

:root {
    --color-bg: #161826;
    --color-surface: #232532;
    --color-text: #e9e9ed;
    --color-accent: #4a8fe7;
    --color-divider: color-mix(in srgb, #e9e9ed 16%, transparent);

    /*
        Tonal ramps, generated in OKLCH on one shared lightness scale — the same step of any
        role carries the same visual weight. On this dark ground: 700–900 for tinted fills and
        subtle borders, 500 as the base, 100–300 for text sitting on those tints.
    */
    --color-neutral-100: #f3f5fe;
    --color-neutral-200: #e4e7f5;
    --color-neutral-300: #cfd3e5;
    --color-neutral-400: #b2b6ca;
    --color-neutral-500: #9397ab;
    --color-neutral-600: #75798c;
    --color-neutral-700: #595d6c;
    --color-neutral-800: #3f424d;
    --color-neutral-900: #292b31;

    --color-accent-100: #eef4fd;
    --color-accent-200: #d4e4fa;
    --color-accent-300: #a9c9f4;
    --color-accent-400: #74a8ec;
    --color-accent-500: #4a8fe7;
    --color-accent-600: #2f74cf;
    --color-accent-700: #1b5faa;
    --color-accent-800: #164a84;
    --color-accent-900: #0f2f55;

    /*
        Fonts. Loaded from Google in index.html, but every stack falls through to a system face,
        so a blocked CDN costs the typeface and nothing else — no layout shift into a serif.
    */
    --font-heading: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
    --font-heading-weight: 500;
    --font-body: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
    --font-mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, "Cascadia Mono", Consolas, monospace;

    /* Density 0.70x — this system is compact on purpose. */
    --space-1: 2.8px;
    --space-2: 5.6px;
    --space-3: 8.4px;
    --space-4: 11.2px;
    --space-6: 16.8px;
    --space-8: 22.4px;

    --radius-sm: 4px;
    --radius-md: 8px;
    --radius-lg: 14px;

    /* On a dark ground, elevation is a hairline edge plus ambient darkness — never a heavy drop. */
    --shadow-sm: 0 0 0 1px #3f424d;
    --shadow-md: 0 0 0 1px #595d6c, 0 6px 18px rgba(0, 0, 0, 0.55);
    --shadow-lg: 0 0 0 1px #9397ab, 0 16px 40px rgba(0, 0, 0, 0.65);

    --nav-width: 226px;
    --nav-rail-width: 64px;
    --topbar-height: 68px;

    /* A filled accent that white text can sit on. The ramp swaps ends in the light theme, so it names a different step there. */
    --accent-fill: var(--color-accent-700);

    /*
        Aliases kept so older markup and scoped stylesheets keep resolving. New rules should use
        the tokens above directly; these exist to stop a stray var() from painting nothing.
    */
    --radius: var(--radius-md);
    --font: var(--font-body);
    --ink: var(--color-text);
    --ink-muted: var(--color-neutral-400);
    --ink-faint: var(--color-neutral-500);
    --line: var(--color-divider);
    --line-strong: var(--color-neutral-700);
    --surface: var(--color-surface);
    --surface-sunken: var(--color-bg);
    --surface-raised: var(--color-surface);
    --brand: var(--color-accent);
    --danger: var(--color-accent-300);
}

/*
    The light theme: the same tokens, other values. Pillarbox's, with Switchboard's blue (David,
    2026-10-03).

    Every rule in this file is written against the tokens above, so a second theme is a second set of
    values and nothing else: no rule anywhere knows which theme it is under. The ramps are reversed
    rather than re-invented: on the dark ground 700–900 are tinted fills and subtle borders, 500 the
    base, 100–300 text on those tints; on a light ground the same STEPS keep the same JOBS by swapping
    ends, so a badge that was a dark tint with pale text is now a pale tint with dark text without being
    told. The accent itself stays: it means the same thing on either ground.

    Chosen per browser (js/prefs.js), applied before this stylesheet loads, and never the default: dark
    is what the desk has always been.
*/
:root[data-theme="light"] {
    color-scheme: light;

    --color-bg: #f4f5f9;
    --color-surface: #ffffff;
    --color-text: #1b1d27;
    --color-accent: #2f74cf;
    --color-divider: color-mix(in srgb, #1b1d27 12%, transparent);

    --color-neutral-100: #1e2029;
    --color-neutral-200: #30333f;
    --color-neutral-300: #464a5c;
    --color-neutral-400: #5f6478;
    --color-neutral-500: #7d8296;
    --color-neutral-600: #9ea3b8;
    --color-neutral-700: #c3c7d6;
    --color-neutral-800: #dcdfe9;
    --color-neutral-900: #e9ebf3;

    --color-accent-100: #0f2f55;
    --color-accent-200: #164a84;
    --color-accent-300: #1b5faa;
    --color-accent-400: #2f74cf;
    --color-accent-500: #4a8fe7;
    --color-accent-600: #74a8ec;
    --color-accent-700: #a9c9f4;
    --color-accent-800: #d4e4fa;
    --color-accent-900: #eef4fd;
    --accent-fill: var(--color-accent-300);

    /* Rings a shade darker than the surface, shadows a fraction of what a dark ground hides. */
    --shadow-sm: 0 0 0 1px #e2e4ec;
    --shadow-md: 0 0 0 1px #d6d9e3, 0 6px 18px rgba(20, 22, 40, 0.10);
    --shadow-lg: 0 0 0 1px #c3c7d6, 0 16px 40px rgba(20, 22, 40, 0.16);
}

*,
*::before,
*::after { box-sizing: border-box; }

/*
    Text size, Velocity cloud's way by way of Pillarbox (David, 2026-10-03): a zoom on the whole app
    rather than a root font size, because every size in this stylesheet is in pixels. Auto, with no
    attribute, is 1, and a step larger on a very large screen; Smaller and Larger move a step either
    side of it. js/prefs.js sets the attribute before this loads.
*/
/* stylelint-disable-next-line no-duplicate-selectors -- a token kept beside the rules that set it */
:root { --ui-zoom: 1; }
@media (min-width: 1920px) and (min-height: 800px) { :root { --ui-zoom: 1.125; } }
@media (min-width: 2400px) and (min-height: 1100px) { :root { --ui-zoom: 1.25; } }
@media (min-width: 3000px) and (min-height: 1400px) { :root { --ui-zoom: 1.4; } }
:root[data-text-size="smaller"] { --ui-zoom: 0.9; }
:root[data-text-size="larger"] { --ui-zoom: 1.125; }

@media (min-width: 1920px) and (min-height: 800px) {
    :root[data-text-size="smaller"] { --ui-zoom: 1; }
    :root[data-text-size="larger"] { --ui-zoom: 1.25; }
}

@media (min-width: 2400px) and (min-height: 1100px) {
    :root[data-text-size="smaller"] { --ui-zoom: 1.125; }
    :root[data-text-size="larger"] { --ui-zoom: 1.4; }
}

@media (min-width: 3000px) and (min-height: 1400px) {
    :root[data-text-size="smaller"] { --ui-zoom: 1.25; }
    :root[data-text-size="larger"] { --ui-zoom: 1.6; }
}

#app { height: 100%; zoom: var(--ui-zoom); }

html, body {
    margin: 0;
    padding: 0;
    height: 100%;
    font-family: var(--font-body);
    font-size: 14px;
    line-height: 1.55;
    color: var(--color-text);
    background: var(--color-bg);
    -webkit-font-smoothing: antialiased;
}

/*
   ══════════════════════════════════════════════════════════════════════════════════════
   Scrollbars.

   The operating system's default on Windows is a 17px grey slab with arrow buttons, drawn in
   light colours on a dark console — the one piece of chrome on the screen this stylesheet did
   not draw. Asked for on 2026-09-07 ("sleeker").

   Two implementations, because there are two models and a browser honours only one of them:

   - Chromium and WebKit take the ::-webkit-scrollbar pseudo-elements. The gutter stays 10px so
     nothing beside it reflows on hover; the thumb is drawn 6px wide by clipping its background
     inside a transparent border. The track is invisible, so what remains is a rounded pill that
     reads as part of the content rather than a frame around it.
   - Firefox takes the two standard properties. They are gated behind @supports because Chromium
     has honoured them too since 121, and a Chromium page that sets them IGNORES the pseudo-
     elements above — so without the gate Chrome would get the plainer of the two.

   Previews render inside sandboxed iframes with their own documents; their scrollbars are the
   email's, not the console's, and are left alone on purpose.

   macOS and iOS are left alone too. Their native scrollbars are already thin overlays that
   vanish when idle, which is the nicer behaviour, and this styling would make them permanent.
   index.html marks those platforms with data-native-scrollbars on <html> before this sheet is
   applied; every rule below is scoped so it does nothing there. (Asked for by David, who is
   on a Mac.)
   ══════════════════════════════════════════════════════════════════════════════════════ */

html:not([data-native-scrollbars]) *::-webkit-scrollbar { width: 10px; height: 10px; }
html:not([data-native-scrollbars]) *::-webkit-scrollbar-track { background: transparent; }
html:not([data-native-scrollbars]) *::-webkit-scrollbar-corner { background: transparent; }

/* 600 at rest, not 700: at 6px wide the pill has to carry its own contrast, and 700 on the
   page background read as a smudge in the first screenshot rather than a control. */
html:not([data-native-scrollbars]) *::-webkit-scrollbar-thumb {
    background-color: var(--color-neutral-600);
    background-clip: padding-box;
    border: 2px solid transparent;
    border-radius: 999px;
}

html:not([data-native-scrollbars]) *::-webkit-scrollbar-thumb:hover { background-color: var(--color-neutral-400); }
html:not([data-native-scrollbars]) *::-webkit-scrollbar-thumb:active { background-color: var(--color-neutral-300); }

@supports not selector(::-webkit-scrollbar) {
    html:not([data-native-scrollbars]) * {
        scrollbar-width: thin;
        scrollbar-color: var(--color-neutral-600) transparent;
    }
}

h1, h2, h3, h4 {
    font-family: var(--font-heading);
    font-weight: var(--font-heading-weight);
    line-height: 1.12;
    letter-spacing: -0.015em;
    margin: 0 0 var(--space-2);
}

/* Hierarchy here is size and space. Nothing goes past the 500 weight. */
h1 { font-size: 21px; }
h2 { font-size: 17px; }
h3 { font-size: 13px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--color-neutral-500); }

p { margin: 0 0 var(--space-3); }

a {
    color: var(--color-accent-300);
    text-decoration: none;
    border-bottom: 1px solid color-mix(in srgb, var(--color-accent) 40%, transparent);
}

a:hover { color: var(--color-accent-200); border-bottom-color: var(--color-accent); }

code, .mono { font-family: var(--font-mono); font-size: 0.92em; }

/* Interactive states are themed, never browser defaults. */
:focus { outline: none; }
:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; }

/*
    FocusOnNavigate moves focus to the page heading after every route change so a screen reader
    announces the new screen. That is worth keeping — but the browser then paints a focus ring
    around the <h1>, and a permanent box around the title on every screen reads as a control

    somebody has selected. The ring goes; the focus, and the announcement, stay.
*/
[tabindex="-1"]:focus-visible { outline: none; }
::selection { background: color-mix(in srgb, var(--color-accent) 30%, transparent); }

/* ══════════════════════════════════════════════════════════════════════════════════════════
   Shell
   ══════════════════════════════════════════════════════════════════════════════════════ */

/*
    A grid, not a flex row, and the page itself does not scroll: the sidebar and the main column
    each own their overflow. That is what lets the topbar stick without the nav sliding away
    with it.
*/
.shell {
    display: grid;
    grid-template-columns: var(--nav-width) minmax(0, 1fr);

    /*
        The ROW has to be constrained, not just the items in it.

        Without this there is one implicit row sized `auto`, which resolves to the height of its
        content. With content taller than the viewport there is no free space to stretch into, so
        the row grows past 100vh, `overflow: hidden` clips it, and .content never scrolls because
        it was never shorter than its content. The result is a page whose bottom simply cannot be
        reached — no scrollbar, nothing to say anything is missing.

        Latent since this was written and invisible while every page fitted. It surfaced on a
        laptop the day the account cards grew a third row of buttons.

        min-height: 0 on .content is the other half and is also required — see there. Fixing only
        one of the two leaves the column 872px tall in a 700px viewport, which is what the first
        attempt at this did.
    */
    grid-template-rows: minmax(0, 1fr);

    /* 100% of #app, which is the window: 100vh would be drawn at the text-size zoom and overflow. */
    height: 100%;
    overflow: hidden;
}

.sidebar {
    border-right: 1px solid var(--color-divider);
    display: flex;
    flex-direction: column;
    min-width: 0;
    background: linear-gradient(180deg, color-mix(in srgb, var(--color-surface) 55%, transparent), transparent 40%);
}

.brand {
    height: var(--topbar-height);
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 0 var(--space-8);
    flex: none;
}

/*
    The mark: an accent hairline square holding the letter slot, with the glow the accent is
    allowed to carry. Small and quiet on purpose — in a mono scheme the brand must not compete
    with the alarm state, because they are the same hue.
*/
.brand-mark {
    width: 22px;
    height: 22px;
    border-radius: var(--radius-sm);
    border: 1px solid var(--color-accent);
    display: grid;
    place-items: center;
    flex: none;
    box-shadow: 0 0 12px color-mix(in srgb, var(--color-accent) 35%, transparent);
}

.brand-mark span {
    width: 9px;
    height: 2px;
    background: var(--color-accent);
}

.brand-word {
    font-family: var(--font-heading);
    font-weight: var(--font-heading-weight);
    font-size: 16px;
    letter-spacing: -0.01em;
}

.nav {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: var(--space-4);
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
}

.nav a {
    display: flex;
    align-items: center;
    gap: 10px;
    border: 1px solid transparent;
    border-radius: var(--radius-md);
    font-size: 13.5px;
    color: var(--color-neutral-400);
    text-decoration: none;
    padding: 9px 12px;
}

/* The active mark is a short accent bar. Short accent marks stay solid — only free rules fade. */
.nav a .nav-bar {
    width: 2px;
    height: 14px;
    flex: none;
    border-radius: 2px;
    background: transparent;
}

/*
    Nav icons.

    Stroked on currentColor, so they follow the link through every state — resting, hover, active,
    and both themes — without a single extra colour rule. `flex: none` because a flex item with an
    SVG child will otherwise shrink it when a label wraps, and a squashed icon reads as a
    rendering fault rather than as a narrow sidebar.

    vector-effect keeps the 1.25px stroke honest: without it the browser scales the stroke with
    the viewBox and the icons come out heavier than the 13.5px text beside them.
*/
.nav a .nav-icon,
.nav-tool .nav-icon {
    width: 16px;
    height: 16px;
    flex: none;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.25;
    stroke-linecap: round;
    stroke-linejoin: round;
    vector-effect: non-scaling-stroke;

    /* Slightly quieter than the label. The icon locates the row; the word identifies it. */
    opacity: 0.75;
}

.nav a:hover .nav-icon,
.nav a.active .nav-icon { opacity: 1; }

/*
    The rule between sections. Inset to the text column rather than run edge to edge, so it reads

    as a grouping of the links and not as a border on the sidebar itself.
*/
.nav-divider {
    height: 1px;
    margin: var(--space-3) 12px;
    background: var(--color-neutral-800);

    /*
        flex: none, or these vanish on a short screen.

        .nav is a column flex container, and a flex item's automatic minimum size is its CONTENT
        size — which for a divider is zero, because it has none. So when the nav is taller than the
        sidebar, these are the only items that can be shrunk to nothing, and they are: the section
        rules disappear while every link stays put, which reads as a styling change rather than as
        a lack of room.

        They were safe while .shell's row was auto-sized and the sidebar simply grew. Constraining
        that row to the viewport — which is what makes the CONTENT column scroll — is what put the
        nav under pressure, on the same day it gained two more entries.
    */
    flex: none;
}

.nav a .nav-label { flex: 1 1 auto; min-width: 0; text-align: left; }

.nav a .nav-count {
    font-family: var(--font-mono);
    font-size: 12px;
    color: var(--color-neutral-600);
}

.nav a:hover { background: color-mix(in srgb, var(--color-text) 6%, transparent); color: var(--color-text); }

.nav a.active {
    border-color: color-mix(in srgb, var(--color-accent) 40%, transparent);
    background: var(--color-accent-900);
    font-weight: var(--font-heading-weight);
    color: var(--color-text);
}

.nav a.active .nav-bar { background: var(--color-accent); }

/* The failure count is the one number in the nav allowed to draw the eye. */
.nav a .nav-count-alert { color: var(--color-accent-300); }

.nav-footer {
    padding: var(--space-4) var(--space-4) var(--space-6);
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    flex: none;
    border-top: 1px solid var(--color-divider);
}

.nav-env {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: 0 var(--space-2);
    font-size: 11px;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--color-neutral-400);

    /*
        min-width:0 is the load-bearing one. A flex item defaults to min-width:auto, which refuses
        to shrink below its content — so a long hostname pushed the sidebar wider than its track
        and drew over the menu instead of being clipped by it. Every other rule here is inert
        without it.
    */
    min-width: 0;
}

.nav-env-name {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.nav-env::before {
    content: "";
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--color-accent);
    flex: none;
    box-shadow: 0 0 8px var(--color-accent);
}

/* ---- The account pill, its menu, and the fold beside it ----

   Velocity cloud's foot of the sidebar, by way of Pillarbox (David, 2026-10-03): who is signed in as
   one button that opens their menu (theme, text size, sign out), with the button that folds the
   sidebar beside it. The preferences are this browser's (js/prefs.js), applied from attributes on
   <html> before the stylesheet, so the first frame is already the right colour, width and size.

   The menu hangs above the pill from its left edge and is positioned here alone: in the column it
   overlaps the content to its right, on the rail it opens beside the icons. The scrim behind it is
   transparent and only there to take the click outside.
*/
.nav-account {
    display: flex;
    align-items: center;
    gap: 6px;
    min-width: 0;
}

.acct { position: relative; flex: 1 1 auto; min-width: 0; display: flex; }

/*
    justify-content and white-space are named here, not inherited: the global `button` rule centres and
    forbids wrapping, which is right for a button with a word on it and wrong for a row of text.
*/
.acct-pill {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    align-items: center;
    justify-content: flex-start;
    gap: 10px;
    padding: 7px 8px;
    font: inherit;
    text-align: left;
    white-space: normal;
    color: var(--color-text);
    background: var(--color-surface);
    border: 1px solid var(--color-divider);
    border-radius: 10px;
    cursor: pointer;
}

.acct-pill:hover:not(:disabled) {
    background: var(--color-surface);
    border-color: color-mix(in srgb, var(--color-text) 35%, transparent);
}

.acct-pill[aria-expanded="true"] {
    border-color: var(--color-accent);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-accent) 15%, transparent);
}

.acct-pill:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; }

.acct-avatar {
    flex: none;
    display: inline-grid;
    place-items: center;
    width: 32px;
    height: 32px;
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.02em;
    color: var(--color-accent-300);
    background: color-mix(in srgb, var(--color-accent) 16%, transparent);
    border-radius: 50%;
}

.acct-avatar-lg { width: 40px; height: 40px; font-size: 14px; }

.acct-text { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; line-height: 1.3; }

.acct-name {
    overflow: hidden;
    font-size: 13.5px;
    font-weight: 600;
    white-space: nowrap;
    text-overflow: ellipsis;
}

.acct-sub {
    overflow: hidden;
    font-size: 12px;
    color: var(--color-neutral-500);
    white-space: nowrap;
    text-overflow: ellipsis;
}

.acct-more { flex: none; width: 16px; height: 16px; fill: var(--color-neutral-500); }
.acct-pill:hover .acct-more, .acct-pill.open .acct-more { fill: var(--color-text); }

.acct-scrim { position: fixed; inset: 0; z-index: 60; }

.acct-menu {
    position: absolute;
    z-index: 61;
    left: 0;
    bottom: calc(100% + 8px);
    width: 290px;
    padding: 6px;
    color: var(--color-text);
    background: var(--color-surface);
    border: 1px solid var(--color-divider);
    border-radius: 12px;
    box-shadow: 0 18px 40px rgb(0 0 0 / 45%);
}

.acct-head { display: flex; align-items: center; gap: 10px; padding: 8px 8px 10px; }

.acct-rule { margin: 4px 0; border: 0; border-top: 1px solid var(--color-divider); }

.acct-item,
.acct-choice {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    gap: 10px;
    width: 100%;
    height: 36px;
    padding: 0 10px;
    font: inherit;
    font-size: 13.5px;
    text-align: left;
    text-decoration: none;
    color: var(--color-text);
    background: none;
    border: 0;
    border-radius: 8px;
}

.acct-item { cursor: pointer; }

.acct-item:hover,
.acct-item:focus-visible {
    color: var(--color-text);
    text-decoration: none;
    background: color-mix(in srgb, var(--color-text) 7%, transparent);
    outline: none;
}

.acct-item:focus-visible { box-shadow: inset 0 0 0 2px var(--color-accent); }

.acct-item svg,
.acct-choice svg {
    flex: none;
    width: 16px;
    height: 16px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.25;
    stroke-linecap: round;
    stroke-linejoin: round;
    color: var(--color-neutral-400);
}

.acct-item > span,
.acct-choice > span:not(.acct-seg) { flex: 1 1 auto; }

/* A small switch inside a menu row: Light | Dark, A | Auto | A. */
.acct-seg {
    flex: none;
    display: inline-flex;
    padding: 2px;
    background: var(--color-bg);
    border: 1px solid var(--color-divider);
    border-radius: 8px;
}

.acct-seg button {
    height: 26px;
    min-height: 0;
    padding: 0 10px;
    font: inherit;
    font-size: 12.5px;
    color: var(--color-neutral-400);
    background: none;
    border: 0;
    border-radius: 6px;
    cursor: pointer;
}

.acct-seg button:hover:not(:disabled) { color: var(--color-text); background: none; }

.acct-seg button[aria-checked="true"] {
    font-weight: 500;
    color: var(--color-text);
    background: var(--color-surface);
    box-shadow: 0 1px 2px rgb(0 0 0 / 30%);
}

.acct-seg button:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 1px; }

.acct-seg.text-size button { min-width: 32px; }
.acct-seg .ts-smaller, .acct-seg .ts-larger { padding: 0 8px; font-weight: 600; line-height: 1; }
.acct-seg .ts-smaller { font-size: 11px; }
.acct-seg .ts-larger { font-size: 16px; }

.nav-tool {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    min-height: 0;
    padding: 0;
    border: 1px solid transparent;
    border-radius: var(--radius-md);
    background: transparent;
    color: var(--color-neutral-400);
    cursor: pointer;
    flex: none;
}

.nav-tool:hover:not(:disabled) { color: var(--color-text); border-color: var(--color-divider); background: transparent; }
.nav-tool .nav-icon { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.25; stroke-linecap: round; stroke-linejoin: round; opacity: 1; }

/* A rail: the same links, the same order, the icon alone. Words go, titles stay for a hover. */
:root[data-nav="collapsed"] { --nav-width: var(--nav-rail-width); }

[data-nav="collapsed"] .nav-label,
[data-nav="collapsed"] .brand-word,
[data-nav="collapsed"] .nav-env-name { display: none; }

[data-nav="collapsed"] .brand { padding: 0 var(--space-3); justify-content: center; gap: 0; }

/* Never a sideways scrollbar on the rail: a badge that would reach the edge is clipped instead. */
[data-nav="collapsed"] .nav { padding: var(--space-4) var(--space-3); overflow-x: hidden; }
[data-nav="collapsed"] .nav a { justify-content: center; padding: 9px 0; position: relative; }
[data-nav="collapsed"] .nav-divider { margin: var(--space-3) var(--space-2); }

/*
    The unread count stays on the rail as a badge on the icon's top-right corner, as a phone does it:
    filled, so ten-pixel digits read, and cut out of the icon by a ring of the page's own ground.
*/
[data-nav="collapsed"] .nav a .nav-count {
    position: absolute;
    top: 1px;
    left: calc(50% + 13px);
    transform: translateX(-50%);
    min-width: 16px;
    height: 16px;
    padding: 0 4px;
    border-radius: 8px;
    font-family: var(--font-body);
    font-size: 10px;
    font-weight: 700;
    line-height: 16px;
    text-align: center;
    color: var(--color-text);
    background: var(--color-neutral-700);
    box-shadow: 0 0 0 2px var(--color-bg);
}

/* On the rail the pill is its initials alone, with the fold beneath it. */
[data-nav="collapsed"] .nav-footer { padding: var(--space-4) 0 var(--space-6); align-items: center; }
[data-nav="collapsed"] .nav-env { border: none; padding: var(--space-2); }
[data-nav="collapsed"] .nav-account { flex-direction: column; gap: var(--space-2); }
[data-nav="collapsed"] .acct { flex: none; }
[data-nav="collapsed"] .acct-pill { flex: none; padding: 4px; }
[data-nav="collapsed"] .acct-pill .acct-text,
[data-nav="collapsed"] .acct-pill .acct-more { display: none; }

/*
    On a phone the menu is the rail, whatever this browser chose for a wider window: open, it would take
    226px of a 375px screen. The fold button opens it OVER the page there, a drawer with the page dimmed
    beside it, so the column behind it stays a rail and the page never moves. js/prefs.js says which it
    is on <html>: "collapsed" for the rail, "drawer" for open over the page; this only draws what it
    says. The same 760px as prefs.js and the rest of the phone rules.
*/
.nav-scrim {
    display: none;
    position: fixed;
    inset: 0;
    z-index: 11;
    background: rgba(0, 0, 0, 0.55);
    animation: pb-fadein 120ms ease-out;
}

@media (max-width: 760px) {
    .shell { grid-template-columns: var(--nav-rail-width) minmax(0, 1fr); }

    [data-nav="drawer"] .sidebar {
        position: relative;
        z-index: 12;
        width: var(--nav-width);
        background-color: var(--color-bg);
        box-shadow: 18px 0 40px rgb(0 0 0 / 45%);
    }

    [data-nav="drawer"] .nav-scrim { display: block; }
}

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

/* ---- Sign-in screens ----

   Their own shell: no sidebar, no nav. The application chrome links to screens that require a
   signed-in operator, so showing it around a sign-in page offers a menu for an app you are not
   yet inside.
*/

/*
    Two panels, not a card floating on nothing. The brand side carries the mark and — the part
    that earns its place — WHICH deployment this is, at the last moment before signing in makes
    acting on live mail possible.

    Collapses to one column below 860px, brand above form, so the environment is still the first
    thing read on a phone.
*/
.auth-shell {
    /* Of #app, the window — not 100vh, which the text-size zoom would draw taller than it. */
    min-height: 100%;
    display: grid;
    grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr);
    grid-template-rows: auto minmax(0, 1fr) auto;
    column-gap: clamp(var(--space-8), 5vw, 72px);
    padding: clamp(10px, 1.4vw, 18px);
    background:
        radial-gradient(120% 90% at 12% 8%,
            color-mix(in srgb, var(--color-accent) 9%, transparent), transparent 55%),
        var(--color-bg);
}

/*
    The stage fills its whole column, top to bottom, with only the page margin around it — the
    same shape as Daybook's sign-in, so the two consoles feel like one estate. The card sits in
    the middle of the other column.
*/
.auth-shell > .auth-brand { grid-column: 1; grid-row: 1 / -1; min-width: 0; }
.auth-shell > .auth-card,
.auth-shell > .auth-notice { grid-column: 2; grid-row: 2; align-self: center; justify-self: center; }

/* The two lines beside the card: who this is for, at the top; whose it is, at the foot. */
.auth-shell > .auth-top { grid-column: 2; grid-row: 1; justify-self: end; align-self: start; padding: var(--space-3) var(--space-4) 0 0; }

.auth-shell > .auth-foot {
    grid-column: 2;
    grid-row: 3;
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: var(--space-4);
    padding: 0 var(--space-6) var(--space-4);
    font-size: 13px;
    color: var(--color-neutral-500);
}

.auth-staff {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    padding: 6px 12px;
    border-radius: 999px;
    border: 1px solid var(--color-divider);
    background: var(--color-surface);
    font-size: 13px;
    color: var(--color-neutral-400);
}

.auth-staff svg { fill: none; stroke: currentColor; stroke-width: 1.4; stroke-linecap: round; stroke-linejoin: round; }

@media (max-width: 860px) {
    .auth-shell {
        grid-template-columns: minmax(0, 1fr);
        grid-template-rows: none;
        align-content: start;
        row-gap: var(--space-8);
        padding-bottom: var(--space-8);
    }

    .auth-shell > .auth-brand,
    .auth-shell > .auth-card,
    .auth-shell > .auth-notice,
    .auth-shell > .auth-foot { grid-column: 1; grid-row: auto; }

    .auth-shell > .auth-top { display: none; }
}


/*
    The stage: the brand panel as a dark card with a picture on it, rather than a column of
    words. Velocity, SugaTrak, hacc.app, OrderPoint and Daybook each post letters along their
    own lane into the one box, and the box lights up what it reports back. A slow glow drifts
    behind it. Everything moves gently and nothing moves for anybody who has asked it not to.

    Drawn on the same ground as the rest of the console, with the accent used as the system
    uses it: a line, a dot, a glow — never a flood.
*/
.auth-brand {
    position: relative;
    isolation: isolate;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    gap: clamp(18px, 3.2vh, 34px);
    min-width: 0;

    padding: clamp(22px, 3.6vw, 52px);
    border-radius: var(--radius-lg, 16px);
    background:
        linear-gradient(160deg, color-mix(in srgb, var(--color-surface) 70%, var(--color-bg)) 0%, var(--color-bg) 100%);
    box-shadow: inset 0 0 0 1px var(--color-divider), var(--shadow-md);
}

/* A faint dot grid, fading out towards the edges, so the ground reads as a surface. */
.auth-brand::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    background-image: radial-gradient(color-mix(in srgb, var(--color-text) 9%, transparent) 1px, transparent 1.2px);
    background-size: 22px 22px;
    -webkit-mask-image: radial-gradient(85% 75% at 60% 50%, #000 20%, transparent 80%);
    mask-image: radial-gradient(85% 75% at 60% 50%, #000 20%, transparent 80%);
}

/* Slow light moving behind everything: the accent, at a whisper. */
.auth-glow {
    position: absolute;
    z-index: -1;
    width: 60%;
    aspect-ratio: 1;
    right: -18%;
    top: 10%;
    border-radius: 50%;
    background: radial-gradient(circle, color-mix(in srgb, var(--color-accent) 22%, transparent) 0%, transparent 65%);
    filter: blur(18px);
    animation: auth-drift 14s ease-in-out infinite alternate;
}

@keyframes auth-drift {
    from { transform: translate(0, 0) scale(1); }
    to { transform: translate(-14%, 24%) scale(1.15); }
}

.auth-lockup { display: flex; align-items: center; gap: var(--space-3); }
.auth-lockup .auth-mark { color: var(--color-accent); filter: drop-shadow(0 0 10px color-mix(in srgb, var(--color-accent) 40%, transparent)); flex: none; }

.auth-pitch { display: flex; flex-direction: column; gap: var(--space-4); }

.auth-headline {
    margin: 0;
    max-width: 18ch;
    font-family: var(--font-heading);
    font-size: clamp(28px, 3.1vw, 44px);
    font-weight: 600;
    letter-spacing: -0.03em;
    line-height: 1.08;
    text-wrap: balance;
}

.auth-headline em { font-style: normal; color: var(--color-neutral-400); }

.auth-word {
    font-family: var(--font-heading);
    font-size: 24px;
    font-weight: 600;
    letter-spacing: -0.02em;
    line-height: 1;
}

.auth-tagline {
    margin: 0;
    max-width: 50ch;
    font-size: 15px;
    color: var(--color-neutral-400);
    line-height: 1.55;
    text-wrap: pretty;
}

/* ---- The picture ---- */

/* Whatever height the words and the environment leave, the picture takes — and scales to fit it. */
.auth-scene { position: relative; flex: 1 1 auto; min-height: 300px; }
.auth-post { position: absolute; inset: 0; display: block; width: 100%; height: 100%; overflow: visible; }

.auth-lanes use {
    fill: none;
    stroke: color-mix(in srgb, var(--color-text) 14%, transparent);
    stroke-width: 1.2;
}

/* A letter: an envelope outline that travels its lane and drops into the slot at the end. Each
   one carries its own lane, timing and start on the element; these are what a letter with none
   of those gets — a still one. */
.auth-letter {
    --lane: none;
    --t: 4.5s;
    --d: 0s;
    fill: var(--color-bg);
    stroke: var(--color-accent);
    stroke-width: 1.4;
    stroke-linejoin: round;
    offset-path: var(--lane);
    offset-rotate: 0deg;
    animation: auth-post var(--t) linear infinite;
    animation-delay: var(--d);
}

@keyframes auth-post {
    0% { offset-distance: 0%; opacity: 0; transform: scale(0.85); }
    8% { opacity: 1; transform: scale(1); }
    88% { opacity: 1; transform: scale(1); }
    100% { offset-distance: 100%; opacity: 0; transform: scale(0.6); }
}

.auth-senders circle { fill: var(--color-neutral-500); }
.auth-senders text,
.auth-outcomes text {
    font-family: var(--font-mono);
    font-size: 11px;
    letter-spacing: 0.04em;
    fill: var(--color-neutral-400);
}

.auth-box path,
.auth-box line,
.auth-box rect {
    fill: none;
    stroke: var(--color-accent);
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.auth-box .auth-slot { fill: var(--color-accent); stroke: none; }

/* The slot answers each arrival with a ring that grows and fades. */
.auth-box .auth-slot-ring {
    fill: none;
    stroke: var(--color-accent);
    stroke-width: 1.5;
    transform-box: fill-box;
    transform-origin: center;
    animation: auth-ring 2.4s ease-out infinite;
}

@keyframes auth-ring {
    0% { transform: scale(0.4); opacity: 0.9; }
    100% { transform: scale(2.2); opacity: 0; }
}

/* What came back: three reports, each lit in turn, the rest waiting. */
.auth-outcome { --o: 0s; animation: auth-report 9s ease-in-out infinite; animation-delay: var(--o); }

/*
    Each letter's lane, pace and starting point, and each report's turn. Here rather than on the
    elements: the markup carries no inline styles (RazorStyleTests). The lanes are the paths drawn in
    AuthBrand's defs, so a lane changed there must change here too.
*/
.auth-letter-1 { --lane: path('M78 52 C 200 52, 250 140, 318 148'); --t: 5.2s; --d: 0s; }
.auth-letter-2 { --lane: path('M78 104 C 190 104, 240 142, 318 148'); --t: 4.4s; --d: -1.7s; }
.auth-letter-3 { --lane: path('M78 150 C 180 150, 240 148, 318 148'); --t: 3.8s; --d: -0.6s; }
.auth-letter-4 { --lane: path('M78 196 C 190 196, 240 154, 318 148'); --t: 4.9s; --d: -2.9s; }
.auth-letter-5 { --lane: path('M78 246 C 200 246, 250 156, 318 148'); --t: 4.1s; --d: -3.4s; }
.auth-outcome-2 { --o: -3s; }
.auth-outcome-3 { --o: -6s; }
.auth-outcome circle { fill: var(--color-neutral-600); }
.auth-outcome text { fill: var(--color-neutral-500); }


@keyframes auth-report {
    0%, 28%, 100% { opacity: 0.45; }
    6%, 22% { opacity: 1; }
}

.auth-report {
    fill: none;
    stroke: color-mix(in srgb, var(--color-text) 14%, transparent);
    stroke-width: 1.2;
    stroke-dasharray: 100;
    stroke-dashoffset: 100;
    animation: auth-draw 3s ease-out infinite;
}

@keyframes auth-draw {
    0% { stroke-dashoffset: 100; opacity: 0; }
    30% { stroke-dashoffset: 0; opacity: 1; }
    80% { stroke-dashoffset: 0; opacity: 1; }
    100% { stroke-dashoffset: 0; opacity: 0; }
}

/* Motion off means off: the picture stays, at its end state. */
@media (prefers-reduced-motion: reduce) {
    .auth-glow, .auth-letter, .auth-slot-ring, .auth-outcome, .auth-report { animation: none; }
    .auth-letter { offset-distance: 45%; opacity: 1; }
    .auth-slot-ring { display: none; }
    .auth-outcome { opacity: 1; }
    .auth-report { stroke-dashoffset: 0; opacity: 1; }
}

/* ---- The card, to match ---- */

/* A thread of the accent along the top edge: the card belongs to the stage beside it. */
.auth-card::before {
    content: "";
    position: absolute;
    inset: 0 0 auto 0;
    height: 3px;
    background: linear-gradient(90deg, var(--color-accent), transparent 70%);
}

.auth-eyebrow {
    margin: 0 0 var(--space-2);
    font-family: var(--font-mono);
    font-size: 11px;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--color-accent-400);
}

@media (max-width: 860px) {
    .auth-scene { display: none; }
}

.auth-env {
    display: inline-flex;
    align-items: center;
    gap: var(--space-3);
    align-self: flex-start;
    font-family: var(--font-mono);
    font-size: 11px;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--color-neutral-400);
    border: 1px solid var(--color-divider);
    border-radius: var(--radius-sm);
    padding: var(--space-2) var(--space-4);
}

.auth-env-dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--color-neutral-500);
    flex: none;
}

/*
    Production reads differently on purpose. Filled accent is this system\x27s ''something is wrong
    and someone should act'' weight, and that is close enough to ''you are about to touch live
    customer mail'' to be the right borrowing.
*/
.auth-env-live {
    color: var(--color-accent-200);
    border-color: var(--color-accent);
    background: color-mix(in srgb, var(--color-accent) 14%, transparent);
}

.auth-env-live .auth-env-dot {
    background: var(--color-accent);
    box-shadow: 0 0 8px var(--color-accent);
}

.auth-notice { max-width: 52ch; text-align: center; }
.auth-notice h1 { margin-bottom: var(--space-4); }

/* The sign-in card. Narrow on purpose — a form with two fields spread across a wide card
   reads as an unfinished layout rather than a deliberate one. Positioned and clipped for the
   accent thread (::before, above) that runs along its top edge. */
.auth-card {
    position: relative;
    overflow: hidden;
    width: min(400px, 100%);
    padding: 30px 30px 26px;
    border-radius: var(--radius-lg);
    background: var(--color-surface);
    box-shadow: var(--shadow-md);
}

.auth-card h1 { font-size: 25px; margin-bottom: var(--space-3); }

/* A button that spans its box: the sign-in and the passkey beneath it are the same width. */
.btn-block { width: 100%; min-height: 38px; }

/* The line under the form — "Forgotten your password?", "Back to sign in" — across the box, centred. */
.auth-aside { display: block; margin: var(--space-6) 0 0; font-size: 13px; text-align: center; }

/* The second way in, under the form: a rule with one word on it, then the passkey button. */
.sign-in-passkey { margin-top: var(--space-6); }
.sign-in-or {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    margin-bottom: var(--space-4);
    color: var(--color-neutral-500);
    font-size: 12px;
    text-transform: uppercase;
    letter-spacing: 0.08em;
}
.sign-in-or::before, .sign-in-or::after { content: ""; flex: 1; height: 1px; background: var(--color-divider); }

/*
    The passkey button: the key, and the words in the accent like Sign in above it (David, 2026-09-29) —
    Sterling's drawing of the same button. The outline stays neutral until hovered, so Sign in is still
    the one button on the form that wears the accent at rest.
*/
.passkey-button { gap: 9px; color: var(--color-accent); }
.passkey-button:hover:not(:disabled) {
    border-color: var(--color-accent);
    background: color-mix(in srgb, var(--color-accent) 8%, transparent);
}
.passkey-button .pk-key { width: 18px; height: 18px; flex: none; }

/* "Keep me signed in", under the password. */
.stay-signed-in {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    margin: 0;
    font-size: 13px;

    color: var(--color-neutral-300);
    cursor: pointer;
}

.stay-signed-in input { width: 16px; height: 16px; margin: 0; accent-color: var(--color-accent); }

/* A person's passkeys: one row each, the name and its facts on the left, Remove on the right. */
.passkey-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.passkey {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: var(--space-4);
    padding: var(--space-4) 0;
    border-top: 1px solid var(--color-divider);
}
.passkey:first-child { border-top: 0; padding-top: 0; }
.passkey-name { font-weight: 600; overflow-wrap: anywhere; }

/*
    The main column scrolls; the page inside it does not add its own padding, because the
    topbar has to run edge to edge to stick correctly.
*/
.content {
    min-width: 0;

    /*
        min-height: 0 is what makes this column actually scroll.

        .shell is a grid with height: 100vh and overflow: hidden. A grid item's automatic minimum
        size is its CONTENT size, so without this the column grows past the viewport, overflow-y
        never engages, and .shell simply clips whatever hangs off the bottom — unreachably. On a
        laptop, on the tallest account card, that was the row of buttons.

        Latent since the shell was written and invisible while every page fitted. min-width: 0 was
        already here for the same reason in the other axis.
    */
    min-height: 0;

    overflow-x: hidden;
    overflow-y: auto;
    position: relative;
}

.page-header {
    height: var(--topbar-height);
    border-bottom: 1px solid var(--color-divider);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-8);
    padding: 0 var(--space-8);
    position: sticky;
    top: 0;
    background: var(--color-bg);
    z-index: 6;
}

.page-header h1 { margin: 0; white-space: nowrap; }

.page-header-titles {
    display: flex;
    align-items: baseline;
    gap: var(--space-6);
    min-width: 0;
}

/* The one-line statement of what the screen is for. Truncates rather than wrapping the bar taller. */
.page-note {
    font-size: 12.5px;
    color: var(--color-neutral-500);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.page-body {
    padding: var(--space-8);
    display: flex;
    flex-direction: column;
    gap: var(--space-8);
}

/* A body whose own sections carry the padding — the filter bars that span the full width. */
.page-body-flush { padding: 0; display: block; }

/*
    On a phone the bar's controls do not fit beside the title — the dashboard has two switches and
    a timestamp — and the fixed height let the wrapped row spill out of the bar, the first switch
    hidden under the title. Below 720px the bar grows to hold them instead: the title on its own
    line, the controls wrapping beneath it.
*/
@media (max-width: 720px) {
    .page-header {
        height: auto;
        min-height: var(--topbar-height);
        flex-wrap: wrap;
        gap: var(--space-3) var(--space-4);
        padding: var(--space-4) var(--space-6);
    }
}

/* ══════════════════════════════════════════════════════════════════════════════════════════
   Cards, grids, stats
   ══════════════════════════════════════════════════════════════════════════════════════ */

.card {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    padding: var(--space-6);
    border-radius: var(--radius-md);
    background: var(--color-surface);
    min-width: 0;
}

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

.card h2 { margin: 0; font-size: 17px; }

.grid { display: grid; gap: var(--space-6); }
.grid-4 { grid-template-columns: repeat(auto-fit, minmax(178px, 1fr)); }
.grid-2 { grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); }
.grid-3 { grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); }

.stat { display: flex; flex-direction: column; gap: 0; }

.stat .label {
    font-size: 11px;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--color-neutral-500);
}

.stat .value {
    font-family: var(--font-heading);
    font-weight: var(--font-heading-weight);
    font-size: 32px;
    line-height: 1;
    letter-spacing: -0.03em;
    margin-top: var(--space-4);
    font-variant-numeric: tabular-nums;
}

.stat .hint {
    font-size: 12.5px;
    color: var(--color-neutral-500);
    margin-top: var(--space-3);
    line-height: 1.45;
}


/*
    A metric that is the reason someone is looking at this screen. The accent runs down the
    inside edge and recolours the number — an alarm carried as a line, per the system.
*/
.stat-alarm { box-shadow: inset 2px 0 0 var(--color-accent); }
.stat-alarm .value { color: var(--color-accent-300); }

.elev-sm { box-shadow: var(--shadow-sm); }
.elev-md { box-shadow: var(--shadow-md); }
.elev-lg { box-shadow: var(--shadow-lg); }

/* ══════════════════════════════════════════════════════════════════════════════════════════
   Badges

   Four weights, one hue — see the note at the top of this file for why the difference between
   them is weight and not colour, and which distinction is the load-bearing one.
   ══════════════════════════════════════════════════════════════════════════════════════ */

.badge {
    display: inline-flex;
    align-items: center;
    font-size: 11px;
    letter-spacing: 0.02em;
    padding: 3px 10px;
    border: 1px solid transparent;
    border-radius: calc(var(--radius-md) * 0.75);
    white-space: nowrap;
}

/* Filled accent — act on this. */
.badge-critical { background: var(--color-accent-800); color: var(--color-accent-100); }

/* Accent outline — know about it. */
.badge-warning { border-color: var(--color-accent); color: var(--color-accent-300); }

/* Filled neutral — measured, and fine. */
.badge-healthy { background: var(--color-neutral-800); color: var(--color-neutral-100); }

/* Tinted — informational, no judgement attached. */
.badge-info { background: var(--color-accent-900); color: var(--color-accent-300); }

/*
    Hairline only, and the quietest thing on the screen. This is what "we did not look" renders
    as; it must never be mistaken for the filled neutral above, which means "we looked".
*/
.badge-neutral { border-color: var(--color-neutral-700); color: var(--color-neutral-400); }

/* ══════════════════════════════════════════════════════════════════════════════════════════
   Banners
   ══════════════════════════════════════════════════════════════════════════════════════ */

.banner {
    border: 1px solid transparent;
    border-radius: var(--radius-md);
    padding: var(--space-6) var(--space-8);
    font-size: 13px;
    line-height: 1.55;
    text-wrap: pretty;
}

.banner strong { display: block; font-family: var(--font-heading); font-weight: var(--font-heading-weight); font-size: 15px; }

.banner-critical {
    border-color: color-mix(in srgb, var(--color-accent) 45%, transparent);
    background: linear-gradient(90deg, var(--color-accent-900), color-mix(in srgb, var(--color-surface) 70%, transparent));
    box-shadow: inset 3px 0 0 var(--color-accent);
    color: var(--color-text);
}

.banner-warning {
    border-color: color-mix(in srgb, var(--color-accent) 45%, transparent);
    color: var(--color-text);
}

.banner-info {
    border-color: var(--color-divider);
    background: color-mix(in srgb, var(--color-surface) 70%, transparent);
    color: var(--color-neutral-300);
}

/* The dashboard's incident strip: the banner plus the action that clears it. */
.banner-row {
    display: flex;
    align-items: center;
    gap: var(--space-8);
    flex-wrap: wrap;
}

.banner-row > :first-child { flex: 1 1 300px; min-width: 0; }

/* ══════════════════════════════════════════════════════════════════════════════════════════
   Tables

   Row rules are painted as row-level background strips, not cell borders, so the fade-to-
   transparent at each end spans the whole row rather than restarting inside every cell.
   ══════════════════════════════════════════════════════════════════════════════════════ */

.table-wrap { overflow-x: auto; }

table {
    width: 100%;
    border-collapse: collapse;
    font-size: 14px;
    font-variant-numeric: tabular-nums;
}

th {
    text-align: left;
    font-size: 11px;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: color-mix(in srgb, var(--color-text) 60%, transparent);
    padding: var(--space-3) var(--space-2);
    border-bottom: 1px solid transparent;
    white-space: nowrap;
}

td {
    padding: var(--space-3) var(--space-2);
    border-bottom: 1px solid transparent;
    vertical-align: top;
}

thead tr {
    background: linear-gradient(to right,
        transparent, var(--color-divider) 48px,
        var(--color-divider) calc(100% - 48px), transparent) no-repeat bottom / 100% 1px;
}

tbody tr {
    background: linear-gradient(to right,
        transparent, color-mix(in srgb, var(--color-text) 8%, transparent) 48px,
        color-mix(in srgb, var(--color-text) 8%, transparent) calc(100% - 48px), transparent) no-repeat bottom / 100% 1px;
}

tbody tr.clickable { cursor: pointer; }

/* The hover tint layers OVER the rule, which keeps painting underneath it. */
tbody tr.clickable:hover {
    background:
        linear-gradient(color-mix(in srgb, var(--color-text) 4%, transparent),
                        color-mix(in srgb, var(--color-text) 4%, transparent)) no-repeat 0 0 / 100% 100%,
        linear-gradient(to right,
            transparent, color-mix(in srgb, var(--color-text) 8%, transparent) 48px,
            color-mix(in srgb, var(--color-text) 8%, transparent) calc(100% - 48px), transparent) no-repeat bottom / 100% 1px;
}

.num { text-align: right; font-variant-numeric: tabular-nums; }


/*
   Two badges at the end of a row — a status and a marker beside it.

   text-align: right on the cell separates them by collapsing whitespace, which is not a gap you
   can rely on: they touch at some widths and wrap onto two ragged lines at others, with the
   status hanging off the end. A flex row gives a real gap, keeps them on one baseline, and wraps
   as a unit when the column is genuinely too narrow.

   Right-aligned rather than centred so the status stays where the eye is already scanning down
   the column.
*/
/*
   Recipient lines in the drawer header. Addresses wrap rather than pushing the drawer wide or
   being cut off — a message to six people is normal, and the whole point of showing them all is
   that somebody can read the one the suppression names.
*/
.recipients {
    margin-top: 2px;
    overflow-wrap: anywhere;
    line-height: 1.45;

    /*
        Label in a fixed first column, addresses stacked in the second — one per line, because a
        run-on comma-separated list of six addresses is unreadable at exactly the moment somebody
        is scanning it for one name.

        3ch is fixed rather than auto so the three groups line up with each other: To, Cc and Bcc
        are separate elements, and an auto column would size each independently and stagger them.
        The container is monospace, so 3ch is exactly "Bcc".
    */
    display: grid;
    grid-template-columns: 3ch minmax(0, 1fr);
    gap: 0 var(--space-2);
    align-items: start;
}

.recipient-list {
    display: flex;
    flex-direction: column;

    /* Not stretch: a full-width row would make the "did not receive this" tooltip appear from
       empty space to the right of a short address. */
    align-items: flex-start;
}

/*
    The To/Cc/Bcc label, and one address. Two spaces after the label used to come from a joined
    string; now they are separate elements, so the gap has to be real or the label runs into the
    first address.
*/
/* The To/Cc/Bcc label. Its gap comes from the grid now, not a margin. */
.recipient-label { white-space: nowrap; }

/*
    An address that was dropped from the send. Struck through and dimmed: the strike says "did not
    go" at a glance in a list where every other address did, and the "(blocked)" beside it carries
    the same meaning for anyone who cannot see the strike.
*/
/*
    Addresses are links into the lookup now. Underlined only on hover: a drawer header listing six
    permanently underlined addresses reads as a wall of links rather than as who the message went
    to, and the pointer plus the title already say it is clickable.
*/
a.recipient {
    color: inherit;
    text-decoration: none;
}

a.recipient:hover,
a.recipient:focus-visible { text-decoration: underline; }

/*
    A blocked address keeps its strike while hovered. Replacing it with a plain underline would
    remove the "did not go" signal at the exact moment somebody is pointing at that address,
    which is when they are deciding whether it did.
*/
a.recipient.blocked:hover,
a.recipient.blocked:focus-visible { text-decoration: line-through underline; }

.recipient.blocked {
    color: var(--color-accent-300);
    text-decoration: line-through;
    text-decoration-color: var(--color-accent-700);
}

/*
    Preflight findings in the drawer. A badge and a sentence per line, the badge fixed at the top
    so a two-line explanation does not push it down the middle of the paragraph.
*/
.preflight-list {
    list-style: none;
    padding: 0;
    margin: var(--space-3) 0 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}

.preflight-list li {
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
}

.preflight-list .badge { flex: 0 0 auto; }

/* The "+2" beside the first recipient in a grid row. */
.recipient-more { margin-left: var(--space-2); }

/* The channel tag before a recipient — Email, SMS — with air between it and the address. */
.channel-badge { margin-right: var(--space-2); }

/*
   Text for a screen reader and nobody else.

   Not display:none and not visibility:hidden — both remove the element from the accessibility
   tree as well as from the page, which is the opposite of the point. This is the standard clip
   technique: laid out, rendered, and one pixel nobody can see.
*/
.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

/*
   The paperclip beside a subject.

   Icon plus an optional count, and the icon is aria-hidden with the real sentence next to it in
   .visually-hidden — an SVG on its own is announced as nothing at all, and "2" without context
   is worse than silence.
*/
.attachment-mark {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    margin-left: var(--space-2);
    font-size: 11.5px;
    color: var(--color-neutral-400);
    vertical-align: baseline;
}

.attachment-mark svg {
    width: 13px;

    height: 13px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.4;
    stroke-linecap: round;
    stroke-linejoin: round;
}

/*
    The status and its badges, kept on one baseline.

    Alignment FOLLOWS THE COLUMN rather than being forced here. It used to hard-code flex-end,
    which meant every table using it had to remember `.num` on its header or the header sat left
    while the contents sat right — an invisible contract, and one of its two uses got it wrong:
    Address check's Status header did not line up with its own column.

    Now the cell's own alignment class governs both halves, so a header and its contents cannot
    disagree by omission.
*/
.status-stack {
    display: flex;
    justify-content: flex-start;
    align-items: center;
    gap: var(--space-2);
    flex-wrap: wrap;
}

.num .status-stack { justify-content: flex-end; }

/*
   The resend marker is deliberately quieter than the status beside it. The message DID fail —
   that is the fact being reported — and this is a note about what has since been done about it.
   Same size, less weight, so it reads second.
*/
.resent-badge {
    font-variant-numeric: tabular-nums;
    opacity: 0.85;
}

/* ══════════════════════════════════════════════════════════════════════════════════════════
   Forms and buttons
   ══════════════════════════════════════════════════════════════════════════════════════ */

label {
    display: block;
    font-size: 12px;
    margin-bottom: 5px;
    color: color-mix(in srgb, var(--color-text) 70%, transparent);
}

input, select, textarea {
    width: 100%;
    min-height: 36px;
    padding: 6px 10px;
    font: inherit;
    font-size: 14px;
    color: var(--color-text);
    caret-color: var(--color-accent);

    /*
        A wash, not a slab. These were --color-surface, which on the page ground reads as a grey
        block — and they were the only filled surfaces in a UI built from hairlines and outlined
        buttons, so they drew the eye to the least important thing on the screen. The border still
        carries the affordance; the fill only has to say ''you can type here''.
    */
    background: color-mix(in srgb, var(--color-text) 4%, transparent);
    border: 1px solid var(--color-divider);
    border-radius: var(--radius-md);
}

input:hover, select:hover, textarea:hover {
    border-color: color-mix(in srgb, var(--color-text) 45%, transparent);
}

input:focus-visible, select:focus-visible, textarea:focus-visible {
    border-color: var(--color-accent);
    outline-offset: 0;
}

textarea { min-height: 90px; resize: vertical; line-height: 1.55; }

/*
    A native select paints its own chevron in the OS colour, which on a dark ground reads as a
    light-mode artefact stuck in the corner of the control. Replaced with one drawn inline.

    Two of them, and the reason is the picker. Accent used to be painted on EVERY select at rest,
    which put the loudest colour in the scheme on nine controls that are doing nothing — while the
    segmented picker beside them uses accent to mean "this is the one you chose". Two different
    meanings for one colour, and the quieter one wins by weight of numbers.

    So: neutral at rest, accent when the control is being used. Selects and pickers then say the
    same thing with the same colour, and a page full of filters stops looking alarmed.

    Held as tokens rather than inline data URIs repeated per state — a bare hex in a background
    image is a copy of --color-accent that no longer changes when the palette does, and nothing
    reports the drift.
*/
/* stylelint-disable-next-line no-duplicate-selectors -- tokens kept beside the controls that use them */
:root {
    --chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%238b9099' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6.5 8 10.5 12 6.5'/%3E%3C/svg%3E");
    --chevron-active: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%234a8fe7' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6.5 8 10.5 12 6.5'/%3E%3C/svg%3E");
}

select {
    appearance: none;
    -webkit-appearance: none;
    cursor: pointer;
    padding-right: 32px;
    background-image: var(--chevron);
    background-repeat: no-repeat;
    background-position: right 11px center;
    background-size: 13px;
    text-overflow: ellipsis;
}

/*
    The same hover the picker's segments have — border brightens, a wash comes up underneath — so
    the two controls behave alike under the cursor as well as looking alike at rest.
*/
select:hover {
    background-image: var(--chevron-active);
    background-color: color-mix(in srgb, var(--color-text) 6%, transparent);
}

select:focus-visible { background-image: var(--chevron-active); }

/* The dropdown list itself is OS-painted; these keep it from opening white. */
select option { background: var(--color-surface); color: var(--color-text); }

/*
    The picker — hacc.app's segmented control, in Nocturne's colours.

    Named `.picker`, NOT `.seg`: the dashboard's date-range toggle already owns `.seg`, and CSS
    has one flat namespace with no diagnostic for a redefinition. Two blocks with the same
    selector do not conflict loudly — they interleave, each winning whichever properties it also
    declares, so both controls render subtly wrong and nothing reports it.

    For a CLOSED set of two or three values whose choice has consequences, a native select is the
    wrong shape: it hides every option but one behind a click, and it shows the option's NAME with
    no room to say what the name means. This lays the options out side by side, each a real
    <button>, so the whole choice is visible and one click wide.

    It deliberately does NOT replace every dropdown. A list that grows with customers — accounts,
    applications, streams — renders every option inline here, so forty customers become forty
    buttons; those stay selects. The rule of thumb this came with is two to six fixed values.

    Laid out with `flex: 1 1 0` and `min-width: 0` so the segments share the width evenly and a
    long label ellipsises inside its own segment rather than pushing its neighbour off the row.
*/
.picker { display: flex; gap: var(--space-2); }


.picker button {
    flex: 1 1 0;
    min-width: 0;
    min-height: 36px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 7px;
    padding: 6px 10px;
    font: inherit;
    font-size: 13.5px;
    font-weight: 600;
    color: var(--color-neutral-400);
    background: color-mix(in srgb, var(--color-text) 4%, transparent);
    border: 1px solid var(--color-divider);
    border-radius: var(--radius-md);
    cursor: pointer;
}

.picker button:hover {
    border-color: color-mix(in srgb, var(--color-text) 45%, transparent);
    color: var(--color-text);
}

.picker button.on {
    border-color: var(--color-accent);
    color: var(--color-text);
    background: color-mix(in srgb, var(--color-accent) 14%, transparent);
}

/* Hollow when resting, accent-filled when chosen — the same language as the checkbox. */
.picker button .picker-dot {
    width: 9px;
    height: 9px;
    flex: none;
    border-radius: 50%;
    border: 1px solid currentColor;
}

.picker button.on .picker-dot { background: var(--color-accent); border-color: var(--color-accent); }

/*
    Ticket pickers carry four or five options (channel, priority, type, status), some in a narrow side
    column. A button never shrinks below its words, so when a row cannot hold them all the options wrap
    onto a second row rather than spill out of their borders or push the page sideways. When they fit,
    the zero basis still shares the row out evenly.
*/
.picker { flex-wrap: wrap; }
.picker button { min-width: max-content; }

/*
    The line under the control that says what the CHOSEN option actually does.

    This is most of why the picker beats a select. "Admin" and "Support" are labels, not
    explanations, and the consequence of picking one is invisible at the point of picking. One
    line that follows the selection carries that meaning without giving every option its own
    paragraph, which is what would break the surrounding field row.
*/
.picker-help {
    margin-top: 6px;
    font-size: 12.5px;
    line-height: 1.45;
    color: var(--color-neutral-500);
}

/*
    A native checkbox paints an opaque white box on every platform, and accent-color only
    recolours the CHECKED state — so on this ground an unchecked box is the brightest thing on
    the screen and reads as a lit control rather than an empty one. Drawn from tokens instead.
*/
input[type="checkbox"] {
    appearance: none;
    -webkit-appearance: none;
    width: 16px;
    height: 16px;
    min-height: 0;
    padding: 0;
    flex: none;
    display: inline-grid;
    place-content: center;
    border: 1.5px solid var(--color-divider);
    border-radius: var(--radius-sm);
    background: color-mix(in srgb, var(--color-text) 4%, transparent);
    cursor: pointer;
    vertical-align: -3px;
}

input[type="checkbox"]:hover { border-color: var(--color-accent); }

input[type="checkbox"]:checked { background: var(--color-accent); border-color: var(--color-accent); }

input[type="checkbox"]::before {
    content: "";
    width: 10px;
    height: 10px;
    transform: scale(0);
    background: var(--color-bg);
    clip-path: polygon(14% 44%, 0 65%, 40% 100%, 100% 16%, 84% 0, 39% 68%);
}

input[type="checkbox"]:checked::before { transform: scale(1); }

.filters {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-6);
    align-items: flex-end;
}

.filters > div { min-width: 150px; }

/*
   One height for everything on a filter row.

   There were three. The pickers are 40px, a text input is 36px, and a button is only as tall as
   its padding — about 30px — so a row reading "Reference / Account / Sent by / Kind / Customer /
   Status / Search" stepped down twice along its length. Bottom-aligned, so the mismatch showed as
   ragged tops.

   Scoped to the filter row rather than fixed on the base `button` rule: buttons elsewhere sit in
   card footers and drawers where 40px is too tall, and a global change would silently resize every
   one of them.
*/
/*
   ⚠️ TWO LEVELS DEEP, NOT ANY DEPTH.

   This started as `>` and missed Audit and Lookup, which wrap their button in a positioning div.
   Widening it to a descendant selector fixed those and then reached much too far: `.filters` is
   not only the row at the top of a page — the template editor reuses it as a two-column form
   layout, so the rule found the preview buttons three levels down and the "With layout" CHECKBOX
   four, making both 40px tall. Reported as a checkbox the size of a button.

   Two levels covers every real filter row: a control placed straight in the row, or one inside the
   div that positions it. Anything deeper is inside a card's body, which is somebody else's layout.
*/
.filter-bar > input,
.filter-bar > div > input,
.filter-bar > button,
.filter-bar > div > button,
.filters > input,
.filters > div > input,
.filters > button,
.filters > div > button { min-height: 40px; }

/*
   A checkbox is never a 40px control, at any depth. Belt and braces over the rule above, because
   the depth limit is a proxy for "is this a filter control" and a checkbox in a filter row would
   still be wrong.
*/
.filter-bar input[type="checkbox"],
.filter-bar input[type="radio"],
.filters input[type="checkbox"],
.filters input[type="radio"] { min-height: 0; width: auto; }

/*
   The dropdown's own options. They are <button>s inside a filter control, and 40px each turns a
   six-option panel into a very tall list.
*/

.filter-bar .drop-panel button,
.filters .drop-panel button { min-height: 0; }

/* The full-width filter bar the search screens open with, ruled off from the results. */
.filter-bar {
    padding: var(--space-8);
    border-bottom: 1px solid var(--color-divider);
    display: flex;
    align-items: flex-end;
    gap: var(--space-6);
    flex-wrap: wrap;
}

.result-bar {
    padding: var(--space-4) var(--space-8);
    border-bottom: 1px solid var(--color-divider);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-6);
    flex-wrap: wrap;
    font-size: 13px;
    color: var(--color-neutral-500);
}

button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    font-family: var(--font-heading);
    font-weight: var(--font-heading-weight);
    font-size: 14px;
    line-height: 1.2;
    color: var(--color-text);
    background: transparent;
    border: 1px solid var(--color-divider);
    padding: var(--space-2) calc(var(--space-3) * 1.2);
    border-radius: var(--radius-md);
    cursor: pointer;
    white-space: nowrap;
}

button:hover:not(:disabled) { background: color-mix(in srgb, var(--color-text) 7%, transparent); }
button:active:not(:disabled) { background: color-mix(in srgb, var(--color-text) 14%, transparent); }
button:disabled { opacity: 0.45; cursor: not-allowed; }

/* The primary action is an accent OUTLINE. Never a fill — that is the system's whole posture. */
button.primary { color: var(--color-accent); border-color: var(--color-accent); background: transparent; }
/* Switchboard: the lighter step on the tint. Blue 500 on a 12% blue tint is 3.9:1, under AA's 4.5:1,
   where Pillarbox's red cleared it. */
button.primary:hover:not(:disabled) { color: var(--color-accent-300); background: color-mix(in srgb, var(--color-accent) 12%, transparent); }
button.primary:active:not(:disabled) { color: var(--color-accent-300); background: color-mix(in srgb, var(--color-accent) 22%, transparent); }

/*
    Destructive. The only control carrying a tint rather than an outline — with one hue to work
    with, the way "this cannot be undone" separates from "this is the main action" is weight.
*/
button.danger {
    color: var(--color-accent-200);
    border-color: var(--color-accent);
    background: color-mix(in srgb, var(--color-accent) 18%, transparent);
}

button.danger:hover:not(:disabled) { background: color-mix(in srgb, var(--color-accent) 30%, transparent); }

button.link {
    border-color: transparent;
    background: none;
    color: var(--color-accent);
    padding-inline: var(--space-1);
    font-weight: var(--font-heading-weight);
    text-decoration: none;
}

button.link:hover:not(:disabled) { background: color-mix(in srgb, var(--color-accent) 10%, transparent); }

button.icon { width: 36px; height: 36px; padding: 0; flex: none; }

/* The segmented control — the dashboard's time window. */
.seg {
    display: inline-flex;
    overflow: hidden;
    border: 1px solid var(--color-divider);
    border-radius: var(--radius-md);
}

.seg button {
    border: 0;
    border-radius: 0;
    padding: 7px 12px;
    font-size: 13px;
    background: transparent;
    color: var(--color-text);
}

/*
   The end buttons take the group's inner curve. The group clips its children at an 8px radius,
   and the active ring is an inset shadow that follows the BUTTON's radius — so with square
   buttons the ring's outer corners were clipped off and never met the group's curve, which
   read as corners somebody had forgotten to fill in. One pixel less than the group's radius,
   because that is the radius of the inside of a 1px border.
*/
.seg button:first-child { border-radius: calc(var(--radius-md) - 1px) 0 0 calc(var(--radius-md) - 1px); }
.seg button:last-child { border-radius: 0 calc(var(--radius-md) - 1px) calc(var(--radius-md) - 1px) 0; }

.seg button + button { border-left: 1px solid var(--color-divider); }
.seg button:hover:not(.is-active) { background: color-mix(in srgb, var(--color-text) 7%, transparent); }

.seg button.is-active {
    color: var(--color-accent);
    box-shadow: inset 0 0 0 1px var(--color-accent);
    background: transparent;
}

/* ══════════════════════════════════════════════════════════════════════════════════════════
   Drawer
   ══════════════════════════════════════════════════════════════════════════════════════ */

.drawer-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.55);
    display: flex;
    justify-content: flex-end;
    z-index: 20;
    animation: pb-fadein 120ms ease-out;
}

.drawer {
    position: relative;
    background: var(--color-bg);
    width: min(620px, calc(92vw / var(--ui-zoom)));
    height: 100%;
    overflow-y: auto;
    border-left: 1px solid var(--color-neutral-700);
    box-shadow: var(--shadow-lg);
    animation: pb-slidein 160ms ease-out;
    padding: 0;
}

/* Sticky, so the subject and the close button stay reachable however long the trail runs. */
.drawer-header {
    padding: var(--space-6) var(--space-8);
    border-bottom: 1px solid var(--color-divider);
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-6);
    position: sticky;
    top: 0;
    background: var(--color-bg);

    z-index: 2;
    margin: 0;
}

.drawer-header h2 { font-size: 18px; letter-spacing: -0.01em; text-wrap: pretty; }

.drawer-section {
    padding: var(--space-6) var(--space-8);
    border-bottom: 1px solid var(--color-divider);
}

.drawer-section:last-child { border-bottom: 0; }

/* Sticky at the bottom for the same reason the header is sticky at the top. */
.drawer-actions {
    position: sticky;
    bottom: 0;
    background: var(--color-bg);
    border-top: 1px solid var(--color-divider);
    padding: var(--space-6) var(--space-8);
    display: flex;
    gap: var(--space-3);
    flex-wrap: wrap;
    align-items: center;
}

@keyframes pb-slidein {
    from { transform: translateX(24px); opacity: 0; }
    to { transform: none; opacity: 1; }
}

@keyframes pb-fadein {
    from { opacity: 0; }
    to { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
    .drawer, .drawer-backdrop { animation: none; }
}

/*
    The preview iframe. Sandboxed with NO allow-scripts (spec §10) — the API already returns
    server-sanitised HTML, and this is the second defence, not the only one.

    White, deliberately, and not themed: this is a picture of what lands in somebody's inbox, and
    mail clients do not paint the console's dark ground behind it. Showing it on Nocturne's surface
    would make every template look right here and wrong on arrival.

    One rule for both previews (Templates, and a message's drawer). It used to be two, far apart;
    the later one's border quietly replaced this one's, which is the border kept here.
*/
.preview-frame {
    display: block;
    width: 100%;
    height: 420px;
    min-height: 320px;
    border: 1px solid var(--color-divider);
    border-radius: var(--radius-md);
    background: #fff;
}

/*
   In the template editor the frame runs down to the bottom of the sample-data box beside it, so
   the two columns finish level instead of the preview stopping two thirds of the way down.

   ⚠️ THIS IS A MEASURED CONSTANT, and the only honest way to keep one. It is the distance from
   the top of the preview frame to the bottom of the sample-data textarea, which depends on the
   heights of everything stacked above it in the other column — the Code row, Subject, both
   textareas and the gaps between them. No CSS relationship expresses that: flexbox aligns
   siblings, and these are cousins.

   So it is pinned by a test instead. `TemplateTabsTests.The_preview_ends_level_with_the_sample_
   data` measures both boxes in a real browser and fails if they drift apart, which is what turns
   a magic number from a liability into a maintained one. Change either textarea's height and that
   test tells you the new value.

   Scoped to `.preview-column` so the draft-review preview, which sits in a plain two-up
   comparison with nothing to line up against, keeps the default height above.

   Only above the wrapping width. Once the columns stack there is nothing beside the frame to be
   level with, and a 554px iframe on a phone would be a wall.
*/
@media (min-width: 900px) {
    .preview-column .preview-frame { height: 511px; }
}

/*
   The editor's two columns — source on the left, preview on the right, both starting at the top.

   Deliberately NOT `.filters`, which is what this row used to be. That class carries the
   filter-row height rules (`.filters > div > input { min-height: 40px }`), and reaching for it
   here is exactly how a control-height regression got into the template editor once already:
   a rule written for a row of search controls applied to an editor that is not one.

   `align-items: stretch` rather than `.filters`' bottom alignment, so the two columns are equal
   height and the preview has a full-height parent to stick inside.
*/
.editor-columns {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-6);
    align-items: stretch;
}

/*
   Everything that IS the template: the identifiers, the subject, the body, the sample data and
   the findings about them. Narrower than the preview on purpose — this side is monospaced markup,
   which reads fine narrow, and the rendered result is the thing being judged.
*/
.editor-source {
    flex: 1 1 420px;
    min-width: 0;
}

/*
   The rendered message, in a column of its own.

   Wider than the source, because this is the thing being judged and the source is monospaced
   markup that reads fine narrow. Sticky, because the source scrolls and the preview is what you
   are checking it against — a preview that scrolls away from the body it belongs to is the whole
   problem it exists to solve.

   `align-self: flex-start` is what makes position: sticky work here at all: the parent stretches
   its items to equal height by default, and an item as tall as its container has nowhere to stick.
*/
.preview-column {
    flex: 1.3 1 460px;
    min-width: 0;
    position: sticky;
    top: var(--space-6);
    align-self: flex-start;
}

/* ══════════════════════════════════════════════════════════════════════════════════════════
   Timeline
   ══════════════════════════════════════════════════════════════════════════════════════ */

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

.timeline li {
    padding: var(--space-4) 0 var(--space-4) var(--space-6);
    border-left: 2px solid var(--color-neutral-800);
    position: relative;
}

/*
    The address a timeline event concerns, on its own line. A bounce carries both an address and a
    bounce classification, and left inline they run together into one unpunctuated phrase.
*/
.event-recipient {

    display: block;
    overflow-wrap: anywhere;
}

.timeline li::before {
    content: "";
    position: absolute;
    left: -5px;
    top: 15px;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--color-neutral-700);
}

.timeline li.bad::before {
    background: var(--color-accent);
    box-shadow: 0 0 8px color-mix(in srgb, var(--color-accent) 60%, transparent);
}

.timeline li.good::before { background: var(--color-neutral-400); }

/*
    Something worth noticing that is not a failure — a recipient dropped from a send the rest of
    the list received. Hollow, in the accent family: close enough to `.bad` to catch the eye,
    plainly not the same thing, so nobody reads a partial send as a bounce.
*/
.timeline li.warn::before {
    background: transparent;
    border: 2px solid var(--color-accent-700);
}

/* ══════════════════════════════════════════════════════════════════════════════════════════
   Misc
   ══════════════════════════════════════════════════════════════════════════════════════ */

.muted { color: var(--color-neutral-400); }
.faint { color: var(--color-neutral-500); }
.small { font-size: 12.5px; }

/*
   ⚠️ `.tiny` was used in five places and defined in none of them.

   Nothing looked broken, which is the whole problem: an undefined class is not an error, it just
   inherits — so every "tiny" label rendered at its parent's size and the pages looked slightly
   wrong in a way nobody could name. It is most visible in the AWS permission list, where a 43-
   character action name at an inherited 14px is wider than the card it sits in.
*/
.tiny { font-size: 11.5px; }
.stack { display: flex; flex-direction: column; gap: var(--space-3); }
/*
   The action footer on a card. Separated by a rule and pushed to the bottom with margin-top:auto
   so that cards of different heights in one grid still line their buttons up with each other —
   an account with four identities and one with none otherwise put their buttons at different
   heights, which reads as a different kind of card rather than a longer one.
*/
.card-actions {
    /*
        A grid of equal columns, not a wrapping flex row.

        The four labels are very different lengths — "Settings" against "Re-provision SES" and
        "Add another domain" — so flex sized each button to its text and wrapped them wherever
        they happened to run out of room. The result was a ragged block whose shape changed per
        card depending on which state each account was in.

        Two columns, fixed. auto-fit with a minmax floor was tried first and reflowed to whatever
        happened to fit — which on a wide card meant three across and a fourth stranded on its own
        row. Equal widths, but an orphan.

        The footer has exactly four actions in every state the card can be in (the labels change,
        the count does not), so two columns is always a filled 2x2. If a fifth is ever added this
        wants revisiting rather than silently becoming a 3 + 2.

        minmax(0, 1fr), not 1fr. A bare 1fr means minmax(AUTO, 1fr), and that auto floor stops a
        grid item shrinking below its own content — so the column holding "Add sending domain"
        grew wider than the one holding "Settings" and the widths diverged again. It looked right
        on Windows and failed in CI, where Linux renders the same font slightly wider: a layout
        that depends on font metrics is one that is only accidentally correct.
    */
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-3);
    align-items: stretch;
    margin-top: auto;

    /*
        space-6, not space-5 — there is no space-5. The scale is 1, 2, 3, 4, 6, 8, and
        var(--space-5) is simply undefined, so the declaration was invalid and dropped: the
        buttons rendered hard against the rule with no padding at all. CSS fails silently by
        design, which makes a typo'd token indistinguishable from a property nobody wrote.
    */
    padding-top: var(--space-6);
    border-top: 1px solid var(--color-divider);
}

/*
    Buttons fill their cell, so the grid's alignment is visible rather than theoretical.

    Grid items are blockified and stretch by default, so this is belt-and-braces — but the base
    button is display:inline-flex with a natural width, and "the columns line up while the
    buttons inside them do not" looks identical to the ragged row this replaced. Centring and
    nowrap are NOT repeated here: the base button rule already sets both, and a declaration that
    restates the cascade is one more line to keep in step with it.
*/
.card-actions > button {
    width: 100%;

    /*
        Wrapping is required, not cosmetic. The columns are minmax(0, 1fr), so a column can be
        narrower than its label — and the base button rule sets white-space: nowrap, which would
        push the text straight out of the button rather than onto a second line. align-items:
        stretch on the grid keeps a wrapped two-line button the same height as its neighbour.
    */
    white-space: normal;
}

.card-actions + .card-actions-note {
    margin: var(--space-3) 0 0;
    text-wrap: pretty;
}

.row { display: flex; gap: var(--space-3); align-items: center; flex-wrap: wrap; }
.spread { display: flex; justify-content: space-between; gap: var(--space-4); align-items: baseline; }

/*
    An account card's header: the name on the left, its state badges on the right.

    The badges were `flex: none`, so they could neither shrink nor wrap. Three of them together —
    Marketing paused, Resume marketing, Email — took most of the card's width, squeezed the name
    to a word per line and slid over it. Now the header wraps: when the badges need more room
    than is left beside the name, they drop to a line of their own beneath it, still on the right,
    and the name keeps the full width. One or two badges still sit beside the name as before.
*/
.card-head { flex-wrap: wrap; align-items: flex-start; row-gap: var(--space-2); }
.card-head-title { flex: 1 1 12rem; min-width: 0; overflow-wrap: anywhere; }
.card-head-badges { flex: 0 1 auto; justify-content: flex-end; margin-left: auto; }

.empty { padding: 56px 0; text-align: center; color: var(--color-neutral-500); font-size: 14px; }
.prose { max-width: 80ch; line-height: 1.55; text-wrap: pretty; }

/* A DNS record, an API key, a diagnostic code — anything meant to be copied verbatim. */
.record {
    display: flex;
    gap: var(--space-3);
    align-items: center;
    background: var(--color-neutral-900);
    border: 1px solid var(--color-neutral-800);
    border-radius: var(--radius-md);
    padding: var(--space-3) var(--space-4);
    font-family: var(--font-mono);

    font-size: 12px;
    color: var(--color-neutral-300);
    overflow-x: auto;
}

.record .value { white-space: nowrap; }

/*
   A panel that states something in WORDS, not a value to be copied.

   `.record` is for a DNS record, an API key, a diagnostic code — things that must stay on one line
   to be copied verbatim, which is why it is monospaced, does not wrap, and scrolls sideways. Reuse
   it for a sentence and the sentence inherits all three: a long one runs off the side of a drawer
   and the reader has to scroll horizontally to finish reading it.

   Reported on the delete-account dialog, and the template one had it too — the same mistake in the
   same shape, made twice, because .record looks like "a boxed thing" from the markup.
*/
.state-line {
    display: flex;
    gap: var(--space-3);
    align-items: baseline;
    background: var(--color-neutral-900);
    border: 1px solid var(--color-neutral-800);
    border-radius: var(--radius-md);
    padding: var(--space-3) var(--space-4);
    font-size: 13px;
    line-height: 1.5;
    color: var(--color-neutral-300);
    text-wrap: pretty;
}

/* The badge keeps its size; the sentence beside it is what wraps. */
.state-line .badge { flex: none; }
.state-line > span:last-child { min-width: 0; }

/*
   Where the domain's DNS actually lives, above the records it governs.

   A panel rather than a row: it carries three lines of different weights — the provider, what
   that means for whoever has to publish, and the nameservers it was inferred from — and they
   read as one answer only if something holds them together.

   The nameserver line wraps. Four Azure nameservers are longer than the drawer, and a horizontal
   scrollbar on the piece of evidence somebody is checking by eye defeats the point of showing it.
*/
.hosting {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    padding: var(--space-3);
    border: 1px solid var(--color-neutral-800);
    border-radius: var(--radius-md);
    background: var(--color-neutral-900);
}

.hosting .mono { overflow-wrap: anywhere; }

/*
   Deleting an account: last in the settings form, ruled off from the save row above it.

   Not a filled red button. Delete is not what somebody came here to do, and a control weighted
   like the primary action invites the click it should be discouraging — the weight belongs on the
   confirmation, which asks for the account code to be typed.
*/
.account-danger {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--space-2);
    margin-top: var(--space-8);
    padding-top: var(--space-4);
    border-top: 1px solid var(--color-divider);
}

/*
   Retiring a domain: last in the drawer, and separated from the actions above it.

   The controls above are things you press while a customer's IT is on the phone. This one stops
   the domain being offered at all, so it does not belong in the same row as Recheck.
*/
.identity-remove {
    margin-top: var(--space-6);
    padding-top: var(--space-3);
    border-top: 1px solid var(--color-divider);
}

/*
   The AWS permission list, inside a 330px account card.

   Bounded height, so expanding one card cannot resize the grid row every other account shares.
   Eighteen permissions made the card taller than the viewport and stretched its neighbours to
   match, leaving a screen of empty panels with their buttons stranded at the bottom — which read
   as the panel overlapping the other cards.
*/
.perm-list {
    margin-top: var(--space-2);
    max-height: 260px;
    overflow-y: auto;
    overscroll-behavior: contain;
}

.perm-row {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: var(--space-2) 0;
    border-bottom: 1px solid color-mix(in srgb, var(--color-text) 8%, transparent);
}

.perm-row:last-child { border-bottom: none; }

/*
   overflow-wrap: anywhere, because these strings have no spaces to break at.
   `ses:CreateConfigurationSetEventDestination` and an ARN fragment are each wider than the column,
   and without this they set a minimum width the card cannot meet — so the content spilled past the
   card's edge and was cut mid-word.
*/
.perm-row .mono { overflow-wrap: anywhere; }

.perm-verdict {
    display: flex;
    align-items: baseline;
    gap: var(--space-2);
    min-width: 0;
}

/* The badge keeps its size; the resource beside it is what gives way. */
.perm-verdict .badge { flex: none; }
.perm-verdict .mono { min-width: 0; }

/*
   The identity checklist table.

   A DNS record must stay on one line — it is copied verbatim into somebody's zone — and a table
   cell will not shrink below its content. So the long DMARC value dragged the whole table wide:
   the drawer grew a horizontal scrollbar and the Check column was squeezed until "MAIL FROM
   verified at SES" wrapped into a four-line tower.

   max-width: 0 with width: 100% is the fix that looks wrong and is right. It does not make the
   column zero-wide; it stops the cell's CONTENT from setting a minimum, so the column takes the
   space left over and .record's own overflow-x finally has something to scroll against.
*/
.checklist-table td.check-name { white-space: nowrap; }

.checklist-table td.check-record {
    max-width: 0;
    width: 100%;
}


/*
   The record WRAPS rather than scrolls.

   It used to scroll inside its own cell, which kept each record on one line but put a horizontal
   scrollbar on every DKIM and DMARC row — so reading a record meant dragging it past a window
   two-thirds its length, and the card looked broken.

   One line only ever mattered because a DNS record is copied verbatim. The copy button already
   guarantees that: it puts the exact zone-file line on the clipboard, so nobody is selecting this
   text by hand. Once copying is a button rather than a drag, the display is free to wrap and the
   whole record can be read at once.
*/
.checklist-table td.check-record .record {
    max-width: 100%;
    flex-wrap: wrap;
    overflow-x: visible;
}

.checklist-table td.check-record .record .value {
    white-space: normal;
    overflow-wrap: anywhere;
}

/*
   The "Currently published" and "Recommended" lines are for reading, not copying, so they wrap
   rather than scroll — and they need to be blocks, otherwise they run onto the same line as the
   record above them.
*/
.checklist-table td.check-record .small {
    display: block;
    margin-top: var(--space-2);
    overflow-wrap: anywhere;
}

/* A Copy button sitting inside a record drops to the record's size rather than towering over it. */
.record button { font-size: 12px; padding: 2px var(--space-2); flex: none; }

/*
   The copy control: the two overlapping squares, not the word "Copy".

   It is the glyph every developer already reads as copy, and it stops a five-letter English word
   sitting in the middle of a monospaced DNS record where it can be mistaken for part of the
   value. Icon-only, so it carries its own aria-label — a button whose entire content is an SVG is
   announced as "button" and nothing else.
*/
.record button.icon {
    display: inline-grid;
    place-items: center;
    padding: 4px;
    background: none;
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    color: var(--color-neutral-400);
    cursor: pointer;
}

.record button.icon:hover {
    color: var(--color-text);
    border-color: var(--color-divider);
    background: color-mix(in srgb, var(--color-text) 6%, transparent);
}

.record button.icon svg {
    display: block;
    width: 15px;
    height: 15px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.3;
    stroke-linecap: round;
    stroke-linejoin: round;
    vector-effect: non-scaling-stroke;
}

/*
   A text's preview: the words in a bubble about the width of a phone, on the console's own
   ground. Not white and not framed — nothing about a text is a page.
*/
.preview-text {
    margin: 0;
    max-width: 360px;
    padding: var(--space-3) var(--space-4);
    font: inherit;
    line-height: 1.5;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    color: var(--color-text);
    background: color-mix(in srgb, var(--color-text) 7%, transparent);
    border: 1px solid var(--color-divider);
    border-radius: 18px;
    border-bottom-left-radius: 4px;
}

/*
   ══════════════════════════════════════════════════════════════════════════════════════
   The picker dropdown — hacc.app's control, for lists a native select was handling badly.

   Proportions come from hacc.app rather than from the rest of this stylesheet: taller rows,
   a rounder corner, a dot for the chosen one, and room for a line of help under each name.
   That is the shape people recognise as "the nicer one", and it is not reachable by styling a
   <select>, whose option list the operating system paints.
   ══════════════════════════════════════════════════════════════════════════════════════ */

.drop { position: relative; }

.drop-button {
    width: 100%;
    min-height: 40px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    padding: 8px 12px;
    font: inherit;
    font-size: 14px;
    text-align: left;
    color: var(--color-text);
    background: color-mix(in srgb, var(--color-text) 4%, transparent);
    border: 1px solid var(--color-divider);
    border-radius: 10px;
    cursor: pointer;
}

.drop-button:hover {
    border-color: color-mix(in srgb, var(--color-text) 45%, transparent);
    background: color-mix(in srgb, var(--color-text) 6%, transparent);
}

/* Open is a state worth seeing: the panel below it is easy to lose against a busy page. */
.drop-button.is-open { border-color: var(--color-accent); }

.drop .is-disabled, .drop-button:disabled { opacity: 0.55; cursor: default; }

.drop-value { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.drop-value.is-placeholder { color: var(--color-neutral-500); }

.drop-chevron {
    width: 14px;
    height: 14px;
    flex: none;
    fill: none;
    stroke: var(--color-neutral-400);
    stroke-width: 1.6;
    stroke-linecap: round;
    stroke-linejoin: round;
    vector-effect: non-scaling-stroke;
}

.drop-button:hover .drop-chevron,
.drop-button.is-open .drop-chevron { stroke: var(--color-accent); }


/*
   Covers the page so a click anywhere else closes the panel, without a document-level handler.
   Transparent, and beneath the panel in stacking order.
*/
.drop-backdrop {
    position: fixed;
    inset: 0;
    z-index: 40;
}

.drop-panel {
    position: absolute;
    z-index: 41;
    top: calc(100% + 6px);
    left: 0;
    right: 0;
    min-width: 240px;
    max-height: 320px;
    overflow-y: auto;
    padding: var(--space-2);
    display: flex;
    flex-direction: column;
    gap: 4px;
    background: var(--color-surface);
    border: 1px solid var(--color-divider);
    border-radius: 12px;
    box-shadow: 0 18px 40px rgb(0 0 0 / 45%);
}

.drop-search {
    width: 100%;
    margin-bottom: 4px;
    border-radius: 8px;
}

.drop-empty {
    padding: var(--space-3);
    font-size: 13px;
    color: var(--color-neutral-500);
}

/*
   justify-content and white-space are set HERE, not inherited.

   The global `button` rule centres its content and forbids wrapping — right for a button with a
   word on it, wrong for a list row. `.drop-opt` did not declare either, so the element rule won by
   default and every option sat centred in its row. A class beats an element selector only for the
   properties it actually names.
*/
.drop-opt {
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: flex-start;
    white-space: normal;
    gap: 10px;
    padding: 9px 12px;
    font: inherit;
    font-size: 13.5px;
    text-align: left;
    color: var(--color-text);
    background: none;
    border: 1px solid transparent;
    border-radius: 10px;
    cursor: pointer;
}

.drop-opt:hover { background: color-mix(in srgb, var(--color-text) 7%, transparent); }

.drop-opt.on {
    border-color: var(--color-accent);
    background: color-mix(in srgb, var(--color-accent) 12%, transparent);
}

/* Hollow when unchosen, accent-filled when chosen — the same language as the role picker. */
.drop-dot {
    width: 16px;
    height: 16px;
    flex: none;
    margin-top: 2px;
    border-radius: 50%;
    border: 2px solid var(--color-neutral-500);
    display: grid;
    place-items: center;
}

.drop-opt.on .drop-dot { border-color: var(--color-accent); }

.drop-opt.on .drop-dot::after {
    content: "";
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--color-accent);
}

/*
   The row menu — three dots at the end of a grid row (Components/RowMenu.razor).

   Same backdrop and panel as the picker; only the anchoring differs. The panel hangs from the
   RIGHT edge of the button because the control lives in the last column, and it is as wide as
   its longest item rather than as wide as the button.
*/
.row-menu { position: relative; display: inline-flex; }

.row-menu-button { color: var(--color-neutral-500); }

.row-menu-button:hover:not(:disabled),
.row-menu-button.is-open { color: var(--color-text); }

.row-menu-dots { width: 18px; height: 18px; fill: currentColor; }

.row-menu-panel {
    left: auto;
    right: 0;
    top: calc(100% + 4px);
    min-width: 200px;
    width: max-content;
    max-width: 280px;
    text-align: left;
}

.row-menu-item { white-space: nowrap; }

/* The irreversible item wears the confirm button's colour, so the warning arrives twice. */
.row-menu-item.is-danger { color: var(--color-accent-200); }

.row-menu-item.is-danger:hover { background: color-mix(in srgb, var(--color-accent) 12%, transparent); }

/*
   A table whose rows carry menus. `.table-wrap` scrolls horizontally, and a scroll container
   clips on both axes, so a menu hanging below the last row would be cut off. A page whose
   columns fit opts out of the scroll to let the menus show.
*/
.table-wrap-open { overflow: visible; }

/*
   The channels on an account card — Email, SMS — each under its own title, ruled off from the
   one above so "Sent today" cannot be read as the whole account.
*/
.channel {
    margin-top: var(--space-6);
    padding-top: var(--space-4);
    border-top: 1px solid var(--color-divider);
}

.channel-title {
    margin: 0;
    font-size: 14px;

    font-weight: var(--font-heading-weight);
    letter-spacing: -0.01em;
    color: var(--color-text);
}

/* The small uppercase label that names a block without being a heading. */
.kicker {
    font-size: 11px;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--color-neutral-500);
}

/* A number that has crossed a threshold, inside otherwise neutral running text. */
.alarm-text { color: var(--color-accent-300); }

/* ---- Volume chart ----

   Columns are a single auto-flow grid so they stay equal-width at any series length, and the
   axis labels are a second grid with the same track definition rather than being nested inside
   each bar — a label longer than its column would otherwise widen that column alone and put
   every bar out of step with the one above it.
*/

/*
    Grows to whatever the card has left. The card sits in a two-up grid beside Service health,
    so its height is decided by that neighbour — a fixed 164px chart leaves a slab of dead space
    underneath whenever the health list is the taller of the two. Bars are sized as percentages
    of the peak so they rescale with it.
*/
.chart {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
    gap: var(--space-4);
    align-items: end;
    flex: 1 1 auto;
    min-height: 164px;
    margin-top: var(--space-8);
    border-bottom: 1px solid var(--color-divider);
}

.chart-col {
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    gap: 2px;
    height: 100%;
    min-width: 0;
}

.chart-total {
    font-size: 11px;
    color: var(--color-neutral-600);
    margin-bottom: var(--space-2);
    text-align: center;
    overflow: hidden;
    white-space: nowrap;
}

.chart-sent { background: var(--color-neutral-700); border-radius: 0 0 var(--radius-sm) var(--radius-sm); }

/* The accent cap, with the glow it is allowed to carry — this is the bit worth noticing. */
.chart-bounced {
    background: var(--color-accent);
    border-radius: var(--radius-sm) var(--radius-sm) 0 0;
    box-shadow: 0 0 10px color-mix(in srgb, var(--color-accent) 55%, transparent);
}

.chart-labels {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
    gap: var(--space-4);
    margin-top: var(--space-3);
    font-size: 11.5px;
    color: var(--color-neutral-600);
}

.chart-labels div { overflow: hidden; white-space: nowrap; }

.chart-key { width: 9px; height: 9px; border-radius: 2px; display: inline-block; flex: none; }

/*
   Outcome colours. Nocturne has ONE accent hue, so these separate by weight rather than by
   inventing a second palette: neutral for the ordinary case, the accent for the two outcomes
   that cost sending reputation, and faded neutrals for the ones that never reached SES.

   Delivered is deliberately the quiet one. It is the overwhelming majority of every bar, and an
   accent-coloured majority would make a healthy account look alarming at a glance.
*/
.chart-key-sent,       .outcome-bar .chart-key-sent       { background: var(--color-neutral-600); }
.chart-key-bounced,    .outcome-bar .chart-key-bounced    { background: var(--color-accent); }
.chart-key-complained, .outcome-bar .chart-key-complained { background: var(--color-accent-700); }
.chart-key-failed,     .outcome-bar .chart-key-failed     { background: var(--color-neutral-400); }
.chart-key-suppressed, .outcome-bar .chart-key-suppressed { background: var(--color-neutral-300); }
.chart-key-awaiting,   .outcome-bar .chart-key-awaiting   { background: var(--color-neutral-800); }

/* ---- Outcome proportion bar ---- */

.outcome-bar {
    display: flex;
    height: 12px;
    border-radius: 3px;
    overflow: hidden;
    margin-top: var(--space-4);
    background: var(--color-neutral-800);
}

.outcome-bar > span { display: block; height: 100%; }

.outcome-key {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
    gap: var(--space-2) var(--space-4);
    margin: var(--space-4) 0 0;
}

.outcome-key > div {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    padding: 3px 0;
    border-bottom: 1px solid var(--color-divider);
}

.outcome-key dt {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    font-size: 13px;
    color: var(--color-neutral-300);
}

.outcome-key dd {
    margin: 0;
    font-size: 13px;
    font-variant-numeric: tabular-nums;
    color: var(--color-text);
}

/* Zero is shown rather than hidden — "none" and "not counted" are different facts. */
.outcome-key .is-zero dt, .outcome-key .is-zero dd { opacity: 0.45; }

/* ---- Attachment list in the message drawer ---- */

.attachments {
    list-style: none;
    margin: var(--space-3) 0 0;

    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 1px;
    background: var(--color-divider);
    border: 1px solid var(--color-divider);
    border-radius: var(--radius-md);
    overflow: hidden;
}

/*
   Two rows per attachment. Everything on one line meant the filename, the MIME type, the size
   and a Download button competing for the width of a drawer, and the filename — the part that
   identifies it — losing.
*/
.attachments li {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    padding: var(--space-3) var(--space-4);
    background: var(--color-surface);
    min-width: 0;
}

.attachments .attachment-line {
    display: flex;
    align-items: baseline;
    gap: var(--space-3);
    min-width: 0;
}

/* The supporting row sits quieter, with the action pushed to the far end. */
.attachments .attachment-meta { justify-content: space-between; }

/* The filename is the identifying part, so it takes the room and truncates last. */
.attachments .attachment-name {
    flex: 1 1 auto;
    min-width: 0;
    overflow-wrap: anywhere;
    color: var(--color-text);
}

/*
   A long acceptance or rejection message from a recipient's server wraps rather than scrolling.

   The plain .record keeps its value on one nowrap line so a DNS record can be copied verbatim —
   right for a TXT record, wrong for a sentence. Behind a horizontal scrollbar, the part of a
   bounce message that says WHY is the part nobody reads.
*/
.record-wrap { overflow-x: visible; }
.record-wrap .value { white-space: pre-wrap; overflow-wrap: anywhere; }

/* Right-aligned and tabular so sizes can be compared down the column at a glance. */
.attachments .attachment-size {
    flex: none;
    margin-left: auto;
    font-variant-numeric: tabular-nums;
    color: var(--color-neutral-300);
}

/* ---- 30-day trend ---- */

.trend { position: relative; margin-top: var(--space-4); }

.trend-plot { display: block; width: 100%; height: 132px; overflow: visible; }

.trend-grid { stroke: var(--color-divider); stroke-width: 1; }

.trend-path {
    fill: none;
    stroke: var(--color-accent);
    stroke-width: 2;
    stroke-linejoin: round;
    stroke-linecap: round;
}

.trend-area { fill: var(--color-accent); opacity: 0.10; stroke: none; }

/*
   Today's marker, positioned over the plot rather than drawn inside it. The svg stretches with
   preserveAspectRatio="none", which would turn a circle into an ellipse; a DOM element on top
   is round whatever the plot is doing.
*/
.trend-today { position: absolute; transform: translate(-50%, 50%); pointer-events: none; }

.trend-dot {
    display: block;
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: var(--color-accent);
    box-shadow: 0 0 0 3px var(--color-surface);
}

.trend-axis {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: var(--space-4);
    margin-top: var(--space-3);
    font-size: 12px;
    color: var(--color-neutral-400);
}

.trend-axis-end { display: flex; align-items: baseline; gap: var(--space-2); }
.trend-axis-end strong { color: var(--color-text); font-variant-numeric: tabular-nums; }

/* ---- Service health list ---- */

.health-row {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    padding: var(--space-4) 0;
    border-top: 1px solid var(--color-divider);
}

.spark { display: flex; align-items: flex-end; gap: 3px; height: 24px; }

.spark span {
    width: 4px;
    background: var(--color-neutral-700);
    min-height: 1px;
    border-radius: 1px;
}

/* One sending identity inside an account card: domain, its state, and the way in. */
.identity-row {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-3) 0;
    border-top: 1px solid var(--color-divider);
}

.identity-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }

/*
    A checkbox and its label on one line. The global `label` rule stacks the label above its
    control, which is right for a text field and wrong for a checkbox — it puts the caption on
    a line of its own and the box underneath it, orphaned.
*/
.inline-check {
    display: inline-flex;
    align-items: center;
    gap: var(--space-3);
    margin-bottom: 0;
    min-height: 36px;
    font-size: 13.5px;
    color: var(--color-neutral-400);

    cursor: pointer;
    white-space: nowrap;
}

.inline-check input { width: auto; flex: none; }

/* An identifier quoted inside prose — a config key, a flag, a property name. */
code.accent { color: var(--color-accent-300); }

/* A meter — a daily send cap, a share of a budget. */
.meter {
    height: 6px;
    border-radius: 999px;
    background: var(--color-neutral-900);
    overflow: hidden;
}

.meter span { display: block; height: 100%; background: var(--color-accent); }
.meter-unlimited span { background: var(--color-neutral-700); }

/* Label/value pairs that align on a shared column — account facts, message metadata. */
.facts { margin: 0; display: grid; grid-template-columns: auto minmax(0, 1fr); }
.facts > div { display: contents; }

.facts dt {
    font-size: 12.5px;
    color: var(--color-neutral-500);
    padding: var(--space-3) var(--space-6) var(--space-3) 0;
    border-top: 1px solid var(--color-divider);
    white-space: nowrap;
}

.facts dd {
    margin: 0;
    font-family: var(--font-mono);
    font-size: 12.5px;
    text-align: right;
    padding: var(--space-3) 0;
    border-top: 1px solid var(--color-divider);
    overflow: hidden;
    text-overflow: ellipsis;
}

/* ══════════════════════════════════════════════════════════════════════════════════════════
   Loading

   Every wait in this app is the brand mark posting a letter. The animation lives here rather
   than in a scoped stylesheet because index.html needs it too — the first wait a user sees is
   the WASM runtime downloading, before any component exists to render it.

   The mark takes its colour from currentColor, so it is the accent here and stays correct if
   the accent is ever retuned in the token block above.
   ══════════════════════════════════════════════════════════════════════════════════════ */

.pb-loader {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-4);
    padding: var(--space-8);
    color: var(--color-accent);
}

.pb-loader-inline { flex-direction: row; padding: 0; gap: var(--space-3); }

/*
    The boot progress bar. Driven entirely by --blazor-load-percentage, which
    blazor.webassembly.js sets on #app as it downloads.

    Colours are literal rather than tokens: this renders before app.css is guaranteed to have
    landed in a cold load, and a token that has not been defined yet resolves to nothing — which
    for a background means an invisible bar. The rest of the loader is painted inline in
    index.html for the same reason.
*/
/*
    Example questions under the Ask box. A wrapping row of links rather than a list: they are
    prompts, not navigation, and they disappear the moment a question has been asked.
*/
.ask-examples {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-3) var(--space-6);
    margin-top: var(--space-4);
}

/*
    The version notices, at the top of the content column. A row rather than a block so the action
    sits beside the sentence: a banner saying a new version exists, with the button to get it
    somewhere else on the page, is a banner that gets ignored.
*/
.version-banner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);

    /* space-6: there is no space-5. The scale is 1, 2, 3, 4, 6, 8. */
    margin-bottom: var(--space-6);
}

.version-banner button { flex: 0 0 auto; }

@media (max-width: 720px) {
    .version-banner {
        flex-direction: column;
        align-items: stretch;
    }
}

.pb-progress {
    width: 180px;
    height: 3px;
    border-radius: 2px;
    background: #232532;
    overflow: hidden;
}

.pb-progress-fill {
    height: 100%;
    width: var(--blazor-load-percentage, 0%);
    background: #4a8fe7;

    /* Eases the jumps between resources so it reads as progress rather than as a stutter. */
    transition: width 200ms ease-out;
}

/*
    The percentage as text, for anybody who cannot judge a 180px bar — and because "37%" answers
    "is this stuck?" and a bar three pixels tall does not.
*/
.pb-progress-text::after { content: var(--blazor-load-percentage-text, ""); }

.pb-loader-text { font-size: 13px; color: var(--color-neutral-500); }

.pb-loader-mark { filter: drop-shadow(0 0 14px color-mix(in srgb, var(--color-accent) 40%, transparent)); }

.pb-loader-letter {
    /*
        fill-box so the origin is the letter's own centre, which puts the 14° tilt in the same
        place the static mark has it. Without it the origin is the whole 72×72 viewBox and the
        letter swings in from off-centre.
    */
    transform-box: fill-box;
    transform-origin: center;
    animation: pb-post 1.5s cubic-bezier(0.55, 0.06, 0.68, 0.19) infinite;
}

@keyframes pb-post {
    0% { transform: translateY(-11px) rotate(14deg); opacity: 0; }

    18% { transform: translateY(-7px) rotate(14deg); opacity: 1; }
    58% { transform: translateY(13px) rotate(14deg); opacity: 1; }
    70% { transform: translateY(16px) rotate(14deg); opacity: 0; }
    100% { transform: translateY(16px) rotate(14deg); opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
    /*
        No travel at all for anyone who has asked for less motion. The letter sits where the
        static mark puts it and the whole thing breathes instead, which still says "working"
        without anything moving across the screen.
    */
    .pb-loader-letter {
        animation: none;
        transform: rotate(14deg);
    }

    .pb-loader-mark { animation: pb-breathe 2s ease-in-out infinite; }
}

@keyframes pb-breathe {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.45; }
}

.loading { padding: var(--space-8); text-align: center; color: var(--color-neutral-500); }

/* ══════════════════════════════════════════════════════════════════════════════════════════
   The four headline checks on an identity row.

   Weight, not hue — the same rule the badges follow. A filled accent is something to act on,
   an outline is worth knowing, filled neutral is measured and fine, and a hairline is not
   measured at all. Unknown must never render like Ok: "we did not look" and "we looked and it
   was fine" are different facts and the whole point of showing four of these is spotting which.
   ══════════════════════════════════════════════════════════════════════════════════════ */

.checks {
    display: flex;
    gap: var(--space-2);
    flex: none;
    align-items: center;
}

.check {
    font-family: var(--font-mono);
    font-size: 9.5px;
    letter-spacing: 0.08em;
    line-height: 1;
    padding: 3px 5px;
    border-radius: var(--radius-sm);
    border: 1px solid transparent;
    white-space: nowrap;
    cursor: help;
}

.check-ok { background: var(--color-neutral-800); color: var(--color-neutral-200); }
.check-pending { border-color: var(--color-accent); color: var(--color-accent-300); }
.check-bad { background: var(--color-accent-800); color: var(--color-accent-100); }

/* Hairline only. Absence of data, not a state of health. */
.check-unknown { border-color: var(--color-divider); color: var(--color-neutral-500); }

.identity-row.clickable { cursor: pointer; }
.identity-row.clickable:hover { background: color-mix(in srgb, var(--color-text) 5%, transparent); }


/* Switchboard additions */

.environment-label { margin: 0; overflow: hidden; color: var(--color-neutral-400); font-size: 11px; letter-spacing: 0.1em; text-overflow: ellipsis; text-transform: uppercase; white-space: nowrap; }
.page-section { display: flex; flex-direction: column; gap: var(--space-4); padding: var(--space-6); border-radius: var(--radius-md); background: var(--color-surface); }
.page-section h2, .page-section p { margin: 0; }
.wide-section { width: 100%; }
.eyebrow { color: var(--color-accent-300); font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.1em; text-transform: uppercase; }
.update-banner, .updated-notice { display: flex; align-items: center; justify-content: space-between; gap: var(--space-6); margin: var(--space-4) var(--space-8) 0; padding: var(--space-4) var(--space-6); border: 1px solid var(--color-accent); border-radius: var(--radius-md); background: var(--color-surface); box-shadow: 0 0 18px color-mix(in srgb, var(--color-accent) 20%, transparent); }
.updated-notice { max-width: 220px; margin-left: auto; }
.updated-notice button { border: 0; color: var(--color-neutral-400); background: transparent; }
.settings-links { display: grid; gap: var(--space-4); }
.settings-links a { display: grid; gap: var(--space-2); padding: var(--space-6); border: 1px solid var(--color-divider); border-radius: var(--radius-md); color: var(--color-text); text-decoration: none; }
.settings-links a:hover { border-color: var(--color-accent); box-shadow: 0 0 18px color-mix(in srgb, var(--color-accent) 20%, transparent); }
.settings-links span, .form-note { color: var(--color-neutral-400); }
.form-section { max-width: 620px; }
.form-section form, .inline-form { display: grid; gap: var(--space-3); }
.form-section label, .inline-form label { margin-top: var(--space-2); color: var(--color-neutral-400); font-size: 13px; }
.form-section .primary, .inline-form .primary { justify-self: start; margin-top: var(--space-3); }
.back-link { display: inline-block; margin-top: var(--space-6); }
.form-error, .auth-error { color: var(--color-accent-200); }
/* Each label directly above its own field, however many fields a form has: the grid fills a column at a
   time, label then field, so a three-field form cannot push a field under the wrong label. */
.inline-form { grid-template-rows: auto auto; grid-auto-flow: column; grid-auto-columns: minmax(120px, 1fr); align-items: end; margin-bottom: var(--space-8); }
.inline-form label { grid-row: 1; }
.inline-form .primary { grid-row: 2; align-self: end; margin: 0; }
.copy-field { display: flex; gap: var(--space-3); }
.copy-field input { flex: 1; min-width: 0; font-family: var(--font-mono); }
.copy-status { margin: var(--space-2) 0 0; }
.confirm-panel, .link-result, .key-result { margin-top: var(--space-6); padding: var(--space-6); border: 1px solid var(--color-accent); border-radius: var(--radius-md); background: var(--color-surface); }
.confirm-panel button + button { margin-left: var(--space-3); }
.monospace { font-family: var(--font-mono); }
/* Failed intake: the raw copy is whatever someone outside sent, shown as text and never wider than the page. */
.raw-copy { margin-top: var(--space-6); padding: var(--space-6); border: 1px solid var(--color-divider); border-radius: var(--radius-md); background: var(--color-surface); }
.raw-copy h2 { margin: 0; font-size: 16px; }
.raw-copy pre { max-height: 420px; overflow: auto; margin: var(--space-3) 0; padding: var(--space-4); border-radius: var(--radius-md); background: var(--color-bg); font-family: var(--font-mono); font-size: 13px; white-space: pre-wrap; overflow-wrap: anywhere; }
.stack-table td[data-label="Actions"] button + button { margin-left: var(--space-2); }
.alarm-list { display: grid; gap: var(--space-3); margin: 0; padding: 0; list-style: none; }
.alarm-list li { display: flex; align-items: flex-start; gap: var(--space-4); padding: var(--space-4); border: 1px solid var(--color-divider); border-radius: var(--radius-md); }
.alarm-list li > div { flex: 1; min-width: 0; }
.alarm-list p { margin: var(--space-1) 0 0; overflow-wrap: anywhere; }
.alarm-list .badge { flex: none; }
@media (max-width: 560px) { .alarm-list li { flex-wrap: wrap; } }
.older-button { margin-top: var(--space-6); }
.auth-card label { display: block; margin: var(--space-4) 0 var(--space-2); color: var(--color-neutral-400); font-size: 13px; }
.auth-card input { width: 100%; }
.auth-card .primary { width: 100%; margin-top: var(--space-6); }
.auth-lede { margin: 0 0 var(--space-4); color: var(--color-neutral-400); font-size: 13px; }
/* "Keep me signed in" is a label and an input too, but a row with a small box, not a field. */
.auth-card .stay-signed-in { display: flex; margin: var(--space-4) 0 0; color: var(--color-neutral-300); }
.auth-card .stay-signed-in input { width: 16px; }
.auth-note { margin: 0 0 var(--space-4); color: var(--color-neutral-400); font-size: 13px; }
.auth-link { display: inline-block; margin-top: var(--space-6); font-size: 13px; }
#blazor-error-ui { display: none; position: fixed; z-index: 1000; inset: auto var(--space-6) var(--space-6); padding: var(--space-4) var(--space-6); color: var(--color-text); background: var(--color-surface); border: 1px solid var(--color-accent); box-shadow: var(--shadow-md); }
[tabindex="-1"]:focus { outline: none; }
@media (max-width: 900px) { .inline-form { grid-auto-flow: row; grid-template-rows: none; grid-template-columns: 1fr; } .inline-form label, .inline-form .primary { grid-row: auto; } }
@media (max-width: 760px) {
    .page-body { padding: var(--space-6); }
    .stack-table thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
    .stack-table, .stack-table tbody, .stack-table tr { display: block; }
    .stack-table tr { padding: 10px 0; border-bottom: 1px solid var(--color-divider); }
    .stack-table td { display: grid; grid-template-columns: 76px minmax(0, 1fr); gap: 10px; justify-items: start; padding: 4px 0; border-bottom: 0; overflow-wrap: anywhere; background: none; }
    .stack-table td::before { content: attr(data-label); color: var(--color-neutral-400); font-size: 0.78rem; font-weight: 650; line-height: 1.6; }
    .stack-table td.actions-cell { grid-template-columns: minmax(0, 1fr); padding-top: 10px; }
    /* "Invited until 06 Oct 2026 16:35" is wider than a card on a 320px screen. */
    .stack-table .badge { max-width: 100%; white-space: normal; }
    .stack-table td.actions-cell::before { content: none; }
    .stack-table td.actions-cell:not(:has(button)) { display: none; }
}
.named-jobs {
    display: grid;
    gap: var(--space-4);
}

.named-job {
    display: grid;
    gap: var(--space-2);
}

.edit-panel {
    margin-top: var(--space-4);
}

/* SLA settings (phase 1 step 4): stacked fields like the other settings forms, checkbox rows, a target grid. */
.sla-editor { display: flex; flex-direction: column; gap: var(--space-2); max-width: 760px; }
.sla-editor h2 { margin-top: var(--space-6); }
.sla-editor > label:not(.check-option), .sla-preview label, .closure-add label:not(.check-option) { color: var(--color-neutral-400); font-size: 13px; margin-top: var(--space-2); }
.check-options { border: 0; margin: var(--space-3) 0 0; padding: 0; display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-6); }
.check-options legend { color: var(--color-neutral-400); font-size: 13px; margin-bottom: var(--space-2); padding: 0; }
.check-option { display: inline-flex; align-items: center; gap: var(--space-2); color: var(--color-text); font-size: 14px; }
.target-grid input.target-hours { width: 6rem; }
.form-actions { display: flex; align-items: center; gap: var(--space-3); margin-top: var(--space-4); }
.form-actions .primary { margin-top: 0; }
.weekly-hours input[type="time"]:disabled { opacity: 0.4; }
.section-note { color: var(--color-neutral-400); font-size: 13px; }
.sla-preview, .closure-add { display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-2); margin-top: var(--space-3); }
.preview-result { margin: var(--space-3) 0 0; padding-left: var(--space-6); }

/* SLA clocks on a ticket (phase 1 step 5): one line per promise, its badge, then when. */
.sla-clocks ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-3); }
.sla-clocks li { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-2); }
.sla-clocks .sla-metric { font-weight: 600; }
.sla-clocks li .cell-sub { flex-basis: 100%; }
.sla-clocks .sla-policy { color: var(--color-neutral-400); font-size: 13px; margin-top: 0; }
.queue-table td[data-label="SLA"] .badge + .due-time { display: block; margin-top: 2px; }

/* Canned replies (phase 1 step 6): the editor stacks like the other settings forms. */
.canned-editor { display: flex; flex-direction: column; gap: var(--space-2); max-width: 760px; margin-top: var(--space-6); }
.canned-editor label { color: var(--color-neutral-400); font-size: 13px; margin-top: var(--space-2); }
.canned-editor textarea { min-height: 180px; }

/* Notifications (phase 1 step 7): one full-width button per item; unread ones carry the accent line. */
.notification-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-2); }
.notification-list li { border-left: 2px solid var(--color-divider); border-radius: var(--radius-sm); }
.notification-list li.is-unread { border-left-color: var(--color-accent); }
.notification-list .notification { width: 100%; display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-1); padding: var(--space-4); border: 0; white-space: normal; text-align: left; }
.notification-list .notification-title { display: flex; align-items: center; gap: var(--space-2); }
.notification-list .notification-body { color: var(--color-neutral-400); font-weight: 400; }

/* The live desk (phase 1 step 7): who else is here, and a change that arrived while typing. */
.presence-notice { margin: 0; color: var(--color-neutral-400); font-size: 13px; }
.live-change { display: flex; align-items: center; gap: var(--space-3); padding: var(--space-3) var(--space-4); border-left: 2px solid var(--color-accent); background: var(--color-surface); border-radius: var(--radius-sm); }

/* Away (phase 1 step 8). */
.away-note { display: block; }
.away-form { display: flex; flex-direction: column; gap: var(--space-2); }
.away-form label { color: var(--color-neutral-400); font-size: 13px; margin-top: var(--space-2); }

/* The queue (phase 1 step 3): a view and, for By product, a product on the left; the number finder on the right. */
.queue-toolbar { display: flex; flex-wrap: wrap; align-items: end; gap: var(--space-4); margin-bottom: var(--space-6); }
.queue-choice { display: flex; flex-direction: column; gap: var(--space-2); min-width: 240px; }
.queue-choice label { color: var(--color-neutral-400); font-size: 13px; }
/* In the toolbar the finder's label sits above its box, like the view picker's, not squeezed beside it. */
.queue-toolbar .ticket-finder { margin-left: auto; flex-wrap: wrap; column-gap: var(--space-3); row-gap: var(--space-2); }
.queue-toolbar .ticket-finder label { flex-basis: 100%; }
.queue-toolbar .ticket-finder input { width: 10rem; }
.queue-pager { display: flex; align-items: center; justify-content: flex-end; gap: var(--space-3); margin-top: var(--space-4); color: var(--color-neutral-400); font-size: 13px; }
.priority-panel { display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-3); margin-top: var(--space-4); }
/* Daybook's row link: the name in a row-click grid is a real link, quiet until hovered. */
.row-link { font-weight: var(--font-heading-weight); color: var(--color-text); border-bottom: 0; }
.row-link:hover { color: var(--color-accent); border-bottom: 0; }
.cell-sub { color: var(--color-neutral-400); font-size: 12.5px; }
.queue-table .badge + .due-time { display: block; margin-top: 2px; }
.ticket-finder { display: flex; align-items: end; gap: var(--space-3); }
.ticket-finder label, .ticket-form label, .field-editor label, .composer label, .composer .composer-label, .status-control label, .control-label { color: var(--color-neutral-400); font-size: 13px; }
.ticket-header { display: flex; align-items: center; justify-content: space-between; gap: var(--space-6); padding: var(--space-6); border-bottom: 1px solid var(--color-divider); background: var(--color-surface); }
.ticket-header h2 { margin: var(--space-2) 0 0; }
.ticket-layout { display: grid; grid-template-columns: minmax(0, 1fr) minmax(260px, 340px); gap: var(--space-6); align-items: start; }
.ticket-conversation, .ticket-side, .ticket-fields, .sla-clocks, .status-control, .composer, .field-editor, .ticket-form { display: flex; flex-direction: column; gap: var(--space-3); }
.ticket-side { gap: var(--space-6); }
.ticket-fields, .sla-clocks, .status-control, .composer { padding: var(--space-6); border-radius: var(--radius-md); background: var(--color-surface); }
.ticket-fields h3, .status-control h3 { margin: 0; }
.ticket-fields dl, .requester-panel dl, .account-facts { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: var(--space-2) var(--space-4); margin: 0; }
.ticket-fields dt, .requester-panel dt, .account-facts dt { color: var(--color-neutral-400); }
.ticket-fields dd, .requester-panel dd, .account-facts dd { margin: 0; overflow-wrap: anywhere; }
/* Not `.timeline`: the house activity list already owns that name. */
.ticket-timeline { display: flex; flex-direction: column; gap: var(--space-4); }
.timeline-entry { padding: var(--space-6); border-left: 2px solid var(--color-divider); border-radius: var(--radius-sm); background: var(--color-surface); }
.timeline-entry header { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-2) var(--space-4); color: var(--color-neutral-400); font-size: 13px; }
.timeline-entry header time { margin-left: auto; }
.timeline-entry p { margin-bottom: 0; white-space: pre-wrap; }
/* What an entry is, told by weight rather than colour: a reply the customer was given carries the accent
   line, what the customer said a solid neutral one, and an internal note only a dashed hairline. */
.timeline-kind { font-weight: 600; color: var(--color-text); }
.timeline-entry.is-reply { border-left-color: var(--color-accent); }
.timeline-entry.is-customer { border-left-color: var(--color-neutral-400); }
.timeline-entry.is-note { border-left-style: dashed; }
.composer textarea, .ticket-form textarea { min-height: 130px; }
.ticket-form-section { width: 100%; max-width: 900px; }
.ticket-form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-4); }
.ticket-form-grid > div { display: flex; flex-direction: column; gap: var(--space-2); }
.ticket-form fieldset { margin: 0; padding: 0; border: 0; }
.ticket-form legend { margin-bottom: var(--space-2); color: var(--color-neutral-400); font-size: 13px; }
@media (max-width: 760px) {
    .ticket-layout, .ticket-form-grid { grid-template-columns: 1fr; }
    .ticket-side { order: 2; }
    .ticket-finder { align-items: stretch; flex-direction: column; }
    .queue-toolbar .ticket-finder { margin-left: 0; width: 100%; }
    .queue-toolbar .ticket-finder label { flex-basis: auto; }
    .queue-toolbar .ticket-finder input { width: 100%; }
    .queue-choice { min-width: 0; width: 100%; }
}

/* Accounts and the requester (phase 1 step 10). */
.requester-panel, .site-notes { display: flex; flex-direction: column; gap: var(--space-3); }
.requester-panel { padding: var(--space-6); border-radius: var(--radius-md); background: var(--color-surface); }
.requester-panel h3, .site-notes h4 { margin: 0; }
.requester-panel > button, .site-notes > button { align-self: flex-start; }
.unmatched-note { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); margin: 0; color: var(--color-neutral-400); }
.link-contact { display: flex; flex-direction: column; gap: var(--space-3); padding-top: var(--space-3); border-top: 1px solid var(--color-divider); }
.contact-search, .account-search { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-3); }
.contact-search label, .account-search label { flex-basis: 100%; color: var(--color-neutral-400); font-size: 13px; }
.contact-search input, .account-search input { flex: 1 1 12rem; min-width: 0; }
.contact-results { flex-direction: column; align-items: stretch; }
.contact-results button { justify-content: flex-start; text-align: left; white-space: normal; min-width: 0; }
.site-notes { margin-top: var(--space-4); }
.site-notes label { color: var(--color-neutral-400); font-size: 13px; }
.site-note-list { display: grid; gap: var(--space-3); margin: 0; padding: 0; list-style: none; }
.site-note-list li { display: flex; flex-direction: column; gap: var(--space-2); padding: var(--space-3) var(--space-4); border: 1px solid var(--color-divider); border-radius: var(--radius-md); }
.site-note-list p { margin: 0; }
.site-note-body { white-space: pre-wrap; overflow-wrap: anywhere; }
.note-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.note-warning { margin: 0; padding: var(--space-2) var(--space-3); border-left: 2px solid var(--color-accent); color: var(--color-accent-200); }
.note-warning:empty { display: none; }
.held-products { display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-4); margin: var(--space-2) 0 0; padding: 0; list-style: none; }
.held-products .badge { margin-left: var(--space-2); }
.account-toolbar { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: flex-end; gap: var(--space-4); margin-bottom: var(--space-4); }
.sync-now { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3); }
.account-tickets-heading { margin: var(--space-4) 0 var(--space-2); font-size: 15px; }

/* The account's standing across a ticket or account (phase 1 step 11). ON STOP is the filled critical
   weight, overdue the accent outline, and a clear account a hairline: weight, never a new hue. */
.status-strip { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-4); padding: var(--space-3) var(--space-4); border: 1px solid var(--color-divider); border-radius: var(--radius-md); }
.status-strip-on-stop { border-color: var(--color-accent-800); background: var(--color-accent-800); color: var(--color-accent-100); }
.status-strip-overdue { border-color: var(--color-accent); color: var(--color-accent-200); }
.status-strip-clear { color: var(--color-neutral-400); }
.status-headline { letter-spacing: 0.06em; }
.status-strip-on-stop .status-headline { font-size: 15px; }
.status-detail { flex: 1 1 18rem; min-width: 0; overflow-wrap: anywhere; }
.status-asof { font-size: 12.5px; opacity: 0.8; }

/* Development requests (phase 1 step 12). */
.request-form { display: grid; gap: var(--space-3); }
.request-form label, .request-form .control-label { color: var(--color-neutral-400); font-size: 13px; }
.request-form .primary { justify-self: start; margin-top: var(--space-3); }
.request-form .check-label { display: flex; align-items: center; gap: var(--space-2); color: var(--color-text); font-size: 14px; }
.development-request-action { align-self: flex-start; }
/* A link that goes somewhere but looks like the action it is, as a button would: the same outline,
   the primary one in the accent. Without this, "Show my tickets" and these read as plain links. */
a.button { display: inline-flex; align-items: center; justify-content: center; gap: 6px; font-family: var(--font-heading); font-weight: var(--font-heading-weight); font-size: 14px; line-height: 1.2; color: var(--color-text); border: 1px solid var(--color-divider); padding: var(--space-2) calc(var(--space-3) * 1.2); border-radius: var(--radius-md); text-decoration: none; white-space: nowrap; }
a.button:hover { color: var(--color-text); border-color: var(--color-divider); background: color-mix(in srgb, var(--color-text) 7%, transparent); }
a.button.primary { color: var(--color-accent); border-color: var(--color-accent); }
a.button.primary:hover { color: var(--color-accent-300); border-color: var(--color-accent); background: color-mix(in srgb, var(--color-accent) 12%, transparent); }

/* Creating a contact in Daybook from the link flow (phase 1 step 13). */
.create-contact { display: flex; flex-direction: column; gap: var(--space-2); }
.create-contact h4 { margin: 0; }
.create-contact label { color: var(--color-neutral-400); font-size: 13px; }
.create-contact-names { display: grid; grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr)); gap: var(--space-2) var(--space-3); }
.create-contact-names input { width: 100%; min-width: 0; }

/* Log a request (phase 1 step 14): on every screen, in the page header on a desk and along the bottom on a
   phone, where a thumb reaches it. */
.page-header-actions { display: flex; align-items: center; gap: var(--space-3); margin-left: auto; }
.log-request .request-form { max-width: 640px; }
.request-files { margin: 0; padding-left: var(--space-4); }
.chosen-customer { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); margin: 0; }
.ticket-attachments { padding: var(--space-6); border-radius: var(--radius-md); background: var(--color-surface); }
.ticket-attachments h3 { margin: 0 0 var(--space-2); }
.ticket-attachments ul { display: grid; gap: var(--space-2); margin: 0; padding: 0; list-style: none; }
.ticket-attachments button.link { padding-inline: 0; white-space: normal; text-align: left; overflow-wrap: anywhere; }
@media (max-width: 760px) {
    /* Beside the rail, not over it: the fold button and the account pill sit at the rail's foot. */
    .log-request-button { position: fixed; bottom: 12px; left: calc(var(--nav-rail-width) + 16px); right: 16px; z-index: 30; background: var(--color-bg); }
    .content { padding-bottom: 72px; }

    /* With the menu open over the page it belongs to the dimmed page, under the scrim (11) and the menu (12). */
    [data-nav="drawer"] .log-request-button { z-index: 10; }
}

/* One search box (phase 1 step 15). */
.header-search input { width: 12rem; min-width: 0; }
.search-heading { margin: var(--space-6) 0 var(--space-2); font-size: 16px; }
.search-list { display: grid; gap: var(--space-2); margin: 0; padding: 0; list-style: none; }
@media (max-width: 760px) { .header-search { flex: 1 1 10rem; } .header-search input { width: 100%; } }
/* Leave without saving? Fixed in view wherever the person clicked, and above the phone's Log a request bar. */
.leave-panel { position: fixed; left: 50%; bottom: var(--space-6); z-index: 70; width: min(480px, calc(100vw - 32px)); margin: 0; transform: translateX(-50%); box-shadow: var(--shadow-md); }
.leave-panel p { margin: 0 0 var(--space-3); }
.composer-draft { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3); min-height: 32px; }
.composer-draft .confirm-panel { margin-top: 0; padding: var(--space-4); }
/* Merge and split (phase 1 step 17). */
.ticket-header-actions { display: flex; align-items: center; gap: var(--space-3); }
.merged-notice { margin: var(--space-4) 0 0; padding: var(--space-4) var(--space-6); border: 1px solid var(--color-divider); border-radius: var(--radius-md); background: var(--color-surface); }
.merge-panel h2 { margin: 0 0 var(--space-3); font-size: 16px; }
.merge-find { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3); }
.merge-find label { flex-basis: 100%; }
.merge-find input { width: 10rem; }
.split-excerpt { max-height: 8rem; overflow: auto; margin: 0 0 var(--space-4); padding-left: var(--space-4); border-left: 2px solid var(--color-divider); white-space: pre-wrap; overflow-wrap: anywhere; }
.timeline-entry header .row-menu { margin-left: var(--space-2); }
/* How this page works: a quiet line under the header, opened on request (after phase 1). */
.page-help { display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-3); margin-bottom: calc(var(--space-4) - var(--space-8)); }
.page-help-toggle { padding: 0; border: 0; background: none; color: var(--color-accent); font-size: 13px; cursor: pointer; }
.page-help-toggle:hover { text-decoration: underline; }
.page-help-body { max-width: 72ch; padding: var(--space-4) var(--space-6); border: 1px solid var(--color-divider); border-radius: var(--radius-md); background: var(--color-surface); color: var(--color-neutral-300); font-size: 14px; }
.page-help-body ul { display: grid; gap: var(--space-2); margin: 0; padding-left: var(--space-6); }
/* A ticket's changes among its messages: one quiet line each, so the conversation still reads first. */
.timeline-tools { display: flex; justify-content: flex-end; }
.timeline-tools button[aria-pressed="true"] { border-color: var(--color-accent); color: var(--color-accent); }
.timeline-change { display: flex; flex-wrap: wrap; gap: var(--space-1) var(--space-3); margin: 0; padding: var(--space-2) var(--space-4); border-left: 1px dotted var(--color-divider); color: var(--color-neutral-400); font-size: 13px; }
.timeline-change time { color: var(--color-neutral-500); font-variant-numeric: tabular-nums; }
.timeline-change strong { color: var(--color-neutral-300); font-weight: 500; }
