@font-face { font-family: "Thruline Phosphor"; src: url("../fonts/phosphor/Phosphor-Thin.ttf") format("truetype"); font-style: normal; font-weight: 100; font-display: block; }
@font-face { font-family: "Thruline Phosphor"; src: url("../fonts/phosphor/Phosphor-Light.ttf") format("truetype"); font-style: normal; font-weight: 300; font-display: block; }
@font-face { font-family: "Thruline Phosphor"; src: url("../fonts/phosphor/Phosphor.ttf") format("truetype"); font-style: normal; font-weight: 400; font-display: block; }
@font-face { font-family: "Thruline Phosphor"; src: url("../fonts/phosphor/Phosphor-Bold.ttf") format("truetype"); font-style: normal; font-weight: 700; font-display: block; }
@font-face { font-family: "Thruline Phosphor Fill"; src: url("../fonts/phosphor/Phosphor-Fill.ttf") format("truetype"); font-style: normal; font-weight: 400; font-display: block; }

:root {
    color-scheme: dark;
    font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
    /* Theme-invariant control geometry (desktop ControlCornerRadius / OverlayCornerRadius). */
    --radius-control: 6px;
    --radius-overlay: 8px;
    /* Spacing scale (2026-07-30 mobile pass). The chrome used raw px at 2/3/4/5/6/7/8/9/10/12/14/16/18/24/26/44,
       which is why no two panels shared an edge. New chrome composes these; existing page CSS is being migrated
       onto them opportunistically rather than in one sweep. */
    --sp-1: 4px;
    --sp-2: 8px;
    --sp-3: 12px;
    --sp-4: 16px;
    --sp-5: 24px;
    --sp-6: 32px;
    --sp-7: 48px;
    /* The one comfortable touch target. Previously re-declared as a literal 44px in ~30 places across five
       stylesheets; those copies had already drifted (28/30/32px survivors on real touch surfaces). */
    --tap: 44px;
    /* ONE text pair for every picker family (2026-08-08 owner ruling: "some picker families render row text in
       different colours"). A choosable row's NAME is the row's content and reads at primary; a count, a
       group caption, a placeholder or an empty-state line is metadata and reads muted. Both are aliases over
       tokens the active theme already defines — no new colour anywhere — declared once here so the entity
       picker, the Library folder-scope list, the inline checklists inside the Viewer controls, and the
       timeline fork picker cannot drift apart again. Audited before this landed: entity
       rows were `--text-secondary` (name), the fork picker's options `--text-primary`, and the folder-scope
       rows `--text-secondary` — three answers to the same question. */
    --picker-row-text: var(--text-primary);
    --picker-row-meta: var(--text-muted);
    /* Elevation scale (2026-07-28 consolidation): every floating box-shadow across the Viewer used to hand-tune
       its own black alpha (22%/25%/30%/32%/35%/38% all seen). These two tokens are DERIVED from desktop's own
       BoxShadow conventions rather than invented — desktop's floating pill/panel chrome (CameraControls.axaml's
       cam-pill, the desktop's shared legend-pill in CanvasStyles.axaml) all agree on
       "0 2 8 0 #40000000" (25.1%); desktop's floating command/tour overlays (QuickOpenOverlay, DesktopGuidedTourHost,
       WriterSurfaceView's zen chrome) all independently agree on "#60000000" (37.6%), and DesktopGuidedTourHost's
       own "0 10 32 0 #60000000" is the exact source of --shadow-overlay's numbers — a deliberate pairing since
       ViewerGuidedTourHost is that control's web counterpart. Every non-exempt box-shadow in this project's *.css
       now resolves to one of these two; see each site for which tier it took. */
    --shadow-panel: 0 2px 8px rgb(0 0 0 / 25%);
    --shadow-overlay: 0 10px 32px rgb(0 0 0 / 38%);
    --surface-app: #151110;
    --surface-header: #201913;
    --surface-rail: #262019;
    --surface-nav: #221c15;
    --surface-raised: #2e251d;
    --surface-hover: #3a322b;
    --surface-pressed: #251d15;
    --surface-input: #201913;
    --chrome-background: #201913f0;
    --chrome-border: #3e322840;
    --chrome-text-primary: #e8dcc8dc;
    --chrome-text-secondary: #b8a88899;
    --text-primary: #c9bca6;
    --text-secondary: #b8a888;
    --text-muted: #8a7860;
    --text-disabled: #60504a;
    --stroke: #3e3228;
    --stroke-strong: #524238;
    --accent: #d2b078;
    --accent-hover: #dec28f;
    --accent-soft: #41341f;
    --text-on-accent: #0f0d0b;
    /* Prose note-mention colour (FormattedDocumentView's .note-annotation-link): background notes carry no
       colour of their own — presence only — so every note-target annotation paints with this ONE consistent
       themed caramel tone, mirroring the desktop TextAnnotationNoteColor palette key. */
    --note-accent: #b58a57;
    --danger: #8a3a2a;
    --warning: #d39a42;
    --scrollbar-thumb: #6b4f3a;
    --scrollbar-thumb-hover: #8c6b4a;
    --scrollbar-thumb-pressed: #ad8759;
}
* { box-sizing: border-box; }
html, body, #app { width: 100%; height: 100%; margin: 0; }
body { overflow: hidden; background: var(--surface-app); color: var(--text-primary); }
button, input, select, textarea { color-scheme: inherit; font: inherit; }
button { color: inherit; }
/* Systemic anchor backstop (2026-07-28): no bare <a>/NavLink anywhere in the Viewer may ever fall through to
   the browser's default blue/purple link colours — every themed surface is expected to style its own anchors,
   but this floor catches the one that forgets (e.g. Read's Walk NavLink). Matches FormattedDocumentView's
   .annotation-link accent tone; underline stays reserved for that ONE prose-link idiom, so the default here is
   underline-free and any surface can still opt into its own hover/visited treatment on top. */
a { color: var(--accent); text-decoration: none; }
/* Systemic BUTTON backstop (2026-07-30), the exact counterpart of the anchor backstop above and added for
   the same reason: a <button> that never opted into a skin falls through to the UA's own ButtonFace, which
   under `color-scheme: dark` Chrome paints a cold blue-grey — visibly outside the palette (owner-reported on
   the Library "Folders: All" trigger). Wrapped in :where() so its specificity is ZERO and every real rule,
   including the bare-element `button` rules above, still wins; this only catches the ones nothing else styles. */
:where(.app-root button) {
    border: 0;
    background: transparent;
    color: inherit;
}
a:visited { color: var(--accent); }
/* Button-styled anchors (nav/section tabs, icon buttons, brand mark, CTA pills) opt OUT of the
   backstop above: they render as buttons, not hyperlinks, and must never shift colour once the
   browser marks their href visited. Each keeps an explicit :visited rule restating its own resting
   colour (a class selector alone loses specificity to the a:visited rule above, so this cannot be
   left implicit). */

/* Root-owned appearance. Palette values mirror the six desktop canonical colour sets; components
   consume semantic roles rather than importing Avalonia dictionaries. --chrome-hover is a low-opacity
   wash over the active theme's primary text, matching the desktop ChromeHoverColor treatment. */
/* Palette hexes in the [data-theme] blocks below are hand-copied from the canonical desktop palettes at
   Shared/Themes/Palettes/Base/*.axaml (CozyDark, CozyDay, Dark, Day, SolarizedDark, SolarizedLight)
   and must stay in sync with them. */
.app-root {
    position: relative;
    isolation: isolate;
    display: grid;
    grid-template-rows: 52px minmax(0, 1fr);
    width: 100%;
    height: 100%;
    overflow: hidden;
    background: var(--surface-app);
    color: var(--text-primary);
    font-family: var(--font-interface);
    --chrome-hover: color-mix(in srgb, var(--text-primary) 9%, transparent);
    /* Control borders are a BORDER role, not a text role. This used to resolve to --text-muted, so every
       button, select, and input on the page was outlined in the same value as its own label text — the single
       biggest reason the control chrome read as muddy rather than as a set of edges. */
    /* REASSIGNMENT (2026-07-30 mobile pass, existing palette value, no new colour): was var(--text-muted),
       so every button/select/input was outlined in the same value as its own label text. --stroke-strong is
       already defined by all six themes; this only changes which existing value the border role points at. */
    --stroke-control: var(--stroke-strong);
}
.app-root--landing { grid-template-rows: minmax(0, 1fr); }
/* --surface-selected and --warning map desktop SubtleSecondaryColor and WarningCautionColor exactly.
   --accent-soft remains the one intentional Web-only washed-control token. */
.app-root[data-theme="cozy-dark"] { color-scheme: dark; --surface-app: #151110; --surface-header: #201913; --surface-rail: #262019; --surface-nav: #221c15; --surface-raised: #2e251d; --surface-hover: #3a322b; --surface-pressed: #251d15; --surface-input: #201913; --surface-selected: #b9955f1a; --chrome-background: #201913f0; --chrome-border: #3e322840; --chrome-hover: #ffd8a814; --chrome-text-primary: #e8dcc8dc; --chrome-text-secondary: #b8a88899; --text-primary: #c9bca6; --text-secondary: #b8a888; --text-muted: #8a7860; --text-disabled: #60504a; --stroke: #3e3228; --stroke-strong: #524238; --accent: #d2b078; --accent-hover: #dec28f; --accent-soft: #41341f; --text-on-accent: #0f0d0b; --danger: #8a3a2a; --warning: #d18a4a; --scrollbar-thumb: #6b4f3a; --scrollbar-thumb-hover: #8c6b4a; --scrollbar-thumb-pressed: #ad8759; --note-accent: #b08a5a; }
/* --accent-soft is Web-only; every other semantic value on this line maps the desktop palette. */
.app-root[data-theme="cozy-day"] { color-scheme: light; --surface-app: #ebe6e0; --surface-header: #dcd1c6; --surface-rail: #d0c0af; --surface-nav: #f3f0ed; --surface-raised: #e5ded7; --surface-hover: #d8cabb; --surface-pressed: #cbbca9; --surface-input: #ebe6e0; --surface-selected: #80603914; --chrome-background: #dcd1c6f5; --chrome-border: #c6ae9566; --chrome-hover: #432d190f; --chrome-text-primary: #432d19e0; --chrome-text-secondary: #6a4c2fb3; --text-primary: #432d19; --text-secondary: #6a4c2f; --text-muted: #926f4f; --text-disabled: #af9883; --stroke: #c6ae95; --stroke-strong: #bc9976; --accent: #806039; --accent-hover: #997a4c; --accent-soft: #e6d3bc; --text-on-accent: #fff8ed; --danger: #b2402e; --warning: #96551f; --scrollbar-thumb: #c2a986; --scrollbar-thumb-hover: #ad8c60; --scrollbar-thumb-pressed: #8f6d45; --note-accent: #8a6d4f; }
/* --accent-soft is Web-only; every other semantic value on this line maps the desktop palette. */
.app-root[data-theme="dark"] { color-scheme: dark; --surface-app: #151515; --surface-header: #1c1c1c; --surface-rail: #232323; --surface-nav: #191919; --surface-raised: #222; --surface-hover: #2c2c2c; --surface-pressed: #181818; --surface-input: #1c1c1c; --surface-selected: #e6e6e612; --chrome-background: #1c1c1cf0; --chrome-border: #35353540; --chrome-hover: #e6e6e614; --chrome-text-primary: #e6e6e6dc; --chrome-text-secondary: #a6a6a699; --text-primary: #c6c6c6; --text-secondary: #a6a6a6; --text-muted: #767676; --text-disabled: #4e4e4e; --stroke: #353535; --stroke-strong: #474747; --accent: #7aa3cc; --accent-hover: #6a91b8; --accent-soft: #293745; --text-on-accent: #141414; --danger: #8a3a3a; --warning: #c7863d; --scrollbar-thumb: #4e4e4e; --scrollbar-thumb-hover: #707070; --scrollbar-thumb-pressed: #9c9c9c; --note-accent: #b58a57; }
/* --accent-soft is Web-only; every other semantic value on this line maps the desktop palette. */
.app-root[data-theme="day"] { color-scheme: light; --surface-app: #fafaf8; --surface-header: #f0eee8; --surface-rail: #e8e6df; --surface-nav: #fff; --surface-raised: #edebe3; --surface-hover: #e4e2da; --surface-pressed: #dad8cf; --surface-input: #fafaf8; --surface-selected: #1c1c1a10; --chrome-background: #f0eee8f5; --chrome-border: #c8c5bc66; --chrome-hover: #1c1c1a0f; --chrome-text-primary: #1c1c1ae0; --chrome-text-secondary: #4a4a46b3; --text-primary: #2a2a26; --text-secondary: #4a4a46; --text-muted: #6e6e68; --text-disabled: #9e9e98; --stroke: #c8c5bc; --stroke-strong: #b8b5ac; --accent: #5b7fa3; --accent-hover: #6a91b8; --accent-soft: #dce5ed; --text-on-accent: #fff; --danger: #b04040; --warning: #8f5a1e; --scrollbar-thumb: #c6c2b8; --scrollbar-thumb-hover: #9e9a90; --scrollbar-thumb-pressed: #76736a; --note-accent: #8a6d4f; }
/* --accent-soft is Web-only; every other semantic value on this line maps the desktop palette. */
.app-root[data-theme="solarized-dark"] { color-scheme: dark; --surface-app: #002b36; --surface-header: #073642; --surface-rail: #0a3f4e; --surface-nav: #073642; --surface-raised: #0b3c4b; --surface-hover: #114957; --surface-pressed: #052d3b; --surface-input: #073642; --surface-selected: #fdf6e312; --chrome-background: #073642f0; --chrome-border: #657b8346; --chrome-hover: #fdf6e31a; --chrome-text-primary: #839496e6; --chrome-text-secondary: #93a1a1b4; --text-primary: #839496; --text-secondary: #93a1a1; --text-muted: #657b83; --text-disabled: #586e75; --stroke: #1a4f5e; --stroke-strong: #2a6275; --accent: #4aadec; --accent-hover: #3a9de0; --accent-soft: #124d66; --text-on-accent: #002b36; --danger: #dc322f; --warning: #cb4b16; --scrollbar-thumb: #2e6478; --scrollbar-thumb-hover: #4c8296; --scrollbar-thumb-pressed: #79a7b4; --note-accent: #b58900; }
/* --accent-soft is Web-only; every other semantic value on this line maps the desktop palette. */
.app-root[data-theme="solarized-light"] { color-scheme: light; --surface-app: #fdf6e3; --surface-header: #eee8d5; --surface-rail: #f3ecda; --surface-nav: #fdf6e3; --surface-raised: #f3ecda; --surface-hover: #e8e0cd; --surface-pressed: #dcd3be; --surface-input: #fdf6e3; --surface-selected: #07364212; --chrome-background: #eee8d5f0; --chrome-border: #002b363c; --chrome-hover: #07364214; --chrome-text-primary: #073642e6; --chrome-text-secondary: #586e75b4; --text-primary: #073642; --text-secondary: #586e75; --text-muted: #657b83; --text-disabled: #93a1a1; --stroke: #c5bca8; --stroke-strong: #aba29a; --accent: #268bd2; --accent-hover: #3a9de0; --accent-soft: #d2e6e8; --text-on-accent: #fdf6e3; --danger: #dc322f; --warning: #cb4b16; --scrollbar-thumb: #b5ac98; --scrollbar-thumb-hover: #9a9382; --scrollbar-thumb-pressed: #7c7669; --note-accent: #8b6914; }

.app-root, .app-root * {
    scrollbar-color: var(--scrollbar-thumb) transparent;
    scrollbar-width: thin;
    -webkit-tap-highlight-color: transparent;
}
.app-root *::-webkit-scrollbar { width: 10px; height: 10px; }
.app-root *::-webkit-scrollbar-track, .app-root *::-webkit-scrollbar-corner { background: transparent; }
.app-root *::-webkit-scrollbar-thumb { min-width: 28px; min-height: 28px; border: 2px solid transparent; border-radius: 999px; background: var(--scrollbar-thumb); background-clip: content-box; }
.app-root *::-webkit-scrollbar-thumb:hover { background-color: var(--scrollbar-thumb-hover); }
.app-root *::-webkit-scrollbar-thumb:active { background-color: var(--scrollbar-thumb-pressed); }
.app-root *::-webkit-scrollbar-button { display: none; }

/* ---------------------------------------------------------------------------
   Type-scale utilities (desktop TypographyStyles roles). Usable everywhere.
   --------------------------------------------------------------------------- */
/* 2026-07-30 retune. The scale ran 10-20px with weights up to 700 on its SMALLEST roles, so the chrome read
   as tiny-and-shouty rather than hierarchical: the two label roles were 10-11px uppercase at 600/700 with
   .08-.12em tracking, and the card header sat at 650. Hierarchy now comes from size and colour; nothing is
   heavier than 600, no role is uppercase-tracked by default, and the smallest role is 11px. Class names and
   roles are unchanged, so every consuming stylesheet inherits this without moving. */
.t-header-page { font-size: 20px; font-weight: 600; line-height: 1.25; color: var(--text-primary); }
.t-header-stat { font-size: 15px; font-weight: 600; line-height: 1.3; color: var(--text-primary); }
.t-header-section { font-size: 15px; font-weight: 600; line-height: 1.3; color: var(--text-primary); margin-bottom: var(--sp-1); }
.t-header-card { font-size: 14px; font-weight: 600; line-height: 1.35; color: var(--text-primary); }
.t-row-title { font-size: 14px; font-weight: 400; line-height: 1.4; color: var(--text-primary); }
.t-label-field { min-width: 60px; font-size: 13px; font-weight: 500; line-height: 1.35; color: var(--text-secondary); }
.t-meta { font-size: 13px; font-weight: 400; line-height: 1.35; color: var(--text-secondary); }
.t-header-group { font-size: 12px; font-weight: 600; line-height: 1.35; color: var(--text-secondary); }
.t-caption { font-size: 12px; font-weight: 400; line-height: 1.4; color: var(--text-secondary); }
.t-micro-label { font-size: 11px; font-weight: 400; line-height: 1.35; color: var(--text-secondary); }
.t-body { font-size: 14px; font-weight: 400; line-height: 1.5; color: var(--text-secondary); }
.t-body-primary { font-size: 14px; font-weight: 400; line-height: 1.5; color: var(--text-primary); }
.t-empty-state { font-size: 14px; font-weight: 400; line-height: 1.5; color: var(--text-secondary); }

/* Explainer paragraph — a popover's or section page's "here's what this does" opening line
   (desktop TextBlock.explainer). Same shape as .t-body (12px secondary) with wrapping made explicit
   for the helper-paragraph role specifically. Prefer this name at explainer call sites so the class
   reads as a deliberate choice rather than a coincidence of .t-body. */
.t-explainer { font-size: 14px; font-weight: 400; line-height: 1.5; color: var(--text-secondary); white-space: normal; }

/* Accent "kicker" line above a heading (page eyebrow / section kicker). Was hand-copied at three
   drifting sizes (9/10/11px) across Settings and Focus — Settings' own page-level ".eyebrow" and
   per-section ".settings-kicker" had already drifted from each other. One role, one class.
   2026-07-30 mobile pass: was 10px/700 uppercase at .12em tracking, the fattest and least legible text on
   the page at phone size. Size, weight, and tracking only; the colour role is unchanged, and accent stays
   reserved for focus/selection/state per this file's own rule. */
.t-eyebrow { margin: 0 0 var(--sp-1); color: var(--text-secondary); font-size: 12px; font-weight: 500; letter-spacing: 0; text-transform: none; }

/* ---------------------------------------------------------------------------
   Interactive control kit (desktop compact-control/filter-control parity).
   One button geometry, one dropdown-trigger geometry, one popover-menu shell, one checkbox row —
   toolbar pickers, axis/facet toggles, and popover filters compose these instead of each
   re-declaring border/radius/padding/font-size (several had silently lost their hover state in the
   copy-paste). Value-bearing button text sits at 12px (desktop filter-control); compact icon-only
   buttons carry no text so the size class is about geometry, not the two-tier type scale.
   --------------------------------------------------------------------------- */
.btn {
    display: inline-flex;
    box-sizing: border-box;
    min-height: 32px;
    align-items: center;
    justify-content: center;
    gap: 6px;
    border: 1px solid var(--stroke-control);
    border-radius: var(--radius-control);
    background: var(--surface-input);
    padding: 6px var(--sp-3);
    color: var(--text-primary);
    font: inherit;
    font-size: 13px;
    white-space: nowrap;
    cursor: pointer;
    transition: border-color 120ms ease, color 120ms ease, background-color 120ms ease;
}
/* Pressed feedback. The kit had hover and disabled but no active state at all, so on touch — where hover
   never fires — a tap produced no acknowledgement whatsoever until the app re-rendered. */
.btn:not(:disabled):active { background: var(--surface-pressed); }
/* aria-disabled carries the same meaning for the label-wrapped file pickers below, which cannot be :disabled
   themselves — only the <input> they wrap can. */
.btn:disabled, .btn[aria-disabled="true"] { opacity: .5; cursor: default; pointer-events: none; }
/* Hover background wash (main's Button.tool/ComboBox.tool parity — ButtonBackgroundPointerOver/
   ComboBoxBackgroundPointerOver both resolve to ControlHoverColor, --surface-hover here): a border-color
   swap alone under-signals "this is interactive" next to desktop chrome, where every bordered control's
   fill actually changes on pointerover, not just its edge. */
.btn:hover { border-color: var(--stroke-strong); background: var(--surface-hover); color: var(--text-primary); }
.btn.is-active, .btn[aria-pressed="true"] { border-color: var(--accent); background: var(--accent-soft); color: var(--text-primary); }
/* --accent is the SELECTED tone (an accent-washed toggle), not a call to action. */
.btn--accent { border-color: var(--accent); background: var(--accent-soft); color: var(--text-primary); }
.btn--accent:hover:not(:disabled) { border-color: var(--accent-hover); background: var(--surface-hover); color: var(--text-primary); }
/* An anchor wearing the kit is still a button to the eye: the global a:visited colour would otherwise beat
   .btn's own colour on specificity and repaint a visited link accent. */
a.btn, a.btn:visited { color: var(--text-primary); text-decoration: none; }
.btn--ghost { border-color: transparent; background: transparent; }
.btn--menu-row { width: 100%; min-height: 36px; justify-content: flex-start; border-color: transparent; background: transparent; text-align: left; }
.btn--tab { border-color: transparent; border-radius: 0; background: transparent; }
/* Active tab = accent-coloured label. No underline, no shadow (owner ruling 2026-07-30). */
.btn--tab.is-active, .btn--tab[aria-selected="true"] { border-color: transparent; background: transparent; color: var(--accent); }
.btn--sm { min-height: 0; gap: 4px; padding: 3px 7px; font-size: 11px; }
.btn--icon { min-height: 0; width: 28px; height: 28px; flex: 0 0 auto; padding: 0; }
.btn--icon.btn--sm { width: 24px; height: 24px; }
.btn--icon.btn--round { border-radius: 50%; }
/* File-picker modifier: a <label> that LOOKS like a button and covers itself with a transparent <InputFile>.
   The landing "Open .thrv", the header "Replace project", and Review's "Resume prior response CSV" each carried
   their own copy of this positioning; one modifier now owns the idiom, and focus-within is what draws the ring
   because the focusable element is the invisible input, not the label. */
.btn--file { position: relative; overflow: hidden; }
.btn--file input { position: absolute; inset: 0; opacity: 0; cursor: pointer; }
.btn--file:focus-within { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Owner ruling (fix): every glyph+label "open a control sheet" trigger
   must read as ONE button, not an icon sitting next to a separately-drifting text label — each page previously
   hand-rolled its own near-identical border/background/radius for this, at a wide 6px gap and the same soft
   .btn-style corner radius the icon's own dots-three glyph (itself a small horizontal cluster) could get lost
   inside. A single shared class fixes the idiom once: a tight gap and a full pill radius state unambiguously
   that the glyph and the label share one hit shape, rather than each site tuning its own numbers. Each call site
   still owns its OWN position/size/margin (floating placement, row membership, etc.) locally. */
.sheet-trigger-pill {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    border: 1px solid var(--stroke);
    border-radius: 999px;
    background: var(--surface-input);
    color: var(--text-primary);
    font: inherit;
    font-weight: 600;
    cursor: pointer;
}
.sheet-trigger-pill:hover { border-color: var(--accent); background: var(--surface-hover); }

/* Popover-menu shell (filter/marker/axis/folder pickers). Was copy-pasted near-identically three
   times (folder picker, selection controls, and
   subject-type filter) with drifting min-width/padding. Callers still set their own position-area,
   margin-block, min-width, and max-height inline since those are legitimately per-context. */
.dropdown-popover {
    border: 1px solid var(--stroke-control);
    border-radius: var(--radius-overlay);
    background: var(--surface-raised);
    box-shadow: var(--shadow-overlay);
    padding: 7px;
    color: var(--text-secondary);
}

/* Checkbox row inside a popover-menu: a plain inline checkbox + label, one font size, one spacing. */
.dropdown-popover .check-inline,
.check-inline {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    padding: var(--sp-1) 5px;
    white-space: nowrap;
    font-size: 13px;
    color: var(--text-secondary);
}
/* Checkbox row with a stacked strong/small description (a settings toggle's own explanation).
   Consolidates Settings' ".check-row" and Focus's ".focus-link-toggle" — same widget, same shape,
   previously duplicated at two different sizes. */
.check-row { display: flex; align-items: start; gap: 10px; width: fit-content; cursor: pointer; }
.check-row input { margin-top: 3px; }
.check-row > span { display: grid; gap: 2px; }
.check-row strong { color: var(--text-primary); font-size: 14px; font-weight: 600; }
/* The description line carries the shared .t-caption class in markup; this just tightens its wrap. */
.check-row small { line-height: 1.35; }

/* ---------------------------------------------------------------------------
   Surface kit (desktop PanelStyles). Backgrounds map onto existing semantic
   web tokens: --surface-nav == desktop SceneColor (opaque card/pane),
   --surface-rail == desktop LayerAltColor (recessed group box). No new hexes.
   Avalonia Padding="x,y" is horizontal,vertical, converted to CSS "v h" here.
   --------------------------------------------------------------------------- */
.surface-card { border: 1px solid var(--stroke); border-radius: var(--radius-overlay); background: var(--surface-nav); padding: 10px 12px; }
.surface-card--interactive { cursor: pointer; transition: background-color 120ms ease, border-color 120ms ease; }
.surface-card--interactive:hover { border-color: var(--stroke-strong); background: var(--surface-raised); }
/* A group box GROUPS; it does not recess. Filling it with --surface-app made it darker than the reading
   ground every consumer sits on, so a named bucket read as a well punched into the page rather than as a set
   of things belonging together (owner call). It is now hollow and outlined — the same vocabulary the chips and
   the hover card use, where a 1px stroke says "one object" without changing the surface behind it.
   (Desktop's own Border.group-box recesses to the page colour via LayerFillColorAltBrush, which is where the
   fill came from; the web's reading surfaces are not the page colour, so the same value does not translate.)
   The compact tier drops even the outline — see its own rule further down. */
.surface-group-box { border: 1px solid var(--stroke); border-radius: var(--radius-control); background: transparent; padding: 8px 10px; }
.surface-content-pane { max-width: 860px; border-radius: var(--radius-overlay); background: var(--surface-nav); padding: 14px 16px; }
.surface-pill, .surface-badge { display: inline-flex; align-items: center; gap: 6px; border: 1px solid var(--stroke); border-radius: var(--radius-overlay); background: var(--surface-hover); padding: 3px var(--sp-2); color: var(--text-secondary); font-size: 12px; font-weight: 500; }
.stat-tile { display: grid; gap: 2px; border: 1px solid var(--stroke); border-radius: var(--radius-overlay); background: var(--surface-raised); padding: 10px 12px; }
.stat-tile strong { color: var(--text-primary); font-size: 26px; font-weight: 600; line-height: 1.1; }
.stat-tile span { color: var(--text-secondary); font-size: 12px; }

.app-content { position: relative; z-index: 1; grid-column: 1; grid-row: 2; width: 100%; min-width: 0; min-height: 0; overflow: hidden; }
.app-root--library .app-content { z-index: 3; }
.viewer-landing { position: relative; z-index: 3; display: grid; width: 100%; min-height: 0; padding: 24px; }
.viewer-landing .empty-state { width: min(580px, 100%); max-width: none; border: 1px solid var(--stroke); border-radius: var(--radius-overlay); background: var(--surface-nav); padding: 44px 42px; box-shadow: var(--shadow-panel); }
.viewer-landing__title { font-family: var(--font-reading); font-weight: 560; letter-spacing: -.01em; }
/* The landing's copy reads at the CARD's measure, not the shared 360px empty-state one: at 360px inside a 580px
   card the paragraph sat in a narrow trench with 110px of dead air either side of it. */
.viewer-landing .empty-state p { max-width: 420px; }
.viewer-landing__aside { margin-top: var(--sp-2); color: var(--text-muted); }

/* The reading-font preview demonstrates that choice; manuscript hosts opt into it explicitly. */
.type-preview { font-family: var(--font-reading); }

/* Shared manuscript prose tokens — the ONE font-size/line-height/paragraph-spacing definition every
   prose-reading surface consumes, so Read's single-Scene/Full-script column and the Library Scene inspector's
   Manuscript tab can never drift onto two different "what does prose look like" answers (they used to: 17px/
   1.6 was hand-copied straight into Pages/Read.razor.css). Consumers apply these as VALUES inside their own
   scoped ::deep rule targeting FormattedDocumentView's .formatted-document/.prose-line (see
   Pages/Read.razor.css and Components/SceneReadDetailView.razor.css) rather than re-declaring numbers here as
   selectors: an unscoped global rule at the same specificity as FormattedDocumentView's own component-scoped
   default would only win by CSS load-order luck, where a component's own ::deep-scoped selector reliably wins
   on specificity alone. --read-font-size/--read-line-height (Pages/Read.razor.css, user-adjustable via the
   Reading settings panel) fall back to these when unset; --prose-paragraph-spacing mirrors desktop's
   TextRenderMetrics.ParagraphSpacing. */
:root {
    --prose-font-size: 17px;
    /* One of the four named line-spacing steps the Reading panel offers, so the Library's Manuscript tab (which
       has no control of its own) and Read's own default column are the same prose rather than a step apart. */
    --prose-line-height: 1.5;
    --prose-paragraph-spacing: 4px;
}

/* Native controls retain browser keyboard/accessibility behavior; only their semantic skin is shared.
   Height/radius/padding match the picker-family vocabulary established by WebEntityPicker and the
   Viewer control ribbon (~29-30px, var(--radius-control), 6px/9px padding) — this base rule is the fallback every
   plain, unclassed <select>/<input> falls back to (e.g. Settings' font/effect pickers), so it must already
   match rather than let those be the one un-styled outlier. */
select, input[type="text"], input[type="search"], textarea {
    border: 1px solid var(--stroke-control);
    border-radius: var(--radius-control);
    outline: none;
    background: var(--surface-input);
    color: var(--text-primary);
}
select { min-height: 32px; padding: 6px 9px; color: var(--text-primary); background: var(--surface-input); }
/* ComboBox parity: main's ComboBoxBackgroundPointerOver also resolves to ControlHoverColor
   (--surface-hover) — every native <select> in the app (ribbon pickers, popover trigger lists, Settings
   sliders' unit pickers) previously had no hover state at all. */
select:hover:not(:disabled) { border-color: var(--stroke-strong); background: var(--surface-hover); }
button, a, input, select { accent-color: var(--accent); }
/* Every genuinely keyboard-focusable element gets the SAME themed accent ring, never the browser's own
   default blue glow — :focus-visible (not :focus) so a mouse click never flashes it. `summary` (every
   <details> disclosure across the app — legends, coverage, and fortune-log panels) and a
   plain `[tabindex]` custom control (e.g. FormattedDocumentView's span-based annotation links, which also
   carry their own more specific .annotation-link:focus-visible rule below — harmless overlap, same values)
   are natively focusable too but were falling through to the UA default; tabindex="-1" is excluded since
   that idiom marks a container focused only programmatically (Read/Walk page roots), never by Tab. */
button:focus-visible, a:focus-visible, input:focus-visible, select:focus-visible, textarea:focus-visible,
summary:focus-visible, [tabindex]:not([tabindex="-1"]):focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}
button:disabled, input:disabled, select:disabled { color: var(--text-disabled); cursor: default; }

/* Shared checkbox skin, ONE style for every checkbox surface in the Viewer (ribbon filter/display toggles,
   picker rows, in-place controls region sections, Settings) — owner audit: unchecked boxes were a bare
   unstyled native square (every OS/browser draws that differently) while checked boxes only picked up a look
   at all because of the app-wide `accent-color` rule above, so the two states read as two different widgets.
   `accent-color` cannot touch the UNCHECKED box's fill/border (no browser exposes that half to CSS), which is
   exactly the half that was inconsistent — so this replaces the native box outright with `appearance: none`
   and paints both states from the theme, rather than leaning on accent-color at all.
   DESKTOP PARITY (Thruline.AvaloniaUI/Themes/Palettes/FluentBackgroundAliases.axaml CheckBox roles, backed by
   the same Shared/Themes/Palettes/Base/*.axaml this app's own CSS palette below was copied from — verified
   dark ControlFillColor #222222 = --surface-raised #222, StrokeColor #353535 = --stroke, AccentDefaultColor
   #7aa3cc = --accent, TextOnAccentColor #141414 = --text-on-accent; cozy-dark ControlFillColor #2e251d =
   --surface-raised, StrokeColor #3e3228 = --stroke, AccentDefaultColor #d2b078 = --accent, TextOnAccentColor
   #0f0d0b = --text-on-accent — every var below is the SAME semantic role desktop's CheckBox paints with, per
   theme, not a fixed color):
     unchecked fill   -> CheckBoxCheckBackgroundFillUnchecked   (ControlFillColor)   -> --surface-raised
     unchecked stroke -> CheckBoxCheckBackgroundStrokeUnchecked (StrokeColor)        -> --stroke
     hover fill        -> CheckBoxCheckBackgroundFillUncheckedPointerOver (ControlHoverColor) -> --surface-hover
     hover stroke       -> CheckBoxCheckBackgroundStrokeUncheckedPointerOver (Stroke2Color, no viewer alias yet) -> --stroke-strong
     checked fill/stroke -> CheckBoxCheckBackgroundFill/StrokeChecked (AccentDefaultColor) -> --accent
     checked+hover       -> …CheckedPointerOver (AccentSecondaryColor, no viewer alias yet) -> --accent-hover
     check glyph          -> CheckBoxCheckGlyphForegroundChecked (TextOnAccentColor) -> --text-on-accent */
input[type="checkbox"] {
    appearance: none;
    -webkit-appearance: none;
    box-sizing: border-box;
    width: 16px;
    height: 16px;
    flex: 0 0 auto;
    display: inline-grid;
    place-content: center;
    margin: 0;
    border: 1px solid var(--stroke);
    border-radius: 4px;
    background: var(--surface-raised);
    cursor: pointer;
    transition: background-color 120ms ease, border-color 120ms ease;
}
input[type="checkbox"]:hover:not(:disabled):not(:checked) { border-color: var(--stroke-strong); background: var(--surface-hover); }
input[type="checkbox"]:checked { border-color: var(--accent); background: var(--accent); }
input[type="checkbox"]:checked:hover:not(:disabled) { border-color: var(--accent-hover); background: var(--accent-hover); }
input[type="checkbox"]:checked::after {
    content: "";
    width: 4px;
    height: 8px;
    border: solid var(--text-on-accent);
    border-width: 0 2px 2px 0;
    transform: rotate(45deg) translate(-1px, -1px);
}
input[type="checkbox"]:disabled { opacity: .5; cursor: default; }

/* Shared range-slider skin (thin track + accent thumb), promoted from the shared step slider so every slider in
   the app — the step scrubber, Settings' ambient/graph-tuning sliders, and the reading controls
   — renders the same on-brand control instead of the bare OS slider some of these previously fell back to.
   Callers keep their own width/margin (already set per context); only the visual skin is shared here. */
input[type="range"] {
    appearance: none;
    -webkit-appearance: none;
    border: 0;
    padding: 0;
    background: transparent;
    accent-color: var(--accent);
    cursor: pointer;
}
input[type="range"]::-webkit-slider-runnable-track { height: 3px; border-radius: 999px; background: var(--stroke-strong); }
input[type="range"]::-webkit-slider-thumb { width: 14px; height: 14px; margin-top: -5.5px; border: 2px solid var(--surface-nav); border-radius: 50%; background: var(--accent); appearance: none; }
input[type="range"]::-moz-range-track { height: 3px; border-radius: 999px; background: var(--stroke-strong); }
input[type="range"]::-moz-range-thumb { width: 12px; height: 12px; border: 2px solid var(--surface-nav); border-radius: 50%; background: var(--accent); }
input[type="range"]:hover:not(:disabled)::-webkit-slider-thumb,
input[type="range"]:hover:not(:disabled)::-moz-range-thumb { filter: brightness(1.08); }
input[type="range"]:focus-visible { outline-offset: 4px; }
input[type="range"]:disabled { background: transparent; cursor: default; opacity: .5; }

.phosphor-icon { display: inline-block; flex: 0 0 auto; font-family: "Thruline Phosphor"; font-style: normal; font-weight: 400; line-height: 1; speak: never; }
.phosphor-icon--thin { font-weight: 100; }.phosphor-icon--light { font-weight: 300; }.phosphor-icon--regular { font-weight: 400; }.phosphor-icon--bold { font-weight: 700; }.phosphor-icon--fill { font-family: "Thruline Phosphor Fill"; font-weight: 400; }

/* ---------------------------------------------------------------------------
   App shell chrome (ribbon band 0 parity). Header/landing/banners stay global.
   --------------------------------------------------------------------------- */
.app-header {
    position: relative;
    z-index: 4;
    display: grid;
    grid-column: 1;
    grid-row: 1;
    grid-template-columns: minmax(240px, 1fr) auto minmax(240px, 1fr);
    align-items: center;
    border-bottom: 1px solid var(--chrome-border);
    background: var(--chrome-background);
    /* Exception (2026-07-28 elevation-token sweep): a persistent chrome bottom-edge separator, not a floating
       panel/overlay — mirrors desktop RibbonHeader's own independent one-off "0 4 8 0 #20000000" (12.5%) edge
       shadow, weaker than either elevation tier by design. Left off the --shadow-panel/--shadow-overlay scale. */
    box-shadow: 0 4px 8px rgb(0 0 0 / 12.5%);
    padding: 0 12px;
}
.brand-block { display: flex; grid-column: 1; align-items: center; gap: 10px; min-width: 0; color: inherit; text-decoration: none; }
.brand-block:visited { color: inherit; }
.brand-copy { display: grid; min-width: 0; }
.brand-block strong { color: var(--chrome-text-primary); font-size: 14px; font-weight: 600; letter-spacing: 0; }
.brand-block small { color: var(--chrome-text-secondary); font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.brand-mark { display: grid; place-items: center; width: 28px; height: 28px; border-radius: var(--radius-overlay); background: var(--accent); }
.brand-mark img { width: 82%; height: 82%; object-fit: contain; }
.section-tabs { position: relative; z-index: 5; display: flex; grid-column: 1; grid-row: 1; align-self: center; justify-self: center; align-items: center; gap: 2px; min-width: 0; padding: 0 4px; }
.section-tab { display: inline-flex; align-items: center; gap: 5px; border: 0; border-radius: var(--radius-control); background: transparent; padding: 7px var(--sp-3); color: var(--text-secondary); font-size: 14px; text-decoration: none; cursor: pointer; transition: background-color 120ms ease, color 120ms ease; }
.section-tab:visited { color: var(--text-secondary); }
.section-tab:hover:not(:disabled), .section-tab.active { background: var(--chrome-hover); color: var(--text-primary); }
/* Selected section = accent-coloured icon and label; the icon inherits, so one rule covers both. The
   existing --chrome-hover fill from the rule above is kept. No underline/edge bar, no shadow. */
.section-tab.active { color: var(--accent); }
.section-tab:disabled { opacity: .35; }
.header-actions { display: flex; grid-column: 3; justify-self: end; align-items: center; gap: 8px; }
.header-icon-button {
    display: grid;
    place-items: center;
    width: 28px;
    height: 28px;
    border: 0;
    border-radius: var(--radius-control);
    background: transparent;
    /* Chrome action icons (search, settings, help, replace-project) are text drawn ON CHROME, so they take
       the chrome text role rather than the content text role. --text-secondary is a warm brown tuned for
       body copy on a content surface and left these icons muddy against the header. */
    color: var(--chrome-text-secondary);
    text-decoration: none;
    cursor: pointer;
    transition: background-color 120ms ease, color 120ms ease;
}
.header-icon-button:visited { color: var(--chrome-text-secondary); }
.header-icon-button:hover, .header-icon-button.active { background: var(--chrome-hover); color: var(--chrome-text-primary); }
.section-tab__icon { display: none; font-size: 14px; }
.section-tab__label { white-space: nowrap; }
.header-more-button { display: none; }
.header-overflow-actions[popover] {
    position: static;
    inset: auto;
    display: flex;
    align-items: center;
    gap: 8px;
    width: auto;
    height: auto;
    margin: 0;
    overflow: visible;
    border: 0;
    background: transparent;
    padding: 0;
    color: inherit;
}
.header-overflow-action.header-icon-button > span:not(.phosphor-icon) { display: none; }
.open-artifact-button > .phosphor-icon { display: none; }
/* Chrome comes from the shared .btn/.btn--file kit; this class owns only where the control sits in the header. */
.open-artifact-button { justify-self: end; }

.load-error { position: fixed; z-index: 21; inset: 52px 0 auto; padding: 8px 16px; background: color-mix(in srgb, var(--danger) 20%, var(--surface-raised)); color: color-mix(in srgb, var(--danger) 55%, var(--text-primary)); font-size: 13px; border-bottom: 1px solid color-mix(in srgb, var(--danger) 45%, var(--stroke)); pointer-events: none; }
.artifact-loading { position: fixed; z-index: 20; top: 50%; left: 50%; display: flex; align-items: center; gap: 10px; transform: translate(-50%, -50%); border: 1px solid var(--stroke); border-radius: var(--radius-overlay); background: var(--surface-raised); padding: 10px 16px; color: var(--text-primary); box-shadow: var(--shadow-panel); }
.app-root--landing .load-error { inset: 0 0 auto; }
.viewer-loading-mark { width: 28px; height: 28px; object-fit: contain; transform-origin: center; animation: viewer-loading-rock .7s ease-in-out infinite alternate; }
@keyframes viewer-loading-rock {
    from { transform: rotate(-12deg) translateX(-2px); }
    to { transform: rotate(12deg) translateX(2px); }
}
@media (prefers-reduced-motion: reduce) {
    .viewer-loading-mark { animation: none !important; }
    .app-root *, .app-root *::before, .app-root *::after {
        scroll-behavior: auto !important;
        transition-duration: .01ms !important;
        animation-duration: .01ms !important;
        animation-iteration-count: 1 !important;
    }
}

.empty-state { display: grid; justify-items: center; place-self: center; max-width: 520px; padding: 8px; text-align: center; color: var(--text-secondary); }
.empty-symbol { display: grid; place-items: center; width: 56px; height: 56px; margin-bottom: 12px; border-radius: var(--radius-overlay); background: color-mix(in srgb, var(--accent) 14%, transparent); color: var(--text-muted); font-size: 28px; }
.empty-state h1 { margin: 0; }
.empty-state p { max-width: 360px; margin: 6px 0 0; line-height: 1.6; }
.empty-actions { display: flex; flex-wrap: wrap; align-items: stretch; justify-content: center; gap: 9px; margin-top: var(--sp-5); }
/* Landing actions compose the shared .btn kit for their chrome; these classes carry only the landing's own
   layout (a roomier hit area than a toolbar button) and the one genuine call-to-action emphasis on the page —
   the landing "Open .thrv file" is the single solid-accent control in the Viewer, which is why it keeps a local
   emphasis rule instead of .btn--accent's washed "selected" tone. */
/* Every action on this row is the same TWO-LINE card (a bold name over one explaining line), so the file picker
   and the sample chooser read as peers rather than as a tall solid slab beside a stack of prose. */
.empty-action { min-width: 112px; flex-direction: column; align-items: flex-start; justify-content: center; gap: 2px; max-width: 220px; padding: 10px 14px; text-align: left; white-space: normal; }
.empty-action--primary, a.empty-action--primary:visited { border-color: var(--accent); background: var(--accent); color: var(--text-on-accent); font-weight: 600; }
.empty-action--primary:hover { border-color: var(--accent-hover); background: var(--accent-hover); color: var(--text-on-accent); }
/* A one-line action (the not-found page's "Return to Library") has no title/subtitle stack to left-align, so it
   keeps the centred button shape rather than hanging its label off the leading edge of a column. */
.empty-action:not(:has(.empty-action__title)) { align-items: center; justify-content: center; text-align: center; }
/* The one solid-accent control in the Viewer, so its own subtitle reads on the accent rather than on the page. */
.empty-action--primary .empty-action__subtitle { color: inherit; opacity: .78; }
.empty-action--primary .empty-action__title .phosphor-icon { margin-inline-end: 6px; }
/* Sample chooser buttons: each names its story and states linear-vs-branching up front, so a reader picks the
   one that matches what they want to see (the branching sample exists specifically to demo timelines/forks/
   compare chrome the linear one hides entirely) instead of guessing from a single generic "View sample" button. */
.empty-action--sample { max-width: 220px; }
.empty-action__title { display: flex; align-items: center; font-size: 14px; font-weight: 600; }
.empty-action__subtitle { font-size: 12px; line-height: 1.4; color: var(--text-secondary); }

.boot-status { display: flex; align-items: center; justify-content: center; gap: 10px; height: 100%; color: var(--text-secondary); }
#blazor-error-ui { display: none; position: fixed; inset: auto 0 0; z-index: 1000; padding: 12px; background: #5a2428; color: white; }

/* Safety boundary for the native WebGL canvas. Keep this global and inline-reinforced: a missing
   isolated stylesheet must never let the backing-buffer dimensions participate in page layout. */
.webgl-surface {
    min-width: 0;
    min-height: 0;
    max-width: 100vw;
    max-height: 100vh;
    overflow: hidden;
    contain: strict;
}

.webgl-surface__canvas {
    position: absolute !important;
    inset: 0;
    display: block;
    width: 100% !important;
    height: 100% !important;
    max-width: 100vw !important;
    max-height: 100vh !important;
}

/* ---------------------------------------------------------------------------
   Shared reading components. These classes back the reusable TagLinkButton
   component plus TagReadDetailView's own inline tag-facts markup, so their
   base skin stays global. Per-context overrides live in the consuming
   component's scoped CSS (e.g. TagReadDetailView ::deep).
   --------------------------------------------------------------------------- */
.tag-facts-section { width: min(780px, calc(100% - 64px)); margin-inline: auto; padding: 26px 0; border-top: 1px solid var(--stroke); }
.tag-facts-section h2 { margin: 0 0 16px; font-size: 18px; }
.tag-facts-section h3 { margin: var(--sp-5) 0 var(--sp-2); color: var(--text-muted); font-size: 13px; font-weight: 600; text-transform: none; letter-spacing: 0; }
.tag-link-list { display: flex; flex-wrap: wrap; gap: 8px; }
.tag-link-button { display: grid; gap: 1px; min-width: 130px; border: 1px solid var(--stroke); border-left: 3px solid var(--semantic-color); border-radius: var(--radius-control); background: var(--surface-nav); padding: 8px 10px; text-align: left; cursor: pointer; }
.tag-link-button small { color: var(--text-muted); font-size: 12px; }
.tag-scene-list { display: grid; gap: 5px; }
.tag-scene-row { display: grid; grid-template-columns: 28px minmax(0, 1fr); gap: 9px; width: 100%; border: 1px solid transparent; border-radius: var(--radius-control); background: var(--surface-nav); padding: 9px; text-align: left; cursor: pointer; }
.tag-scene-row > span:last-child { display: grid; gap: 3px; }
.tag-scene-row small { overflow: hidden; color: var(--text-muted); text-overflow: ellipsis; white-space: nowrap; }
.scene-position { display: grid; place-items: center; width: 24px; height: 24px; border-radius: 50%; background: var(--accent-soft); color: var(--accent); font-size: 11px; }

/* ---------------------------------------------------------------------------
   Responsive shell + shared. Page-specific breakpoints live in each page's
   scoped CSS (Home/Workspace/Settings).
   --------------------------------------------------------------------------- */
/* Compact tier via the shared attribute (wwwroot/js/responsiveTier.js), not a restated @media condition — see
   that file for why a breakpoint cannot be shared in CSS itself. This stylesheet is global (no ::deep, no
   scope attribute), so `:root[data-phone] .thing` wins on specificity alone against the plain rules above —
   no reliance on source order here either. */
:root[data-phone] .app-header { grid-template-columns: auto minmax(0, 1fr) auto; padding-inline: 8px; }
:root[data-phone] .brand-copy { display: none; }
:root[data-phone] .section-tab { padding-inline: 10px; }
:root[data-phone] .open-artifact-button { padding-inline: 9px; }
:root[data-phone] .tag-facts-section { width: calc(100% - 32px); }

:root[data-phone] html, :root[data-phone] body, :root[data-phone] #app { height: 100dvh; }
:root[data-phone] .app-root { height: 100dvh; font-size: 14px; line-height: 1.4; }
:root[data-phone] .t-header-page { font-size: 20px; line-height: 1.25; }
:root[data-phone] .t-header-stat, :root[data-phone] .t-header-section { font-size: 16px; line-height: 1.3; }
:root[data-phone] .t-header-card { font-size: 14px; line-height: 1.35; }
:root[data-phone] .t-row-title, :root[data-phone] .t-body, :root[data-phone] .t-body-primary, :root[data-phone] .t-empty-state, :root[data-phone] .t-explainer {
    font-size: 14px;
    line-height: 1.45;
}
:root[data-phone] .t-label-field, :root[data-phone] .t-meta { font-size: 13px; line-height: 1.35; }
:root[data-phone] .t-header-group, :root[data-phone] .t-caption { font-size: 12px; line-height: 1.35; }
:root[data-phone] .t-micro-label { font-size: 11px; line-height: 1.3; }
:root[data-phone] input:not([type="range"]):not([type="checkbox"]):not([type="radio"]),
:root[data-phone] select,
:root[data-phone] textarea {
    min-height: var(--tap);
    font-family: var(--font-interface);
    font-size: 16px;
}
:root[data-phone] .btn, :root[data-phone] .btn--sm { min-height: var(--tap); font-size: 14px; }
:root[data-phone] .btn--icon, :root[data-phone] .btn--icon.btn--sm { width: var(--tap); height: var(--tap); }
:root[data-phone] input[type="text"], :root[data-phone] input[type="search"], :root[data-phone] select, :root[data-phone] textarea { min-height: var(--tap); }
:root[data-phone] .check-inline, :root[data-phone] .check-row { min-height: var(--tap); }
:root[data-phone] .app-header, :root[data-phone] .section-tabs { background: var(--surface-header); }
:root[data-phone] .app-content { background: var(--surface-app); }
:root[data-phone] .surface-content-pane { border: 1px solid var(--stroke); padding: 12px; }
/* Compact drops the outline too: at phone width a bucket already owns the full column, so its own edge adds
   nothing but a line. The background half of this rule is now the base value, not an override. */
:root[data-phone] .surface-group-box { border: 0; }
:root[data-phone] .surface-pill, :root[data-phone] .surface-badge { background: var(--surface-raised); font-size: 12px; }
/* Taste pass: the app-surface backing stays (it is the canvas's ground and keeps a slow first paint from
   showing page through), but the stroke + radius are gone. Desktop draws NO frame around any of these three
   surfaces, and at phone size the frame was actively working against each of them: on the canvas — explicitly a
   full-bleed "canvas + overlays only" surface — it drew a hairline box tracing the screen edge; on a reading
   arc, whose wide aspect necessarily letterboxes in a tall narrow column, it turned the unavoidable empty space
   into a big visible EMPTY BOX rather than page; and on Workspace it put an arbitrary edge around a canvas that
   is conceptually infinite. A box should mean an affordance, not a grouping. */
:root[data-phone] .webgl-surface {
    border: 0;
    border-radius: 0;
    background: var(--surface-app);
}
:root[data-phone] .section-tab.active {
    background: transparent;
    color: var(--text-primary);
}
:root[data-phone] .app-header {
    padding-top: env(safe-area-inset-top);
    padding-right: max(6px, env(safe-area-inset-right));
    padding-left: max(6px, env(safe-area-inset-left));
}
:root[data-phone] .section-tab__icon { display: inline-block; }
:root[data-phone] .section-tab--large-screen-only { display: none; }
:root[data-phone] .section-tab--read { order: 1; }
:root[data-phone] .section-tab--library { order: 2; }
:root[data-phone] .section-tab--workspace { order: 3; }
:root[data-phone] .header-actions { gap: 2px; }
:root[data-phone] .header-actions > .header-icon-button,
:root[data-phone] .header-more-button {
    display: inline-flex;
    width: auto;
    min-width: var(--tap);
    height: var(--tap);
    align-items: center;
    justify-content: center;
    gap: 5px;
    border: 0;
    border-radius: var(--radius-control);
    background: transparent;
    padding: 0 8px;
    color: var(--text-secondary);
    font-size: 14px;
    cursor: pointer;
}
:root[data-phone] .header-actions > .header-icon-button:hover,
:root[data-phone] .header-more-button:hover { background: var(--chrome-hover); color: var(--text-primary); }
:root[data-phone] .header-overflow-actions[popover] { display: none; }
:root[data-phone] .header-overflow-actions[popover]:popover-open {
    position: fixed;
    inset: calc(48px + env(safe-area-inset-top)) max(8px, env(safe-area-inset-right)) auto auto;
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 4px;
    box-sizing: border-box;
    width: max-content;
    min-width: 12rem;
    max-width: calc(100vw - 24px - env(safe-area-inset-left) - env(safe-area-inset-right));
    max-height: min(60dvh, 360px);
    margin: 0;
    overflow-y: auto;
    border: 1px solid var(--stroke);
    border-radius: var(--radius-overlay);
    background: var(--surface-raised);
    box-shadow: var(--shadow-overlay);
    padding: 7px;
    color: var(--text-primary);
    color-scheme: inherit;
    font-family: var(--font-interface);
}
:root[data-phone] .header-overflow-action.header-icon-button {
    display: flex;
    width: 100%;
    min-height: var(--tap);
    justify-content: flex-start;
    gap: 9px;
    padding: 8px 10px;
}
:root[data-phone] .header-overflow-action.header-icon-button > span:not(.phosphor-icon),
:root[data-phone] .open-artifact-button > .phosphor-icon { display: inline-block; }
/* The guide surface itself is hidden at this tier (ViewerGuidedTourHost.razor.css), so its entry point
   goes with it — a button that opens nothing is worse than no button. */
:root[data-phone] .header-overflow-action[data-tour-anchor="help-button"] { display: none; }
:root[data-phone] .header-overflow-actions .open-artifact-button {
    display: flex;
    min-height: var(--tap);
    align-items: center;
    justify-content: flex-start;
    gap: 9px;
    padding: 8px 10px;
    white-space: nowrap;
}
:root[data-phone] .viewer-landing {
    min-height: 100dvh;
    padding: max(20px, env(safe-area-inset-top)) max(16px, env(safe-area-inset-right))
             max(20px, env(safe-area-inset-bottom)) max(16px, env(safe-area-inset-left));
}
:root[data-phone] .viewer-landing .empty-state { border: 0; background: transparent; padding: 24px 8px; box-shadow: none; }
:root[data-phone] .empty-actions { width: min(100%, 320px); margin-top: 18px; }
/* Stacked full-width cards, all the same width: the desktop row's 220px cap is what makes them read as peers
   SIDE by side, and the same cap under one another just made the first card narrower than the second. */
:root[data-phone] .empty-action { min-height: var(--tap); max-width: none; }
:root[data-phone] .empty-action--primary { width: 100%; }
:root[data-phone] .empty-action--sample { flex: 1 1 100%; }
:root[data-phone] input[type="range"] { min-height: var(--tap); }
:root[data-phone] input[type="range"]::-webkit-slider-thumb {
    width: 22px;
    height: 22px;
    margin-top: -9.5px;
}
:root[data-phone] input[type="range"]::-moz-range-thumb { width: 20px; height: 20px; }

/* Orientation stays a real @media axis; the SIZE half is the shared compact attribute. */
@media (orientation: portrait) {
    :root[data-phone] .app-root {
        grid-template-rows: calc(48px + env(safe-area-inset-top)) minmax(0, 1fr)
                            calc(56px + env(safe-area-inset-bottom));
    }
    :root[data-phone] .app-root--landing { grid-template-rows: minmax(0, 1fr); }
    :root[data-phone] .app-header {
        grid-column: 1;
        grid-row: 1;
        grid-template-columns: minmax(0, 1fr) auto;
        padding-bottom: 0;
    }
    :root[data-phone] .brand-block { grid-column: 1; width: min(100%, 260px); }
    :root[data-phone] .brand-mark { width: 32px; height: 32px; }
    :root[data-phone] .brand-copy { display: grid; }
    :root[data-phone] .brand-block strong { display: none; }
    :root[data-phone] .brand-block small { display: block; max-width: clamp(96px, 42vw, 220px); }
    :root[data-phone] .header-actions { grid-column: 2; }
    :root[data-phone] .section-tabs {
        grid-column: 1;
        grid-row: 3;
        align-self: stretch;
        justify-self: stretch;
        justify-content: space-around;
        width: 100%;
        overflow: hidden;
        border-top: 1px solid var(--chrome-border);
        background: var(--surface-header);
        padding: 0 max(2px, env(safe-area-inset-right)) env(safe-area-inset-bottom)
                 max(2px, env(safe-area-inset-left));
    }
    :root[data-phone] .section-tab {
        min-width: 0;
        min-height: var(--tap);
        flex: 1 1 20%;
        flex-direction: column;
        justify-content: center;
        gap: 3px;
        padding: 4px 2px;
    }
    :root[data-phone] .section-tab__icon { font-size: 18px; }
    :root[data-phone] .section-tab__label { font-size: 11px; }
    :root[data-phone] .app-content { grid-row: 2; }
    :root[data-phone] .load-error { inset: calc(48px + env(safe-area-inset-top)) 0 auto; }
}

/* Same split: orientation is the genuine axis here, compactness comes from the shared attribute. */
@media (orientation: landscape) {
    :root[data-phone] .app-root {
        grid-template-rows: calc(48px + env(safe-area-inset-top)) minmax(0, 1fr);
    }
    :root[data-phone] .app-root--landing { grid-template-rows: minmax(0, 1fr); }
    :root[data-phone] .app-header {
        grid-column: 1;
        grid-row: 1;
        grid-template-columns: minmax(120px, 1fr) auto minmax(100px, 1fr);
        padding-bottom: 0;
    }
    :root[data-phone] .brand-copy { display: grid; }
    :root[data-phone] .brand-block strong { display: none; }
    :root[data-phone] .brand-block small { display: block; max-width: clamp(72px, 16vw, 160px); }
    :root[data-phone] .section-tabs {
        grid-column: 1;
        grid-row: 1;
        align-self: center;
        justify-self: center;
        max-width: calc(100% - 230px);
        overflow-x: auto;
        scrollbar-width: none;
    }
    :root[data-phone] .section-tabs::-webkit-scrollbar { display: none; }
    :root[data-phone] .section-tab {
        min-height: var(--tap);
        flex: 0 0 auto;
        gap: 4px;
        padding: 4px 6px;
    }
    :root[data-phone] .section-tab__icon { display: inline-block; font-size: 14px; }
    :root[data-phone] .section-tab__label { font-size: 11px; }
    :root[data-phone] .app-content { grid-row: 2; }
    :root[data-phone] .load-error { inset: calc(48px + env(safe-area-inset-top)) 0 auto; }
}
