/*
 * Tech Dynamix UI kit 3.11.2
 * One stylesheet for every Tech Dynamix web app: base, the app shell
 * (left sidebar, or the top bar), and components. Fonts and tokens come from foundation/. Rules: web-apps/README.md.
 *
 * Load order in <head>:
 *   td-theme.js (sync, before CSS: applies the saved theme before first paint)
 *   td-ui.css
 *   the app's own CSS (app specific rules only, never redefine tokens)
 *   td-shell.js (defer)
 */

/* ===== Fonts (bundled, never loaded from a third party) ===== */
@font-face { font-family: "Bricolage Grotesque"; src: url("../fonts/BricolageGrotesque-Variable.ttf") format("truetype"); font-weight: 200 800; font-stretch: 75% 100%; font-display: swap; }
@font-face { font-family: "IBM Plex Sans"; src: url("../fonts/IBMPlexSans-Variable.ttf") format("truetype"); font-weight: 100 700; font-stretch: 85% 100%; font-style: normal; font-display: swap; }
@font-face { font-family: "IBM Plex Sans"; src: url("../fonts/IBMPlexSans-Italic-Variable.ttf") format("truetype"); font-weight: 100 700; font-stretch: 85% 100%; font-style: italic; font-display: swap; }
@font-face { font-family: "Inter"; src: url("../fonts/Inter-Variable.ttf") format("truetype"); font-weight: 100 900; font-display: swap; }
@font-face { font-family: "IBM Plex Mono"; src: url("../fonts/IBMPlexMono-Regular.ttf") format("truetype"); font-weight: 400; font-display: swap; }
@font-face { font-family: "IBM Plex Mono"; src: url("../fonts/IBMPlexMono-Medium.ttf") format("truetype"); font-weight: 500; font-display: swap; }
@font-face { font-family: "IBM Plex Mono"; src: url("../fonts/IBMPlexMono-SemiBold.ttf") format("truetype"); font-weight: 600; font-display: swap; }
@font-face { font-family: "IBM Plex Mono"; src: url("../fonts/IBMPlexMono-Bold.ttf") format("truetype"); font-weight: 700; font-display: swap; }
/* Brand display faces with narrow, fixed uses. See README, Fonts. */
@font-face { font-family: "Audiowide"; src: url("../fonts/Audiowide-Regular.ttf") format("truetype"); font-weight: 400; font-display: swap; }

/* ===== Tokens: colors, type, spacing (foundation/css/tokens.css, the same in every kit) ===== */
:root {
    /* Brand */
    --orange: #E87722;
    --orange-hover: #F58C3E;
    --orange-press: #CC6415;
    --on-orange: #2A1B0C;
    --peach: #FBEBDD;
    --violet: #7566A0;
    --teal: #1E7A8C;
    --red: #E5484D;
    --outerspace: #4A4947;

    /* Chart series, in this fixed order (never cycled): teal, orange, violet. The brand hues stepped
       to pass chart checks (lightness band, chroma, colorblind separation, 3:1 on the surface).
       Each mode has its own validated steps. More than 3 series: fold the rest into "Other" (--chart-other). */
    --chart-1: #02A2BC; --chart-2: #E6751E; --chart-3: #7661AB; --chart-other: #9AA2AD;

    /* Surfaces and text (light) */
    --page: #F6F7F8;
    --panel: #FFFFFF;
    --sunken: #F6F7F8;
    --input: #FFFFFF;
    --border: #D9DCE1;
    --border-strong: #C3C8CF;
    --text: #1E2024;
    --text-2: #5E6670;
    --shadow-sm: 0 1px 2px rgba(23, 25, 28, 0.06);
    --shadow-md: 0 8px 24px rgba(23, 25, 28, 0.12);

    /* Type */
    --font-head: "Bricolage Grotesque", "IBM Plex Sans", system-ui, sans-serif;
    --font-body: "IBM Plex Sans", system-ui, sans-serif;
    --font-num: "Inter", "IBM Plex Sans", system-ui, sans-serif;
    --font-mono: "IBM Plex Mono", ui-monospace, monospace;
    --font-mark: "Audiowide", "Bricolage Grotesque", sans-serif;   /* typed wordmark only */

    /* Spacing (8px grid) and radii */
    --s1: 8px; --s2: 16px; --s3: 24px; --s4: 32px; --s5: 40px; --s6: 48px;
    --r-btn: 6px; --r-card: 8px; --r-pill: 999px;

    color-scheme: light;
}

/* ===== Dark tokens (foundation/css/tokens-dark.css) =====
   Dark follows the OS unless the person picked Light; web apps set [data-theme] with td-theme.js. */
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        --page: #131519; --panel: #1E2024; --sunken: #17191C; --input: #2A2E34;
        --border: #3A3F47; --border-strong: #4A505A;
        --text: #F6F7F8; --text-2: #9AA2AD;
        --violet: #A396CF; --teal: #5CC3D8; --peach: #3A2A1C;
        --shadow-sm: none; --shadow-md: 0 8px 24px rgba(0, 0, 0, 0.5);
        --chart-1: #10A7BF; --chart-2: #E1721E; --chart-3: #9A86D5; --chart-other: #5E6670;
        color-scheme: dark;
    }
}
:root[data-theme="dark"] {
    --page: #131519; --panel: #1E2024; --sunken: #17191C; --input: #2A2E34;
    --border: #3A3F47; --border-strong: #4A505A;
    --text: #F6F7F8; --text-2: #9AA2AD;
    --violet: #A396CF; --teal: #5CC3D8; --peach: #3A2A1C;
    --shadow-sm: none; --shadow-md: 0 8px 24px rgba(0, 0, 0, 0.5);
    --chart-1: #10A7BF; --chart-2: #E1721E; --chart-3: #9A86D5; --chart-other: #5E6670;
    color-scheme: dark;
}

/* ===== App tokens (web apps only): the shell's sizes and sidebar colors ===== */
:root {
    --sidebar: #FFFFFF;
    --nav-hover: #F1F2F4;
    --nav-active: #FBEBDD;
    --sidebar-w: 264px;
    --rail-w: 68px;
    --pagehead-h: 64px;
    --content-max: 1200px;
}
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) { --sidebar: #17191C; --nav-hover: #23262B; --nav-active: #3A2A1C; }
}
:root[data-theme="dark"] { --sidebar: #17191C; --nav-hover: #23262B; --nav-active: #3A2A1C; }

/* ===== Base ===== */
*, *::before, *::after { box-sizing: border-box; }
/* The hidden attribute always wins over component display rules. */
[hidden] { display: none !important; }
html, body { margin: 0; }
body {
    background: var(--page);
    color: var(--text);
    font: 400 15px/1.5 var(--font-body);
    -webkit-font-smoothing: antialiased;
}
h1, h2, h3, h4 { font-family: var(--font-head); line-height: 1.2; margin: 0 0 0.5em; color: var(--text); }
h1 { font-size: 26px; font-weight: 600; }
h2 { font-size: 19px; font-weight: 600; margin-top: 1.25em; }
h3 { font-size: 16px; font-weight: 600; }
p { margin: 0 0 1em; }
a { color: var(--violet); }
a:hover { text-decoration-thickness: 2px; }
code, pre, .mono { font-family: var(--font-mono); font-size: 0.9em; }
.num { font-family: var(--font-num); font-variant-numeric: tabular-nums; }

/* Text elements, for notes, help pages, and anything written in paragraphs. */
h4 { font-size: 15px; font-weight: 600; }
/* Plain lists only: lists with a class (stepper, tiles, timeline, menus) set their own spacing. */
ul:not([class]), ol:not([class]) { margin: 0 0 1em; padding-left: 1.4em; }
ul:not([class]) > li + li, ol:not([class]) > li + li { margin-top: 0.25em; }
li > ul:not([class]), li > ol:not([class]) { margin: 0.25em 0 0; }
hr { border: 0; border-top: 1px solid var(--border); margin: var(--s3) 0; }
/* Inline code (without a class: .masked keeps its own look) gets a sunken box; a block of code is a pre, which scrolls instead of widening the page. */
:not(pre) > code:not([class]) { padding: 1px 5px; border-radius: 4px; background: var(--sunken); border: 1px solid var(--border); overflow-wrap: anywhere; }
pre { margin: 0 0 1em; padding: 12px; overflow-x: auto; background: var(--sunken); border: 1px solid var(--border); border-radius: var(--r-btn); font-size: 13px; line-height: 1.5; }
/* A key to press: <kbd>Alt</kbd> + <kbd>Up</kbd>. */
kbd {
    display: inline-block; min-width: 1.6em; padding: 1px 6px; text-align: center;
    font: 500 12px/1.5 var(--font-mono); color: var(--text);
    background: var(--panel); border: 1px solid var(--border-strong); border-bottom-width: 2px; border-radius: 4px;
}
/* A match or the words that matter in a longer text: peach, never yellow. */
mark { background: var(--peach); color: var(--text); padding: 0 2px; border-radius: 2px; }
/* What someone wrote, quoted as they wrote it (a message from a form, a ticket's first email). */
blockquote { margin: 0 0 1em; padding: 10px 14px; border-left: 3px solid var(--border-strong); background: var(--sunken); border-radius: 0 var(--r-btn) var(--r-btn) 0; }
blockquote > :last-child { margin-bottom: 0; }
blockquote footer { margin-top: 6px; font-size: 13px; color: var(--text-2); }
abbr[title] { text-decoration: underline dotted; cursor: help; }

.muted { color: var(--text-2); }
.small { font-size: 13px; }
/* A lead paragraph: the one or two sentences that say what a record or page is about, a step bigger than body text. */
.lead { font-size: 17px; line-height: 1.55; color: var(--text); max-width: 80ch; }
.visually-hidden { position: absolute !important; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
:focus-visible { outline: 2px solid var(--violet); outline-offset: 2px; }
/* "Skip to content", added by td-shell.js as the first thing on the page; shows when focused. */
.skip-link {
    position: absolute; left: var(--s2); top: -100px; z-index: 100;
    padding: 10px 16px; border-radius: var(--r-btn);
    background: var(--orange); color: var(--on-orange); font-weight: 600; text-decoration: none;
}
.skip-link:focus { top: var(--s2); }
main[tabindex="-1"]:focus { outline: none; }

/* ===== Brand pieces (foundation/css/brand.css, the same in every kit): IDs, wordmark, icons, marks, tagline ===== */
/* System IDs: job numbers, asset tags, certificate numbers (ITAD-2610001, ITAD-2610001-001).
   IBM Plex Mono like .mono, kept on one line, with a large size for a single ID on a page. */
.code-id { font-family: var(--font-mono); font-weight: 500; font-size: 0.9em; white-space: nowrap; }
.code-id-lg { font-size: 22px; font-weight: 600; }

/* Typed wordmark, matching techdynamix.com. Only where a logo file cannot be used.
   Markup: <span class="wordmark">Tech<span class="dx">Dynamix</span></span> */
.wordmark { font-family: var(--font-mark); font-weight: 400; line-height: 1; letter-spacing: 0.01em; white-space: nowrap; color: var(--outerspace); }
.wordmark .dx { color: var(--orange); }

/* Icons: solid Material Design Icons from td-icons.svg, filled with the text color. */
.icon { width: 20px; height: 20px; flex: 0 0 auto; fill: currentColor; stroke: none; }
.icon-sm { width: 16px; height: 16px; }
/* Something running right now (loader-circle): turns once a second. Holds still for reduced motion. */
.icon-spin { animation: td-spin 1s linear infinite; }
@keyframes td-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .icon-spin { animation: none; } }
/* Brand marks (td-brands.svg): one color, solid, taking the text color like the icons.
   Same sizes as icons. Only to name a product or integration, always next to its name. */
.brand-mark { width: 20px; height: 20px; flex: 0 0 auto; fill: currentColor; stroke: none; vertical-align: -0.25em; }
.brand-mark-sm { width: 16px; height: 16px; vertical-align: -0.2em; }
/* US state outlines (td-states.svg): one solid shape in the text color, sized like the icons. */
.state-mark { width: 20px; height: 20px; flex: 0 0 auto; fill: currentColor; stroke: none; vertical-align: -0.25em; }
.state-mark-sm { width: 16px; height: 16px; vertical-align: -0.2em; }

/* Tagline: IT That's Refreshingly Human, with the orange double underline under "Refreshingly Human".
   Markup: <p class="tagline"><span class="tagline-a">IT That's</span> <span class="tagline-b">Refreshingly Human</span></p> */
.tagline { margin: 0; font: 600 15px/1.4 var(--font-head); }
.tagline-a { color: #000000; }
/* Brand double underline: hand drawn orange strokes. */
.tagline-b {
    color: var(--orange); white-space: nowrap; padding-bottom: 0.4em;
    background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 300 20' preserveAspectRatio='none'%3E%3Cg fill='none' stroke='%23E87722' stroke-linecap='round'%3E%3Cpath d='M5,7.5 C72,2.6 160,2.8 232,5.8 C263,7.2 283,9.4 295,7.6' stroke-width='4.6'/%3E%3Cpath d='M14,16 C78,11.4 168,10.9 236,13 C262,13.8 278,15.6 288,14.5' stroke-width='3' stroke-opacity='.72'/%3E%3C/g%3E%3C/svg%3E") no-repeat left bottom / 100% 0.5em;
}

/* Theme specific images: give both, CSS shows one. */
.only-dark { display: none !important; }
/* ===== Brand pieces in dark (foundation/css/brand-dark.css): the wordmark, tagline, and theme specific images ===== */
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) .wordmark { color: #F6F7F8; } }
:root[data-theme="dark"] .wordmark { color: #F6F7F8; }
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) .tagline-a { color: #FFFFFF; } }
:root[data-theme="dark"] .tagline-a { color: #FFFFFF; }
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .only-light { display: none !important; }
    :root:not([data-theme="light"]) .only-dark { display: revert !important; }
}
:root[data-theme="dark"] .only-light { display: none !important; }
:root[data-theme="dark"] .only-dark { display: revert !important; }
/* Inside a badge or status chip the mark matches the text: <span class="badge"><svg class="brand-mark">...</svg>Gorelo</span> */
.badge .brand-mark, .status .brand-mark { width: 1.15em; height: 1.15em; margin-right: 4px; vertical-align: -0.2em; }
/* Social links: a row of icon only links to Tech Dynamix profiles, each with an aria-label.
   <ul class="social-links"><li><a href="..." aria-label="Tech Dynamix on LinkedIn" title="LinkedIn"><svg class="brand-mark">...</svg></a></li></ul> */
.social-links { display: flex; flex-wrap: wrap; gap: 4px; list-style: none; margin: 0; padding: 0; }
.social-links a { display: inline-grid; place-items: center; width: 36px; height: 36px; border-radius: var(--r-btn); color: var(--text-2); }
.social-links a:hover { background: var(--nav-hover); color: var(--text); }


/* ===== App shell: left sidebar + main ===== */
.shell { display: flex; min-height: 100vh; }

.sidebar {
    position: sticky; top: 0;
    width: var(--sidebar-w); height: 100vh; flex: 0 0 auto;
    display: flex; flex-direction: column;
    background: var(--sidebar);
    border-right: 1px solid var(--border);
    transition: width 160ms ease;
    z-index: 30;
}
.sidebar-brand {
    display: flex; align-items: center; gap: 10px;
    height: var(--pagehead-h); padding: 0 var(--s2);
    border-bottom: 1px solid var(--border);
    text-decoration: none; color: var(--text);
}
/* .brand-logo and .brand-icon are wrappers holding a light and a dark image (.only-light, .only-dark). */
.sidebar-brand .brand-logo { display: flex; flex: 0 0 auto; }
.sidebar-brand .brand-logo img { height: 20px; width: auto; display: block; }
.sidebar-brand .brand-icon { display: none; }
.sidebar-brand .brand-icon img { height: 32px; width: 32px; display: block; }
.sidebar-brand .brand-app {
    font: 600 15px/1 var(--font-head); color: var(--text-2);
    padding-left: 10px; border-left: 1px solid var(--border); white-space: nowrap;
    min-width: 0; overflow: hidden; text-overflow: ellipsis;
}
/* The app's version under its name: <span class="brand-app">CRM<span class="brand-version">v1.2.0</span></span>. */
.sidebar-brand .brand-app:has(> .brand-version) { display: flex; flex-direction: column; gap: 4px; }
.sidebar-brand .brand-version { font: 500 11px/1 var(--font-num); font-variant-numeric: tabular-nums; color: var(--text-2); opacity: 0.85; }

.nav { flex: 1; overflow-y: auto; padding: var(--s2) 12px; }
.nav-group + .nav-group { margin-top: var(--s2); }
.nav-label {
    font: 600 11px/1 var(--font-body); letter-spacing: 0.08em; text-transform: uppercase;
    color: var(--text-2); padding: 0 12px; margin: 0 0 6px;
}
.nav-item {
    position: relative;
    display: flex; align-items: center; gap: 12px;
    height: 38px; padding: 0 12px; margin: 2px 0;
    border-radius: var(--r-btn);
    color: var(--text); text-decoration: none; font-weight: 500;
    white-space: nowrap;
}
.nav-item .icon { color: var(--text-2); }
.nav-item:hover { background: var(--nav-hover); }
.nav-item[aria-current="page"] { background: var(--nav-active); font-weight: 600; }
.nav-item[aria-current="page"] .icon { color: var(--orange); }
.nav-item[aria-current="page"]::before {
    content: ""; position: absolute; left: -12px; top: 8px; bottom: 8px;
    width: 3px; border-radius: 0 3px 3px 0; background: var(--orange);
}
.nav-count {
    margin-left: auto; font: 600 12px/1 var(--font-num);
    padding: 3px 7px; border-radius: var(--r-pill);
    background: var(--sunken); color: var(--text-2); border: 1px solid var(--border);
}

.sidebar-foot { border-top: 1px solid var(--border); padding: 12px; display: flex; flex-direction: column; gap: 4px; }
button.nav-item { width: 100%; border: 0; background: none; font: inherit; color: var(--text-2); cursor: pointer; text-align: left; }
.collapse-btn .icon { color: var(--text-2); }

/* User menu: a details element, so it works before JS loads. */
.user-menu { position: relative; }
.user-menu > summary {
    list-style: none; cursor: pointer;
    display: flex; align-items: center; gap: 10px;
    padding: 8px; border-radius: var(--r-btn);
}
.user-menu > summary::-webkit-details-marker { display: none; }
.user-menu > summary:hover, .user-menu[open] > summary { background: var(--nav-hover); }
.avatar {
    width: 32px; height: 32px; flex: 0 0 auto; border-radius: 50%;
    display: inline-grid; place-items: center;
    background: var(--orange); color: var(--on-orange);
    font: 700 13px/1 var(--font-body);
}
/* A profile photo in place of initials: <img class="avatar" src="..." alt="">. .avatar-sm for lists. */
img.avatar { object-fit: cover; background: var(--sunken); }
.avatar-sm { width: 24px; height: 24px; font-size: 10px; vertical-align: middle; margin-right: var(--s1); }
.user-meta { min-width: 0; display: flex; flex-direction: column; line-height: 1.25; }
.user-name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.user-role { font-size: 12px; color: var(--text-2); text-transform: capitalize; }
.user-menu .chev { margin-left: auto; color: var(--text-2); }

.menu {
    position: absolute; left: 0; right: 0; bottom: calc(100% + 6px);
    min-width: 220px;
    background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-card);
    box-shadow: var(--shadow-md); padding: 6px; z-index: 40;
}
.menu-item {
    display: flex; align-items: center; gap: 10px; width: 100%;
    padding: 8px 10px; border: 0; border-radius: var(--r-btn);
    background: none; color: var(--text); font: inherit; text-align: left; text-decoration: none; cursor: pointer;
}
.menu-item:hover { background: var(--nav-hover); }
.menu-item .icon { color: var(--text-2); }
.menu-sep { height: 1px; background: var(--border); margin: 6px 0; }
.menu-label { font-size: 12px; color: var(--text-2); padding: 4px 10px; }

/* Theme picker: three segmented buttons. */
.theme-switch { display: flex; gap: 4px; padding: 2px 4px 4px; }
.theme-switch button {
    flex: 1; display: inline-flex; align-items: center; justify-content: center; gap: 6px;
    padding: 6px 4px; border: 1px solid var(--border); border-radius: var(--r-btn);
    background: none; color: var(--text-2); font: 500 12px/1 var(--font-body); cursor: pointer;
}
.theme-switch button[aria-pressed="true"] { border-color: var(--orange); color: var(--text); background: var(--nav-active); }
/* The same buttons on a page (Your account): no wider than they need. */
.field > .theme-switch { max-width: 360px; padding: 0; }

/* Main column */
.main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.pagehead {
    position: sticky; top: 0; z-index: 20;
    display: flex; align-items: center; gap: var(--s2);
    min-height: var(--pagehead-h); padding: 10px var(--s4);
    background: color-mix(in srgb, var(--page) 88%, transparent);
    -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px);
    border-bottom: 1px solid var(--border);
}
.pagehead h1 { font-size: 20px; margin: 0; }
/* The crumbs line (13px x 1.4) and the title (20px x 1.2) with the padding and border come to 63px, under the 64px
   minimum, so the header's bottom border lines up with the sidebar brand row's (exactly 64px) at the corner. */
.pagehead .crumbs { font-size: 13px; line-height: 1.4; color: var(--text-2); }
.pagehead .crumbs a { color: var(--text-2); text-decoration: none; }
.pagehead .crumbs a:hover { color: var(--text); text-decoration: underline; }
.pagehead-title { min-width: 0; }
.pagehead-actions { margin-left: auto; display: flex; gap: var(--s1); align-items: center; flex-wrap: wrap; }
.pagehead .menu-toggle { display: none; }

.content { width: 100%; max-width: var(--content-max); padding: var(--s4); }
.content > * + * { margin-top: var(--s3); }
/* Boards and other wide views use the whole width instead of stopping at 1200px. */
.content.content-wide { max-width: none; }
/* Full width, chosen by the person: a [data-content-width] button in the page header of a list page with a wide table.
   The choice (html.content-full, remembered per browser) applies on every page that has the button and nowhere else,
   so forms and records keep the standard width. The button only shows when the window is wide enough for it to help. */
html.content-full:has([data-content-width]) .content { max-width: none; }
[data-content-width], .btn[data-content-width] { display: none; }   /* .btn alone would show it */
@media (min-width: 1536px) { .btn[data-content-width] { display: inline-flex; } }
@media (min-width: 1340px) { html.sidebar-collapsed .btn[data-content-width] { display: inline-flex; } }
.btn[data-content-width][aria-pressed="true"] { border-color: var(--orange); background: var(--nav-active); }

/* Collapsed rail (desktop only): icons only, labels as tooltips via title.
   Scoped to wide screens so the phone drawer always shows full labels. */
@media (min-width: 901px) {
    html.sidebar-collapsed .sidebar { width: var(--rail-w); }
    html.sidebar-collapsed .sidebar-brand { justify-content: center; padding: 0; }
    html.sidebar-collapsed .sidebar-brand .brand-logo,
    html.sidebar-collapsed .sidebar-brand .brand-app,
    html.sidebar-collapsed .nav-label,
    html.sidebar-collapsed .nav-item span,
    html.sidebar-collapsed .user-meta,
    html.sidebar-collapsed .user-menu .chev { display: none; }
    html.sidebar-collapsed .sidebar-brand .brand-icon { display: flex; }
    html.sidebar-collapsed .nav { padding: var(--s2) 10px; }
    html.sidebar-collapsed .nav-item { justify-content: center; padding: 0; }
    html.sidebar-collapsed .nav-item[aria-current="page"]::before { left: -10px; }
    html.sidebar-collapsed .user-menu > summary { justify-content: center; padding: 6px 0; }
    html.sidebar-collapsed .menu { left: 0; right: auto; }
    html.sidebar-collapsed .nav-group + .nav-group { border-top: 1px solid var(--border); padding-top: var(--s1); }
}

/* Phones and small tablets: sidebar becomes a slide out drawer. */
.scrim { display: none; }
@media (max-width: 900px) {
    .sidebar {
        position: fixed; left: 0; top: 0; bottom: 0;
        width: min(300px, 86vw);
        transform: translateX(-100%);
        transition: transform 180ms ease;
        box-shadow: var(--shadow-md);
    }
    html.nav-open .sidebar { transform: none; }
    html.nav-open .scrim { display: block; position: fixed; inset: 0; background: rgba(0, 0, 0, 0.45); z-index: 25; }
    html.nav-open { overflow: hidden; }
    .collapse-btn { display: none !important; }
    .pagehead .menu-toggle { display: inline-grid; }
    .pagehead { padding: 10px var(--s2); }
    .content { padding: var(--s2); }
}
@media (prefers-reduced-motion: reduce) { .sidebar { transition: none; } }

/* ===== App shell: top bar (the alternative to the sidebar) =====
   For apps with a few top level sections (about seven at most), like the start page: the logo and app name,
   the sections as tabs across the top, and the actions and user menu on the right. The content is centered.
   <div class="shell shell-top"><header class="topbar"><div class="topbar-row">
       .topbar-brand, nav.topnav > a.topnav-item (or details.dropdown > summary.topnav-item), .topbar-actions
   </div></header><div class="main"> .pagehead, main.content </div></div>
   Phones and small tablets: the tabs move to their own row under the logo and scroll sideways. No drawer. */
.shell-top { flex-direction: column; --topbar-h: 60px; }
/* Everything lines up with the centered content: the same left and right edges as .content's text. */
.shell-top .topbar-row, .shell-top .pagehead { padding-inline: max(var(--s4), calc((100% - var(--content-max)) / 2 + var(--s4))); }
.shell-top .content { margin-inline: auto; }
html.content-full:has([data-content-width]) .shell-top :is(.topbar-row, .pagehead) { padding-inline: var(--s4); }
.topbar {
    position: sticky; top: 0; z-index: 30;
    background: var(--sidebar); border-bottom: 1px solid var(--border);
}
.topbar-row { display: flex; align-items: center; gap: var(--s3); min-height: var(--topbar-h); }
.topbar-brand { display: flex; align-items: center; gap: 10px; flex: 0 0 auto; text-decoration: none; color: var(--text); }
.topbar-brand .brand-logo { display: flex; }
.topbar-brand .brand-logo img { height: 20px; width: auto; display: block; }
.topbar-brand .brand-app {
    font: 600 15px/1 var(--font-head); color: var(--text-2);
    padding-left: 10px; border-left: 1px solid var(--border); white-space: nowrap;
}
/* The tabs: an icon (optional) and a short label; the current one has the orange underline. */
.topnav { display: flex; align-items: stretch; gap: 2px; align-self: stretch; min-width: 0; }
.topnav-item {
    position: relative; display: inline-flex; align-items: center; gap: 8px;
    padding: 0 12px; border: 0; background: none; cursor: pointer;
    color: var(--text-2); font: 500 15px/1 var(--font-body); text-decoration: none; white-space: nowrap;
}
.topnav-item .icon { color: var(--text-2); }
.topnav-item:hover { color: var(--text); background: var(--nav-hover); }
.topnav-item[aria-current="page"] { color: var(--text); font-weight: 600; }
.topnav-item[aria-current="page"] .icon { color: var(--orange); }
.topnav-item[aria-current="page"]::after {
    content: ""; position: absolute; left: 8px; right: 8px; bottom: -1px; height: 3px;
    border-radius: 3px 3px 0 0; background: var(--orange);
}
.topnav-item .nav-count { margin-left: 2px; }
/* A tab that opens a menu: <details class="dropdown"><summary class="topnav-item">Reports <svg class="icon nav-chev">... */
.topnav .dropdown { display: flex; }
.topnav .dropdown > .topnav-item { list-style: none; }
.topnav .dropdown > .menu { left: 0; right: auto; }
.topnav .dropdown:has(.menu [aria-current="page"]) > .topnav-item { color: var(--text); font-weight: 600; }
.topnav-item .nav-chev { width: 16px; height: 16px; transition: transform 120ms ease; }
.topnav .dropdown[open] > .topnav-item .nav-chev { transform: rotate(180deg); }
.topbar-actions { margin-left: auto; display: flex; align-items: center; gap: var(--s1); flex: 0 0 auto; }
/* The user menu sits in the top right and opens downward. */
.topbar .user-menu > summary { padding: 6px; }
.topbar .user-menu .menu { top: calc(100% + 6px); bottom: auto; left: auto; right: 0; }
.topbar .user-meta, .topbar .user-menu .chev { display: none; }
@media (min-width: 1100px) { .topbar .user-meta { display: flex; } .topbar .user-menu .chev { display: inline; } }
.shell-top .content > [id] { scroll-margin-top: calc(var(--topbar-h) + var(--s2)); }
/* The page header scrolls away under the top bar; only the top bar stays. */
.shell-top .pagehead { position: static; background: none; -webkit-backdrop-filter: none; backdrop-filter: none; }
@media (max-width: 900px) {
    .topbar-row { flex-wrap: wrap; row-gap: 0; }
    .shell-top .topbar-row, .shell-top .pagehead { padding-inline: var(--s2); }
    .topbar-brand { min-height: 56px; }
    .topnav {
        order: 3; flex: 1 0 100%; height: 48px; margin-inline: calc(-1 * var(--s2)); padding-inline: var(--s1);
        overflow-x: auto; scrollbar-width: none; border-top: 1px solid var(--border);
    }
    .topnav::-webkit-scrollbar { display: none; }
    .topnav-item { padding: 0 10px; }
}
@media (max-width: 480px) { .topbar-brand .brand-app { display: none; } }
@media (prefers-reduced-motion: reduce) { .topnav-item .nav-chev { transition: none; } }

/* ===== Launcher tiles: a grid of apps or links, each an icon, a name, and one line about it ===== */
.tiles { display: grid; gap: var(--s2); grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); list-style: none; margin: 0; padding: 0; }
.tile {
    display: flex; align-items: flex-start; gap: 12px; height: 100%; padding: var(--s2);
    background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-card); box-shadow: var(--shadow-sm);
    color: var(--text); text-decoration: none;
}
a.tile:hover { border-color: var(--orange); }
.tile-icon { display: inline-grid; place-items: center; width: 44px; height: 44px; flex: 0 0 auto; border-radius: var(--r-card); background: var(--nav-active); color: var(--orange); }
.tile-icon .icon, .tile-icon .brand-mark { width: 24px; height: 24px; }
.tile-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.tile-name { font: 600 16px/1.25 var(--font-head); }
.tile-text { font-size: 13px; color: var(--text-2); }
.tile .status { align-self: flex-start; margin-top: 4px; }

/* ===== Components ===== */

/* Buttons. One primary (orange) action per view. */
.btn {
    display: inline-flex; align-items: center; justify-content: center; gap: var(--s1);
    font: 600 14px/1 var(--font-body);
    padding: 10px 18px; border-radius: var(--r-btn); border: 1px solid transparent;
    cursor: pointer; text-decoration: none; white-space: nowrap;
}
/* The icon overhangs the 1em line box evenly, so a button with an icon is as tall as one without. */
.btn .icon { width: 18px; height: 18px; margin-block: calc((1em - 18px) / 2); }
.btn-primary { background: var(--orange); color: var(--on-orange); }
.btn-primary:hover { background: var(--orange-hover); }
.btn-primary:active { background: var(--orange-press); }
.btn-secondary { background: transparent; color: var(--violet); border-color: var(--violet); }
.btn-secondary:hover { background: var(--nav-hover); }
.btn-ghost { background: transparent; color: var(--text); border-color: var(--border); }
.btn-ghost:hover { background: var(--nav-hover); }
.btn-danger { background: transparent; color: var(--red); border-color: var(--red); }
.btn-danger:hover { background: color-mix(in srgb, var(--red) 10%, transparent); }
.btn-sm { padding: 7px 12px; font-size: 13px; }
.btn:disabled, .btn[aria-disabled="true"] { opacity: 0.5; cursor: not-allowed; }
.btn-link { background: none; border: 0; padding: 0; font: inherit; color: var(--violet); text-decoration: underline; cursor: pointer; }
.icon-btn {
    display: inline-grid; place-items: center; width: 36px; height: 36px;
    border: 0; border-radius: var(--r-btn); background: none; color: var(--text-2); cursor: pointer;
}
.icon-btn:hover { background: var(--nav-hover); color: var(--text); }

/* Cards */
.card {
    background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-card);
    padding: var(--s3); box-shadow: var(--shadow-sm);
}
.card > :first-child { margin-top: 0; }
.card > :last-child { margin-bottom: 0; }
.card-narrow { max-width: 480px; }
.card-head { display: flex; align-items: center; justify-content: space-between; gap: var(--s2); flex-wrap: wrap; margin-bottom: var(--s2); }
.card-head h2, .card-head h3 { margin: 0; }
.card-head h2, .card-head h3 { display: inline-flex; align-items: center; gap: var(--s1); }
/* The line under a card's head takes at least two lines, so cards side by side whose descriptions run one line
   and two lines come out the same height (integration and settings cards). Longer text still grows the card. */
.card-head + p { min-height: 3em; min-height: 2lh; }
/* Footer row pinned to the bottom of a card. Cards in a .grid row stretch to the tallest one,
   so footers (Settings, Open) line up across the row without a fixed height. */
.card:has(> .card-foot) { display: flex; flex-direction: column; }
/* Same 8px between buttons as .form-actions, so two small buttons fit side by side in a quarter width card. */
.card-foot { margin-top: auto; padding-top: var(--s2); display: flex; align-items: center; gap: var(--s1); flex-wrap: wrap; }
.stack { display: flex; flex-direction: column; gap: var(--s3); }
.grid { display: grid; gap: var(--s3); grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
/* Wider cards (integrations, people): at least 400px, or the full width on phones, so a title and a status chip fit on one line. */
.grid.grid-wide { grid-template-columns: repeat(auto-fit, minmax(min(400px, 100%), 1fr)); }

/* Stat tile */
.stat { display: flex; flex-direction: column; gap: 4px; }
.stat-label { font-size: 13px; color: var(--text-2); }
.stat-value { font: 600 28px/1.1 var(--font-num); font-variant-numeric: tabular-nums; }
/* A stat tile that links to the list behind it: <a class="card stat" href="..."> */
a.card.stat { text-decoration: none; color: inherit; }
a.card.stat:hover { border-color: var(--orange); }

/* Forms */
.form { display: flex; flex-direction: column; gap: var(--s2); }
/* A form with more under it in the same card (a note box over a timeline, an add row over a table) keeps clear of it. */
.card > .form:not(:last-child) { margin-bottom: var(--s3); }
.field { display: flex; flex-direction: column; gap: 6px; }
.field > span { font-weight: 500; font-size: 14px; }
.field small { color: var(--text-2); }
.row { display: flex; gap: 12px; flex-wrap: wrap; }
.grow { flex: 1 1 200px; }
/* Every text-like input, including date and time pickers, so all fields match. */
input:not([type="checkbox"], [type="radio"], [type="file"], [type="range"], [type="color"], [type="hidden"],
    [type="submit"], [type="button"], [type="reset"], [type="image"]), select, textarea {
    font: inherit; color: var(--text);
    background: var(--input); border: 1px solid var(--border-strong); border-radius: var(--r-btn);
    padding: 9px 11px; width: 100%;
}
/* Date and time pickers: no extra inner padding, so they are the same height as text fields. */
input::-webkit-datetime-edit, input::-webkit-datetime-edit-fields-wrapper { padding: 0; }
input::-webkit-date-and-time-value { text-align: left; min-height: 1.5em; }
/* Selects ignore line-height; match the text field height (1.5 lines + padding + border). */
select { min-height: calc(1.5em + 20px); }
input:focus-visible, select:focus-visible, textarea:focus-visible { outline: 2px solid var(--violet); outline-offset: 0; border-color: var(--violet); }
input[readonly], textarea[readonly] { background: var(--sunken); color: var(--text-2); }
input:disabled, select:disabled, textarea:disabled { background: var(--sunken); color: var(--text-2); opacity: 0.7; cursor: not-allowed; }
/* Field level validation: set aria-invalid on the input, and put the message in .field-error (linked with aria-describedby). */
input[aria-invalid="true"], select[aria-invalid="true"], textarea[aria-invalid="true"] { border-color: var(--red); }
.field-error, .field small.field-error { display: inline-flex; gap: 6px; align-items: center; color: var(--red); font-size: 13px; font-weight: 500; }
fieldset { border: 1px solid var(--border); border-radius: var(--r-card); padding: var(--s2); margin: 0; display: flex; flex-direction: column; gap: 12px; min-width: 0; }
legend { font-family: var(--font-head); font-weight: 600; padding: 0 6px; }
.choice { display: flex; gap: 10px; align-items: flex-start; }
.choice input { margin-top: 4px; accent-color: var(--orange); }
.form-actions { display: flex; gap: var(--s1); flex-wrap: wrap; align-items: center; }
.form-actions + .form-actions { margin-top: 12px; }
/* A button placed directly in a form keeps its natural width (the form is a column). */
.form > .btn, .form > button { align-self: flex-start; }
.row.align-end { align-items: flex-end; }
/* A button beside fields takes the field height (15px text at 1.5 plus 20px padding and border), so tops and bottoms line up. */
.row > .btn:not(.btn-sm), .search-row > .btn:not(.btn-sm) { min-height: calc(15px * 1.5 + 20px); }
/* A button beside a one-time secret takes the secret box's height, and the pair keeps space below it. */
.row:has(> .secret) { align-items: stretch; margin-bottom: var(--s2); }
.row > .secret { margin: 0; display: flex; align-items: center; }
.row:has(> .secret) > .btn { height: auto; }
.inline { display: inline; margin: 0; }

/* Tables */
/* position: relative keeps absolutely placed children (a .visually-hidden "Actions" header) inside the scroll area,
   so a wide table scrolls in its card instead of widening the page on phones. */
.table-wrap { overflow-x: auto; position: relative; }
.table { width: 100%; border-collapse: collapse; font-size: 14px; }
.table th, .table td { text-align: left; padding: 10px 12px; border-bottom: 1px solid var(--border); vertical-align: middle; }
.table th { font-weight: 600; color: var(--text-2); font-size: 12px; text-transform: uppercase; letter-spacing: 0.04em; }
.table tbody tr:hover { background: var(--nav-hover); }
.table tbody tr:last-child > * { border-bottom: 0; }   /* td and row header th alike */
/* Money and quantities: .amount on the th and td of a column lines its figures up on the right.
   Totals go in <tfoot>, row headers on the left: <tr><th scope="row" colspan="3">Subtotal</th><td class="amount">..</td></tr>. */
.table .amount { text-align: right; white-space: nowrap; font-family: var(--font-num); font-variant-numeric: tabular-nums; }
.table thead th.amount { font-family: inherit; }   /* the heading is words: it matches the other headings */
.table tfoot th, .table tfoot td { border-bottom: 0; padding-top: 6px; padding-bottom: 6px; }
.table tfoot th { text-align: right; font-size: 14px; text-transform: none; letter-spacing: 0; font-weight: 500; }
.table tfoot tr:first-child > * { border-top: 2px solid var(--border-strong); padding-top: 10px; }
.table tfoot .total > * { font-size: 16px; font-weight: 700; color: var(--text); }
/* Dense: lists with many columns that people scan (a CRM's contacts, a device list). Tighter cells and one line per
   row: long text stops with an ellipsis (put the full text in title), and the table scrolls sideways in .table-wrap
   when it is wider than its card. td.wrap lets one cell wrap again. Not for forms or records. */
.table.table-dense th, .table.table-dense td { padding: 5px 10px; white-space: nowrap; }
.table.table-dense td { max-width: 280px; overflow: hidden; text-overflow: ellipsis; }
.table.table-dense td.wrap { white-space: normal; max-width: none; }
.table.table-dense .badge, .table.table-dense .status { padding-top: 1px; padding-bottom: 1px; }
/* A left-aligned column right after a right-aligned figure (.amount) gets more room on its left, so the figures don't
   run into the next column's text and the two headings read apart. :where() keeps this from beating .table-stack. */
.table :where(th.amount) + th:not(.amount, [data-sort]), .table :where(td.amount) + td:not(.amount) { padding-left: 32px; }
.table :where(th.amount) + th[data-sort]:not(.amount) .sort-btn { padding-left: 32px; }
.table:where(.table-dense) :where(th.amount) + th:not(.amount, [data-sort]), .table:where(.table-dense) :where(td.amount) + td:not(.amount) { padding-left: 26px; }
.table:where(.table-dense) :where(th.amount) + th[data-sort]:not(.amount) .sort-btn { padding-left: 26px; }
/* Striped: every other row shaded, for long lists read across the row (an audit log). Rows a filter hides do not count. */
.table.table-striped tbody tr:nth-child(even of :not([hidden])) { background: var(--sunken); }
.table.table-striped tbody tr:hover { background: var(--nav-hover); }
/* Bordered: a line around every cell, for grids of figures compared across rows and down columns (a monthly report). */
.table.table-bordered th, .table.table-bordered td,
.table.table-bordered tbody tr:last-child > * { border: 1px solid var(--border); }
.table.table-bordered thead th { background: var(--sunken); }
.table.table-bordered tfoot tr:first-child > * { border-top: 2px solid var(--border-strong); }

/* A table that fills its card edge to edge: .card.card-table. The head, the tools row, and the foot keep the card's
   padding; the table runs to the card's sides, its outer cells lined up with the head. */
.card.card-table { padding: 0; }
.card-table > .card-head, .card-table > .table-tools, .card-table > .table-foot, .card-table > p { margin: 0; padding: var(--s2) var(--s3); }
.card-table > .card-head + p { padding-top: 0; min-height: 0; }
.card-table > .table-wrap { border-top: 1px solid var(--border); }
.card-table > .table-wrap:last-child { border-radius: 0 0 var(--r-card) var(--r-card); }
.card-table .table thead th { background: var(--sunken); }
.card-table .table th:first-child, .card-table .table td:first-child { padding-left: var(--s3); }
.card-table .table th:last-child, .card-table .table td:last-child { padding-right: var(--s3); }
/* Above the table: a search box and filters on the left, how many per page on the right. Labels may be visually hidden. */
.table-tools { display: flex; gap: var(--s1) var(--s2); align-items: center; justify-content: space-between; flex-wrap: wrap; }
.table-tools .field { margin: 0; }
.table-tools .field:has(input[type="search"]) { flex: 0 1 320px; }
/* Below it: what is showing ("1 to 10 of 48 jobs") on the left, paging on the right. */
.table-foot { display: flex; gap: var(--s1) var(--s2); align-items: center; justify-content: space-between; flex-wrap: wrap; border-top: 1px solid var(--border); }
.table-foot .pager { margin-top: 0; }

/* A person or a company in a cell: the avatar, the name, and one short line under it (an email, a job title). */
.cell-person { display: flex; align-items: center; gap: var(--s1); min-width: 0; }
.cell-person .avatar { margin: 0; }
.cell-main { display: flex; flex-direction: column; min-width: 0; line-height: 1.35; }
.cell-sub { color: var(--text-2); font-size: 13px; }
/* Progress in a cell: the bar and its figure side by side. */
.cell-progress { display: flex; align-items: center; gap: var(--s1); min-width: 160px; }
.table .cell-progress progress.progress { flex: 1; margin: 0; }
.cell-progress .num { min-width: 3.5em; text-align: right; font-size: 13px; }

/* Sticky header: a long list in a box of its own (.table-wrap.table-scroll, 480px), its headings stay in view. */
.table-wrap.table-scroll { max-height: 480px; overflow: auto; }
.table-scroll .table thead th { position: sticky; top: 0; z-index: 1; background: var(--panel); box-shadow: inset 0 -1px 0 var(--border); border-bottom: 0; }
.card-table .table-scroll .table thead th { background: var(--sunken); }

/* Groups: each group in its own <tbody>, starting with a heading row (tr.group-head with one th scope="colgroup"). */
.table tr.group-head > th { background: var(--sunken); color: var(--text); font-size: 13px; text-transform: none; letter-spacing: 0; }
.table tr.group-head > th .muted { font-weight: 400; margin-left: var(--s1); }
.table tbody + tbody tr.group-head > th { border-top: 1px solid var(--border); }
.table tbody tr.group-head:hover { background: none; }

/* Expandable rows: a [data-row-toggle] button in the first cell opens the tr.row-more under its row. */
.table td.expand { width: 1%; padding-right: 0; }
.table td.expand .icon-btn { width: 28px; height: 28px; }
.table [data-row-toggle] .icon { transition: transform 0.15s ease; }
.table [data-row-toggle][aria-expanded="true"] .icon { transform: rotate(90deg); }
.table tr.row-more > td { background: var(--sunken); padding-top: var(--s2); padding-bottom: var(--s2); }
.table tbody tr.row-more:hover { background: none; }
.table tr:has(+ tr.row-more:not([hidden])) > td { border-bottom-color: transparent; }
@media (prefers-reduced-motion: reduce) { .table [data-row-toggle] .icon { transition: none; } }

/* Matrix: who can do what, or what each plan includes. A row heading on the left, one column per choice, and a check
   or a dash in each cell, with the word for screen readers. */
.table.table-matrix th, .table.table-matrix td { text-align: center; }
.table.table-matrix th:first-child, .table.table-matrix td:first-child { text-align: left; }
.table.table-matrix tbody th { font-size: 14px; font-weight: 500; color: var(--text); text-transform: none; letter-spacing: 0; }
.table-matrix .matrix-yes { color: var(--teal); }
.table-matrix .matrix-no { color: var(--text-2); }

/* Stacked: under 640px each row becomes a block of label and value pairs, for lists people use on a phone. The first
   cell is the row's title; the labels come from the column headings (td-shell.js copies them into data-label). */
@media (max-width: 640px) {
    .table.table-stack thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
    .table.table-stack, .table.table-stack tbody, .table.table-stack tr, .table.table-stack td { display: block; width: 100%; }
    .table.table-stack tr { padding: var(--s1) 0; border-bottom: 1px solid var(--border); }
    .table.table-stack tbody tr:last-child { border-bottom: 0; }
    .table.table-stack td { position: relative; padding: 4px 0 4px calc(40% + var(--s1)); border: 0; text-align: left; }
    .table.table-stack td::before {
        content: attr(data-label); position: absolute; left: 0; top: 6px; width: 40%;
        color: var(--text-2); font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em;
    }
    .table.table-stack td:first-child, .table.table-stack td[data-label=""] { padding-left: 0; }
    .table.table-stack td:first-child { font-weight: 600; }
    .table.table-stack td:first-child::before, .table.table-stack td[data-label=""]::before { content: none; }
    .table.table-stack .list-actions { justify-content: flex-start; }
}

/* List page helpers: search and filter row above a table, row actions, and paging below it. */
.search-row { display: flex; gap: var(--s1); flex-wrap: wrap; align-items: flex-end; }
.search-row .field { flex: 1 1 260px; }
.list-actions { display: flex; gap: var(--s1); justify-content: flex-end; white-space: nowrap; }
.pager { display: flex; gap: var(--s1); align-items: center; justify-content: flex-end; margin-top: var(--s2); }

/* Record details: label and value pairs in a <dl>. Stacks on phones. */
.details { display: grid; grid-template-columns: max-content 1fr; gap: var(--s1) var(--s3); margin: 0; }
.details dt { color: var(--text-2); font-size: 14px; }
/* Long values with no spaces (hashes, tokens, URLs) break instead of widening the page. */
.details dd { margin: 0; min-width: 0; overflow-wrap: anywhere; }
/* A small button or icon button beside a value (Verify, Copy) sits on the text's line without making the row taller,
   so the label and the value stay level. Tall buttons overhang the line evenly above and below. */
.details dd .btn-sm, .details dd .icon-btn { vertical-align: middle; }
.details dd .btn-sm { margin-block: -5px; }
.details dd .icon-btn { width: 30px; height: 30px; margin-block: -4px; }
@media (max-width: 600px) { .details { grid-template-columns: 1fr; gap: 2px; } .details dd { margin-bottom: var(--s1); } }

/* Change table (audit log, edit history): field, old value struck through, new value. */
.diff { width: 100%; border-collapse: collapse; font-size: 13px; }
.diff th, .diff td { text-align: left; padding: 4px var(--s1); border-bottom: 1px solid var(--border); vertical-align: top; word-break: break-word; }
.diff th { color: var(--text-2); font-weight: 500; width: 22%; }
.diff .was { color: var(--text-2); text-decoration: line-through; }
.diff .now { font-weight: 600; }

/* Photo grid (evidence photos, attachments) with a caption row under each. */
.photo-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 12px; margin-bottom: var(--s2); }
.photo-grid figure { margin: 0; }
.photo-grid img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; border-radius: var(--r-btn); border: 1px solid var(--border); display: block; background: var(--sunken); }
.photo-grid figcaption { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; margin-top: 4px; }

/* Alerts. Color never carries meaning alone: every alert has a text label. */
.alert {
    display: flex; gap: 10px; align-items: flex-start;
    border: 1px solid var(--border); border-left-width: 4px; border-radius: var(--r-btn);
    padding: 10px 12px; margin: 12px 0; background: var(--panel);
}
.alert .icon { margin-top: 1px; }
.alert-label { font-weight: 700; margin-right: 4px; }
.alert-info { border-left-color: var(--violet); } .alert-info .icon { color: var(--violet); }
.alert-ok { border-left-color: var(--teal); } .alert-ok .icon { color: var(--teal); }
.alert-warn { border-left-color: var(--orange); } .alert-warn .icon { color: var(--orange); }
.alert-error { border-left-color: var(--red); } .alert-error .icon, .alert-error .alert-label { color: var(--red); }

/* Status chips. Always a text label; teal for good states, orange for attention, red for failure, neutral otherwise. */
.status {
    display: inline-flex; align-items: center; gap: 6px;
    font: 600 12px/1 var(--font-body); padding: 4px 9px; border-radius: var(--r-pill);
    border: 1px solid var(--border); color: var(--text-2); white-space: nowrap;
}
.status::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
/* A chip with an icon shows the icon in place of the dot. */
.status:has(> .icon)::before { display: none; }
/* The icon overhangs the line evenly, so a chip with an icon is as tall as one without, and as a .badge. */
.status > .icon { margin-block: calc((1em - 16px) / 2); }
/* Chips and badges side by side, or beside text, sit level: centered on the line, not on an icon's bottom edge. */
.status, .badge { vertical-align: middle; }
.status-warn > .icon { color: var(--orange); }
.status-ok { border-color: var(--teal); color: var(--teal); }
.status-warn { border-color: var(--orange); color: var(--text); }
.status-warn::before { background: var(--orange); }
.status-error { border-color: var(--red); color: var(--red); }
.status-info { border-color: var(--violet); color: var(--violet); }

.badge {
    display: inline-block; font: 600 12px/1 var(--font-body);
    padding: 4px 8px; border-radius: var(--r-pill);
    background: var(--sunken); border: 1px solid var(--border); color: var(--text-2);
}

/* A one-time secret shown to the user (temporary password, token). */
.secret {
    font: 500 20px/1.4 var(--font-mono); padding: 14px;
    background: var(--sunken); border: 1px dashed var(--border-strong); border-radius: var(--r-btn);
    word-break: break-all; user-select: all;
}
.log-box {
    font-family: var(--font-mono); font-size: 13px;
    background: var(--sunken); border: 1px solid var(--border); border-radius: var(--r-btn);
    padding: 12px; overflow-x: auto; white-space: pre-wrap; margin: 12px 0;
}
.empty { text-align: center; padding: var(--s5) var(--s2); color: var(--text-2); }
.empty > .icon { width: 32px; height: 32px; margin-bottom: var(--s1); }   /* only the empty state's own icon, not icons in its buttons */
.divider { display: flex; align-items: center; gap: 12px; color: var(--text-2); margin: var(--s2) 0; font-size: 13px; }
.divider::before, .divider::after { content: ""; flex: 1; border-top: 1px solid var(--border); }

/* ===== Charts =====
   Plain inline SVG, no library: examples/analytics.html shows each kind. Rules: README, Charts.
   <figure class="chart"><figcaption><span class="chart-title">..</span><ul class="chart-legend">..</ul></figcaption>
   <div class="chart-scroll"><svg class="chart-svg" viewBox=".." role="img" aria-label=".."> .. </svg></div>
   <details class="chart-table"><summary>Show as a table</summary> .. </details></figure> */
.chart { position: relative; margin: 0; }
.chart figcaption { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: var(--s1) var(--s2); margin-bottom: 12px; }
.chart-title { font: 600 15px/1.3 var(--font-head); }
.chart-legend { display: flex; flex-wrap: wrap; gap: 4px 14px; margin: 0; padding: 0; list-style: none; font-size: 13px; color: var(--text-2); }
.chart-legend li { display: inline-flex; align-items: center; gap: 6px; }
.chart-key { width: 10px; height: 10px; border-radius: 3px; flex: none; }
.chart-key.line { height: 3px; border-radius: 2px; width: 14px; }
/* Chart text is drawn at 12px for the chart's own width, so a chart may only scale a little before its text is
   too small or too big. Three drawing widths, each scaling between 85% and 120%: .chart-sm (viewBox 400 wide,
   for half width cards), .chart-md (640), .chart-lg (880, full width). Narrower than that, the chart scrolls
   sideways in its card, like a table; wider, it stops growing. */
.chart-scroll { overflow-x: auto; position: relative; }
.chart-svg.chart-sm { min-width: 340px; max-width: 480px; }
.chart-svg.chart-md { min-width: 544px; max-width: 768px; }
.chart-svg.chart-lg { min-width: 748px; max-width: 1056px; }
.chart-svg { display: block; width: 100%; height: auto; overflow: visible; font: 400 12px var(--font-num); }
.chart-svg .gridline { stroke: var(--border); stroke-width: 1; shape-rendering: crispEdges; }
.chart-svg .baseline { stroke: var(--border-strong); stroke-width: 1; shape-rendering: crispEdges; }
.chart-svg .tick { fill: var(--text-2); }
.chart-svg .label { fill: var(--text); font-weight: 600; }
.chart-svg .label-in { fill: #FFFFFF; font-weight: 600; }   /* kit-check: allow: inside a filled segment, and only where it fits */
.chart-svg .line { fill: none; stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.chart-svg .area { stroke: none; opacity: 0.1; }
.chart-svg .dot { stroke: var(--panel); stroke-width: 2; }
/* Series color goes on marks only, never on text. */
.chart-svg .s1, .chart-key.s1 { fill: var(--chart-1); stroke: var(--chart-1); background: var(--chart-1); }
.chart-svg .s2, .chart-key.s2 { fill: var(--chart-2); stroke: var(--chart-2); background: var(--chart-2); }
.chart-svg .s3, .chart-key.s3 { fill: var(--chart-3); stroke: var(--chart-3); background: var(--chart-3); }
.chart-svg .so, .chart-key.so { fill: var(--chart-other); stroke: var(--chart-other); background: var(--chart-other); }
.chart-svg .line.s1, .chart-svg .line.s2, .chart-svg .line.s3, .chart-svg .line.so { fill: none; }
.chart-svg .bar { stroke: none; }
.chart-svg .dot.s1, .chart-svg .dot.s2, .chart-svg .dot.s3 { stroke: var(--panel); }
/* Hover and keyboard focus. Each data-tip group holds a hit area bigger than its mark; a column of a
   line chart also holds a crosshair that shows while it is hovered. */
.chart-svg [data-tip] { cursor: default; outline: none; }
.chart-svg .hit { fill: transparent; stroke: none; }
.chart-svg .crosshair { stroke: var(--text-2); stroke-width: 1; opacity: 0; shape-rendering: crispEdges; }
.chart-svg [data-tip]:hover .crosshair, .chart-svg [data-tip]:focus-visible .crosshair { opacity: 0.6; }
.chart-svg [data-tip]:hover .bar, .chart-svg [data-tip]:focus-visible .bar { opacity: 0.8; }
.chart-svg [data-tip]:focus-visible .hit { stroke: var(--violet); stroke-width: 2; }
.chart-tip {
    position: absolute; z-index: 5; pointer-events: none; max-width: 260px;
    padding: 6px 10px; border-radius: var(--r-btn); border: 1px solid var(--border);
    background: var(--panel); color: var(--text); box-shadow: var(--shadow-md);
    font: 500 13px/1.4 var(--font-body); white-space: pre-line;
}
/* ===== Help tips =====
   An info button beside a label or a heading that shows a short explanation on hover, on keyboard focus, and on
   a tap (which pins it open until a tap elsewhere or Escape). The text is also the button's description for
   screen readers. td-shell.js wires it up and keeps the bubble inside the window.
   <span class="tip"><button type="button" class="tip-btn" aria-label="About policy mode">
     <svg class="icon" aria-hidden="true"><use href="...td-icons.svg#i-info"/></svg></button>
     <span class="tip-text">Report only sends the policy without acting on it.</span></span>
   For explanation only. What a person needs to fill a field in correctly, and what a destructive action does,
   stays on the page. No links or buttons inside: the bubble closes as the pointer leaves. */
.tip { position: relative; display: inline-flex; vertical-align: middle; margin-left: 2px; font-weight: 400; text-transform: none; letter-spacing: normal; }
.tip-btn {
    display: inline-grid; place-items: center; width: 22px; height: 22px; padding: 0;
    border: 0; border-radius: 50%; background: none; color: var(--text-2); cursor: help;
}
.tip-btn .icon { width: 16px; height: 16px; }
.tip-btn:hover, .tip.is-open .tip-btn { color: var(--violet); }
.tip-text {
    position: absolute; z-index: 40; left: 50%; bottom: calc(100% + 6px);
    transform: translateX(calc(-50% + var(--tip-shift, 0px)));
    width: max-content; max-width: min(320px, calc(100vw - 32px));
    padding: 8px 12px; border-radius: var(--r-btn); border: 1px solid var(--border);
    background: var(--panel); color: var(--text); box-shadow: var(--shadow-md);
    font: 400 13px/1.45 var(--font-body); text-align: left; white-space: normal;
    visibility: hidden; opacity: 0; transition: opacity 120ms ease; pointer-events: none;
}
/* Placed by td-shell.js against the window, so no card or table that hides its overflow can clip it. */
.tip-text.is-placed { position: fixed; bottom: auto; transform: none; }
.tip:hover .tip-text, .tip-btn:focus-visible + .tip-text, .tip.is-open .tip-text { visibility: visible; opacity: 1; }
.tip.is-dismissed .tip-text { visibility: hidden; opacity: 0; }
.tip-text code { font-size: 12px; }
@media (prefers-reduced-motion: reduce) { .tip-text { transition: none; } }
@media print { .tip { display: none; } }

/* Every chart offers its numbers as a table: for screen readers, exact values, and copying. */
.chart-under { margin-top: 12px; }
.chart-table { margin-top: 12px; font-size: 13px; }
.chart-table summary { cursor: pointer; color: var(--violet); width: max-content; }
.chart-table .table-wrap { margin-top: var(--s1); }
@media print { .chart-tip, .chart-table { display: none; } }

/* Meter: one value against a goal. The fill color says how it is going (.meter-ok, -warn, -error),
   and the number and a label always sit beside it. A native <meter>, so no inline width is needed:
   <div class="meter-row"><span>Response within SLA</span><strong class="num">94%</strong></div>
   <meter class="meter meter-ok" min="0" max="100" value="94">94%</meter> */
meter.meter { display: block; width: 100%; height: 10px; border: 0; border-radius: var(--r-pill); background: color-mix(in srgb, var(--meter) 18%, var(--sunken)); -webkit-appearance: none; appearance: none; }
meter.meter::-webkit-meter-bar { background: color-mix(in srgb, var(--meter) 18%, var(--sunken)); border: 0; border-radius: var(--r-pill); height: 10px; }
meter.meter::-webkit-meter-optimum-value, meter.meter::-webkit-meter-suboptimum-value, meter.meter::-webkit-meter-even-less-good-value { background: var(--meter); border-radius: var(--r-pill); }
meter.meter::-moz-meter-bar { background: var(--meter); border-radius: var(--r-pill); }
.meter, .meter-ok { --meter: var(--teal); } .meter-warn { --meter: var(--orange); } .meter-error { --meter: var(--red); }
.meter-row { display: flex; justify-content: space-between; align-items: baseline; gap: var(--s1); margin-bottom: 6px; font-size: 14px; }
.meter-row + meter + .meter-row { margin-top: var(--s2); }

/* Stat tile extras: the change against a named period, and a 12 point sparkline in the quiet color. Up or down is not good or bad by itself: .good and .bad say which. */
.stat-delta { display: inline-flex; align-items: center; gap: 4px; font: 600 13px/1.2 var(--font-num); color: var(--text); }
.stat-delta .icon { width: 16px; height: 16px; color: var(--text-2); }
.stat-delta.good .icon { color: var(--teal); }
.stat-delta.bad .icon { color: var(--red); }
.stat-delta span { font: 400 13px/1.2 var(--font-body); color: var(--text-2); }
.sparkline { display: block; width: 100%; height: 32px; margin-top: 4px; overflow: visible; }
.sparkline .line { fill: none; stroke: var(--chart-other); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }

/* Jump list: links to the sections of a long page (settings, a long record). Not tabs: every section stays on the page. */
.jump-list { display: flex; flex-wrap: wrap; gap: var(--s1) var(--s3); padding-top: var(--s2); padding-bottom: var(--s2); }
.jump-list a { font-weight: 500; text-decoration: none; }
.jump-list a:hover { text-decoration: underline; }
.content > [id] { scroll-margin-top: calc(var(--pagehead-h) + var(--s2)); }

/* Bulk actions: a bar above a list while rows are selected, with the count and what can be done to them. */
.bulk-bar { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s1) 12px; padding: 8px 12px; margin-bottom: 12px; border: 1px solid var(--orange); border-radius: var(--r-card); background: var(--nav-active); }
.bulk-bar strong { margin-right: auto; font-size: 14px; }
.table .check { width: 1%; padding-right: 0; }
.table input[type="checkbox"] { accent-color: var(--orange); width: 16px; height: 16px; margin: 0; vertical-align: middle; }
.table tr:has(input[type="checkbox"]:checked) td { background: var(--nav-active); }

/* ===== Copy and secrets ===== */
/* Copy button: any button with data-copy="text", or data-copy-from="id" to copy an element's value. td-shell.js copies,
   swaps the icon to a check for 2 seconds, and says so in a toast. */
.copy-btn.is-copied { color: var(--teal); }
/* Beside an ID in a table row or a line of text: smaller than a normal square icon button. */
.icon-btn.copy-btn { width: 28px; height: 28px; vertical-align: middle; }
.icon-btn.copy-btn .icon { width: 16px; height: 16px; }
/* A secret shown in part: the first and last 4 characters around dots, as the server renders it. API keys and tokens
   stay like this for good (the server keeps only their ends). Passwords the app may show get data-secret and a
   Show button (data-reveal), and hide again by themselves after 30 seconds. */
.masked { font: 500 0.9em/1.4 var(--font-mono); letter-spacing: 0.02em; white-space: nowrap; }
.masked .dots { color: var(--text-2); letter-spacing: 0.1em; }
.secret-field {
    display: inline-flex; align-items: center; gap: 2px; max-width: 100%;
    padding: 2px 2px 2px 10px; border: 1px solid var(--border); border-radius: var(--r-btn); background: var(--sunken);
}
.secret-field .masked { overflow: hidden; text-overflow: ellipsis; margin-right: 6px; }
.secret-field .icon-btn { width: 30px; height: 30px; }

/* ===== Column filters (Excel style) =====
   A menu on a column heading to sort it and pick which values to show, for long lists the server filters. Each column's
   GET form sits outside the table (a table is often inside a bulk-select form, and forms can't nest); the menu's
   controls join it with form="...". components/column-filter.html has the markup; td-shell.js runs Search and Select all. */
.th-filter { display: flex; align-items: center; gap: 2px; min-width: 0; }
.th-filter > a, .th-filter > span { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; color: inherit; text-decoration: none; }
.th-filter > a:hover { color: var(--text); }
.col-filter > summary.icon-btn { width: 24px; height: 24px; flex: none; }
.col-filter > summary.icon-btn .icon { width: 14px; height: 14px; }
.col-filter.is-active > summary.icon-btn { color: var(--orange); background: color-mix(in srgb, var(--orange) 12%, transparent); }
.col-filter-menu { width: 280px; white-space: normal; text-transform: none; letter-spacing: 0; font: 400 14px/1.4 var(--font-body); color: var(--text); text-align: left; }
.col-filter-body { display: grid; gap: 8px; padding: 4px; }
.col-filter-body input[type="search"], .col-filter-body input[type="text"] { width: 100%; }
.col-filter-values { max-height: 260px; overflow-y: auto; border: 1px solid var(--border); border-radius: var(--r-btn); padding: 4px 8px; }
.col-filter-values .choice { display: flex; align-items: center; gap: 8px; padding: 3px 0; margin: 0; }
.col-filter-values .choice > span:first-of-type { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; }
.col-filter-count { color: var(--text-2); font: 400 12px var(--font-num); font-variant-numeric: tabular-nums; }
.col-filter-values .col-filter-all { border-bottom: 1px solid var(--border); padding-bottom: 6px; margin-bottom: 2px; }
.col-filter-actions { display: flex; justify-content: flex-end; gap: 8px; }
.col-filter-more { font-size: 12px; color: var(--text-2); }

/* ===== Filter and sort tables =====
   <input type="search" data-filter="t1"> and <select data-filter="t1" data-filter-col="3"> above <table id="t1">;
   <th data-sort> (data-sort="num" or "date" for those columns) becomes a sort button. examples/devices.html has them all. */
.table th[data-sort] { padding: 0; }
.sort-btn {
    display: inline-flex; align-items: center; gap: 4px; width: 100%; padding: 10px 12px;
    border: 0; background: none; color: inherit; font: inherit; letter-spacing: inherit; text-transform: inherit; text-align: left; cursor: pointer;
}
.sort-btn:hover { color: var(--text); }
.sort-btn .icon { width: 14px; height: 14px; opacity: 0.5; }
th[aria-sort] .sort-btn { color: var(--text); }
th[aria-sort] .sort-btn .icon { opacity: 1; color: var(--orange); }
.table th.amount .sort-btn { justify-content: flex-end; text-align: right; }
.table tr[hidden] { display: none; }
.filter-empty { text-align: center; color: var(--text-2); padding: var(--s3) var(--s2); }

/* ===== Sidebar: collapsible groups and submenus ===== */
.nav-label-toggle {
    display: flex; align-items: center; justify-content: space-between; width: 100%;
    border: 0; background: none; cursor: pointer; text-align: left; margin-bottom: 6px;
}
.nav-label-toggle:hover { color: var(--text); }
.nav-label-toggle .icon { width: 14px; height: 14px; transition: transform 120ms ease; }
.nav-label-toggle[aria-expanded="false"] .icon { transform: rotate(-90deg); }
.nav-label-toggle[aria-expanded="false"] + .nav-group-items { display: none; }
.nav-sub { position: relative; }
.nav-sub > button.nav-item { color: var(--text); }
.nav-sub > .nav-item .nav-chev { margin-left: auto; width: 16px; height: 16px; transition: transform 120ms ease; }
.nav-sub > .nav-item[aria-expanded="true"] .nav-chev { transform: rotate(180deg); }
.nav-sub > .nav-item[aria-expanded="false"] + .nav-sub-items { display: none; }
.nav-sub-items .nav-item { height: 34px; padding-left: 44px; font-weight: 400; }
.nav-sub-items .nav-item[aria-current="page"] { font-weight: 600; }
.nav-sub:has(.nav-sub-items [aria-current="page"]) > .nav-item .icon:first-child { color: var(--orange); }
@media (prefers-reduced-motion: reduce) { .nav-label-toggle .icon, .nav-sub > .nav-item .nav-chev { transition: none; } }
/* In the collapsed rail every group shows, and a submenu opens as a flyout beside the rail on hover or focus. */
@media (min-width: 901px) {
    html.sidebar-collapsed .nav { overflow: visible; }
    html.sidebar-collapsed .nav-label-toggle { display: none; }
    html.sidebar-collapsed .nav-label-toggle + .nav-group-items { display: block; }
    html.sidebar-collapsed .nav-sub > .nav-item .nav-chev { display: none; }
    html.sidebar-collapsed .nav-sub > .nav-item + .nav-sub-items {
        display: none; position: absolute; left: 100%; top: -6px; z-index: 40; min-width: 200px;
        padding: 6px 6px 6px 16px; background: transparent;
    }
    html.sidebar-collapsed .nav-sub-items::before {
        content: ""; position: absolute; inset: 0 0 0 10px; z-index: -1;
        background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-card); box-shadow: var(--shadow-md);
    }
    html.sidebar-collapsed .nav-sub:hover > .nav-sub-items, html.sidebar-collapsed .nav-sub:focus-within > .nav-sub-items { display: block; }
    html.sidebar-collapsed .nav-sub-items .nav-item { justify-content: flex-start; padding: 0 12px; }
    html.sidebar-collapsed .nav-sub-items .nav-item span { display: inline; }
}

/* ===== Kanban board =====
   Columns of cards. Drag a card, or focus it and use Alt and the arrow keys, or its Move menu (phones and touch).
   td-shell.js moves the card, updates the counts, says where it went, and fires td:kanban-move for the app to save. */
.kanban { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(260px, 1fr); gap: var(--s2); overflow-x: auto; padding-bottom: var(--s1); align-items: start; }
.kanban-col { background: var(--sunken); border: 1px solid var(--border); border-radius: var(--r-card); padding: 12px; min-height: 160px; display: flex; flex-direction: column; }
.kanban-head { display: flex; align-items: center; gap: var(--s1); margin-bottom: 12px; }
.kanban-head h2 { font-size: 15px; margin: 0; }
.kanban-head .badge { margin-left: auto; }
.kanban-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--s1); flex: 1; min-height: 60px; border-radius: var(--r-btn); }
.kanban-list.is-over { outline: 2px dashed var(--violet); outline-offset: 4px; }
.kanban-card {
    background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-btn); padding: 10px 12px;
    box-shadow: var(--shadow-sm); cursor: grab; display: flex; flex-direction: column; gap: 6px;
}
.kanban-card:focus-visible { outline: 2px solid var(--violet); outline-offset: 2px; }
.kanban-card.is-dragging { opacity: 0.5; cursor: grabbing; }
.kanban-card.is-moved { animation: td-flash 900ms ease; }
@keyframes td-flash { from { box-shadow: 0 0 0 3px var(--orange); } }
.kanban-title { font-weight: 600; font-size: 14px; line-height: 1.35; }
.kanban-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; font-size: 12px; color: var(--text-2); }
.kanban-meta .avatar { margin: 0 0 0 auto; }
@media (prefers-reduced-motion: reduce) { .kanban-card.is-moved { animation: none; } }

/* ===== Tabs =====
   Switching views on one page: <div class="tabs" role="tablist"> with <button role="tab" aria-controls>, and
   role="tabpanel" sections. Tabs that are links to other addresses (Open, Mine, All) use <nav class="tabs"> and aria-current. */
/* The bottom line is an inset shadow rather than a border, so the 2px underline sits inside the bar instead of hanging 1px
   below it: with overflow-x auto that 1px overflow made browsers add a vertical scrollbar to every tab bar. */
.tabs { display: flex; gap: 2px; box-shadow: inset 0 -1px 0 var(--border); margin-bottom: var(--s2); overflow-x: auto; overflow-y: hidden; }
.tabs > a, .tabs > [role="tab"] {
    display: inline-flex; align-items: center; gap: 6px; padding: 10px 14px;
    border: 0; border-bottom: 2px solid transparent; background: none; color: var(--text-2);
    font: 500 14px/1.2 var(--font-body); text-decoration: none; white-space: nowrap; cursor: pointer;
}
.tabs > a:hover, .tabs > [role="tab"]:hover { color: var(--text); }
.tabs > a[aria-current="page"], .tabs > [role="tab"][aria-selected="true"] { color: var(--text); border-bottom-color: var(--orange); font-weight: 600; }
.tabs .badge { font-size: 11px; padding: 2px 6px; }

/* ===== Dropdown menu =====
   <details class="dropdown"><summary class="btn btn-ghost btn-sm">More</summary><div class="menu">..</div></details>
   Opens below its button (above when there is no room), and works inside a scrolling table. */
.dropdown { position: relative; display: inline-block; }
.dropdown > summary { list-style: none; }
.dropdown > summary::-webkit-details-marker { display: none; }
.dropdown > .menu { top: calc(100% + 6px); bottom: auto; left: auto; right: 0; min-width: 200px; }
.dropdown > .menu.is-fixed { position: fixed; right: auto; }
.menu-item.danger { color: var(--red); }
.menu-item.danger .icon { color: var(--red); }

/* ===== Accordion ===== */
.accordion { border: 1px solid var(--border); border-radius: var(--r-card); background: var(--panel); }
.accordion + .accordion { margin-top: -1px; border-top-left-radius: 0; border-top-right-radius: 0; }
.accordion:has(+ .accordion) { border-bottom-left-radius: 0; border-bottom-right-radius: 0; }
.accordion > summary {
    list-style: none; cursor: pointer; display: flex; align-items: center; gap: 10px;
    padding: 14px var(--s2); font: 600 15px/1.3 var(--font-head);
}
.accordion > summary::-webkit-details-marker { display: none; }
.accordion > summary::after {
    content: ""; margin-left: auto; width: 8px; height: 8px; flex: none;
    border-right: 2px solid var(--text-2); border-bottom: 2px solid var(--text-2); transform: rotate(45deg) translateY(-2px); transition: transform 120ms ease;
}
.accordion[open] > summary::after { transform: rotate(-135deg) translateY(-2px); }
.accordion > summary:hover { background: var(--nav-hover); }
.accordion > :not(summary) { padding: 0 var(--s2); }
.accordion > :last-child:not(summary) { padding-bottom: var(--s2); }
@media (prefers-reduced-motion: reduce) { .accordion > summary::after { transition: none; } }

/* ===== Switch: a checkbox for a setting that takes effect right away ===== */
.switch { display: inline-flex; align-items: center; gap: 10px; cursor: pointer; }
.switch input {
    appearance: none; -webkit-appearance: none; margin: 0; flex: none; cursor: pointer;
    width: 36px; height: 20px; border-radius: var(--r-pill); background: var(--border-strong); position: relative; transition: background 120ms ease;
}
.switch input::after {
    content: ""; position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; border-radius: 50%;
    background: #FFFFFF; box-shadow: var(--shadow-sm); transition: transform 120ms ease;   /* kit-check: allow: the knob is white in both themes */
}
.switch input:checked { background: var(--orange); }
.switch input:checked::after { transform: translateX(16px); }
.switch input:disabled { opacity: 0.5; cursor: not-allowed; }
@media (prefers-reduced-motion: reduce) { .switch input, .switch input::after { transition: none; } }

/* ===== Stepper: where a person is in a multi step task ===== */
.stepper { list-style: none; margin: 0 0 var(--s3); padding: 0; display: flex; gap: var(--s1); counter-reset: step; }
.stepper li { flex: 1; display: flex; align-items: center; gap: var(--s1); font-size: 14px; color: var(--text-2); counter-increment: step; min-width: 0; }
.stepper li::before {
    content: counter(step); flex: none; width: 26px; height: 26px; border-radius: 50%; display: grid; place-items: center;
    border: 2px solid var(--border-strong); font: 600 13px/1 var(--font-num); color: var(--text-2);
}
.stepper li:not(:last-child)::after { content: ""; flex: 1; height: 2px; background: var(--border); min-width: 12px; }
.stepper li.done::before { content: "\2713"; border-color: var(--teal); background: var(--teal); color: #FFFFFF; }   /* kit-check: allow: check on a filled circle */
.stepper li.done::after { background: var(--teal); }
.stepper li[aria-current="step"] { color: var(--text); font-weight: 600; }
.stepper li[aria-current="step"]::before { border-color: var(--orange); color: var(--text); }
@media (max-width: 600px) { .stepper li:not([aria-current="step"]) span { display: none; } }

/* ===== Timeline: what happened, newest first ===== */
.timeline { list-style: none; margin: 0; padding: 0; }
.timeline li { position: relative; display: grid; grid-template-columns: 32px 1fr; gap: 12px; padding-bottom: var(--s2); }
.timeline li:not(:last-child)::before { content: ""; position: absolute; left: 15px; top: 34px; bottom: 2px; width: 2px; background: var(--border); }
.timeline-icon { width: 32px; height: 32px; border-radius: 50%; display: grid; place-items: center; background: var(--sunken); border: 1px solid var(--border); color: var(--text-2); }
.timeline-icon .icon { width: 16px; height: 16px; }
.timeline-body { padding-top: 5px; min-width: 0; }
.timeline-body p { margin: 2px 0 0; }
.timeline-when { display: block; font: 400 12px/1.4 var(--font-num); color: var(--text-2); }   /* its own line, under the title or the note */

/* ===== File drop zone =====
   <label class="dropzone"><input type="file" multiple> ..text.. </label><ul class="dropzone-files"></ul>
   Click to pick or drop files; td-shell.js lists what was chosen. */
.dropzone {
    display: flex; flex-direction: column; align-items: center; gap: 6px; text-align: center; cursor: pointer;
    padding: var(--s4) var(--s2); border: 2px dashed var(--border-strong); border-radius: var(--r-card); color: var(--text-2);
}
.dropzone .icon { width: 28px; height: 28px; }
.dropzone strong { color: var(--violet); }
.dropzone:hover, .dropzone.is-over { border-color: var(--violet); background: var(--nav-hover); }
.dropzone input { position: absolute; width: 1px; height: 1px; opacity: 0; }
.dropzone:focus-within { outline: 2px solid var(--violet); outline-offset: 2px; }
.dropzone-files { list-style: none; margin: var(--s1) 0 0; padding: 0; font-size: 14px; }
.dropzone-files li { display: flex; align-items: center; gap: var(--s1); padding: 6px 0; border-bottom: 1px solid var(--border); }
.dropzone-files .num { margin-left: auto; color: var(--text-2); }

/* ===== Progress and loading ===== */
/* Space below a bar, unless it is the last thing in its box (as under a .meter-row label). */
progress.progress:not(:last-child) { margin-bottom: var(--s2); }
progress.progress { display: block; width: 100%; height: 8px; border: 0; border-radius: var(--r-pill); background: var(--sunken); -webkit-appearance: none; appearance: none; overflow: hidden; }
progress.progress::-webkit-progress-bar { background: var(--sunken); border-radius: var(--r-pill); }
progress.progress::-webkit-progress-value { background: var(--violet); border-radius: var(--r-pill); }
progress.progress::-moz-progress-bar { background: var(--violet); border-radius: var(--r-pill); }
progress.progress:indeterminate { background: linear-gradient(90deg, var(--sunken) 0%, var(--violet) 50%, var(--sunken) 100%) 0 0 / 200% 100%; animation: td-slide 1.2s linear infinite; }
progress.progress:indeterminate::-webkit-progress-bar { background: transparent; }
@keyframes td-slide { to { background-position: -200% 0; } }
/* Placeholder blocks while content loads: .skeleton on a block, with a width class or the text it stands in for. */
.skeleton { display: block; height: 0.9em; margin: 0.35em 0; border-radius: 4px; color: transparent; user-select: none;
    background: linear-gradient(90deg, var(--sunken) 25%, var(--nav-hover) 50%, var(--sunken) 75%) 0 0 / 200% 100%; animation: td-slide 1.4s linear infinite; }
.skeleton.w-25 { width: 25%; } .skeleton.w-50 { width: 50%; } .skeleton.w-75 { width: 75%; }
/* In a details value or a table cell a placeholder sits on the text line, so labels and rows keep their height. */
dd > .skeleton, td > .skeleton { display: inline-block; vertical-align: -0.1em; margin: 0; height: 0.9em; }
dd > .skeleton:not([class*="w-"]), td > .skeleton:not([class*="w-"]) { width: 60%; }
@media (prefers-reduced-motion: reduce) { progress.progress:indeterminate, .skeleton { animation: none; } }

/* ===== Photo viewer =====
   Any link around an image inside [data-viewer] (a .photo-grid, say) opens the full image in a viewer:
   <a href="full.webp" data-caption="Front of the server"><img src="thumb.webp" alt="..."></a>
   Arrows or swipe buttons step through that gallery; Escape closes; focus returns to the photo. */
.viewer {
    width: min(1100px, 96vw); max-width: none; max-height: 94vh; padding: 0; border: 0; border-radius: var(--r-card);
    background: #0B0C0E; color: #F6F7F8; box-shadow: var(--shadow-md);   /* kit-check: allow: photos are judged on a near black stage in both themes */
}
.viewer::backdrop { background: rgba(0, 0, 0, 0.8); }
.viewer-stage { position: relative; display: grid; place-items: center; height: min(78vh, 760px); }
.viewer-stage img { max-width: 100%; max-height: 100%; object-fit: contain; display: block; }
.viewer-bar { display: flex; align-items: center; gap: var(--s1); padding: 10px 12px; font-size: 14px; }
.viewer-bar .viewer-caption { flex: 1; min-width: 0; }
.viewer-bar .num { color: #9AA2AD; }   /* kit-check: allow: viewer chrome */
.viewer .icon-btn { color: #F6F7F8; }   /* kit-check: allow: viewer chrome */
.viewer .icon-btn:hover { background: rgba(255, 255, 255, 0.12); color: #FFFFFF; }   /* kit-check: allow: viewer chrome */
.viewer-prev, .viewer-next { position: absolute; top: 50%; transform: translateY(-50%); background: rgba(0, 0, 0, 0.45) !important; }
.viewer-prev { left: 8px; } .viewer-next { right: 8px; }
[data-viewer] a:has(img) { display: block; border-radius: var(--r-btn); cursor: zoom-in; }

/* ===== Video ===== a native player with a poster, captions (<track kind="captions">), and a caption line. */
.video { margin: 0; }
.video video { display: block; width: 100%; aspect-ratio: 16 / 9; background: #0B0C0E; border-radius: var(--r-card); }   /* kit-check: allow: player stage */
.video figcaption { margin-top: 6px; font-size: 13px; color: var(--text-2); display: flex; gap: var(--s1); flex-wrap: wrap; }

/* ===== Slideshow =====
   Slides scroll sideways and snap; previous and next buttons and a count. Never moves by itself.
   <section class="slideshow" aria-roledescription="carousel" aria-label="..">
   <div class="slides"><figure class="slide">..</figure>..</div>
   <div class="slideshow-bar"><button data-slide="prev">..</button><span class="slideshow-count"></span><button data-slide="next">..</button></div></section> */
.slideshow { position: relative; }
.slides { display: grid; grid-auto-flow: column; grid-auto-columns: 100%; overflow-x: auto; scroll-snap-type: x mandatory; scroll-behavior: smooth; border-radius: var(--r-card); scrollbar-width: none; }
.slides::-webkit-scrollbar { display: none; }
.slide { margin: 0; scroll-snap-align: start; position: relative; }
.slide img { display: block; width: 100%; aspect-ratio: 16 / 9; object-fit: cover; background: var(--sunken); }
.slide figcaption { position: absolute; left: 0; right: 0; bottom: 0; padding: var(--s3) var(--s2) 12px; color: #FFFFFF; font-size: 14px;
    background: linear-gradient(transparent, rgba(0, 0, 0, 0.65)); }   /* kit-check: allow: caption over a photo */
.slideshow-bar { display: flex; align-items: center; justify-content: center; gap: var(--s1); margin-top: var(--s1); }
.slideshow-count { font: 500 13px/1 var(--font-num); color: var(--text-2); min-width: 4.5em; text-align: center; }
@media (prefers-reduced-motion: reduce) { .slides { scroll-behavior: auto; } }

/* ===== Street map =====
   The kit never loads map tiles from another site. The app serves the map image itself (from its own tile cache or
   a saved image), pins go on by percent (data-x, data-y), and a link opens the place in the person's map app.
   Credit the map data under the image, as its license asks (for OpenStreetMap: © OpenStreetMap contributors). */
.map { margin: 0; }
.map-frame { position: relative; overflow: hidden; border: 1px solid var(--border); border-radius: var(--r-card); background: var(--sunken); }
.map-frame > img { display: block; width: 100%; height: auto; }
.map-pin { position: absolute; transform: translate(-50%, -100%); color: var(--orange); display: none; }
.map-pin.is-placed { display: block; }
.map-pin .icon { width: 34px; height: 34px; filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.45)); }
.map-pin-label { position: absolute; left: 50%; bottom: calc(100% + 2px); transform: translateX(-50%); white-space: nowrap;
    padding: 2px 8px; border-radius: var(--r-pill); background: var(--panel); color: var(--text); font: 600 12px/1.5 var(--font-body); box-shadow: var(--shadow-sm); border: 1px solid var(--border); }
.map figcaption { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s1) var(--s2); margin-top: var(--s1); font-size: 14px; }
.map-credit { margin-left: auto; font-size: 12px; color: var(--text-2); }
.map-credit a { color: inherit; }
@media print { .map-pin { display: block; } }
/* Interactive map (figure.map[data-map]): tiles from the app's own server, panned and zoomed by td-shell.js.
   The static image is the fallback without scripts; paper gets the view on screen, without the buttons. */
.map-frame.is-interactive { aspect-ratio: 16 / 10; cursor: grab; touch-action: none; user-select: none; }
.map-frame.is-interactive:focus-visible { outline: 2px solid var(--violet); outline-offset: 2px; }
.map-frame.is-dragging { cursor: grabbing; }
.map-frame.is-interactive > img { display: none; }
.map-tiles { position: absolute; inset: 0; overflow: hidden; }
.map-tiles img { position: absolute; left: 0; top: 0; width: 256px; height: 256px; max-width: none; pointer-events: none; }
.map-tiles img.is-missing { visibility: hidden; }
.map-frame.is-interactive .map-pin { z-index: 2; }
.map-controls { position: absolute; top: var(--s1); right: var(--s1); z-index: 3; display: flex; flex-direction: column; gap: 2px;
    padding: 2px; background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-btn); box-shadow: var(--shadow-sm); }
.map-controls .icon-btn { width: 32px; height: 32px; }
.map-controls .icon-btn:disabled { opacity: 0.4; cursor: default; }
.map-hint { position: absolute; inset: 0; z-index: 4; display: grid; place-items: center; pointer-events: none;
    background: rgba(0, 0, 0, 0.45); color: #FFFFFF; font: 600 15px/1.3 var(--font-body); }   /* kit-check: allow: overlay on the map */
@media print { .map-controls, .map-hint { display: none; } }

/* ===== Wallboard =====
   For a screen on the wall (a NOC TV): a [data-wallboard] button turns html.wallboard on, which hides the sidebar and
   the page's own buttons, uses the whole width, and goes full screen. Escape or the button turns it off. */
html.wallboard .sidebar, html.wallboard .scrim, html.wallboard .menu-toggle, html.wallboard .pagehead-actions > :not([data-wallboard]),
html.wallboard .crumbs, html.wallboard .no-wallboard { display: none !important; }
html.wallboard .content { max-width: none; }
html.wallboard .pagehead { position: static; }
html.wallboard body { font-size: 16px; }
html.wallboard .stat-value { font-size: 34px; }

/* ===== Message pages (sign in, signed out, errors before sign in) =====
   Centered frosted glass card on the server room photo, as on start.techdynamix.com. */
:root { --glass: rgba(246, 247, 248, 0.70); --glass-edge: rgba(255, 255, 255, 0.60); --glass-text-2: #3F454D; }
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) { --glass: rgba(30, 32, 36, 0.48); --glass-edge: rgba(255, 255, 255, 0.10); --glass-text-2: #9AA2AD; } }
:root[data-theme="dark"] { --glass: rgba(30, 32, 36, 0.48); --glass-edge: rgba(255, 255, 255, 0.10); --glass-text-2: #9AA2AD; }

body.message-page { min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: var(--s2); background: #131519; }
body.message-page::before {
    content: ""; position: fixed; inset: 0; width: 100vw; height: 100vh; z-index: -1; pointer-events: none;
    background: linear-gradient(180deg, rgba(19, 21, 25, 0.60), rgba(19, 21, 25, 0.68)), url("../brand/bg-server-room.webp") center / cover no-repeat #131519;
}
.message-card {
    width: min(440px, 100%); margin: auto 0; padding: var(--s5) var(--s4);
    text-align: center; display: flex; flex-direction: column; align-items: center; gap: var(--s2);
    border: 1px solid var(--glass-edge); border-radius: var(--r-card);
    background-color: var(--glass);
    background-image:
        url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 1  0 0 0 0 1  0 0 0 0 1  0 0 0 .55 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='.35'/%3E%3C/svg%3E"),
        linear-gradient(180deg, rgba(255, 255, 255, 0.14), rgba(255, 255, 255, 0) 35%);
    -webkit-backdrop-filter: blur(28px) saturate(120%); backdrop-filter: blur(28px) saturate(120%);
    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.28), inset 0 1px 0 var(--glass-edge);
}
@media (prefers-reduced-transparency: reduce) { .message-card { background-image: none; background-color: var(--panel); backdrop-filter: none; -webkit-backdrop-filter: none; } }
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) { .message-card { background-color: var(--panel); } }
.message-card .muted, .message-text { color: var(--glass-text-2); }
.message-card h1 { font-size: 22px; margin: 0; }
.message-text { margin: 0; }
.message-logo { width: 100%; }
.message-logo img { display: block; margin: 0 auto; height: auto; width: min(320px, 100%); }
.message-card .small { font-size: 12px; margin: 0; }
.message-card .alert { width: 100%; text-align: left; margin: 0; }
.message-card .form { width: 100%; text-align: left; margin-top: 12px; }
.message-card .form .btn { width: 100%; }
.btn-ms { margin-top: var(--s1); }
.reveal { width: 100%; text-align: left; }
.reveal > summary { cursor: pointer; text-align: center; color: var(--glass-text-2); font-size: 13px; list-style: none; }
.reveal > summary::-webkit-details-marker { display: none; }
.reveal > summary:hover { color: var(--text); text-decoration: underline; }

/* Wider message card for longer forms shown before sign in (first time setup, for example). */
.message-card-wide { width: min(640px, 100%); text-align: left; align-items: stretch; }
.message-card-wide h1, .message-card-wide .message-logo, .message-card-wide .tagline, .message-card-wide > .message-text, .message-card-wide > .small { text-align: center; }
/* A wide card holds whole forms, so their buttons keep their own width (a narrow card's single button fills it). */
.message-card-wide .form .btn { width: auto; }
.message-card-wide > .btn { align-self: center; }

/* ===== Form extras: choice cards, segmented control, prefixes, counts, sliders ===== */
/* Choice cards: radios or checkboxes as cards, for a choice that needs a line of explanation per option.
   <div class="choice-cards"><label class="choice-card"><input type="radio" name="plan"><span class="choice-card-body">
   <span class="choice-card-title">Monthly</span><span class="small muted">Billed on the 1st</span></span></label>..</div> */
.choice-cards { display: grid; gap: 10px; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); border: 0; padding: 0; margin: 0; }
.choice-card {
    display: flex; gap: 10px; align-items: flex-start; padding: 12px 14px; cursor: pointer;
    border: 1px solid var(--border-strong); border-radius: var(--r-card); background: var(--panel);
}
.choice-card:hover { border-color: var(--text-2); }
.choice-card input { margin-top: 4px; accent-color: var(--orange); flex: none; }
.choice-card:has(input:checked) { border-color: var(--orange); background: var(--nav-active); }
.choice-card:has(input:focus-visible) { outline: 2px solid var(--violet); outline-offset: 2px; }
.choice-card:has(input:disabled) { opacity: 0.55; cursor: not-allowed; }
.choice-card-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.choice-card-title { font-weight: 600; }

/* Segmented control: two to five short, exclusive options shown side by side. Radios, so it works without script.
   <fieldset class="segmented"><legend class="visually-hidden">View</legend>
   <label><input type="radio" name="view" checked><span>List</span></label>..</fieldset> */
.segmented { display: inline-flex; flex-direction: row; gap: 0; padding: 2px; margin: 0; border: 1px solid var(--border-strong); border-radius: var(--r-btn); background: var(--sunken); min-width: 0; max-width: 100%; overflow-x: auto; }
.segmented label { position: relative; flex: 1 0 auto; cursor: pointer; }
.segmented input { position: absolute; inset: 0; opacity: 0; margin: 0; cursor: pointer; }
.segmented span {
    display: inline-flex; align-items: center; justify-content: center; gap: 6px; width: 100%;
    padding: 6px 14px; border-radius: 4px; border: 1px solid transparent;
    font: 500 14px/1.3 var(--font-body); color: var(--text-2); white-space: nowrap;
}
.segmented span .icon { width: 16px; height: 16px; }
.segmented label:hover span { color: var(--text); }
.segmented input:checked + span { background: var(--panel); color: var(--text); font-weight: 600; border-color: var(--orange); box-shadow: var(--shadow-sm); }
.segmented input:focus-visible + span { outline: 2px solid var(--violet); outline-offset: 1px; }

/* A field with a fixed part before or after what is typed: an address prefix, a unit.
   <span class="input-affix"><span class="affix">techdynamix.com/blog/</span><input type="text"></span> */
.input-affix { display: flex; align-items: stretch; width: 100%; }
.input-affix > input, .input-affix > select { flex: 1 1 auto; min-width: 0; }
.input-affix > .affix {
    display: inline-flex; align-items: center; padding: 0 10px; white-space: nowrap;
    background: var(--sunken); color: var(--text-2); border: 1px solid var(--border-strong); font-size: 14px;
}
.input-affix > .affix:first-child { border-right: 0; border-radius: var(--r-btn) 0 0 var(--r-btn); }
.input-affix > .affix:last-child { border-left: 0; border-radius: 0 var(--r-btn) var(--r-btn) 0; }
.input-affix > .affix:first-child + input, .input-affix > .affix:first-child + select { border-top-left-radius: 0; border-bottom-left-radius: 0; }
.input-affix > input:has(+ .affix), .input-affix > select:has(+ .affix) { border-top-right-radius: 0; border-bottom-right-radius: 0; }
@media (max-width: 600px) { .input-affix > .affix { max-width: 45%; overflow: hidden; text-overflow: ellipsis; } }

/* Character count under a field with a limit: <textarea maxlength="160" data-char-count="c1"></textarea>
   <small class="char-count" id="c1"></small>. td-shell.js writes "42 of 160" and marks it once it is over a
   soft limit (data-char-max, when the field may go over) or within 10% of maxlength. */
.char-count { font: 400 13px/1.4 var(--font-num); color: var(--text-2); align-self: flex-end; }
.char-count.is-near { color: var(--text); font-weight: 600; }
.char-count.is-over { color: var(--red); font-weight: 600; }

/* Slider with its value: <div class="range-row"><input type="range" id="r1"><output for="r1">50%</output></div>.
   td-shell.js keeps the output current (data-suffix="%" adds a unit). Always also allow typing the number when it matters. */
input[type="range"] { accent-color: var(--orange); width: 100%; margin: 0; cursor: pointer; }
.range-row { display: flex; align-items: center; gap: 12px; }
.range-row output { flex: none; min-width: 3.5em; text-align: right; font: 600 14px/1 var(--font-num); }

/* ===== Pickers: choose one picture or icon from tiles =====
   Radios, so the form posts the choice without script. [data-pick-filter="gridId"] on a search box filters the
   tiles by their name and data-keywords; [data-pick-count="gridId"] says how many show. The chosen tile stays
   chosen while it is filtered out.
   <div class="pick-grid" id="g1" role="radiogroup" aria-label="Picture"><label class="pick-tile" data-keywords="rack server">
   <input type="radio" name="image" value="12"><span class="pick-art"><img src=".." alt=""></span>
   <span class="pick-name">rack-front.webp</span></label>..</div>  .pick-grid-icons for icon tiles. */
.pick-grid {
    display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: 12px;
    max-height: 360px; overflow-y: auto; padding: 4px;
}
.pick-tile { position: relative; display: flex; flex-direction: column; gap: 4px; min-width: 0; cursor: pointer; }
.pick-tile input { position: absolute; opacity: 0; width: 1px; height: 1px; margin: 0; }
.pick-art {
    display: grid; place-items: center; aspect-ratio: 4 / 3; overflow: hidden;
    border: 2px solid var(--border); border-radius: var(--r-btn); background: var(--sunken); color: var(--text-2);
}
.pick-art img { width: 100%; height: 100%; object-fit: cover; display: block; }
.pick-art .icon { width: 28px; height: 28px; }
.pick-name { font-size: 12px; color: var(--text-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pick-tile:hover .pick-art { border-color: var(--text-2); }
.pick-tile input:checked ~ .pick-art { border-color: var(--orange); box-shadow: 0 0 0 2px var(--nav-active); }
.pick-tile input:checked ~ .pick-name { color: var(--text); font-weight: 600; }
.pick-tile input:focus-visible ~ .pick-art { outline: 2px solid var(--violet); outline-offset: 2px; }
.pick-grid-icons { grid-template-columns: repeat(auto-fill, minmax(56px, 1fr)); gap: 6px; max-height: 260px; }
.pick-grid-icons .pick-art { aspect-ratio: 1; background: var(--panel); }
.pick-grid-icons .pick-art .icon { width: 24px; height: 24px; }
.pick-grid-icons .pick-name { text-align: center; font-size: 11px; }
.pick-tile input:checked ~ .pick-art .icon { color: var(--text); }

/* Thumbnail in a list row or beside a name: a small square photo, or a file type in its place.
   <img class="thumb" src=".." alt="">  <span class="thumb"><svg class="icon">..</svg></span>  .thumb-lg is 72px. */
.thumb {
    width: 48px; height: 48px; flex: none; display: inline-grid; place-items: center; vertical-align: middle;
    object-fit: cover; border-radius: var(--r-btn); border: 1px solid var(--border); background: var(--sunken); color: var(--text-2);
}
.thumb-lg { width: 72px; height: 72px; }
.thumb .icon { width: 22px; height: 22px; }
/* A thumbnail and the name and details beside it, in a table cell or a list. */
.media-row { display: flex; align-items: center; gap: 12px; min-width: 0; }
.media-row > :last-child { min-width: 0; overflow-wrap: anywhere; }

/* ===== Reorder rows: drag a table row by its grip, or move it with buttons or Alt and the arrow keys =====
   <tbody class="reorder" data-reorder-name="slides"> of <tr data-id="7">, each with
   <td class="reorder-cell"><span class="reorder-handle" aria-hidden="true">grip icon</span>
   <button type="button" class="icon-btn" data-move="up" aria-label="Move Spring sale up">..</button>
   <button type="button" class="icon-btn" data-move="down" aria-label="Move Spring sale down">..</button></td>.
   Every move fires td:reorder on the tbody with {id, ids, index}; save the new order, and say so. */
.reorder-cell { width: 1%; white-space: nowrap; }
.reorder-cell > * { vertical-align: middle; }
.reorder-handle { display: inline-grid; place-items: center; width: 28px; height: 28px; color: var(--text-2); cursor: grab; }
.reorder-handle:hover { color: var(--text); }
.reorder-cell .icon-btn { width: 28px; height: 28px; }
.reorder-cell .icon-btn .icon { width: 18px; height: 18px; }
.reorder-cell .icon-btn:disabled { opacity: 0.3; cursor: default; background: none; }
.reorder tr.is-dragging { opacity: 0.55; background: var(--nav-active); }
.reorder tr.is-moved > td { background: var(--nav-active); transition: background 600ms ease; }
@media (prefers-reduced-motion: reduce) { .reorder tr.is-moved > td { transition: none; } }
@media print { .reorder-cell { display: none; } }

/* ===== Masonry: a page of separate cards of different heights, packed into columns =====
   <div class="masonry" data-masonry-min="340">cards..</div>. td-shell.js deals the cards, in the order written,
   to the shortest column, as many columns as fit (data-masonry-min, 340px by default) and never more than
   data-masonry-max (4) or the number of cards. data-masonry-pin="last" on a card (Save) heads the last column;
   data-masonry-span="2" gives a card two columns once there are three or more. Without script: CSS columns. */
.masonry { columns: 4 340px; column-gap: var(--s3); }
.masonry > * { break-inside: avoid; width: 100%; margin: 0 0 var(--s3); min-width: 0; }
.masonry.is-laid-out { columns: auto; display: grid; grid-template-columns: repeat(var(--masonry-cols, 1), minmax(0, 1fr)); gap: var(--s3); align-items: start; }
.masonry.is-laid-out > .masonry-col { display: grid; gap: var(--s3); align-content: start; min-width: 0; margin: 0; }
.masonry.is-laid-out > .masonry-col.is-wide { grid-column: span 2; }
.masonry-col > * { margin: 0; min-width: 0; }

/* ===== Bar table: a short ranked list with a bar behind each label (top pages, where visits came from) =====
   <table class="table table-bars"><tr><td><span class="bar" data-bar="64"></span><span class="bar-label">/services</span></td>
   <td class="amount">412</td></tr>. data-bar is the percent of the largest row; td-shell.js sets the width, so it works
   under a strict Content Security Policy. The bar is a shape to compare, not something to read: the number is beside it. */
.table-bars td:first-child { position: relative; }
.table-bars .bar { position: absolute; left: 4px; top: 4px; bottom: 4px; width: 0; max-width: calc(100% - 8px); border-radius: 3px; background: var(--chart-1); opacity: 0.16; pointer-events: none; }
.table-bars .bar-label { position: relative; display: block; overflow-wrap: anywhere; }
@media print { .table-bars .bar { opacity: 0.25; -webkit-print-color-adjust: exact; print-color-adjust: exact; } }

/* ===== Modal =====
   For a task that needs its own box above the page: pick a picture, edit one row, a short form. Not for questions
   (data-confirm) or messages (toasts). A native <dialog>: focus stays inside, Escape closes, focus goes back.
   <button data-modal-open="m1">..</button>
   <dialog class="modal" id="m1" aria-labelledby="m1-title"><form method="dialog" class="modal-head"><h2 id="m1-title">..</h2>
   <button class="icon-btn" value="cancel" aria-label="Close">x</button></form><div class="modal-body">..</div>
   <div class="modal-foot">..<button type="button" class="btn btn-ghost" data-modal-close>Cancel</button></div></dialog> */
.modal {
    width: min(640px, calc(100vw - 32px)); max-height: min(86vh, 900px); padding: 0;
    display: none; flex-direction: column;
    background: var(--panel); color: var(--text);
    border: 1px solid var(--border); border-radius: var(--r-card); box-shadow: var(--shadow-md);
}
.modal[open] { display: flex; }
/* Centered whatever it sits in: a modal written inside a card is its last child, and .card > :last-child would take its margin. */
dialog.modal:modal { margin: auto; }
.modal-lg { width: min(960px, calc(100vw - 32px)); }
.modal::backdrop { background: rgba(0, 0, 0, 0.45); }
.modal-head { display: flex; align-items: center; justify-content: space-between; gap: var(--s2); padding: 14px var(--s3); border-bottom: 1px solid var(--border); margin: 0; }
.modal-head h2 { margin: 0; font-size: 19px; }
.modal-head .icon-btn { margin-right: -8px; }
.modal-body { padding: var(--s3); overflow-y: auto; flex: 1 1 auto; min-height: 0; }
.modal-body > :last-child { margin-bottom: 0; }
.modal-foot { display: flex; align-items: center; justify-content: flex-end; gap: var(--s1); flex-wrap: wrap; padding: 14px var(--s3); border-top: 1px solid var(--border); background: var(--sunken); border-radius: 0 0 var(--r-card) var(--r-card); }
.modal-foot > .modal-note { margin-right: auto; font-size: 13px; color: var(--text-2); }
@media (max-width: 600px) {
    .modal, .modal-lg { width: 100vw; max-width: 100vw; height: 100dvh; max-height: 100dvh; border-radius: 0; border: 0; }
    .modal-foot { border-radius: 0; }
}
body:has(.modal[open]) { overflow: hidden; }

/* ===== Side panel =====
   A dialog.modal.panel comes in from the right edge, the full height of the window: a quick look at a record, or
   filters, without leaving the list. Opened and closed like a modal (data-modal-open, data-modal-close, TD.modal). */
dialog.modal.panel:modal { margin: 0 0 0 auto; width: min(480px, 100vw); height: 100dvh; max-height: 100dvh; border-width: 0 0 0 1px; border-radius: 0; }
.modal.panel .modal-foot { border-radius: 0; }
.modal.panel[open] { animation: td-panel-in 0.18s ease-out; }
@keyframes td-panel-in { from { transform: translateX(32px); opacity: 0; } }
@media (prefers-reduced-motion: reduce) { .modal.panel[open] { animation: none; } }

/* ===== Lists =====
   ul.list of rows that are not a table: notifications, members, recent items, search results. A row is an optional
   .avatar or .list-icon, then .list-main (a .list-title and a .list-sub line), then .list-meta (a time, a chip, a button).
   a.list-link makes the whole row one link. li.unread puts a dot before the title (say "Unread" in .visually-hidden). */
.list { list-style: none; margin: 0; padding: 0; }
.list > li { display: flex; align-items: center; gap: 12px; padding: 12px 0; border-bottom: 1px solid var(--border); }
.list > li:last-child { border-bottom: 0; }
.list-icon { width: 32px; height: 32px; flex: none; border-radius: 50%; display: grid; place-items: center; background: var(--sunken); color: var(--text-2); }
.list-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; line-height: 1.35; }
.list-title { font-weight: 600; overflow-wrap: anywhere; }
.list-sub { color: var(--text-2); font-size: 13px; overflow-wrap: anywhere; }
.list-meta { flex: none; display: flex; align-items: center; gap: var(--s1); color: var(--text-2); font-size: 13px; white-space: nowrap; }
.list > li > a.list-link { flex: 1 1 auto; min-width: 0; display: flex; align-items: center; gap: 12px; margin: -12px -8px; padding: 12px 8px; color: inherit; text-decoration: none; border-radius: var(--r-btn); }
.list-link:hover { background: var(--nav-hover); }
.list-link .list-title { color: var(--violet); }
.list-link > .icon:last-child { color: var(--text-2); flex: none; }
.list > li.unread .list-title::before { content: ""; display: inline-block; width: 8px; height: 8px; margin-right: 6px; border-radius: 50%; background: var(--orange); vertical-align: 2px; }
@media (max-width: 640px) { .list > li { flex-wrap: wrap; } .list-meta { width: 100%; padding-left: 44px; white-space: normal; } }

/* ===== Conversation =====
   ol.thread, oldest first: li.msg (an .avatar, .msg-head with the name and time, .msg-body), li.msg-mine for the
   person reading (on the right), li.msg-note for an internal note staff see and clients never do, and li.thread-event
   for what happened in between (status changes). The .composer goes under it. */
.thread { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--s2); }
.msg { display: grid; grid-template-columns: 32px minmax(0, 1fr); gap: 4px var(--s1); max-width: min(680px, 94%); }
.msg > .avatar { grid-row: 1 / span 2; }
.msg-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px var(--s1); font-size: 13px; color: var(--text-2); }
.msg-head strong { font-size: 14px; color: var(--text); }
.msg-body { padding: 10px 14px; background: var(--sunken); border: 1px solid var(--border); border-radius: 4px var(--r-card) var(--r-card) var(--r-card); overflow-wrap: anywhere; }
.msg-body > :first-child { margin-top: 0; }
.msg-body > :last-child { margin-bottom: 0; }
.msg-mine { align-self: flex-end; grid-template-columns: minmax(0, 1fr) 32px; }
.msg-mine > .avatar { grid-column: 2; }
.msg-mine > .msg-head, .msg-mine > .msg-body { grid-column: 1; grid-row: auto; }
.msg-mine > .msg-head { justify-content: flex-end; }
.msg-mine > .msg-body { background: color-mix(in srgb, var(--orange) 10%, var(--panel)); border-radius: var(--r-card) 4px var(--r-card) var(--r-card); }
.msg-note > .msg-body { background: color-mix(in srgb, var(--violet) 7%, var(--panel)); border-style: dashed; border-color: var(--violet); }
.thread-event { align-self: center; display: flex; align-items: center; gap: 6px; font-size: 13px; color: var(--text-2); text-align: center; }
.composer { margin-top: var(--s3); }
.composer textarea { min-height: 96px; resize: vertical; }
.composer-foot { display: flex; align-items: center; justify-content: space-between; gap: var(--s1); flex-wrap: wrap; margin-top: var(--s1); }
.composer-foot .form-actions { margin: 0; }

/* ===== Task list =====
   ul.task-list of li.task: a checkbox with the task, then who has it and when it is due. A ticked task is struck
   through. The checkboxes are ordinary form fields; [data-task-count] and progress[data-task-progress] (with the
   list's id) keep "3 of 7 done" and a bar current. */
.task-list { list-style: none; margin: 0; padding: 0; }
.task { display: flex; align-items: center; gap: 12px; padding: 10px 0; border-bottom: 1px solid var(--border); }
.task:last-child { border-bottom: 0; }
.task > label { flex: 1 1 auto; min-width: 0; display: flex; align-items: flex-start; gap: 10px; cursor: pointer; }
.task input[type="checkbox"] { flex: none; width: 18px; height: 18px; margin: 2px 0 0; accent-color: var(--orange); }
.task-text { overflow-wrap: anywhere; }
.task:has(input:checked) .task-text { text-decoration: line-through; color: var(--text-2); }
.task-meta { flex: none; display: flex; align-items: center; gap: var(--s1); font-size: 13px; color: var(--text-2); white-space: nowrap; }
.task-due.overdue { color: var(--red); font-weight: 600; }
.task:has(input:checked) .task-due.overdue { color: var(--text-2); font-weight: 400; }
@media (max-width: 640px) { .task { flex-wrap: wrap; } .task-meta { width: 100%; padding-left: 28px; } }

/* ===== Calendar =====
   Month: .calendar, seven columns of .cal-day (its .cal-date, then a.cal-event links), with .cal-dow headings.
   Week: .cal-week, a column per day on an hour grid; td-shell.js places each event by its data-start and data-end
   (24 hour "9:30"), counting from the grid's data-from hour. .cal-event-2 and -3 are other kinds of event, shown in
   a .chart-legend. On phones the month shows each event as a short bar: put an agenda list beside it. */
.cal-head { display: flex; align-items: center; justify-content: space-between; gap: var(--s1) var(--s2); flex-wrap: wrap; margin-bottom: var(--s2); }
.cal-head h2, .cal-head h3 { margin: 0; }
.cal-nav { display: flex; align-items: center; gap: 4px; }
.calendar { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 1px; background: var(--border); border: 1px solid var(--border); border-radius: var(--r-card); overflow: hidden; }
.cal-dow { padding: 6px 8px; background: var(--sunken); color: var(--text-2); font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; }
.cal-day { min-width: 0; min-height: 108px; padding: 6px; display: flex; flex-direction: column; gap: 3px; background: var(--panel); }
.cal-day.other { background: var(--sunken); }
.cal-date { width: 26px; height: 26px; display: grid; place-items: center; border-radius: 50%; font-size: 13px; font-weight: 600; }
.cal-day.other .cal-date { color: var(--text-2); font-weight: 400; }
.cal-day.today .cal-date { background: var(--orange); color: var(--on-orange); }
.cal-event {
    display: block; min-width: 0; padding: 2px 6px; border-left: 3px solid var(--chart-1); border-radius: 4px;
    background: color-mix(in srgb, var(--chart-1) 14%, var(--panel)); color: var(--text);
    font-size: 12px; line-height: 1.35; text-decoration: none; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
a.cal-event:hover { background: color-mix(in srgb, var(--chart-1) 24%, var(--panel)); }
.cal-event-2 { border-left-color: var(--chart-2); background: color-mix(in srgb, var(--chart-2) 14%, var(--panel)); }
.cal-event-3 { border-left-color: var(--chart-3); background: color-mix(in srgb, var(--chart-3) 16%, var(--panel)); }
.cal-event time { font-family: var(--font-num); font-variant-numeric: tabular-nums; color: var(--text-2); margin-right: 4px; }
.cal-more { font-size: 12px; color: var(--text-2); padding-left: 6px; }
@media (max-width: 700px) {
    .cal-day { min-height: 64px; padding: 4px; }
    .cal-dow { padding: 4px; font-size: 11px; text-align: center; }
    .cal-date { margin: 0 auto; }
    .calendar .cal-event { height: 6px; padding: 0; font-size: 0; border-left-width: 0; background: var(--chart-1); }
    .calendar .cal-event-2 { background: var(--chart-2); }
    .calendar .cal-event-3 { background: var(--chart-3); }
    .cal-more { display: none; }
}
.cal-week { --hour: 48px; display: grid; grid-template-columns: 52px repeat(7, minmax(110px, 1fr)); min-width: 860px; border: 1px solid var(--border); border-radius: var(--r-card); background: var(--panel); }
.cal-wk-day { padding: 8px 6px; border-left: 1px solid var(--border); border-bottom: 1px solid var(--border); background: var(--sunken); font-size: 12px; text-align: center; color: var(--text-2); }
.cal-wk-day strong { display: block; font-size: 16px; color: var(--text); }
.cal-wk-day.today strong { color: var(--orange); }
.cal-wk-corner { border-bottom: 1px solid var(--border); background: var(--sunken); border-top-left-radius: var(--r-card); }
.cal-hours { display: flex; flex-direction: column; }
.cal-hours span { height: var(--hour); padding-right: 6px; text-align: right; font-size: 11px; color: var(--text-2); transform: translateY(-7px); }
.cal-hours span:first-child { visibility: hidden; }
.cal-col { position: relative; border-left: 1px solid var(--border); background-image: repeating-linear-gradient(to bottom, var(--border) 0 1px, transparent 1px var(--hour)); }
.cal-col.today { background-color: color-mix(in srgb, var(--orange) 5%, var(--panel)); }
.cal-col .cal-event { position: absolute; left: 3px; right: 3px; white-space: normal; overflow: hidden; }

/* ===== Signature =====
   .signature: a box to sign in with a finger, pen, or mouse (a canvas, white like paper in both themes), a Clear
   button, and a hidden input that holds the drawing as a PNG data URL for the form to post. A typed full name goes
   with it, so anyone who cannot draw can still sign. .signed shows a signature already given. */
.signature { display: flex; flex-direction: column; gap: var(--s1); max-width: 560px; }
.signature canvas {
    display: block; width: 100%; height: 180px; touch-action: none; cursor: crosshair;
    border: 1px dashed var(--border-strong); border-radius: var(--r-card);
    background: linear-gradient(#b8b8b8, #b8b8b8) no-repeat 20px calc(100% - 44px) / calc(100% - 40px) 1px, #fff;
}
.signature canvas:focus-visible { outline: 2px solid var(--violet); outline-offset: 2px; }
.signature-foot { display: flex; align-items: center; justify-content: space-between; gap: var(--s1); font-size: 13px; color: var(--text-2); }
.signed { display: inline-flex; flex-direction: column; gap: 2px; }
.signed img { display: block; width: 260px; max-width: 100%; height: auto; padding: 4px 8px; border-bottom: 1px solid var(--text); background: #fff; border-radius: var(--r-btn) var(--r-btn) 0 0; }
.signed-name { font-weight: 600; margin-top: 4px; }
.signed-when { font-size: 13px; color: var(--text-2); }
@media print { .signature canvas, .signed img { -webkit-print-color-adjust: exact; print-color-adjust: exact; } }

/* ===== Uptime =====
   .uptime: one bar per day (or hour), oldest on the left. Plain bars are up; .degraded, .down, and .nodata say the
   rest. Each bar's title says the day and what happened, the strip's aria-label sums it up, and .uptime-head gives
   the name and the percent. A table of incidents goes under it. */
.uptime-row + .uptime-row { margin-top: var(--s3); }
.uptime-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--s1); margin-bottom: 6px; }
.uptime-head .num { font-weight: 600; }
.uptime { display: flex; align-items: stretch; gap: 2px; height: 32px; }
.uptime > span { flex: 1 1 0; min-width: 2px; border-radius: 2px; background: var(--teal); }
.uptime > .degraded { background: var(--orange); }
.uptime > .down { background: var(--red); }
.uptime > .nodata { background: var(--sunken); box-shadow: inset 0 0 0 1px var(--border); }
.uptime > span:hover { opacity: 0.7; }
.uptime-foot { display: flex; justify-content: space-between; gap: var(--s1); margin-top: 4px; font-size: 12px; color: var(--text-2); }
@media print { .uptime > span { -webkit-print-color-adjust: exact; print-color-adjust: exact; } }

/* ===== Tags =====
   .tag: a label people add themselves (CRM tags, asset labels), in ul.tags. .tag-remove inside one takes it off.
   .tag-input: the tags and a text box together; Enter or a comma adds what was typed, Backspace in an empty box
   takes the last one off, and each tag posts as a hidden input named by data-tag-name. Counts and system labels
   stay .badge. */
.tags { display: flex; flex-wrap: wrap; gap: 6px; list-style: none; margin: 0; padding: 0; }
.tag { display: inline-flex; align-items: center; gap: 4px; padding: 4px 9px; border: 1px solid var(--border); border-radius: var(--r-pill); background: var(--sunken); color: var(--text); font-size: 13px; line-height: 1.2; white-space: nowrap; }
.tag-remove { width: 18px; height: 18px; margin: -2px -5px -2px 0; padding: 0; display: grid; place-items: center; border: 0; border-radius: 50%; background: none; color: var(--text-2); cursor: pointer; }
.tag-remove:hover { background: var(--nav-hover); color: var(--text); }
.tag-remove .icon { width: 13px; height: 13px; }
.tag-input { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; min-height: calc(1.5em + 20px); padding: 5px 8px; border: 1px solid var(--border-strong); border-radius: var(--r-btn); background: var(--input); cursor: text; }
.tag-input:focus-within { outline: 2px solid var(--violet); border-color: var(--violet); }
.tag-input input:not([type="hidden"]) { flex: 1 1 140px; width: auto; min-width: 140px; padding: 3px 4px; border: 0; background: transparent; }
.tag-input input:focus-visible { outline: 0; }

/* ===== Number stepper =====
   .number-step: a number field between a minus and a plus button (data-step="-1" and "1"), for small counts people
   nudge (drives in a box, copies to print). The buttons stop at min and max; typing still works. */
.number-step { display: inline-flex; align-items: stretch; }
.number-step input[type="number"] { width: 5.5em; text-align: center; border-radius: 0; -moz-appearance: textfield; appearance: textfield; }
.number-step input[type="number"]::-webkit-inner-spin-button, .number-step input[type="number"]::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
.number-step button { width: 40px; display: grid; place-items: center; padding: 0; border: 1px solid var(--border-strong); background: var(--panel); color: var(--text); cursor: pointer; }
.number-step button:first-child { border-right: 0; border-radius: var(--r-btn) 0 0 var(--r-btn); }
.number-step button:last-child { border-left: 0; border-radius: 0 var(--r-btn) var(--r-btn) 0; }
.number-step button:hover:not(:disabled) { background: var(--nav-hover); }
.number-step button:disabled { color: var(--text-2); opacity: 0.5; cursor: not-allowed; }

/* ===== Stars =====
   .stars shows a score: five star icons, .on for the filled ones, and the score in words (.visually-hidden, or
   beside it). fieldset.stars-input gives one: five radios, each with a label holding a star and "3 stars" for
   screen readers. Hovering fills the stars up to the pointer; arrow keys move between them. */
.stars { display: inline-flex; align-items: center; gap: 1px; color: var(--border-strong); vertical-align: middle; }
.stars .icon { width: 18px; height: 18px; }
.stars .icon.on { color: var(--orange); }
.stars-input { display: flex; flex-direction: row; flex-wrap: wrap; align-items: center; gap: 2px; margin: 0; padding: 0; border: 0; border-radius: 0; }
.stars-input legend { width: 100%; margin-bottom: var(--s1); padding: 0; }
.stars-input input { position: absolute; width: 1px; height: 1px; opacity: 0; }
.stars-input label { display: grid; place-items: center; padding: 2px; border-radius: var(--r-btn); color: var(--border-strong); cursor: pointer; }
.stars-input label .icon { width: 30px; height: 30px; }
.stars-input input:checked + label, .stars-input label:has(~ input:checked) { color: var(--orange); }
.stars-input:hover label { color: var(--border-strong); }
.stars-input:hover label:hover, .stars-input:hover label:has(~ label:hover) { color: var(--orange); }
.stars-input input:focus-visible + label { outline: 2px solid var(--violet); outline-offset: 1px; }

/* ===== Avatars: sizes, a status dot, and a stack =====
   .avatar-lg (48px) heads a person's own page or card; .avatar-xl (72px) only on Your account. .avatar-status wraps an
   avatar to add a dot (.dot-ok online, .dot-warn away, .dot-off offline) with the state in words beside it or in its
   title. .avatar-stack overlaps a few avatars (people on a ticket) and ends with a +3 count. */
.avatar-lg { width: 48px; height: 48px; font-size: 17px; }
.avatar-xl { width: 72px; height: 72px; font-size: 24px; }
.avatar-status { position: relative; display: inline-flex; flex: none; vertical-align: middle; }
.avatar-status > .dot { position: absolute; right: -1px; bottom: -1px; width: 10px; height: 10px; border-radius: 50%; border: 2px solid var(--panel); background: var(--text-2); }
.avatar-status > .avatar-lg + .dot, .avatar-status > .avatar-xl + .dot { width: 14px; height: 14px; }
.avatar-status > .dot-ok { background: var(--teal); }
.avatar-status > .dot-warn { background: var(--orange); }
.avatar-status > .dot-off { background: var(--border-strong); }
.avatar-stack { display: inline-flex; align-items: center; vertical-align: middle; }
.avatar-stack > .avatar { margin: 0 0 0 -8px; box-shadow: 0 0 0 2px var(--panel); }
.avatar-stack > .avatar:first-child { margin-left: 0; }
.avatar-stack > .avatar-more { background: var(--sunken); color: var(--text-2); font-size: 12px; }

/* ===== Buttons: groups and busy =====
   .btn-group joins related buttons into one bar (Day, Week, Month as links; Copy, Download, Print).
   A submit button with data-busy="Saving" shows a spinner and that word while its form is sent, and cannot be pressed
   twice; td-shell.js sets aria-busy and puts it back when the page is shown again from the back button. */
.btn-group { display: inline-flex; vertical-align: middle; }
.btn-group > .btn { border-radius: 0; }
.btn-group > .btn + .btn { margin-left: -1px; }
.btn-group > .btn:first-child { border-radius: var(--r-btn) 0 0 var(--r-btn); }
.btn-group > .btn:last-child { border-radius: 0 var(--r-btn) var(--r-btn) 0; }
.btn-group > .btn:hover, .btn-group > .btn:focus-visible { position: relative; z-index: 1; }
.btn-group > .btn[aria-current="page"], .btn-group > .btn[aria-pressed="true"] { background: var(--nav-active); border-color: var(--orange); position: relative; z-index: 2; }
.btn[aria-busy="true"] { cursor: progress; pointer-events: none; }
.btn[aria-busy="true"] > .icon:not(.icon-spin) { display: none; }

/* ===== Numbered pages =====
   .pager-pages inside a .pager: links to nearby pages, the current one with aria-current="page", gaps as .pager-gap. */
.pager-pages { display: flex; align-items: center; gap: 4px; }
.pager-pages a { min-width: 34px; justify-content: center; padding-inline: 8px; }
.pager-pages a[aria-current="page"] { background: var(--nav-active); border-color: var(--orange); color: var(--text); }
.pager-gap { padding: 0 4px; color: var(--text-2); }
@media (max-width: 640px) { .pager-pages a:not([aria-current]), .pager-gap { display: none; } }

/* ===== Jump list: where you are =====
   td-shell.js marks the link to the section being read with aria-current="location" as the page scrolls. */
.jump-list a[aria-current="location"] { color: var(--text); text-decoration: underline; text-decoration-color: var(--orange); text-decoration-thickness: 2px; text-underline-offset: 6px; }

/* ===== Tabs that do not fit =====
   nav.tabs[data-tabs-more]: tabs that would run past the edge move into a More menu at the end instead of scrolling.
   The current tab always stays in the row. td-shell.js does the moving, again when the window changes size. */
.tabs[data-tabs-more] { overflow: visible; }
.tabs .tabs-more { margin-left: auto; }
.tabs .tabs-more > summary { list-style: none; display: inline-flex; align-items: center; gap: 6px; padding: 10px 14px; color: var(--text-2); cursor: pointer; border-bottom: 2px solid transparent; font-weight: 500; }
.tabs .tabs-more > summary::-webkit-details-marker { display: none; }
.tabs .tabs-more > summary:hover { color: var(--text); }
.tabs .tabs-more[hidden] { display: none; }
.tabs .tabs-more .menu { right: 0; left: auto; }
.tabs .tabs-more .menu a[aria-current="page"] { font-weight: 600; }

/* ===== Details in columns =====
   dl.details-grid of div pairs (a dt label over its dd value), in as many columns as fit, at least 200px each.
   For a record's facts at a glance; .details (label beside value) for a short list in a narrow card. */
.details-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: var(--s2) var(--s3); margin: 0; }
.details-grid > div { min-width: 0; }
.details-grid dt { margin-bottom: 2px; color: var(--text-2); font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; }
.details-grid dd { margin: 0; overflow-wrap: anywhere; }

/* ===== Parts of a whole =====
   .progress-stack: one bar split into parts (.s1 to .s3, and .so for Other: the chart colors), each span's data-part its percent, set by
   td-shell.js so pages need no inline styles. Always with a .chart-legend that gives each part's number. */
.progress-stack { display: flex; height: 10px; border-radius: var(--r-pill); overflow: hidden; background: var(--sunken); }
.progress-stack > span { width: 0; background: var(--chart-1); }
.progress-stack > span + span { box-shadow: inset 2px 0 0 var(--panel); }
.progress-stack > .s2 { background: var(--chart-2); }
.progress-stack > .s3 { background: var(--chart-3); }
.progress-stack > .so { background: var(--chart-other); }
.progress-stack + .chart-legend { margin-top: var(--s1); }
@media print { .progress-stack > span { -webkit-print-color-adjust: exact; print-color-adjust: exact; } }

/* ===== Icon lists =====
   ul.icon-list: a short list where each line starts with an icon that says what kind of line it is: what a plan
   includes (check), what it does not (x), steps, or facts. The icon is decoration; the words say it all. */
.icon-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--s1); }
.icon-list > li { display: flex; align-items: flex-start; gap: var(--s1); }
.icon-list > li > .icon { flex: none; margin-top: 2px; color: var(--text-2); }
.icon-list .icon-yes { color: var(--teal); }
.icon-list .icon-no { color: var(--red); }

/* ===== Confetti =====
   TD.confetti() or [data-confetti] on a button (or on a .alert the server renders) bursts confetti over the page once,
   for a real milestone: a launch, a hundredth job, a certificate for a big job. Never for routine saves. Nothing moves
   for people who ask for reduced motion. The canvas ignores the pointer and removes itself. */
.td-confetti { position: fixed; inset: 0; width: 100vw; height: 100vh; pointer-events: none; z-index: 1000; }
@media print { .td-confetti { display: none; } }

/* ===== Confirm dialog =====
   Built by td-shell.js for any form or button with data-confirm. See README, Confirm dialog. */
.dialog {
    width: min(440px, calc(100vw - 32px)); padding: var(--s3);
    background: var(--panel); color: var(--text);
    border: 1px solid var(--border); border-radius: var(--r-card); box-shadow: var(--shadow-md);
}
.dialog::backdrop { background: rgba(0, 0, 0, 0.45); }
.dialog h2 { margin: 0 0 var(--s1); font-size: 19px; display: flex; gap: 10px; align-items: center; }
.dialog-danger h2 .icon { color: var(--red); }
.dialog p { margin: 0 0 var(--s3); color: var(--text-2); }
.dialog .form-actions { justify-content: flex-end; }

/* ===== Toasts =====
   Short messages after an action. Server rendered: <div class="alert alert-ok toast" role="status">...</div>.
   From script: TD.toast('Saved.', 'ok'), or a td:toast event. They stack in the bottom right corner. */
.toast-region {
    position: fixed; right: var(--s3); bottom: var(--s3); z-index: 60;
    display: flex; flex-direction: column; gap: var(--s1); align-items: flex-end;
    width: min(420px, calc(100vw - 32px)); pointer-events: none;
}
.toast { pointer-events: auto; width: 100%; margin: 0; box-shadow: var(--shadow-md); animation: td-toast-in 160ms ease-out; }
.toast .toast-close { margin: -6px -6px -6px auto; width: 28px; height: 28px; flex: 0 0 auto; }
.toast.is-leaving { opacity: 0; transform: translateY(8px); transition: opacity 160ms ease, transform 160ms ease; }
@keyframes td-toast-in { from { opacity: 0; transform: translateY(8px); } }
@media (max-width: 900px) { .toast-region { right: var(--s2); bottom: var(--s2); } }
@media (prefers-reduced-motion: reduce) { .toast { animation: none; } .toast.is-leaving { transition: none; } }

/* ===== Print =====
   Records print clean on white: logo and app name at the top, then the page title and content.
   Navigation, buttons, forms' actions, and toasts are left out. .no-print and .print-only for page specific control. */
.print-only { display: none !important; }
@media print {
    :root:not(#td-print) {
        --page: #FFFFFF; --panel: #FFFFFF; --sunken: #F6F7F8; --input: #FFFFFF;
        --border: #C3C8CF; --border-strong: #9AA2AD; --text: #000000; --text-2: #3F454D;
        --violet: #4F4380; --teal: #155A68; --shadow-sm: none; --shadow-md: none;
        --sidebar: #FFFFFF; --nav-hover: #FFFFFF; --nav-active: #FFFFFF; --peach: #FBEBDD;
        color-scheme: light;
    }
    :root:not(#td-print) .only-dark { display: none !important; }
    :root:not(#td-print) .only-light { display: revert !important; }
    @page { margin: 16mm 14mm; }
    body { background: #FFFFFF; font-size: 11pt; }
    .shell { display: block; min-height: 0; }
    .sidebar { position: static; width: auto !important; height: auto; border: 0; background: #FFFFFF; transform: none !important; box-shadow: none; }
    .sidebar-brand { height: auto; padding: 0 0 12px; border-bottom: 2px solid var(--orange); justify-content: flex-start !important; }
    .sidebar-brand .brand-logo { display: flex !important; }
    .sidebar-brand .brand-logo img { height: 28px; }
    .sidebar-brand .brand-icon { display: none !important; }
    .sidebar-brand .brand-app { display: inline !important; }
    .topbar { position: static; background: #FFFFFF; border: 0; }
    .shell-top .topbar-row, .shell-top .pagehead { padding-inline: 0; }
    .topbar-row { min-height: 0; padding-bottom: 12px; border-bottom: 2px solid var(--orange); }
    .topbar-brand .brand-logo img { height: 28px; }
    .topbar-brand .brand-app { display: inline !important; }
    .topnav, .topbar-actions,
    .nav, .sidebar-foot, .scrim, .menu-toggle, .pagehead-actions, .crumbs, .toast-region, .dialog, .skip-link,
    .form-actions, .search-row, .list-actions, .pager, .icon-btn, .btn, .no-print,
    nav.tabs, .cal-nav, .composer, .tag-remove, .number-step button { display: none !important; }
    .print-only { display: revert !important; }
    .pagehead { position: static; padding: 12px 0; min-height: 0; background: none; backdrop-filter: none; -webkit-backdrop-filter: none; border: 0; }
    .pagehead h1 { font-size: 18pt; }
    .content { max-width: none; padding: 0; }
    .card { box-shadow: none; padding: 12px 0; border: 0; border-top: 1px solid var(--border); border-radius: 0; break-inside: avoid; }
    .table-wrap, .table-wrap.table-scroll { overflow: visible; max-height: none; }
    .table-scroll .table thead th { position: static; }
    .table tr.row-more[hidden] { display: table-row; }
    .table thead { display: table-header-group; }
    .table tr, .details dt, .details dd, .photo-grid figure { break-inside: avoid; }
    .table tbody tr:hover { background: none; }
    a { color: inherit; text-decoration: none; }
    .status, .badge { border-color: currentColor; }
}
