/*
 * Design tokens - TRACE design system (2026-10-06 redesign, see
 * .claude/Plan.md's "Full UI redesign to match TRACE" entry). Values are
 * taken 1:1 from TRACE's own style.css: forest/sage greens (#0B2019,
 * #153C2E, #5C7868, #E3EAE3, #F9F9F4) with a single orange accent.
 *
 * Every token NAME that existed before the redesign is kept (including the
 * back-compat aliases at the bottom of :root), so per-screen $extraCss
 * blocks keep resolving - only the values changed.
 *
 * Light is the unattributed :root default; :root[data-theme="dark"] is the
 * explicit dark choice; the prefers-color-scheme block covers "system"
 * (no stored choice - see public/ui/theme-init.js). The two dark blocks are
 * deliberately identical.
 */
:root {
    --bg:#F9F9F4; --panel-bg:#FFFFFF; --card-bg:#E3EAE3; --card-bg-hover:#D6E1D4; --card-bg-selected:var(--accent-soft);
    --border:#DCE5DA; --text:#0B2019; --text-muted:#5C7868;
    --shadow:0 2px 10px rgba(11, 32, 25, 0.08); --overlay:rgba(11, 32, 25, 0.5); --radius:12px;
    --thumb-bg:#F3F5F1; --input-bg:#F9F9F4;

    --header-grad-1:#E3EAE3; --header-grad-2:#F9F9F4; --header-text:#0B2019; --header-text-muted:rgba(11,32,25,0.65);
    --header-btn-bg:rgba(11,32,25,0.08); --header-btn-bg-hover:rgba(11,32,25,0.14); --header-btn-border:rgba(11,32,25,0.18);
    --header-btn-border-disabled:rgba(11,32,25,0.08);
    --header-segmented-bg:rgba(11,32,25,0.06); --header-segmented-hover:rgba(11,32,25,0.08);
    --header-ghost-border:rgba(11,32,25,0.35);
    --brand-icon-bg:#153C2E; --brand-icon-text:#F9F9F4;

    --accent:#C37B22; --accent-hover:#A0651C; --accent-soft:#F7F0E9; --accent-soft-text:#825217; --accent-on-accent:#FFFFFF;
    /* TRACE has exactly one accent; the old ORBIT "highlight" pair now
       points at it so nothing still renders the previous orange/teal mix. */
    --highlight:var(--accent); --highlight-soft:var(--accent-soft); --highlight-text:var(--accent-soft-text);

    --status-active-bg:#D9F2DF;    --status-active-text:#146C2E;
    --status-ongoing-bg:#E6E0FB;   --status-ongoing-text:#5B3DC4;
    --status-paused-bg:#FFF2CF;    --status-paused-text:#8A5B00;
    --status-finished-bg:#DBE9FF;  --status-finished-text:#1C53B5;
    --status-aborted-bg:#FBDEDE;   --status-aborted-text:#B3261E;

    --sidebar-width:200px;

    /* --- back-compat aliases: pre-redesign token names --- */
    --bg-elevated:var(--panel-bg); --bg-sidebar:var(--panel-bg);
    --text-dim:var(--text-muted); --accent-text:var(--accent);
    --danger:var(--status-aborted-bg); --danger-text:var(--status-aborted-text);
    --success:var(--status-active-bg); --success-text:var(--status-active-text);
    --warning-text:var(--status-paused-text); --warning-border:var(--accent);
}

@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        --bg:#0B2019; --panel-bg:#153C2E; --card-bg:#11291F; --card-bg-hover:#17341F;
        --border:#234433; --text:#F9F9F4; --text-muted:#8FA89B;
        --shadow:0 2px 14px rgba(0, 0, 0, 0.45); --overlay:rgba(3, 8, 6, 0.7);
        --thumb-bg:#D7DCE3; --input-bg:#0B2019;
        --header-grad-1:#153C2E; --header-grad-2:#0B2019; --header-text:#F9F9F4; --header-text-muted:rgba(249,249,244,0.72);
        --header-btn-bg:rgba(249,249,244,0.12); --header-btn-bg-hover:rgba(249,249,244,0.22); --header-btn-border:rgba(249,249,244,0.22);
        --header-btn-border-disabled:rgba(249,249,244,0.08);
        --header-segmented-bg:rgba(249,249,244,0.14); --header-segmented-hover:rgba(249,249,244,0.12);
        --header-ghost-border:rgba(249,249,244,0.5);
        --brand-icon-bg:#E3993E; --brand-icon-text:#0B2019;
        --accent:#E5AF6C; --accent-hover:#EBC18E; --accent-soft:#513B1F; --accent-soft-text:#F1D4B1; --accent-on-accent:#0B1020;
        --status-active-bg:#16301F;   --status-active-text:#6FDB92;
        --status-ongoing-bg:#26204A;  --status-ongoing-text:#C3B3FB;
        --status-paused-bg:#3A2E10;   --status-paused-text:#F0C04D;
        --status-finished-bg:#16233D; --status-finished-text:#8AB3FF;
        --status-aborted-bg:#3A1717;  --status-aborted-text:#FF8F87;
    }
}

:root[data-theme="dark"] {
    --bg:#0B2019; --panel-bg:#153C2E; --card-bg:#11291F; --card-bg-hover:#17341F;
    --border:#234433; --text:#F9F9F4; --text-muted:#8FA89B;
    --shadow:0 2px 14px rgba(0, 0, 0, 0.45); --overlay:rgba(3, 8, 6, 0.7);
    --thumb-bg:#D7DCE3; --input-bg:#0B2019;
    --header-grad-1:#153C2E; --header-grad-2:#0B2019; --header-text:#F9F9F4; --header-text-muted:rgba(249,249,244,0.72);
    --header-btn-bg:rgba(249,249,244,0.12); --header-btn-bg-hover:rgba(249,249,244,0.22); --header-btn-border:rgba(249,249,244,0.22);
    --header-btn-border-disabled:rgba(249,249,244,0.08);
    --header-segmented-bg:rgba(249,249,244,0.14); --header-segmented-hover:rgba(249,249,244,0.12);
    --header-ghost-border:rgba(249,249,244,0.5);
    --brand-icon-bg:#E3993E; --brand-icon-text:#0B2019;
    --accent:#E5AF6C; --accent-hover:#EBC18E; --accent-soft:#513B1F; --accent-soft-text:#F1D4B1; --accent-on-accent:#0B1020;
    --status-active-bg:#16301F;   --status-active-text:#6FDB92;
    --status-ongoing-bg:#26204A;  --status-ongoing-text:#C3B3FB;
    --status-paused-bg:#3A2E10;   --status-paused-text:#F0C04D;
    --status-finished-bg:#16233D; --status-finished-text:#8AB3FF;
    --status-aborted-bg:#3A1717;  --status-aborted-text:#FF8F87;
}
/*
 * Component library - TRACE design system (2026-10-06 redesign, see
 * .claude/Plan.md "Full UI redesign to match TRACE"). Shell structure and
 * component values follow TRACE's own style.css: light gradient topbar
 * with the logo, a module ("view") switch dropdown, small rounded .btn
 * buttons; below it a full-height grid of independent cards - contextual
 * sidebar, main panel, optional info panel - each scrolling on its own.
 *
 * Concatenated after tokens.css into public/ui/app.css at deploy time.
 */
* { box-sizing:border-box; }

/* Overflow safety net (kept from the pre-redesign shell): html AND body
   clip horizontally, so one oversized descendant can never drag the whole
   page wider than an iPhone screen; overflow-wrap:anywhere also shrinks
   min-content sizing inside grid/flex items, which break-word does not.
   text-size-adjust:100% stops iOS "font boosting" per element. */
html { overflow-x:hidden; -webkit-text-size-adjust:100%; text-size-adjust:100%; }
html, body { height:100%; }
body {
    font-family:"Segoe UI","Inter",system-ui,-apple-system,sans-serif;
    font-size:14px; background:var(--bg); color:var(--text); margin:0;
    display:flex; flex-direction:column; height:100vh;
    overflow-wrap:anywhere; overflow-x:hidden; overflow-y:hidden;
    transition:background 0.2s ease, color 0.2s ease;
}
a { color:var(--accent); }
a:focus-visible, button:focus-visible, summary:focus-visible {
    outline:2px solid var(--accent); outline-offset:2px; border-radius:4px;
}
.muted { color:var(--text-muted); }
hr { border:none; border-top:1px solid var(--border); }

/* ---------- Top bar ---------- */
.topbar {
    display:flex; align-items:center; gap:20px; flex-wrap:wrap; flex:0 0 auto;
    padding:12px 24px; position:relative; z-index:30;
    background:linear-gradient(135deg, var(--header-grad-1), var(--header-grad-2));
    color:var(--header-text); box-shadow:var(--shadow);
}
.topbar__brand { display:flex; align-items:center; gap:10px; color:var(--header-text); text-decoration:none; flex-shrink:0; }
.topbar__icon {
    width:44px; height:44px; border-radius:10px; flex-shrink:0; display:flex; align-items:center;
    justify-content:center; font-weight:700; font-size:15px;
    background:var(--brand-icon-bg); color:var(--brand-icon-text);
}
.topbar__logo-img { height:56px; width:auto; max-width:320px; object-fit:contain; flex-shrink:0; }
/* Light/dark logo: both <img> are always rendered, the theme decides which
   one shows - same three selectors tokens.css uses to pick a palette. */
.topbar__logo-img--dark { display:none; }
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .topbar__logo-img--light { display:none; }
    :root:not([data-theme="light"]) .topbar__logo-img--dark { display:block; }
}
:root[data-theme="dark"] .topbar__logo-img--light { display:none; }
:root[data-theme="dark"] .topbar__logo-img--dark { display:block; }

/* Module switch (TRACE's view switch): a <details> dropdown. */
.view-switch { position:relative; flex:0 0 auto; }
.view-switch__trigger {
    display:flex; align-items:center; gap:6px; list-style:none; cursor:pointer; white-space:nowrap;
    background:var(--header-segmented-bg); color:var(--header-text); border-radius:8px;
    padding:8px 12px; font-size:13px; font-weight:600; user-select:none;
}
.view-switch__trigger::-webkit-details-marker { display:none; }
.view-switch__trigger:hover { background:var(--header-segmented-hover); }
.view-switch__caret { font-size:11px; opacity:0.75; }
.view-switch__dropdown {
    position:absolute; top:calc(100% + 6px); left:0; display:flex; flex-direction:column; gap:2px;
    min-width:220px; background:var(--card-bg); border:1px solid var(--border); border-radius:10px;
    padding:6px; box-shadow:var(--shadow); z-index:60;
}
.view-switch__item {
    display:flex; align-items:center; gap:8px; color:var(--text); text-decoration:none;
    padding:8px 10px; border-radius:6px; font-size:14px;
}
.view-switch__item:hover { background:var(--card-bg-hover); }
.view-switch__item.active { background:var(--accent); color:var(--accent-on-accent); }

.topbar__actions { display:flex; gap:8px; flex-wrap:wrap; align-items:center; }
.topbar__actions form { display:inline-flex; margin:0; }
.topbar__tools { margin-left:auto; display:flex; gap:8px; align-items:center; flex-wrap:wrap; }
.topbar__tools form { display:inline-flex; margin:0; }
/* Specificity beats the generic input[type=text] rule further down. */
.topbar .topbar__search input {
    border:1px solid var(--header-btn-border); border-radius:8px; padding:9px 12px; margin:0;
    width:auto; min-width:220px; font-size:14px; background:rgba(255,255,255,0.94); color:#1A2027;
}
.topbar .topbar__search input::placeholder { color:#5C6670; }
.topbar .topbar__search input:focus { outline:none; border-color:var(--accent); box-shadow:0 0 0 3px var(--accent-soft); }
.topbar .menu-toggle, .topbar .search-toggle { display:none; }

/* ---------- Buttons (TRACE .btn) ----------
   Header-coloured by default (translucent on the gradient); inside panels
   the generic `button` rules further down apply instead. */
.btn {
    display:inline-flex; align-items:center; justify-content:center; gap:6px;
    border:1px solid var(--header-btn-border); border-radius:8px; padding:9px 16px;
    font-size:14px; font-weight:600; line-height:1.2; cursor:pointer; font-family:inherit;
    background:var(--header-btn-bg); color:var(--header-text); text-decoration:none; white-space:nowrap;
    transition:background 0.15s, opacity 0.15s, transform 0.05s, border-color 0.15s;
}
.btn:hover { background:var(--header-btn-bg-hover); }
.btn:active { transform:translateY(1px); }
.btn:disabled { opacity:0.45; cursor:not-allowed; border-color:var(--header-btn-border-disabled); }
.btn--primary { background:var(--accent); color:var(--accent-on-accent); border-color:transparent; }
.btn--primary:hover { background:var(--accent-hover); }
.btn--ghost { background:transparent; border:1px solid var(--header-ghost-border); }
.btn--small { padding:7px 12px; font-size:13px; }
.btn--icon { padding:8px 11px; font-size:15px; line-height:1; }
.btn--danger { border-color:var(--status-aborted-text); color:var(--status-aborted-text); }
.btn--danger:hover { background:var(--status-aborted-bg); }
/* Plain links/buttons passed as topbar actions by older screens get the
   same treatment without needing their own classes. */
.topbar__actions > a:not(.btn), .topbar__actions > form > button:not(.btn), .topbar__actions > button:not(.btn) {
    display:inline-flex; align-items:center; gap:6px; border:1px solid var(--header-btn-border); border-radius:8px;
    padding:9px 16px; font-size:14px; font-weight:600; line-height:1.2; font-family:inherit; cursor:pointer;
    background:var(--header-btn-bg); color:var(--header-text); text-decoration:none; white-space:nowrap;
}
.topbar__actions > a:not(.btn):hover, .topbar__actions > form > button:not(.btn):hover { background:var(--header-btn-bg-hover); }
.topbar__actions .link-button, .topbar__actions form.delete-form button:not(.btn) {
    border-color:var(--status-aborted-text); color:var(--status-aborted-text); background:var(--header-btn-bg);
}
.topbar__actions form.delete-form button:not(.btn):hover { background:var(--status-aborted-bg); }

/* Custodian admin/normal toggle - status colours mark the elevated mode. */
.ui-mode-badge--admin { background:var(--status-paused-bg); color:var(--status-paused-text); border-color:transparent; }
.ui-mode-badge--normal { background:var(--status-active-bg); color:var(--status-active-text); border-color:transparent; }
.ui-mode-badge--admin:hover, .ui-mode-badge--normal:hover { filter:brightness(0.96); }

/* Compact header for laptop-width screens (TRACE's 1500px step). */
@media (max-width: 93.75rem) {
    .topbar { gap:10px; padding:10px 16px; }
    .topbar .btn, .topbar__actions > a:not(.btn) { padding:7px 11px; font-size:13px; }
    .topbar .btn--icon { padding:7px 9px; }
    .topbar__logo-img { height:40px; max-width:220px; }
    .topbar__icon { width:36px; height:36px; font-size:13px; }
    .topbar .topbar__search input { min-width:140px; }
}

/* ---------- Layout: sidebar | main | info, each its own card ---------- */
.layout {
    display:grid; grid-template-columns:var(--sidebar-width) minmax(0, 1fr); gap:16px; padding:16px;
    flex:1 1 auto; min-height:0;
}
.layout--has-info { grid-template-columns:var(--sidebar-width) minmax(0, 1fr) minmax(0, 360px); }
.layout--has-info.layout--info-wide { grid-template-columns:var(--sidebar-width) minmax(0, 1.3fr) minmax(0, 1fr); }
@media (max-width: 68.75rem) {
    .layout { grid-template-columns:180px minmax(0, 1fr); }
    .layout--has-info { grid-template-columns:180px minmax(0, 1fr) minmax(0, 300px); }
    .layout--has-info.layout--info-wide { grid-template-columns:180px minmax(0, 1.1fr) minmax(0, 1fr); }
}
.sidebar, .content, .info-panel {
    background:var(--panel-bg); border:1px solid var(--border); border-radius:var(--radius);
    box-shadow:var(--shadow); min-height:0; height:100%; overflow-y:auto; min-width:0;
}

/* Sidebar: uppercase group labels + stacked pill buttons. */
.sidebar { padding:16px 12px; display:flex; flex-direction:column; gap:6px; }
.sidebar h3 { margin:4px 0 8px; font-size:12px; text-transform:uppercase; letter-spacing:0.06em; color:var(--text-muted); }
.sidebar hr { width:100%; margin:6px 0; }
.sidebar__btn {
    display:block; background:var(--card-bg); color:var(--text); border:1px solid transparent; border-radius:8px;
    padding:9px 10px; text-align:left; cursor:pointer; font-size:13px; font-weight:600; text-decoration:none;
    font-family:inherit; width:100%; transition:background 0.15s, border-color 0.15s;
}
.sidebar__btn:hover { background:var(--accent-soft); border-color:var(--accent); }
.sidebar__btn.active { background:var(--accent); color:var(--accent-on-accent); }
.sidebar__btn--clear { margin-top:4px; color:var(--text-muted); }
.sidebar__empty { color:var(--text-muted); font-size:13px; margin:0; }
.sidebar label { font-size:12px; font-weight:700; color:var(--text-muted); margin-top:2px; }
.sidebar select { padding:7px 8px; font-size:13px; background:var(--card-bg); }
.sidebar__extra { display:flex; flex-direction:column; gap:6px; }
.sidebar-backdrop, .sidebar-close { display:none; }

/* Main panel. Prose screens keep a comfortable reading width. */
.content { padding:20px 24px; }
.content .inner { max-width:52rem; margin:0 auto; }
.content .inner.wide { max-width:none; }
.content h1:first-of-type { margin-top:0; }

/* Info panel (right card). */
.info-panel { padding:20px; font-size:14px; }
.info-panel h1, .info-panel h2:first-child { margin-top:0; }
.info-toggle { display:none; }

/* TRACE info rows - label/value lines with a thin divider. */
.info-row { display:flex; gap:10px; padding:6px 0; border-bottom:1px solid var(--border); font-size:14px; }
.info-row__label { font-weight:700; width:130px; flex-shrink:0; }
.info-desc { margin-top:14px; white-space:pre-line; font-size:14px; color:var(--text-muted); }
.event-item { background:var(--card-bg); border:1px solid var(--border); border-radius:10px; padding:10px 12px; margin-top:10px; }

/* TRACE list rows: thumbnail + title/badge + meta + description. */
.project-list { display:flex; flex-direction:column; gap:10px; margin-top:16px; }
.project-card {
    display:flex; gap:14px; background:var(--card-bg); border-radius:var(--radius); padding:14px;
    border:2px solid transparent; color:var(--text); text-decoration:none;
    transition:background 0.15s, border-color 0.15s;
}
.project-card:hover { background:var(--card-bg-hover); border-color:var(--accent); }
.project-card.selected { background:var(--card-bg-selected); border-color:var(--accent); }
.project-card__thumb {
    width:72px; height:72px; border-radius:10px; flex-shrink:0; display:flex; align-items:center; justify-content:center;
    color:var(--accent-on-accent); font-weight:700; font-size:18px; background:var(--accent); overflow:hidden;
}
.project-card__thumb img { width:100%; height:100%; object-fit:contain; background:var(--thumb-bg); }
.project-card__body { flex:1; min-width:0; display:block; }
.project-card__title, .project-card__meta, .project-card__desc { display:block; }
.project-card__top { display:flex; justify-content:space-between; align-items:baseline; gap:10px; flex-wrap:wrap; }
.project-card__title { font-size:17px; font-weight:700; margin:0; color:var(--text); }
.project-card__badges { display:flex; gap:6px; flex-wrap:wrap; }
.project-card .gem-type-badge, .project-card .tag-pill { background:var(--panel-bg); }
.project-card__meta { color:var(--text-muted); font-size:13px; margin:4px 0 0; }
.project-card__desc { color:var(--text-muted); font-size:13px; margin:6px 0 0; }
.panel-header { display:flex; align-items:center; justify-content:space-between; gap:12px; flex-wrap:wrap; }
.panel-header h1 { margin:0; }
.panel-count { color:var(--text-muted); font-size:13px; margin:4px 0 0; }
.empty-state { text-align:center; color:var(--text-muted); padding:60px 20px; }

/* ---------- Mobile (TRACE's 860px step) ----------
   Page flows naturally and scrolls as a whole; the sidebar becomes an
   off-canvas drawer opened from the ☰ button (body.sidebar-open, app.js);
   the info card stacks under the main card. */
@media (max-width: 53.75rem) {
    html, body { height:auto; min-height:100vh; overflow-y:visible; }
    body { display:block; }
    .topbar { padding:10px 14px; gap:8px; position:sticky; top:0; }
    .topbar__logo-img { height:36px; max-width:150px; }
    .topbar__icon { width:36px; height:36px; }
    .topbar .menu-toggle, .topbar .search-toggle { display:inline-flex; }
    .topbar__actions .btn__label { display:none; }
    .topbar .topbar__search { display:none; order:99; flex-basis:100%; }
    .topbar .topbar__search.open { display:flex; }
    .topbar .topbar__search input { width:100%; min-width:0; font-size:16px; }
    .layout, .layout--has-info, .layout--has-info.layout--info-wide {
        display:flex; flex-direction:column; padding:12px; gap:12px;
    }
    .content, .info-panel { height:auto; overflow:visible; width:100%; }
    .content { padding:16px; }
    .content .inner, .content article { max-width:100%; }
    .sidebar {
        display:none; position:fixed; top:0; left:0; bottom:0; width:85%; max-width:20rem; height:auto;
        z-index:50; border-radius:0; box-shadow:0.5rem 0 2rem rgba(0,0,0,0.4);
    }
    body.sidebar-open .sidebar { display:flex; }
    .sidebar-backdrop { display:none; position:fixed; inset:0; background:var(--overlay); z-index:40; border:none; padding:0; margin:0; }
    body.sidebar-open .sidebar-backdrop { display:block; }
    body.sidebar-open { overflow:hidden; }
    .sidebar-close { display:block; align-self:flex-end; background:none; border:none; color:var(--text-muted); font-size:1.5rem; line-height:1; padding:0; cursor:pointer; }
    /* "Page info" collapses behind a toggle so the article comes first. */
    .info-toggle {
        display:block; width:100%; text-align:left; background:var(--card-bg); border:1px solid var(--border);
        border-radius:8px; padding:0.6rem 0.9rem; font-size:0.85rem; color:var(--text); cursor:pointer;
    }
    .info-body { display:none; }
    .info-panel.open .info-body { display:block; margin-top:0.75rem; }
    table { display:block; overflow-x:auto; white-space:nowrap; }
    .item { flex-wrap:wrap; }
    .project-card__thumb { width:56px; height:56px; }
}
@media (max-width: 40rem) {
    .view-switch__label { display:none; }
    .topbar__tools { gap:6px; }
    .topbar .btn--icon { padding:7px 8px; }
}

/* 2026-09-16 contrast/hierarchy pass (.claude/Plan.md): h1 and the
   Section/Page/Subsection "entity name" colour below used to be the
   static light-mode teal hex (#3C7586) rather than var(--accent) - since
   --accent deliberately SHIFTS to aqua in dark mode (tokens.css's Round D
   comment: teal-on-navy measures ~2.7:1, failing WCAG AA), every one of
   these hardcoded copies was silently stuck at the old light-mode value
   and failed the same way in dark mode (verified 3.0-3.6:1, see this
   round's done-note in Plan.md for the full table) while every OTHER
   accent-coloured element on the page correctly shifted to the readable
   aqua. Switching to var(--accent) fixes the dark-mode contrast bug and
   is a no-op in light mode (--accent's light value already equals the old
   hardcoded hex). The other hardcoded copies (home.php's TOC headings,
   pages_view.php's article headings, literature_index.php's/
   literature_duplicates.php's title links, .subsection-card/.section-list
   .item .title below) get the same fix for the same reason. */
h1 { font-size:22px; font-weight:700; margin:0 0 0.25rem; color:var(--text); }
h2 { font-size:1.1rem; }
/* Sidebar group titles: spec size/case/tracking, applied via .nav-group-label above. */
.error { background:var(--status-aborted-bg); color:var(--status-aborted-text); padding:0.6rem 0.9rem; border-radius:8px; margin:1rem 0; font-size:0.9rem; }
.notice { background:var(--status-active-bg); color:var(--status-active-text); padding:0.6rem 0.9rem; border-radius:8px; margin:1rem 0; font-size:0.9rem; }
.hint { background:var(--card-bg); color:var(--text-muted); padding:0.6rem 0.9rem; border-radius:8px; margin:1rem 0; font-size:0.85rem; }
.banner { background:var(--status-ongoing-bg); color:var(--status-ongoing-text); padding:0.6rem 0.9rem; border-radius:8px; margin:1rem 0; font-size:0.9rem; }
/* 2026-09-23 user feedback: a plain text link ("just text and sometimes
   gets missed") wasn't a strong enough affordance for a control this
   important - every "back to X" navigation link across the app uses this
   class. Styled as a real pill button now, matching the weight of
   button.secondary elsewhere in this file (bordered, own background,
   rounded, hover feedback) rather than reading as ordinary body text with
   an arrow glyph in front of it. */
a.back {
    display:inline-flex; align-items:center; gap:0.35rem; color:var(--accent);
    text-decoration:none; font-size:0.85rem; font-weight:600;
    background:var(--card-bg); border:1px solid var(--border); border-radius:999px;
    padding:0.45rem 0.9rem; margin-bottom:0.75rem;
    transition:background 0.15s, border-color 0.15s;
}
a.back:hover { background:var(--card-bg-hover); border-color:var(--accent); }

/* Breadcrumb - src/Http/breadcrumb.php. Segments before the current page
   are plain text (color:var(--text-muted)), not links - there's no
   reader-facing section/collection browse page to link them to yet. */
.breadcrumb { margin-bottom:0.75rem; }
.breadcrumb ol { display:flex; flex-wrap:wrap; align-items:center; list-style:none; margin:0; padding:0; gap:0.3rem; font-size:0.8rem; color:var(--text-muted); }
.breadcrumb li:not(:last-child)::after { content:"▸"; margin-left:0.3rem; color:var(--text-muted); }
.breadcrumb li:last-child span { color:var(--text); }
label { display:block; margin-top:1rem; font-size:0.85rem; color:var(--text-muted); }
.field-hint { color:var(--text-muted); font-size:0.8rem; margin-top:0.25rem; }
/* Form fields: unified per spec (border/radius/padding, --bg background,
   accent focus ring). font-size:1rem (16px) is required, not cosmetic -
   iOS Safari auto-zooms the whole page on focus for any text input/
   textarea whose *computed* font-size is under 16px, and neither of these
   rules set one explicitly otherwise (textarea's UA-stylesheet default is
   smaller than 16px in most browsers), so every field on the site was
   triggering it - keep this at 1rem across every future edit here. */
input[type=text], input[type=email], input[type=password], input[type=number],
input[type=date], input[type=search], input[type=url], input[type=file], select, textarea {
    width:100%; padding:9px 10px; margin-top:0.25rem; border-radius:8px; border:1px solid var(--border);
    background:var(--bg); color:var(--text); box-sizing:border-box; font-family:inherit; font-size:1rem;
    transition:border-color 0.15s, box-shadow 0.15s;
}
input[type=text]:focus, input[type=email]:focus, input[type=password]:focus, input[type=number]:focus,
input[type=date]:focus, input[type=search]:focus, input[type=url]:focus, select:focus, textarea:focus {
    outline:none; border-color:var(--accent); box-shadow:0 0 0 3px var(--accent-soft);
}
textarea { font-family:ui-monospace,monospace; min-height:16rem; resize:vertical; }

/* Attached-images gallery - pages_edit.php + ui/app.js's caption-sync. */
.media-gallery { display:grid; grid-template-columns:repeat(auto-fill, minmax(12rem, 1fr)); gap:1rem; margin:0.75rem 0 1.25rem; }
.media-item { background:var(--card-bg); border:1px solid var(--border); border-radius:var(--radius); padding:0.75rem; }
.media-item img { width:100%; height:8rem; object-fit:cover; border-radius:8px; display:block; }
.media-item-name { font-size:0.8rem; margin-top:0.4rem; word-break:break-all; }
.media-item-used { display:block; color:var(--status-active-text); font-size:0.7rem; margin-top:0.1rem; }
.media-item label { margin-top:0.5rem; font-size:0.75rem; }
.media-item label input { font-size:1rem; }
.media-item form { margin-top:0.5rem; }
.media-item button.danger, .media-item button.secondary { padding:0.3rem 0.6rem; font-size:0.8rem; }
.media-item .media-apply-btn { margin-top:0.4rem; width:100%; }
.media-item .field-hint { margin-top:0.25rem; }

/* Markdown editor toolbar - src/Http/markdown_toolbar.php + ui/app.js.
   Sits directly above its textarea (label > .md-toolbar + textarea), so
   the toolbar's bottom border and the textarea's top corners join into
   one visual block. */
.md-toolbar { display:flex; flex-wrap:wrap; gap:0.3rem; align-items:center; margin-top:0.25rem; padding:0.4rem; background:var(--card-bg); border:1px solid var(--border); border-radius:8px 8px 0 0; border-bottom:none; }
.md-toolbar + textarea { margin-top:0; border-top-left-radius:0; border-top-right-radius:0; }
.md-toolbar button { padding:0.3rem 0.55rem; font-size:0.8rem; line-height:1; background:var(--panel-bg); border:1px solid var(--border); color:var(--text); border-radius:6px; font-weight:600; }
.md-toolbar button:hover { background:var(--card-bg-hover); }
.md-toolbar-sep { width:1px; align-self:stretch; background:var(--border); margin:0 0.1rem; }
.md-toolbar .md-link-picker, .md-toolbar .md-color-picker, .md-toolbar .md-symbol-picker { padding:0.3rem 0.5rem; margin-top:0; font-size:1rem; border-radius:6px; border:1px solid var(--border); background:var(--panel-bg); color:var(--text); max-width:9rem; width:auto; }
.md-toolbar .md-link-picker { margin-left:auto; max-width:11rem; }

/* "Cite…" dialog (Stage 8.15) - a native <dialog>, not a hand-rolled
   overlay, so Escape/backdrop-click and focus-trapping come for free from
   the browser. showModal()/close() in ui/app.js. Re-skinned per the
   spec's modal treatment: var(--overlay) backdrop, card body, dark
   gradient header (matches .topbar), border-separated action footer. */
.md-cite-dialog {
    width:min(32rem, 92vw); max-height:80vh; padding:0; border:1px solid var(--border);
    border-radius:var(--radius); background:var(--panel-bg); color:var(--text); box-shadow:var(--shadow);
    overflow:hidden;
}
.md-cite-dialog::backdrop { background:var(--overlay); }
.md-cite-dialog h2 {
    margin:0; padding:1rem 1.25rem; font-size:17px; font-weight:700;
    background:linear-gradient(135deg, var(--header-grad-1), var(--header-grad-2)); color:var(--header-text);
}
.md-cite-dialog-body { padding:1.25rem; max-height:calc(80vh - 3.5rem); overflow-y:auto; }
.md-cite-dialog-search { width:100%; padding:9px 10px; font-size:1rem; border-radius:8px; border:1px solid var(--border); background:var(--bg); color:var(--text); }
.md-cite-dialog-results { list-style:none; margin:0.6rem 0; padding:0; max-height:16rem; overflow-y:auto; border:1px solid var(--border); border-radius:8px; }
.md-cite-dialog-results li { border-bottom:1px solid var(--border); }
.md-cite-dialog-results li:last-child { border-bottom:none; }
.md-cite-dialog-results label { display:flex; align-items:flex-start; gap:0.5rem; padding:0.45rem 0.6rem; margin:0; font-size:0.9rem; cursor:pointer; }
.md-cite-dialog-results label:hover { background:var(--card-bg); }
.md-cite-dialog-results input[type=checkbox] { margin-top:0.2rem; width:auto; }
.md-cite-dialog-empty { padding:0.6rem; color:var(--text-muted); font-size:0.85rem; }
.md-cite-dialog-selected { display:flex; flex-wrap:wrap; gap:0.35rem; min-height:0; }
.md-cite-chip { display:inline-flex; align-items:center; gap:0.35rem; font-size:0.8rem; padding:0.15rem 0.3rem 0.15rem 0.6rem; border-radius:999px; background:var(--card-bg); border:1px solid var(--border); color:var(--text-muted); }
.md-cite-chip button { background:none; border:none; color:var(--text-muted); padding:0; cursor:pointer; font-size:1rem; line-height:1; }
.md-cite-chip button:hover { color:var(--text); }
.md-cite-dialog-actions { display:flex; justify-content:flex-end; gap:0.5rem; padding:0.9rem 1.25rem; border-top:1px solid var(--border); margin:0; }
@media (max-width: 30rem) {
    .md-toolbar .md-link-picker { margin-left:0; max-width:none; flex-basis:100%; }
    .md-toolbar .md-color-picker, .md-toolbar .md-symbol-picker { max-width:none; flex-basis:100%; }
    .md-cite-dialog { width:100vw; height:100%; max-height:100vh; border-radius:0; }
    .md-cite-dialog-body { max-height:calc(100vh - 3.5rem); }
}

/* --- Buttons: primary (filled accent) is the unstyled default; .secondary
   is the ghost/ORBIT variant (transparent, border only); .danger is the
   destructive variant. Every state transition is 0.15s, :active gets a
   1px press instead of a colour shift, and :disabled is opacity-only -
   never a colour change, so a disabled control still reads as "the same
   button" rather than a different, confusing one. On the dark topbar
   these same classes render translucent-white via the .topbar parent
   selector instead of a second class set. */
button, input[type=submit] {
    padding:9px 16px; background:var(--accent); color:var(--accent-on-accent); border:none;
    border-radius:8px; cursor:pointer; font-size:0.9rem; font-weight:600; font-family:inherit;
    transition:background 0.15s, opacity 0.15s, transform 0.15s; -webkit-tap-highlight-color:transparent;
}
button:hover, input[type=submit]:hover { background:var(--accent-hover); }
button:active, input[type=submit]:active { transform:translateY(1px); }
button:disabled, input[type=submit]:disabled { opacity:0.45; cursor:default; transform:none; }
button.secondary { background:transparent; color:var(--text); border:1px solid var(--border); }
button.secondary:hover { background:var(--card-bg-hover); }
button.danger { background:transparent; color:var(--status-aborted-text); border:1px solid var(--status-aborted-text); }
button.danger:hover { background:var(--status-aborted-bg); }
button.small { padding:5px 10px; font-size:0.8rem; }
button.icon-square { padding:0; width:2.15rem; height:2.15rem; display:inline-flex; align-items:center; justify-content:center; }
button.is-loading { cursor:wait; opacity:0.75; }
/* Round E polish pass: this spinner sits on a --accent-filled button, whose
   own text already correctly uses --accent-on-accent to auto-invert between
   themes (white text on the dark-teal light-mode button, dark-navy text on
   the light sage-aqua dark-mode button). The ring was hardcoded to
   rgba(255,255,255,*)/#fff regardless of theme - a stale copy of the same
   "always white" assumption the surrounding text rule doesn't make, so it
   read fine in light mode but went low-contrast (near-invisible) on the
   pale dark-mode button fill. var(--accent-on-accent) fixes both themes at
   once, same class of bug as the "Contrast/hierarchy pass" round's
   hardcoded-hex fixes. */
button.is-loading::after { content:""; display:inline-block; width:0.8em; height:0.8em; margin-left:0.5em; vertical-align:-0.1em; border:2px solid color-mix(in srgb, var(--accent-on-accent) 40%, transparent); border-top-color:var(--accent-on-accent); border-radius:50%; animation:orbit-spin 0.6s linear infinite; }
@keyframes orbit-spin { to { transform:rotate(360deg); } }

/* Post-redirect flash toast (flash.php + app.js) - TRACE toast: dark pill
   centred at the bottom, inverted text, error variant in the danger red. */
.toast-container { position:fixed; bottom:24px; left:50%; transform:translateX(-50%); z-index:200; max-width:min(32rem, calc(100vw - 2rem)); }
.toast {
    background:var(--text); color:var(--panel-bg); border-radius:8px; padding:12px 18px;
    display:flex; justify-content:space-between; align-items:flex-start; gap:1rem; font-size:14px;
    box-shadow:var(--shadow); animation:orbit-toast-in 0.2s ease-out;
}
@keyframes orbit-toast-in { from { transform:translateY(20px); opacity:0; } to { transform:translateY(0); opacity:1; } }
.toast.error { background:var(--status-aborted-text); color:#FFFFFF; }
.toast-close { background:none; border:none; color:inherit; opacity:0.75; cursor:pointer; font-size:1.1rem; line-height:1; padding:0; }
.toast-close:hover { background:none; opacity:1; }
fieldset { border:1px solid var(--border); border-radius:var(--radius); margin-top:1.5rem; padding:1rem; }
legend { padding:0 0.4rem; color:var(--text-muted); font-size:0.85rem; }
table { width:100%; border-collapse:collapse; margin:1rem 0; }
td, th { text-align:left; padding:0.4rem 0.3rem; border-bottom:1px solid var(--border); font-size:0.9rem; }
form.inline { display:inline-flex; gap:0.4rem; align-items:center; }

/* --- List-item card - apply.php, membership_requests.php, approvals.php,
   trash.php, and (via .section-list below) collection_view.php/
   section_view.php. Spec pattern: 2px transparent border that becomes
   var(--accent) on hover/selected, background steps --card-bg ->
   --card-bg-hover/--card-bg-selected. Add class="selected" server-side
   (or via JS) wherever a row represents "the thing currently open" - e.g.
   literature_index.php's item list against ?item=. */
.item {
    background:var(--card-bg); border:2px solid transparent; border-radius:var(--radius);
    padding:0.9rem 1.1rem; margin-bottom:0.6rem; display:flex; justify-content:space-between;
    align-items:center; gap:1rem; transition:background 0.15s, border-color 0.15s;
}
.item:hover { background:var(--card-bg-hover); border-color:var(--accent); }
.item.selected { background:var(--card-bg-selected); border-color:var(--accent); }
.item .title { font-weight:700; font-size:17px; }
.item .meta { font-size:13px; color:var(--text-muted); }

/* Shared "table of contents" list styling - collection_view.php (top-level
   sections in a Collection) and section_view.php (pages, and subsections
   via .subsection-grid below). .title gets the same teal accent as the
   entity's own page title (h1) elsewhere, so "this is a Section/Page
   name" reads consistently everywhere it appears, not just on its own
   page. 2026-09-16 contrast/hierarchy pass: this used to be a static hex
   copy of the accent colour, deliberately kept as its own constant so it
   read as "content typography" rather than the interactive --accent
   colour - but .title here (like .subsection-card below) is itself a
   real <a> to the Section/Page, i.e. a CONTROL, and the hardcoded copy
   didn't follow --accent's dark-mode shift to aqua, so it read fine in
   light mode but failed WCAG AA in dark mode (~3.0:1 - see this round's
   Plan.md done-note). Now var(--accent) itself: identical colour in light
   mode, correct in dark mode, and correctly reads as an interactive
   element rather than plain content. */
.section-list .item { justify-content:flex-start; flex-wrap:wrap; }
.section-list .item .title { flex:1; min-width:8rem; color:var(--accent); }
.section-list .item .reorder { display:flex; gap:0.25rem; margin-right:0.5rem; }
.section-list .item .reorder form { display:inline; }
.section-list .item .reorder button { padding:0.2rem 0.5rem; font-size:0.8rem; }
.section-list .item .edit-controls { display:flex; gap:0.4rem; align-items:center; margin-left:auto; }
.section-list .item .edit-controls form { display:inline-flex; gap:0.3rem; }
.section-list .item .edit-controls input[type=text] { width:10rem; padding:0.3rem 0.5rem; margin-top:0; font-size:0.85rem; }
.section-list .item .edit-controls button { padding:0.25rem 0.55rem; font-size:0.8rem; }

/* Subsection cards (section_view.php) - same teal accent as .section-list
   .item .title above, so a Subsection reads the same "kind of thing"
   whether it's a card here or a row in a Section's page list. */
.subsection-grid { display:grid; grid-template-columns:repeat(auto-fill, minmax(13rem, 1fr)); gap:0.75rem; margin:0.75rem 0 1.5rem; }
.subsection-card {
    display:block; background:var(--card-bg); border:2px solid transparent; border-radius:var(--radius);
    padding:0.85rem 1rem; text-decoration:none; color:var(--accent); font-weight:700; transition:background 0.15s, border-color 0.15s;
}
.subsection-card:hover { background:var(--card-bg-hover); border-color:var(--accent); }

/* Empty-state text - apply.php, literature_index.php, pages_history.php, membership_requests.php, approvals.php, trash.php */
.empty { color:var(--text-muted); font-size:0.9rem; padding:0.5rem 0; }

/* Current-position row (a role tag next to the "current" checkbox) -
   contact_edit.php and contact_new.php's repeated-positions fieldset
   (src/Http/contact_repeat_fields.php renders the markup, included by
   both). Round D consistency cleanup (.claude/Plan.md, 2026-09-16):
   identical rule reinvented in both files. */
.contact-position-current { display:flex; align-items:center; gap:0.4rem; font-weight:400; }

/* Retroactive past positions' start/end period selects (.claude/Plan.md,
   2026-09-17) - two month+year <select> pairs per Position row, shared by
   contact_new.php/contact_edit.php via contact_repeat_fields.php's own
   contact_render_period_select(). */
.contact-position-period { display:flex; align-items:center; gap:0.5rem; margin-top:0.4rem; }
.contact-position-period .field-hint { margin-top:0; min-width:9rem; }

/* Card-thumbnail gallery - contact_view.php (read-only) and
   contact_edit.php (with a per-thumb delete form, kept in that file's own
   $extraCss since it doesn't apply to the read-only view). Round D
   consistency cleanup (.claude/Plan.md, 2026-09-16): identical base rule
   reinvented in both files. */
.card-gallery { display:flex; flex-wrap:wrap; gap:0.75rem; margin:0.75rem 0; }
.card-thumb { width:140px; border:1px solid var(--border); border-radius:0.5rem; overflow:hidden; background:var(--bg-elevated); }
.card-thumb img { width:100%; display:block; }

/* GEM organization-type badge - gem_form_css.php (new/edit form preview),
   gem_index.php (list rows), pages_view.php (organization detail panel).
   Round D consistency cleanup (.claude/Plan.md, 2026-09-16): identical
   rule was reinvented in all three files; white-space:nowrap folded in
   from gem_index.php's copy since it's harmless in the other two contexts
   (a single short type name never wraps there anyway). */
.gem-type-badge { display:inline-block; padding:0.15rem 0.55rem; border-radius:999px; font-size:0.75rem; font-weight:600; background:var(--card-bg); border:1px solid var(--border); color:var(--text-muted); white-space:nowrap; }

/* Tag/group pills - literature_view.php, literature_index.php's item list
   and its right-hand detail panel. .tag-pill styles both <a> (filter
   links) and <button> (the clickable "add to input" chips on the new/edit
   forms) identically - a class selector already beats the plain
   `button`/`a` element rules elsewhere in this file, so no !important is
   needed either way. */
.tag-list { display:flex; flex-wrap:wrap; gap:0.4rem; margin:0.6rem 0; }
.tag-pill { display:inline-block; font-size:0.8rem; padding:0.15rem 0.6rem; border-radius:999px; background:var(--card-bg); border:1px solid var(--border); color:var(--text-muted); text-decoration:none; font-family:inherit; line-height:1.4; }
/* 2026-09-16 contrast/hierarchy pass: .tag-pill is deliberately reused on
   both a plain <span> (read-only display of an item's own tags/terms -
   CONTENT, e.g. pages_view.php's classification chips) and an <a>/<button>
   (a tag filter link or an "add to input" chip - a CONTROL, e.g.
   literature_index.php's tag/group list, literature_edit.php's chip
   picker). Same markup class, different underlying element, so this can
   be a pure element-selector split with no markup change: the <span>
   case keeps the muted base rule above (still a real de-emphasised label,
   not a naked control hiding in it), while <a>/<button> get accent colour
   plus an underline on hover so the two never look identical to each
   other even for someone who can't distinguish the colour. */
a.tag-pill, button.tag-pill { color:var(--accent); cursor:pointer; }
a.tag-pill:hover, button.tag-pill:hover { color:var(--accent-hover); border-color:var(--accent); text-decoration:underline; }
.group-pill { background:var(--accent); color:var(--accent-on-accent); border-color:transparent; }

/* --- Status badges/pills: the 5 semantic slots from the spec, used
   everywhere ORBIT shows a status-like value. Mapping ORBIT's own
   statuses onto them (see Plan.md for the full rationale):
     .status-active   (green)  - published page, active/enabled user, approved request
     .status-ongoing   (purple) - pending approval (page or membership request) - "in progress"
     .status-paused    (yellow) - draft (not yet submitted) - "on hold"
     .status-finished  (blue)   - reserved for a future "archived/superseded" state
     .status-aborted   (red)    - disabled user, rejected/withdrawn request
   11px/bold/pill per the spec; class="badge status-*" on a <span>. */
.badge {
    display:inline-block; font-size:11px; font-weight:700; padding:3px 9px; border-radius:999px;
    line-height:1.4; white-space:nowrap;
}
.badge.status-active   { background:var(--status-active-bg);   color:var(--status-active-text); }
.badge.status-ongoing  { background:var(--status-ongoing-bg);  color:var(--status-ongoing-text); }
.badge.status-paused   { background:var(--status-paused-bg);   color:var(--status-paused-text); }
.badge.status-finished { background:var(--status-finished-bg); color:var(--status-finished-text); }
.badge.status-aborted  { background:var(--status-aborted-bg);  color:var(--status-aborted-text); }
/* users.php's pre-existing .status-badge name, kept as a compatible alias
   so that file's two literal modifiers keep working unchanged. */
.status-badge { display:inline-block; font-size:11px; font-weight:700; padding:3px 9px; border-radius:999px; line-height:1.4; }
.status-badge.active { background:var(--status-active-bg); color:var(--status-active-text); }
.status-badge.disabled { background:var(--status-aborted-bg); color:var(--status-aborted-text); }

/* Approve/reject action pair - membership_requests.php, approvals.php */
button.approve { background:var(--status-active-bg); color:var(--status-active-text); }
button.approve:hover { background:var(--status-active-bg); opacity:0.85; }
button.reject { background:var(--status-aborted-bg); color:var(--status-aborted-text); }
button.reject:hover { background:var(--status-aborted-bg); opacity:0.85; }

/* --- Segmented control: pill-shaped container (--card-bg, 4px padding),
   borderless transparent buttons, active one fully accent-filled. The
   topbar's Wiki/Literature switch is a dark-background instance of this
   same pattern (.topbar-switch, above) - .segmented is this formalized
   as a reusable, light/panel-context class for anywhere a plain <select>
   would read better as a small set of exclusive choices (e.g. a type or
   view filter). Markup: <div class="segmented"><a>/<button> children,
   one carrying .active>. */
.segmented { display:inline-flex; gap:0.25rem; background:var(--card-bg); border-radius:999px; padding:4px; }
.segmented a, .segmented button {
    text-decoration:none; background:none; border:none; color:var(--text-muted); font-size:0.85rem;
    font-weight:600; padding:0.4rem 0.9rem; border-radius:999px; cursor:pointer; font-family:inherit;
    transition:background 0.15s, color 0.15s;
}
.segmented a.active, .segmented button.active { color:var(--accent-on-accent); background:var(--accent); }
.segmented a:not(.active):hover, .segmented button:not(.active):hover { color:var(--text); background:var(--card-bg-hover); }

/* --- Metadata label/value pairs - consolidates the near-identical
   dl.metadata (literature_view.php) and dl.lit-detail-meta
   (literature_index.php) that had drifted into two copies of the same
   pattern (this project's had that exact kind of drift before - see the
   .section-list comment above). Both files now share this single class;
   label column ~130px per spec, bold label, thin bottom border per row. */
dl.metadata { display:grid; grid-template-columns:130px 1fr; gap:0; margin:1rem 0; font-size:13px; }
dl.metadata dt, dl.metadata dd { padding:0.5rem 0; border-bottom:1px solid var(--border); }
dl.metadata dt { color:var(--text); font-weight:700; margin:0; }
dl.metadata dd { color:var(--text-muted); margin:0; }
@media (max-width: 30rem) {
    dl.metadata { grid-template-columns:1fr; }
    dl.metadata dt { border-bottom:none; padding-bottom:0.15rem; }
    dl.metadata dd { padding-top:0; }
}

/* Math (Markdown.php's $x$/$$..$$, ui/app.js's lazy KaTeX loader). Before
   JS enhances them these just show the raw LaTeX source as inline/block
   code - readable, not broken, on a page that never runs the script.
   .katex-block gets its own overflow-x:auto (same reasoning as `pre`
   above) since a wide rendered equation is exactly the kind of long
   unbroken content overflow-wrap:anywhere on body can't help with -
   KaTeX's output is real inline-block markup, not breakable text. */
/* max-width/overflow-x/display:inline-block (not just overflow-wrap,
   which can't reach KaTeX's own rendered output - see .katex-block's
   comment just below: once KaTeX JS enhances this span, its content is
   real inline-block character markup, not breakable text) - a genuinely
   long inline formula scrolls horizontally within its own small box
   instead of forcing the whole page wider on mobile. */
.katex-inline { display:inline-block; max-width:100%; overflow-x:auto; vertical-align:middle; font-family:ui-monospace,monospace; background:var(--card-bg); padding:0.05rem 0.3rem; border-radius:0.2rem; font-size:0.9em; }
.katex-block { font-family:ui-monospace,monospace; background:var(--card-bg); padding:0.75rem 1rem; border-radius:8px; margin:1rem 0; overflow-x:auto; white-space:pre; }
.katex-block .katex-display { margin:0; }

/* Image lightbox (Contacts v2, .claude/Plan.md's 2026-09-16 entry) - a
   native <dialog>, same "browser gives us Escape/backdrop-click/focus-
   trapping for free" convention .md-cite-dialog already established, not a
   hand-rolled overlay. Full-screen, near-opaque backdrop, image centered
   and capped to the viewport so it never overflows. Any thumbnail with
   class .lightbox-trigger opens it via ui/app.js's single delegated click
   handler - generic, not Contacts-specific, so a future screen can reuse
   the same #lightbox-dialog/.lightbox-trigger pair with no extra JS. */
.lightbox-dialog { padding:0; border:none; background:transparent; max-width:100vw; max-height:100vh; width:100vw; height:100vh; }
.lightbox-dialog::backdrop { background:rgba(0, 0, 0, 0.88); }
.lightbox-dialog-inner { display:flex; align-items:center; justify-content:center; width:100%; height:100%; padding:2rem; box-sizing:border-box; }
.lightbox-dialog img { max-width:100%; max-height:100%; object-fit:contain; border-radius:0.4rem; box-shadow:var(--shadow); }
.lightbox-close { position:fixed; top:1rem; right:1.25rem; background:rgba(0, 0, 0, 0.5); color:#fff; border:none; border-radius:999px; width:2.25rem; height:2.25rem; font-size:1.25rem; line-height:1; cursor:pointer; }
.lightbox-trigger { cursor:zoom-in; }

/* ---------- Login card (TRACE login.html) - login, register, forgot
   password, two-factor enrolment ---------- */
body.auth-body {
    display:flex; align-items:center; justify-content:center; min-height:100vh; height:auto;
    overflow-y:auto; padding:16px;
}
.login-card {
    width:100%; max-width:420px; background:var(--panel-bg); border:1px solid var(--border); border-radius:14px;
    box-shadow:var(--shadow); padding:28px 28px 24px; display:flex; flex-direction:column; gap:18px;
}
.login-card--wide { max-width:560px; }
.login-logo { width:100%; height:auto; max-height:140px; object-fit:contain; }
.login-badge {
    width:64px; height:64px; align-self:center; border-radius:14px; background:var(--brand-icon-bg); color:var(--brand-icon-text);
    display:flex; align-items:center; justify-content:center; font-weight:700; font-size:22px;
}
.login-info-rows { display:flex; flex-direction:column; gap:4px; }
.login-info-row { display:flex; justify-content:space-between; gap:12px; font-size:13px; }
.login-card h1 { font-size:17px; margin:0; }
.login-card form { display:flex; flex-direction:column; gap:10px; margin:0; }
.login-card label { margin-top:0; font-size:13px; font-weight:600; color:var(--text-muted); }
.login-card input[type=text], .login-card input[type=email], .login-card input[type=password] {
    background:var(--card-bg); padding:10px 12px;
}
.login-card button { width:100%; padding:11px 16px; font-size:14px; }
.login-card .error { margin:0; }
.login-card .notice { margin:0; }
.login-hint { font-size:12px; color:var(--text-muted); text-align:center; margin:0; }
.login-divider { font-size:12px; color:var(--text-muted); text-align:center; margin:0; text-transform:uppercase; letter-spacing:0.05em; }
.login-card code { display:block; background:var(--card-bg); padding:10px; border-radius:8px; word-break:break-all; font-size:13px; }
.login-codes {
    display:grid; grid-template-columns:1fr 1fr; gap:6px; padding:6px; margin:0; list-style:none;
    font-family:ui-monospace,monospace; font-size:15px; letter-spacing:1px; text-align:center;
    border:1px dashed var(--accent); border-radius:10px; background:var(--card-bg);
}
.login-codes li { padding:4px; }
