/* ============================================================
   ArchiOSK / B-Hive — component and layout CSS

   Color, typography, and spacing are NOT defined here - see
   static/css/tokens.css (loaded before this file in base.html), the
   one controlled place for the app's visual character. Every rule
   below should reference those tokens (var(--text-secondary),
   var(--space-6), ...), never a raw hex/rem value that duplicates what
   a token already names. This file is component/layout rules only:
   what things look like structurally, not what the base palette or
   scale is.

   Page-template families: derived from actually inspecting every route's
   composition and interaction mode, not assumed up front. Two were real,
   already-duplicated geometries and are now real shared templates:
     - Dead-end (templates/errors/error.html): one message, one way out,
       no ongoing state, reachable pre- or post-auth. 403/404/500 used to
       be three near-identical files; now one parameterized template.
     - Confirm interrupt (templates/confirm_base.html, .confirm-page):
       one centered card, one question, one action set, one way back,
       always mid-flow inside the authenticated shell. Approval Gate
       actions and project deletion used to each hijack .case-workspace's
       grid via an inline style for this; now a real family with two
       members (confirm_action.html, confirm_delete_project.html).
   Others are single-instance today - named here so a second member
   inherits the grammar deliberately instead of being invented fresh, not
   built out as empty template scaffolding for a family of one:
     - Entry/gateway (login.html, gateway.html): centered card, one
       primary decision. CORRECTED: an earlier pass here called
       gateway.html dead because no template links to it - wrong,
       checked only navigation links, not redirect targets.
       routes/portal.py's login() redirects here by default whenever no
       `?next=` is present (see its own comment), and index.html's
       "Sign in to get started" link never sets one - so this is the
       actual, frequently-hit landing page for a fresh login, not an
       orphan. This makes Entry a real two-member family already
       sharing CSS (.gateway-page/.gateway-card/.gateway-brand) but
       still duplicating the brand-lockup markup between the two files
       - a genuine consolidation candidate, not yet done.
     - Single-task capture (upload.html): hero intro, one primary input,
       one submit, inline validation error.
     - Directory (projects.html): search + filterable card list.
     - Instrumented workspace (case_workspace.html, case open): three
       panes, heavy disclosure, high information density. Already
       correctly ONE template governing four behavioral states (private/
       shared/collaborative, archived read-only, derived) via data, not
       four duplicated files - the reference example other families
       should match, not a gap. Its geometry is also explicitly
       space-driven, not just state-driven - see .app-shell-body/
       .display-divisions below (CLAUDE-P40-E3A: this used to be a named-
       grid-area .case-workspace, now a flex row of Lists|Display|Toolbox
       plus a dynamic-count Display grid inside it): panel-dividers keep
       the reference/navigation panes collapsible at every width, instead
       of a single "collapse to one column" breakpoint dumping eight
       accordions above the actual conversation.
     - Hub (case_workspace.html, no case open / "Project Home"):
       single column, conversational composer, light disclosure.
   Deliberately NOT templated: the legacy dashboard (dashboard.html)
   has its own header/stats/lattice/table composition, but the product
   is actively moving traffic off it (see the "Open Case Workspace" /
   "Legacy Dashboard" cross-links in both this file and case_workspace.html)
   - investing in a family template for a surface being superseded would
     be building for the wrong direction.
   ============================================================ */
* { box-sizing: border-box; }

/* Browsers do NOT give form controls the page's font (or its colors)
   by default - they use OS UI defaults unless told otherwise. The
   font-family/font-size backstop below predates CLAUDE-P40-VW6; the
   background-color/color/border-color lines are this stage's own
   addition, for the identical reason applied to color: most buttons/
   inputs already set their own explicit colors (and those rules still
   win here on specificity - a class selector beats this bare-tag
   one), but any control that doesn't - <select> most visibly, browsers
   never theme a bare <select> from surrounding CSS at all - rendered
   with the browser's own native white-background/black-text chrome
   regardless of the page's chosen appearance mode. That is a large
   share of what CLAUDE-P40-VW6's "portions of the panel do not
   receive the Dark/Light/Tinted treatment" defect actually was:
   www.display-division-picker (the VW1/VW4 "open a Document here"
   dropdown) is exactly this case, and it is far from the only one.
   Values are the standard tokens, so this backstop automatically
   tracks whichever surface's Light/Dark/Tinted scope it renders
   inside, the same as every other component rule in this file. */
button, input, select, textarea {
    font-family: inherit;
    font-size: inherit;
    background-color: var(--surface-primary);
    color: var(--text-primary);
    border-color: var(--border);
}

/* CLAUDE-P40-VW8-QA, Section 10: the page itself never scrolls - only
   .app-shell's own children with a real bounded height do (each panel
   independently, via its own overflow-y:auto below). This is what
   makes the Menu bar "fixed" without a single position:fixed/sticky
   rule of its own: it is simply never inside anything that scrolls.
   Before this stage, html/body had no height/overflow constraint at
   all, so any page whose content grew taller than the viewport (a
   long Overview, a tall Investigation, many Project cards) scrolled
   the WHOLE page - Menu included - out of view; the panel-level sticky/
   scroll rules already on .launcher-panel/.chat-region only ever
   worked by coincidence, when nothing else on the page was tall enough
   to trigger page-level scroll first. */
html, body {
    margin: 0;
    padding: 0;
    height: 100%;
    overflow: hidden;
    background: var(--canvas);
    color: var(--text-primary);
    font-family: var(--font-body);
    /* CLAUDE-P40-VW8-QA (Site-Wide Visual-System Consistency Addendum):
       font-stretch: condensed removed - it was never actually a no-op.
       --font-body's own stack already names the condensed variants
       explicitly ("Arial Nova Cond", "Arial Narrow") - font-family
       matching alone already selects those correctly wherever they're
       installed. The font-stretch declaration only had a real effect
       on the FALLBACK tier (plain "Arial"/sans-serif, i.e. any system
       without Arial Nova installed - not just Windows 11/365): browsers
       synthesize a horizontal squish for a static font asked to stretch
       to a width it doesn't have a real face for, which is exactly
       "unjustified synthetic font stretching" - distorted, narrower-
       than-designed text, and specifically on whichever system DIDN'T
       have the intended font, the opposite of graceful degradation.
       Removing it changes zero px of which FONT FAMILY loads (the
       fallback chain itself is untouched, per this stage's own "do not
       change the font family" instruction) - only stops the browser
       from artificially distorting the fallback face. */
    line-height: 1.55;
}

/* Light where large text stays comfortably readable at that weight;
   regular weight is the default everywhere else - see the large-heading
   overrides below (hero/entry/gateway/dashboard/project-home h1s) for
   where 300 actually gets used. Never bold-by-default the way the
   browser UA stylesheet would. */
h1, h2, h3, h4, h5, h6 { font-weight: 500; }

a { color: var(--machine-blue); text-decoration: none; }
a:hover { text-decoration: underline; }
a:focus-visible, button:focus-visible, input:focus-visible {
    outline: 2px solid var(--machine-blue);
    outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
    * { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
}

/* -- blueprint grid backdrop ------------------------------------------
   Genuinely blue, not the sand --border tone: a blueprint's own defining
   feature is its cyanotype blue line, and blue sits roughly complementary
   to this app's warm ~40deg canvas hue - the one place a structural blue
   wash belongs, kept faint enough (0.3 opacity) to stay a backdrop, not a
   foreground color. */
.blueprint-grid {
    position: fixed;
    inset: 0;
    z-index: -1;
    background-image:
        linear-gradient(var(--blueprint-grid-line) 1px, transparent 1px),
        linear-gradient(90deg, var(--blueprint-grid-line) 1px, transparent 1px);
    background-size: 48px 48px;
    opacity: 0.3;
}

/* -- app shell: application-wide top bar + one left launcher panel -------
   CLAUDE-P40-E2B1: replaces the old expandable icon-only/labeled
   .side-rail (a width toggle between a narrow icon rail and a wider
   labeled panel) with a single restrained launcher panel, shown or
   hidden in full (no intermediate width state) via the top bar's own
   "Lists" toggle - see html.launcher-hidden below. .app-shell is a
   column: the top bar (full width, spans Launcher/Display/Toolbox) then
   .app-shell-body (the launcher panel beside the main content area,
   the exact same flex-row arrangement .side-rail/.app-main used before,
   just renamed and simplified). */
.app-shell {
    display: flex;
    flex-direction: column;
    /* CLAUDE-P40-VW8-QA: was min-height:100vh, letting .app-shell (Menu
       included) grow taller than the viewport and scroll with the page
       - see html/body's own comment above. Locked to the viewport
       exactly; every panel below scrolls internally instead. */
    height: 100vh;
    overflow: hidden;
    /* CLAUDE-P40-VW7A-QA2 (product-owner browser correction): a base
       canvas for anything that is shell CHROME rather than one of the 5
       independently-themed surfaces (Menu/Lists/Display/Toolbox/Chat) -
       principally the panel dividers/splitters, which sit as siblings
       of those surfaces, not descendants, and so never inherited any
       surface's own --surface-primary redefinition ("white splitter
       tracks... in the dark theme" - the actual root cause). .app-shell
       itself gets a 6th appearance class below (base.html's own inline
       scripts - piggybacked on the Menu surface's resolved mode, the
       one always-visible, always-defined surface, not a new separate
       preference), so --surface-primary here already resolves to the
       correct theme by the time any var() below reads it - this
       declaration is what actually paints it. A themed surface nested
       inside .app-shell still wins locally via normal CSS custom-
       property cascade (closer ancestor), so per-surface Appearance
       independence (Section 5's own "mixed" state) is unaffected. */
    background: var(--surface-primary);
}
.app-shell-body {
    display: flex;
    align-items: stretch;
    flex: 1;
    min-height: 0;
    overflow: hidden;
}
/* CLAUDE-P40-VW7A-QA2 (product-owner browser correction): Lists
   (.launcher-panel) and .workspace-main-column (Display+Chat) share
   .app-shell-body's row - this is what makes Lists' own height:100%
   actually cover the SAME vertical extent as Chat's row, closing the
   "light rectangle beneath Lists in dark modes" gap that existed while
   Chat was a full-width sibling of .app-shell-body itself.
   CLAUDE-P40-EYE1: a third sibling, .workspace-right-column
   (Toolbox+Eye - see that rule below), was added for the SAME reason
   on the opposite side - it used to be Toolbox alone, a plain flex
   sibling of .app-main inside .workspace-content-row (retired: with
   Toolbox moved out, that row wrapper had only one child left,
   .app-main, so it added nothing .workspace-main-column doesn't
   already provide directly). */
.workspace-main-column {
    display: flex;
    flex-direction: column;
    flex: 1;
    /* CLAUDE-P40-EYE1 (product-owner browser correction), Section 1:
       "enforce practical minimum widths for both the centre and right
       column" - a real CSS-level floor for Display+Chat (the actual
       flex item competing for width against Lists and the right
       column), defense-in-depth alongside the drag logic's own clamp
       in base.html (which keeps the user from dragging past this in
       the first place). */
    min-width: 320px;
    min-height: 0;
    overflow: hidden;
}
/* CLAUDE-P40-VW8-QA (panel-border hierarchy correction): the desktop
   rule no longer draws its own border-right - .panel-divider's own
   ::before (below) already draws a line ~4px to the right of this
   panel's edge, so the two together were a "double line" (product-
   owner report: "the Lists-to-Display boundary" among the distracting
   areas). One line now, from the divider alone - it already carries
   the correct quiet-resting/accent-on-hover-or-focus hierarchy this
   correction asks for everywhere. The narrow-viewport drawer variant
   further below KEEPS its own border-right deliberately: at that width
   the drawer floats over content with no adjacent .panel-divider
   beside it (the divider is what collapses the panel in the first
   place), so there is no second line to double up with there. */
.launcher-panel {
    /* Bounded UI pass (listing-panel resize): --launcher-width is set by
       base.html's own drag handler on #lists-divider, same CSS-custom-
       property write point every other resizable divider in this file
       already uses (--right-column-width, --toolbox-height, --chat-
       height) - the 240px fallback is what unhydrated/no-JS page loads
       still get, unchanged from before. */
    width: var(--launcher-width, 240px);
    flex-shrink: 0;
    display: flex;
    flex-direction: column;
    background: var(--surface-primary);
    /* CLAUDE-P40-VW7A-QA2: overflow-y:auto used to live directly on this
       element (the nav scrolled as one region) - now that .lists-pane/
       .thumbnails-pane are two independently-scrolling sub-regions
       (Section 3's own "efficient rendering, independent scroll"), this
       level only clips so the draggable divider's drag range never
       spills outside the column; each pane below owns its own scroll. */
    overflow: hidden;
    /* CLAUDE-P40-VW8-QA: was height:100vh + position:sticky/top:0 - a
       workaround for the page itself scrolling (see html/body's own
       comment). Now that .app-shell is height-locked and .app-shell-
       body no longer scrolls as a whole, 100% of .app-shell-body's own
       (already well-defined) height is correct and sticky positioning
       has nothing left to stick against - simplified accordingly. */
    height: 100%;
}
/* CLAUDE-P40-LTH1 (correction): used to be flex:1 1 auto by default,
   only taking the persisted/default percentage once .launcher-panel
   got a .has-thumbnails class that static/js/pdf_viewer.js added
   solely while the active Document was a PDF - meaning Lists silently
   filled the WHOLE column, with no visible split at all, on every
   Overview/Investigation/Chat/non-PDF-Document page (a real
   product-owner-reported screenshot defect: "Project/List records
   continuing uninterrupted to the bottom edge"). Thumbnails is now a
   PERMANENT structural pane (see the template's own comment on
   .lists-pane), so this rule is unconditional - the exact same
   "outer column owns sizing" split .workspace-pane-toolbox already
   uses relative to Eye, applied here instead of the old class-gated
   version. */
.lists-pane {
    flex: 0 0 var(--lists-height, 60%);
    min-height: 0;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    /* CLAUDE-LEFTPANEL-CALM-01 / CLAUDE-PANEL-CALM-02: quiet/transparent
       by default, revealed only on :hover - a real Product Owner report
       named this panel "too agitated," then confirmed the fix and asked
       for the SAME treatment on every other panel (see the shared
       ":hover" rule and the shared WebKit block below, which now cover
       Thumbnails/Toolbox/Eye/Display/Chat/tab-strips too - this is no
       longer this ONE panel's special case). `transparent transparent`
       keeps the SAME reserved gutter width overflow-y:auto already
       allocates (no layout shift - the scrollbar still exists and still
       functions, it's simply invisible until hovered). */
    scrollbar-color: transparent transparent;
}
/* CLAUDE-POST-SIGNIN-GATEWAY-SIMPLIFICATION-01, Addendum I: applied only
   when Thumbnails/its divider are absent from the DOM entirely (a
   genuinely project-less page - base.html's own template comment on
   .lists-pane covers the reasoning). Without this, the fixed flex-basis
   above would still cap Lists at 60% with nothing below it to fill the
   remaining 40% - "preserve layout stability" (Addendum I's own words),
   not merely hide a pane and leave dead space. */
.lists-pane-full {
    flex: 1 1 auto;
}
.lists-thumbnails-divider {
    flex-shrink: 0;
    width: 100%;
    height: 9px;
    border: none;
    padding: 0;
    cursor: row-resize;
    /* CLAUDE-P40-VW7A-QA2 (product-owner browser correction): was
       transparent - unlike .panel-divider (a sibling of the 5 themed
       surfaces), this element IS a descendant of .launcher-panel, so it
       already inherits Lists' own --surface-primary redefinition
       directly; a real background here (rather than falling through to
       whatever's behind .launcher-panel) is what actually paints it,
       merging with Lists' own theme specifically, never the shell
       fallback. */
    background: var(--surface-primary);
    position: relative;
    z-index: 1;
}
.lists-thumbnails-divider::before {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    top: 4px;
    height: 1px;
    background: var(--border);
}
.lists-thumbnails-divider:hover::before,
.lists-thumbnails-divider:focus-visible::before,
.lists-thumbnails-divider.dragging::before {
    background: var(--machine-blue);
    height: 2px;
    top: 3.5px;
}
/* CLAUDE-P40-LTH1, Section 6 ("visible focus"): a real focus-visible
   outline on the divider ITSELF - the ::before accent line above only
   changes color/thickness, which is not the same as a genuine focus
   indicator for a keyboard user tabbing through the shell. Same
   treatment .toolbox-eye-divider already has. */
.lists-thumbnails-divider:focus-visible {
    outline: 2px solid var(--machine-blue);
    outline-offset: 1px;
}
.thumbnails-pane {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
    overflow: hidden;
    border-top: 1px solid var(--border);
}
.thumbnails-pane-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0.5rem 0.7rem;
    flex-shrink: 0;
}
.thumbnails-pane-header h2 {
    font-family: var(--font-body);
    font-weight: 600;
    font-size: 0.78rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-metadata);
    margin: 0;
}
.thumbnails-maximize-btn {
    background: none;
    border: none;
    cursor: pointer;
    color: var(--text-secondary);
    font-family: var(--font-body);
    font-size: 0.72rem;
    padding: 0.2rem 0.4rem;
    border-radius: 4px;
}
.thumbnails-maximize-btn:hover, .thumbnails-maximize-btn:focus-visible {
    background: var(--surface-hover);
    color: var(--text-primary);
}
.thumbnails-maximize-btn[aria-pressed="true"] { color: var(--machine-blue); }
/* CLAUDE-P40-LTH1, Section 4: the pane's own "nothing to show" state -
   quiet and compact (Section 4's own explicit "do not fill the pane
   with unrelated Project records"), visible by default in the
   server-rendered markup, hidden by static/js/pdf_viewer.js the
   moment real thumbnails are built (see that file's own comment on
   buildThumbnails/clearThumbnails). Deliberately its own small rule
   rather than reusing .pane-note directly - that class' own negative
   top margin is tuned for sitting directly under a Lists tree heading,
   not this pane's padded flex layout. */
.thumbnails-empty-state {
    font-size: var(--text-sm);
    color: var(--text-secondary);
    padding: 0.6rem;
    margin: 0;
    text-align: center;
}
.thumbnails-list {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    padding: 0.6rem;
    scrollbar-color: transparent transparent;
}
/* CLAUDE-P40-VW7A-QA2, Section 3: one real page per row - a <button>
   (not a decorative <div>) so click-to-navigate is a real activate-able
   control, aria-current="true" on whichever row matches the current
   page (kept in sync by static/js/pdf_viewer.js on every page change,
   regardless of whether that change came from scrolling, the top-menu
   controls, or a thumbnail click itself). */
.thumbnail-row {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.3rem;
    cursor: pointer;
    border: 2px solid transparent;
    border-radius: 4px;
    padding: 0.3rem;
    background: none;
    width: 100%;
    font-family: var(--font-body);
}
.thumbnail-row:hover { background: var(--surface-hover); }
.thumbnail-row:focus-visible { outline: 2px solid var(--machine-blue); outline-offset: 1px; }
.thumbnail-row canvas { display: block; max-width: 100%; box-shadow: 0 0 0 1px var(--border); }
.thumbnail-row[aria-current="true"] {
    border-color: var(--machine-blue);
    border-width: 3px;
    background: var(--surface-selected);
}
.thumbnail-row-label {
    font-size: 0.7rem;
    color: var(--text-metadata);
    font-variant-numeric: tabular-nums;
}
/* CLAUDE-P40-LTH1, Section 6: "selected-page indication that does not
   rely on color alone" - border-color/background above are still the
   primary cue, but border-WIDTH (2px -> 3px) and the label's own
   weight/underline are real geometry/shape cues, not color, so the
   current page still reads correctly in grayscale or for a color-
   vision-deficient reviewer. */
.thumbnail-row[aria-current="true"] .thumbnail-row-label {
    font-weight: 700;
    text-decoration: underline;
}
/* CLAUDE-P40-E3A, Section 9 (Chat is a full-width row beneath Lists/
   Display/Toolbox, sibling to .app-shell-body, not confined to the
   Display column) - CLAUDE-P40-VW8-QA: the explicit height-subtraction
   rule this comment used to document (".launcher-panel's sticky-fill
   height must stop above the sticky Chat bar") is gone, not just
   updated - .launcher-panel's own height:100% (above) is 100% of
   .app-shell-body's ACTUAL rendered height, which ordinary flexbox
   distribution already computes as "100vh minus Menu's own height
   minus .chat-region's own height" automatically (.chat-region is a
   flex-shrink:0 sibling of .app-shell-body within .app-shell, so it
   already claims its own space before .app-shell-body's flex:1 gets
   whatever's left). A second, manual height:calc(...) subtracting
   --chat-height AGAIN would double-subtract it. */

/* CLAUDE-P40-E3A, Section 7: the panel-dividing lines themselves are the
   Lists/Toolbox collapse controls (base.html's own script), replacing
   the old top-bar toggle buttons - a thin, full-height clickable seam
   between panels, not a labeled button. */
.panel-divider {
    flex-shrink: 0;
    width: 9px;
    border: none;
    padding: 0;
    cursor: pointer;
    /* CLAUDE-P40-VW7A-QA2 (product-owner browser correction): was
       transparent - a sibling of the 5 themed surfaces (never a
       descendant of any of them), so "transparent" fell through to
       .app-shell's OWN background, which now correctly resolves via
       the shell-level appearance class (see .app-shell's own comment)
       instead of the unthemed :root light default this used to expose
       as a literal white channel in every dark mode. */
    background: var(--surface-primary);
    position: relative;
    /* CLAUDE-P40-E3A-QA, Section 7: explicit low z-index (rather than
       relying on implicit stacking order) so this line can never paint
       above the top-bar menus (z-index 20), the Display context menu
       (z-index 40), or the narrow-screen Lists/Toolbox drawers (z-index
       30) even if a future change gives an ancestor of theirs its own
       stacking context - verified structurally, not just by omission. */
    z-index: 1;
}
/* CLAUDE-EYE-TOOLBOX-LAYOUT-01: quiet gap at rest, revealed only on
   hover/focus/drag - this used to be Deep Ocean's own scoped exception
   ("Deep Ocean currently has no visible line between panels; other
   themes still show separator lines" - Product Owner). Generalized here
   into the shared base rule so all 5 Appearances behave identically ("do
   not make Deep Ocean the exception; do not leave old theme-specific
   borders active elsewhere" - do not differ by Appearance). The
   collapsed state ([aria-expanded="false"]) is the one deliberate
   exception, staying visible at rest - it's the only way back to
   re-expand a hidden panel, so it can't wait for a hover that can't
   happen because the control can't be found. */
.panel-divider::before {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    left: 4px;
    width: 1px;
    background: var(--border);
    opacity: 0;
    transition: opacity 0.15s ease;
}
.panel-divider[aria-expanded="false"]::before {
    opacity: 1;
}
.panel-divider:hover::before,
.panel-divider:focus-visible::before,
.panel-divider-toolbox.dragging::before {
    background: var(--machine-blue);
    width: 2px;
    left: 3.5px;
    opacity: 1;
}
.panel-divider[aria-expanded="false"] {
    width: 14px;
}
.panel-divider[aria-expanded="false"]::before {
    left: 6px;
}
/* CLAUDE-P40-EYE1 (product-owner browser correction), Section 1: the
   Toolbox/right-column divider is now ALSO a real, mouse-draggable
   width resize handle (base.html's own drag logic), not click-to-
   toggle only - .panel-divider-lists (Lists) stays click-only, so this
   modifier-scoped rather than added to the shared .panel-divider base.
   ew-resize is the standard "drag left/right" cursor affordance; the
   accent line gets the same .dragging class treatment as every other
   draggable divider in this file (:hover/:focus-visible/.dragging all
   painting the same accent, since a drag can continue after the
   pointer strays off this thin line - see .lists-thumbnails-divider's
   own comment on why :hover alone isn't enough). */
.panel-divider-toolbox {
    cursor: ew-resize;
}
/* Bounded UI pass (listing-panel resize): the comment above ("Lists
   stays click-only") was true when written but is now superseded -
   Lists gets the SAME real drag-to-resize treatment, mirrored left-
   side (drag right widens, drag left narrows - see base.html's own
   drag handler on #lists-divider for the direction math). Same
   ew-resize/.dragging idiom as .panel-divider-toolbox above, so the
   two panels read identically as resizable. */
.panel-divider-lists {
    cursor: ew-resize;
}
/* .panel-divider-toolbox.dragging::before's own reveal is now handled by
   the shared .panel-divider:hover/:focus-visible/.dragging::before rule
   above (CLAUDE-EYE-TOOLBOX-LAYOUT-01) - no separate rule needed here. */
/* CLAUDE-P40-E2B1, Section B: one show/hide control (the top bar's
   "Lists" button), a reviewer-wide localStorage preference (not
   per-Project like the old per-Workspace Lists toggle it replaces -
   this panel is now present on every authenticated page, not just the
   Workspace) - applied before first paint via base.html's own
   extra_head script, the same "avoid a flash of the wrong layout"
   pattern .toolbox-hidden below already established. display:none also
   satisfies "not keyboard-focusable when hidden" for free. */
html.launcher-hidden .launcher-panel { display: none; }
.launcher-panel-body {
    display: flex;
    flex-direction: column;
    padding: 0.8rem 0.7rem;
    gap: 1.1rem;
}
.launcher-section { display: flex; flex-direction: column; gap: 0.1rem; }
/* CLAUDE-P40-VW7A-QA (Clarify Project Hierarchy and Selection State):
   uppercase/letter-spaced/--text-metadata was ALREADY a distinct
   structural-title language from every other row (Section 2's own
   "dedicated structural title color for main/root headings") - the
   thing that made a root heading like PROJECTS read as "selected" was
   never its resting typography, it was sharing .active's fill with
   every genuinely selected leaf below (.launcher-link.active, same
   file). .launcher-heading.active no longer fills a background at all
   - a root heading's OWN expand/collapse state is already fully
   conveyed by aria-expanded and the caret rotation
   (.tree-toggle[aria-expanded="true"] .tree-label::before, below) and
   needs no fill-based echo of it. :hover/:focus-visible still give
   real interactive feedback - only the PERSISTENT "currently expanded"
   state stopped painting a highlight. */
.launcher-heading {
    font-family: var(--font-body);
    font-weight: 600;
    font-size: var(--text-sm);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-metadata);
    /* CLAUDE-LEFTPANEL-DENSITY-03: left inset only reduced (var(--tree-
       row-inset), was a symmetric 0.55rem baked into every side) - the
       right side keeps its own original 0.55rem unchanged, since
       nothing to the right (chevron/count badge) was reported too
       loose, only the left margin before a row's own content was.
       CLAUDE-LEFTPANEL-DENSITY-04 (Product Owner refinement, "denser,
       quieter, more index-like"): top/bottom went from 0.35rem to
       0.24rem - since .tree-node/.tree-children/.tree carry no margin
       or gap of their own (checked - all zero), a row's own top/bottom
       padding IS the entire "space between adjacent rows," so this one
       change is what actually tightens the whole tree, at every depth,
       for free. */
    padding: 0.24rem 0.55rem 0.24rem var(--tree-row-inset);
    border-radius: 5px;
}
.launcher-heading:hover, .launcher-heading:focus-visible { background: var(--surface-hover); color: var(--text-primary); text-decoration: none; }
.launcher-link {
    display: block;
    /* CLAUDE-LEFTPANEL-DENSITY-04: 0.45rem -> 0.3rem, same reasoning as
       .launcher-heading above. Still clears a real touch/click target
       (0.3rem*2 + var(--text-sm) line-height is comfortably above the
       usual ~24px floor) - "navigation can be dense," this is the
       navigation, not one of the spacious action controls (Send,
       checkboxes, + disclosure) that stayed untouched by this pass. */
    padding: 0.3rem 0.55rem 0.3rem var(--tree-row-inset);
    border-radius: 5px;
    color: var(--text-secondary);
    font-family: var(--font-body);
    font-size: var(--text-sm);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.launcher-link:hover { background: var(--surface-hover); color: var(--text-primary); text-decoration: none; }
/* CLAUDE-P40-VW7A-QA: reserved for the ONE destination actually
   projected into Display (Section 1) - a Document/Case/Overview/Chats/
   Task/Tag leaf whose own href is what's currently rendered. The
   current Project's own name row (.current-project, below) no longer
   qualifies for this class at all - see templates/base.html's own
   comment on why "Chats" (or whichever child is actually open) is the
   sole thing allowed to look selected.

   CLAUDE-LEFTPANEL-DENSITY-04 (Product Owner refinement, "reduce the
   opacity/intensity of the current filled highlight; prefer a
   restrained tint, subtle edge, or slim accent rather than a large
   solid block"): the flat, fully-opaque --surface-selected fill read as
   too heavy/button-like for a navigation row. Replaced with the SAME
   translucent color-mix() idiom this stage's highlight/focus work
   already established (a low-alpha wash, not a solid block) plus a
   slim 2px left accent - distinct in BOTH weight and color from
   .current-project's own 3px, fill-less --border-strong landmark just
   below, so "hover, active, and selected states remain
   distinguishable" holds: hover is a plain --surface-hover wash with no
   border; active is a lighter tinted wash WITH a thin --machine-blue
   edge (machine/system "this is what's displayed" - a genuine semantic
   match, not --border-strong's own "structural landmark" meaning);
   current-project is border-only, no fill at all. 35% keeps this
   comfortably lighter than the already-verified 100% case (--text-
   primary was contrast-checked against the full --surface-selected
   fill; a lower-alpha version sits strictly between that and the
   panel's own resting background, so contrast can only be as good or
   better). */
.launcher-link.active {
    background: color-mix(in srgb, var(--surface-selected) 35%, transparent);
    color: var(--text-primary);
    border-left: 2px solid var(--machine-blue);
    padding-left: calc(var(--tree-row-inset) - 2px);
}
/* CLAUDE-P40-VW7A-QA: the current Project's own restrained "you are
   here" marker - Section 3's own "distinct title text... a narrow edge
   indicator... do not reuse the full selected-row fill." A left-edge
   accent (the same idiom .finding-card's own border-left already uses
   for "this is flagged/notable" elsewhere in this file) plus a bolder
   weight, never a background fill - reads as a structural landmark,
   not a second selected row. --border-strong (not --machine-blue/
   --surface-selected) keeps it quiet, matching the panel-border-
   hierarchy correction's own "reserve stronger accent color for
   meaningful states" discipline - "current Project" is a genuinely
   meaningful, but not an ACTION, state. */
.launcher-link.current-project {
    font-weight: 600;
    color: var(--text-primary);
    border-left: 3px solid var(--border-strong);
    /* CLAUDE-LEFTPANEL-DENSITY-03: compensates against the SAME
       --tree-row-inset .launcher-link's own base rule now uses (was a
       hardcoded 0.55rem) - the 3px accent border must still visually
       replace, not add to, this row's own left inset. */
    padding-left: calc(var(--tree-row-inset) - 3px);
}
.launcher-new-project { color: var(--machine-blue); }

/* CLAUDE-P40-E3A, Section 4: the recursive Lists hierarchy. .tree-toggle
   (a <button>, for parents) and .tree-leaf (an <a>, for leaves) both
   also carry .launcher-heading/.launcher-link respectively for their
   text styling (color/padding/hover/active, above) - this block only
   adds what's specific to the tree structure itself: reset, nesting
   indentation, the hover-reveal/pin-open expand indicator, and the
   count badge. Indentation is recursive and open-ended (each nested
   .tree-children adds its own left padding) rather than hardcoded per
   level, since the hierarchy's depth is not fixed. */
.tree, .tree-children {
    list-style: none;
    margin: 0;
    padding: 0;
    /* CLAUDE-LEFTPANEL-DENSITY-03: shared spacing variables, declared
       once here (inherited by every nested .tree-children) rather than
       repeating a literal value at each of the two places that used
       one - a real Product Owner report found the panel's cumulative
       indentation too wide, wasting working width. --tree-indent-step
       is the recursive per-level step (.tree-children's own padding-
       left, below); --tree-row-inset is the constant left inset of
       every row's own text (.launcher-heading/.launcher-link, further
       below) - previously 0.75rem and 0.55rem respectively, now a
       visibly tighter but still legible VS-Code-Explorer-scale step. */
    --tree-indent-step: 0.4rem;
    --tree-row-inset: 0.4rem;
}
/* CLAUDE-P40-VW7A-QA (Clarify Project Hierarchy and Selection State),
   Section 4: "the rendered hierarchy must visibly and semantically
   read as... Use consistent indentation, spacing, and a restrained
   tree guide or equivalent hierarchy cue. Do not rely on background
   highlighting to communicate nesting." A single 1px left border, the
   same quiet --border token used everywhere else in this file for
   ordinary (not accent-colored) structure, applied uniformly to every
   nested .tree-children - the classic file-tree "vertical connector"
   convention, restrained enough not to compete with row content, and
   the SAME cue at every depth (Project -> Documents/Investigations/.../
   Chats, and any further nesting below those) rather than a Project-
   specific special case. */
.tree-children {
    padding-left: var(--tree-indent-step);
    border-left: 1px solid var(--border);
}
.tree-children:not([data-tree-open]) {
    display: none;
}
.tree-node { margin: 0; }
.tree-toggle {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    background: none;
    border: none;
    cursor: pointer;
    font: inherit;
    text-align: left;
}
.tree-toggle .tree-label {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.tree-toggle .tree-label::before {
    content: "";
    flex-shrink: 0;
    width: 0.5em;
    height: 0.5em;
    border-right: 1.5px solid currentColor;
    border-bottom: 1.5px solid currentColor;
    transform: rotate(-45deg);
    transition: transform 0.12s ease;
    opacity: 0.6;
}
.tree-toggle[aria-expanded="true"] .tree-label::before {
    transform: rotate(45deg);
}
.launcher-count {
    font-variant-numeric: tabular-nums;
    font-size: 0.72rem;
    color: var(--text-metadata);
    font-weight: 400;
    text-transform: none;
    letter-spacing: normal;
}
/* CLAUDE-DOCUMENT-RAIL-SEARCH-01, Part 2/17: "Text | Image... restrained
   compact mode controls consistent with ARCHIOSK" - a two-option
   segmented toggle, same restrained border/pill idiom as the Marked
   filter below it, not a heavier tab-bar control. */
.documents-search-mode-toggle {
    display: flex;
    gap: 0.2rem;
    margin: 0.3rem 0 0.3rem;
}
.project-tree-controls {
    margin: 0 0 0.45rem;
    padding: 0 0.35rem 0.4rem;
    border-bottom: 1px solid var(--border);
}
.project-tree-controls .documents-search-mode-toggle {
    margin-bottom: 0;
}
.project-tree-controls .documents-marked-filter-btn {
    flex: 1 1 auto;
    margin: 0;
}
.project-tree-row {
    display: flex;
    align-items: center;
    min-width: 0;
}
.project-tree-row .current-project {
    flex: 1 1 auto;
    min-width: 0;
}
.project-tree-disclosure {
    width: auto;
    flex: 0 0 auto;
    padding: 0.3rem 0.1rem 0.3rem 0.25rem;
    color: var(--text-secondary);
}
.project-tree-disclosure .tree-label {
    overflow: visible;
}
.project-documents-projection > .tree-toggle {
    display: none;
}
.project-documents-projection > .project-source-tree {
    padding-left: 0;
    border-left: 0;
}
.documents-search-mode-btn {
    flex: 1 1 auto;
    padding: 0.15rem 0.4rem;
    border: 1px solid var(--border);
    border-radius: 4px;
    background: none;
    color: var(--text-metadata);
    font-family: var(--font-body);
    font-size: 0.7rem;
    cursor: pointer;
}
.document-primary-actions,
.document-replace-actions {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.4rem;
    margin: 0.55rem 0 0.8rem;
}
.document-primary-actions form,
.workspace-menubar-panel form {
    margin: 0;
}
.document-remove-action,
.workspace-menubar-item-danger {
    color: var(--failure-red);
}
.workspace-menubar-item-danger {
    width: 100%;
    text-align: left;
    border: 0;
    background: transparent;
}
.documents-search-mode-btn:hover, .documents-search-mode-btn:focus-visible {
    background: var(--surface-hover);
    color: var(--text-primary);
}
.documents-search-mode-btn[aria-pressed="true"] {
    color: var(--machine-blue);
    border-color: var(--machine-blue);
    background: var(--surface-selected);
}
/* Part 1/3: "compact... must not dominate the rail" - a plain, small
   single-line field matching this rail's own established form-control
   sizing, not a decorative search-bar treatment. */
.documents-search-input {
    display: block;
    width: 100%;
    box-sizing: border-box;
    padding: 0.25rem 0.5rem;
    margin: 0 0 0.2rem;
    border: 1px solid var(--border);
    border-radius: 4px;
    background: var(--surface-primary);
    color: var(--text-primary);
    font-family: var(--font-body);
    font-size: 0.75rem;
}
.documents-search-input[hidden] {
    display: none;
}
.documents-search-input:focus-visible {
    outline: 2px solid var(--machine-blue);
    outline-offset: 1px;
}
.documents-search-status,
.documents-image-search-status {
    font-size: 0.7rem;
    color: var(--text-metadata);
    margin: 0 0 0.3rem;
}
/* CLAUDE-GO-MULTIMODAL-PERCEPTION-GAMES-01: an inline text-button inside
   the small status line above - .btn/.btn-ghost's own padding (0.85rem
   1.5rem) is sized for a standalone control, not a word embedded
   mid-sentence in 0.7rem text. Same restrained treatment
   .documents-image-search-collapsed-btn already established for small
   inline controls in this same rail. */
.documents-image-search-status-action {
    font: inherit;
    color: var(--text-primary);
    background: none;
    border: none;
    padding: 0;
    text-decoration: underline;
    cursor: pointer;
}
.documents-image-search-status-action:hover,
.documents-image-search-status-action:focus-visible {
    color: var(--machine-blue);
}
/* Part 8: "a larger TEMPORARY image-search tray... use the existing
   restrained/glassy ARCHIOSK surface language" - same drop-target idiom
   Eye's own #eye-drop-target already established (dashed border only on
   an active drag/focus state, not a permanent heavy box), scaled for
   the rail's own narrower column. */
.documents-image-search-tray {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    min-height: 5rem;
    margin: 0 0 0.3rem;
    padding: 0.6rem;
    border: 1px dashed var(--border);
    border-radius: 4px;
    background: var(--surface-secondary);
    text-align: center;
}
/* An author `display` declaration on these three selectors otherwise
   beats the UA stylesheet's own `[hidden] { display: none }` (equal
   specificity, author rule comes later in cascade order) - live
   verification on archiosk.com caught this immediately (Text and Image
   mode both visible simultaneously at page load) - same root cause,
   same fix, as CLAUDE-DUAL-DOCUMENT-FOCUS-01's own .toolbox-eye-
   thumbnails-panel[hidden] fix earlier this session. */
.documents-image-search-tray[hidden],
.documents-image-search-preview[hidden],
.documents-image-search-collapsed[hidden] {
    display: none;
}
.documents-image-search-tray:focus-visible {
    outline: 2px solid var(--machine-blue);
    outline-offset: 1px;
    border-color: var(--machine-blue);
}
.documents-image-search-empty {
    margin: 0;
    font-size: 0.72rem;
    color: var(--text-metadata);
}
.documents-image-search-preview {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.4rem;
    width: 100%;
}
.documents-image-search-preview-img {
    max-width: 100%;
    max-height: 8rem;
    border-radius: 4px;
    box-shadow: 0 0 0 1px var(--border);
}
.documents-image-search-preview-actions {
    display: flex;
    gap: 0.4rem;
    flex-wrap: wrap;
    justify-content: center;
}
/* Part 9: the collapsed state - "regains vertical space... a small
   thumbnail/preview; Expand; Replace; Clear." */
.documents-image-search-collapsed {
    display: flex;
    align-items: center;
    gap: 0.3rem;
    margin: 0 0 0.3rem;
    padding: 0.25rem 0.4rem;
    border: 1px solid var(--border);
    border-radius: 4px;
    background: var(--surface-secondary);
}
.documents-image-search-collapsed-thumb {
    width: 22px;
    height: 22px;
    object-fit: cover;
    border-radius: 3px;
    flex-shrink: 0;
}
.documents-image-search-collapsed-btn {
    flex-shrink: 0;
    border: none;
    background: none;
    color: var(--text-metadata);
    font-family: var(--font-body);
    font-size: 0.7rem;
    padding: 0.1rem 0.3rem;
    border-radius: 3px;
    cursor: pointer;
}
.documents-image-search-collapsed-btn:hover, .documents-image-search-collapsed-btn:focus-visible {
    background: var(--surface-hover);
    color: var(--text-primary);
}
/* CLAUDE-DOCUMENT-RAIL-PROBE-EYE-TOOL-01, Part 4: "compact Marked
   filter/toggle... do not create a separate large panel" - same small-
   pill-toggle idiom as .toolbox-compare-btn, sized for the rail's own
   narrower column rather than Toolbox's. RAIL-SEARCH-01 (next stage)
   places the actual search field beside this same control. */
.documents-marked-filter-btn {
    margin: 0.2rem 0 0.3rem;
    padding: 0.15rem 0.5rem;
    border: 1px solid var(--border);
    border-radius: 4px;
    background: none;
    color: var(--text-metadata);
    font-family: var(--font-body);
    font-size: 0.7rem;
    cursor: pointer;
}
.documents-marked-filter-btn:hover, .documents-marked-filter-btn:focus-visible {
    background: var(--surface-hover);
    color: var(--text-primary);
}
.documents-marked-filter-btn[aria-pressed="true"] {
    color: var(--machine-blue);
    border-color: var(--machine-blue);
}
.tree-leaf.launcher-new-project { color: var(--machine-blue); }
/* CLAUDE-P40-VW8-QA (New Investigation Action in Lists): same accent
   "+ New X" already uses (.launcher-new-project, above) - a generic
   reusable modifier, not a one-off, in case a future family adopts the
   same action-row pattern (see this stage's own audit of whether other
   expandable families already have an equivalent). */
.tree-leaf.tree-leaf-action { color: var(--machine-blue); }

/* CLAUDE-EYE-COMPARE-01: a compact, always-visible per-row action
   letting the reviewer send a document to Eye without navigating away
   from whatever is currently open in Display - "do not create a large
   new control system." Row becomes a flex container so the icon button
   sits beside (not inside) the existing full-width .tree-leaf link;
   the leaf keeps its own ellipsis truncation via min-width:0. */
.tree-node-document { display: flex; align-items: center; gap: 0.15rem; }
/* An author `display` declaration here otherwise beats the UA
   stylesheet's own `[hidden] { display: none }` (equal specificity,
   author rule comes later in cascade order) - this was a REAL, latent
   bug from the moment the Marked filter first shipped
   (CLAUDE-DOCUMENT-RAIL-PROBE-EYE-TOOL-01): static/js/document_marks.js
   correctly set row.hidden on every filtered-out row, and JS reads of
   `!row.hidden` correctly reflected that, but the rows stayed VISUALLY
   present regardless - caught only now, by actually looking at a
   screenshot instead of trusting a JS-computed "visible count" alone,
   while live-verifying CLAUDE-DOCUMENT-RAIL-SEARCH-01. Same root cause,
   same fix, as every other `[hidden]` override already added this
   session (.toolbox-eye-thumbnails-panel, the Image tray/search input). */
.tree-node-document[hidden] {
    display: none;
}
.tree-node-document .tree-leaf { flex: 1 1 auto; min-width: 0; }
/* CLAUDE-DOCUMENT-RAIL-PROBE-EYE-TOOL-01, Part 3: a restrained review-
   shortlist checkbox, same compact footprint as the icon buttons beside
   it - "following the restrained document-review grammar familiar from
   professional review applications" (Product Owner), not a decorative
   custom control.

   Bounded UI pass (row-control proportions + transparency): two
   corrections layered on top of the original 14px control, both purely
   visual - click/mark behavior (native checkbox semantics, JS reads of
   .checked) is untouched:
   1) shrunk slightly (14px -> 12px) so it reads as visually quieter
      next to the triangle Keep control beside it, which goes the other
      direction (see .keep-open-btn below);
   2) appearance:none + a transparent background + a thin border,
      replacing the browser's native solid white checkbox fill - accent-
      color only paints a native control's own rendering, so it's
      dropped now that appearance:none opts out of that rendering;
      the checked state below supplies the mark indicator instead. */
.pm-mark-checkbox {
    flex-shrink: 0;
    position: relative;
    width: 12px;
    height: 12px;
    margin: 0;
    cursor: pointer;
    appearance: none;
    -webkit-appearance: none;
    background: transparent;
    border: 1px solid var(--text-metadata);
    border-radius: 2px;
}
/* CLAUDE-ICON-INTELLIGENCE-01: "X/check/badge = recorded/persistent
   state" (Section 1's own vocabulary) - stays transparent, never a
   solid fill (explicitly not "a solid white block"); the X itself is
   the recorded-state signal, drawn with a pseudo-element since
   appearance:none already opted this control out of native checkbox
   rendering. */
.pm-mark-checkbox:checked {
    background: transparent;
    border-color: var(--machine-blue);
}
.pm-mark-checkbox:checked::after {
    content: "\2715";
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    font-size: 11px;
    line-height: 1;
    color: var(--machine-blue);
    font-weight: 700;
}
.pm-mark-checkbox:hover {
    border-color: var(--machine-blue);
}
.pm-mark-checkbox:focus-visible {
    outline: 2px solid var(--machine-blue);
    outline-offset: 1px;
}
/* Part 6: "do not give the Eye document another filled selection color
   competing with Main - normal row background + thin restrained dashed
   outline/frame." Main's own selection (.tree-leaf.active, server-
   rendered) stays a filled background elsewhere in this file - this is
   deliberately the opposite treatment (border only, transparent fill)
   so the two states are never visually confusable, and both can be true
   on the same row at once (a Document can be both Main and, briefly
   before this stage's own Part 7 auto-clear takes effect, Eye) without
   one drowning out the other. */
.tree-leaf.eye-projected {
    outline: 1px dashed var(--machine-blue);
    outline-offset: -1px;
    border-radius: 3px;
}
/* ADDENDUM (DOCUMENT-ROW CONTROL ORDER / MARK VS KEEP): "visually and
   functionally distinct from PM Mark - a triangle, never a second
   square." Same compact footprint as the icon buttons around it;
   aria-pressed reflects the SAME real pinned-tab state document_tabs.js
   itself already tracks (findPinned), not a separate boolean of its
   own.

   Bounded UI pass (row-control proportions): the glyph itself
   (0.75rem, well below the 22px hit-target box it sits in) is bumped
   slightly so the triangle is easier to read/recognize, going the
   opposite direction from .pm-mark-checkbox's own shrink above - the
   22px box, line-height, and every click/hover/focus rule below are
   unchanged, so the glyph stays centered in the same footprint. */
.keep-open-btn {
    flex-shrink: 0;
    width: 22px;
    height: 22px;
    padding: 0;
    border: none;
    border-radius: 4px;
    background: none;
    color: var(--text-metadata);
    font-size: 0.9rem;
    line-height: 22px;
    text-align: center;
    cursor: pointer;
}
.keep-open-btn:hover, .keep-open-btn:focus-visible {
    background: var(--surface-hover);
    color: var(--machine-blue);
}
.keep-open-btn:focus-visible {
    outline: 2px solid var(--machine-blue);
    outline-offset: 1px;
}
.keep-open-btn[aria-pressed="true"] {
    color: var(--machine-blue);
    font-weight: 700;
}
.eye-send-btn {
    flex-shrink: 0;
    width: 22px;
    height: 22px;
    padding: 0;
    border: none;
    border-radius: 4px;
    background: none;
    color: var(--text-metadata);
    font-size: 0.75rem;
    line-height: 22px;
    text-align: center;
    cursor: pointer;
}
.eye-send-btn:hover, .eye-send-btn:focus-visible {
    background: var(--surface-hover);
    color: var(--machine-blue);
}
.eye-send-btn:focus-visible {
    outline: 2px solid var(--machine-blue);
    outline-offset: 1px;
}
/* CLAUDE-LEFTRAIL-ROUTING-01, Part 2/7: "the user must still be able to
   tell at a glance... avoid heavy borders or bright controls" - a plain
   color/weight shift on the SAME glyph, matching the restrained
   .toolbox-compare-btn[aria-pressed="true"] precedent already
   established, not a filled/bordered "active" box. */
.eye-send-btn[aria-pressed="true"] {
    color: var(--machine-blue);
    font-weight: 700;
}
/* Part 3: the Tool/Toolbox icon - same compact per-row footprint as
   .eye-send-btn immediately beside it, a real glyph (gear, U+2699) per
   this Product Owner's own "not a literal text T" instruction, not a
   new icon asset/library (tools/dependency_fit.py's no-new-dependency
   discipline). */
.toolbox-send-btn {
    flex-shrink: 0;
    width: 22px;
    height: 22px;
    padding: 0;
    border: none;
    border-radius: 4px;
    background: none;
    color: var(--text-metadata);
    font-size: 0.75rem;
    line-height: 22px;
    text-align: center;
    cursor: pointer;
}
.toolbox-send-btn:hover, .toolbox-send-btn:focus-visible {
    background: var(--surface-hover);
    color: var(--machine-blue);
}
.toolbox-send-btn:focus-visible {
    outline: 2px solid var(--machine-blue);
    outline-offset: 1px;
}
.toolbox-send-btn[aria-pressed="true"] {
    color: var(--machine-blue);
    font-weight: 700;
}
/* CLAUDE-ICON-INTELLIGENCE-01: "Greyed-out must always mean actually
   unavailable" - reuses the SAME disabled grammar already established
   for .doc-control-btn/.conv-selection-btn elsewhere in this file
   (color: var(--text-disabled); cursor: default), rather than
   inventing a second disabled treatment. Placed after the three
   [aria-pressed="true"] rules above so it wins on equal specificity -
   a control that is both "pressed" and disabled must still read as
   unavailable, not active. */
.keep-open-btn:disabled,
.eye-send-btn:disabled,
.toolbox-send-btn:disabled {
    color: var(--text-disabled);
    cursor: default;
}
.keep-open-btn:disabled:hover,
.eye-send-btn:disabled:hover,
.toolbox-send-btn:disabled:hover {
    background: none;
    color: var(--text-disabled);
}
/* Gear/Main-tool state (Section 5): the one row whose document is both
   projected on Main AND owns the active Main-tool state gets a real,
   distinguishing highlight - aria-pressed alone can't do this (it's
   already true on EVERY row's identical Gear whenever the right side
   is in Toolbox mode, the ordinary resting state - see refreshEyeLayout
   in eye_pane.js). --surface-selected is the SAME contrast-verified
   "this is the selected/current one" token already used for
   .launcher-link.active/.chip.active/.doc-annotation-tool[aria-pressed=
   "true"] elsewhere in this file - reused here rather than inventing a
   new highlight treatment. */
.toolbox-send-btn.main-tool-active-row {
    background: var(--surface-selected);
    color: var(--machine-blue);
}

/* CLAUDE-P40-VW7: Tasks/Tags branches - reuse the tree idiom above as-
   is (.tree-node/.tree-toggle/.tree-children/.launcher-count already do
   the collapsing, indentation, and count-badge work); this block only
   adds what's specific to a Task/Tag row: the Open/Completed and per-
   tag sub-labels (a plain heading, not a second toggle - Tasks/Tags
   itself is the one collapsible unit per branch), the inline row that
   pairs a navigable link with its own small action form, the tag-color
   swatch dot, and the two empty/unavailable states. */
.launcher-subheading {
    font-family: var(--font-body);
    font-size: 0.76rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--text-metadata);
    /* CLAUDE-LEFTPANEL-DENSITY-03: same tightened left inset as every
       other tree row (.launcher-heading/.launcher-link, above).
       CLAUDE-LEFTPANEL-DENSITY-04: top/bottom tightened to match the
       same pass on .launcher-heading/.launcher-link above. */
    padding: 0.26rem 0.55rem 0.1rem var(--tree-row-inset);
    margin: 0;
    display: flex;
    align-items: center;
    gap: 0.35rem;
}
.tree-node-group { margin: 0; }
.tree-node-empty { padding: 0.16rem 0.55rem 0.16rem var(--tree-row-inset); }
.tree-node-empty .pane-note { margin: 0; font-size: var(--text-xs); }
.launcher-task-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.4rem;
    min-width: 0;
}
.launcher-task-row .launcher-link {
    flex: 1;
    min-width: 0;
}
.launcher-task-completed {
    text-decoration: line-through;
    color: var(--text-metadata);
}
/* CLAUDE-CA1D-RIVER-PO-02 (Product Owner interaction correction, "task
   checkbox not a text link"): a conventional checkbox as the PRIMARY
   completion affordance, replacing the "Mark complete"/"Reopen" text
   links - restrained (thin square outline; a quiet check, not a large
   consumer-app control), matching this app's own stated "color appears
   rarely" discipline. Uses --accepted-green - already the token whose
   declared meaning in tokens.css is literally "accepted / confirmed,"
   a genuine semantic match for "this task is done," not a repurposed
   or newly-invented color. A real <input type="checkbox"> (not a div
   with aria-checked) so keyboard (Tab + Space) and screen-reader
   checked/unchecked state come from the browser itself; the visually-
   hidden label text names the specific task, and the SAME pre-existing
   complete_task_route/reopen_task_route form-POST this replaces is
   still what actually runs (on the native "change" event) - this is a
   new affordance for one existing action, not a second completion
   mechanism competing with it. */
.task-checkbox-form { flex: 0 0 auto; margin: 0; }
.task-checkbox-wrap {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.5rem;
    height: 1.5rem;
    cursor: pointer;
}
.task-checkbox {
    appearance: none;
    -webkit-appearance: none;
    margin: 0;
    width: 0.95rem;
    height: 0.95rem;
    border: 1.5px solid var(--text-metadata);
    border-radius: 3px;
    background: transparent;
    cursor: pointer;
    position: relative;
    flex: 0 0 auto;
}
.task-checkbox:hover { border-color: var(--text-secondary); }
.task-checkbox:checked {
    border-color: var(--accepted-green);
    background: color-mix(in srgb, var(--accepted-green) 20%, transparent);
}
.task-checkbox:checked::after {
    content: "";
    position: absolute;
    left: 2px;
    top: -1px;
    width: 4px;
    height: 8px;
    border: solid var(--accepted-green);
    border-width: 0 2px 2px 0;
    transform: rotate(45deg);
}
.launcher-link-unavailable {
    color: var(--text-disabled);
    cursor: default;
}
.launcher-link-unavailable:hover { background: none; }
.launcher-link-unavailable small { font-style: italic; }
.launcher-tag-swatch {
    display: inline-block;
    width: 0.6rem;
    height: 0.6rem;
    border-radius: 50%;
    border: 1px solid var(--border-strong);
    flex-shrink: 0;
}
.conv-tag-color-yellow { background: var(--tagcolor-yellow); }
.conv-tag-color-orange { background: var(--tagcolor-orange); }
.conv-tag-color-red { background: var(--tagcolor-red); }
.conv-tag-color-green { background: var(--tagcolor-green); }
.conv-tag-color-blue { background: var(--tagcolor-blue); }
.conv-tag-color-purple { background: var(--tagcolor-purple); }

/* CLAUDE-P40-VW8-QA, Section 11: the inline tagged-text treatment - a
   real, visible consequence of "Add Tag" on the source text itself
   (app.py's own `hotlinks` filter wraps the exact tagged substring in
   this <mark>, the ONE shared primitive every tag type - Highlight/
   Important/Question/a custom color - renders through). Deliberately
   does NOT reuse --tagcolor-* as a fill background - those are raw,
   saturated swatch-dot colors, never contrast-verified against text,
   and reusing one directly here could easily fail legibility in at
   least one of Light/Dark/Tinted. Instead the FILL reuses
   --surface-selected (already contrast-verified against --text-primary
   in every mode, the same "selected/current" treatment used elsewhere
   in this app) and the per-tag color comes through as a border-bottom
   stripe only - a 2px line has no text-contrast requirement the way a
   fill does, so the raw swatch color is safe to use there directly.

   CLAUDE-CA1D-HIGHLIGHT-CONTRAST-01 (Product Owner visual-accessibility
   correction): `color` was never set here, so a real browser's own
   built-in default `<mark>` style (background: yellow; color: black -
   not a leftover from this file, an actual user-agent stylesheet rule,
   which wins over plain inheritance since inheritance only applies when
   NOTHING - including the UA sheet - already sets the property on the
   element itself) silently forced literal black text regardless of
   theme. Invisible in Light mode (--text-primary is already near-black
   there, so the wrong value looked almost like the right one) but a
   real contrast failure in Dark mode specifically: --dark-surface-
   selected is a dark warm brown and the UA default's literal black
   text against it is exactly the "dark lettering in a brownish
   block" reported live. The fix is the same token this rule's own
   comment already says was chosen for exactly this pairing -
   --text-primary was already contrast-verified against --surface-
   selected in every mode; it simply was never actually applied.

   CLAUDE-CA1D-HIGHLIGHT-CONTRAST-02 (Product Owner visual refinement,
   "translucent focus, not opaque highlight"): the fix above made text
   readable but the fill was still an OPAQUE, mode-invariant neutral
   (--surface-selected) - a solid marker block that fully replaces
   whatever was visually behind it, and looks identical no matter which
   tag color created it. Replaced with a translucent wash of the tag's
   OWN color via color-mix() against the element's own background -
   "content remains primary; highlight is an atmospheric layer behind
   it," per an editor-diff-highlighting mental model. This is a PERSISTENT
   state (the user deliberately tagged this text) so the wash does not
   animate or fade - see .conv-source-flash below for the separate,
   deliberately-animated TEMPORARY-focus sibling of this same pattern.
   22% was verified by hand (relative-luminance contrast ratio, WCAG
   formula) against this app's two extreme surfaces - --canvas (Light,
   near-white) and --dark-canvas (Dark/Tinted/Deep Forest, all a near-
   black canvas) - using the DARKEST/most saturated swatch (--tagcolor-
   red) as the worst case, and cleared 4.5:1 with a wide margin in both
   directions (>10:1 Light, ~14:1 Dark).

   CLAUDE-CA1D-HIGHLIGHT-CONTRAST-03 (Product Owner visual refinement,
   "reduce highlight strength"): 22% still read as "a block of color"
   on a live multi-line passage, not the intended "faint translucent
   tint" - lowered to 12%. Since a LOWER alpha only pulls the resulting
   background closer to the surface the CONTRAST-02 math above already
   verified, contrast can only improve further at 12% than it was at
   22% - no new verification needed, this is a strictly safer point on
   the same curve. The border-bottom stripe (full, un-mixed swatch
   color) is unchanged - it is what still signals "this passage carries
   tag X" from a glance; the fill's job is now just atmosphere, not
   identification. */
.tag-highlight-inline {
    background: color-mix(in srgb, var(--tagcolor-yellow) 12%, transparent);
    color: var(--text-primary);
    border-bottom: 2px solid var(--tagcolor-yellow);
    border-radius: 2px;
    padding: 0.05em 0.1em;
    box-decoration-break: clone;
    -webkit-box-decoration-break: clone;
}
.tag-highlight-inline.conv-tag-color-yellow { background: color-mix(in srgb, var(--tagcolor-yellow) 12%, transparent); border-bottom-color: var(--tagcolor-yellow); }
.tag-highlight-inline.conv-tag-color-orange { background: color-mix(in srgb, var(--tagcolor-orange) 12%, transparent); border-bottom-color: var(--tagcolor-orange); }
.tag-highlight-inline.conv-tag-color-red { background: color-mix(in srgb, var(--tagcolor-red) 12%, transparent); border-bottom-color: var(--tagcolor-red); }
.tag-highlight-inline.conv-tag-color-green { background: color-mix(in srgb, var(--tagcolor-green) 12%, transparent); border-bottom-color: var(--tagcolor-green); }
.tag-highlight-inline.conv-tag-color-blue { background: color-mix(in srgb, var(--tagcolor-blue) 12%, transparent); border-bottom-color: var(--tagcolor-blue); }
.tag-highlight-inline.conv-tag-color-purple { background: color-mix(in srgb, var(--tagcolor-purple) 12%, transparent); border-bottom-color: var(--tagcolor-purple); }

/* A small, quiet text-style action inside a Lists row (Mark complete /
   Reopen / Remove) - .btn's own padding (0.85rem 1.5rem) is sized for a
   standalone page action, far too large for an inline list row. */
.link-button {
    background: none;
    border: none;
    padding: 0;
    font: inherit;
    font-size: 0.72rem;
    color: var(--machine-blue);
    text-decoration: underline;
    cursor: pointer;
    flex-shrink: 0;
}
.link-button:hover { color: var(--text-primary); }

/* Appearance menu (CLAUDE-P40-E3A Section 10; CLAUDE-P40-VW3 replaced
   the single plain/tinted checkbox with three independent modes per
   surface - Light/Dark/Tinted; CLAUDE-P40-VW6 corrected both Dark and
   Tinted's actual coverage). Light is the unmarked default - identical
   to every rule in this file already, nothing added for it. Dark and
   Tinted BOTH now REDEFINE the standard token names locally on the
   surface's own root element (see tokens.css's own comment on why
   scoping, not a file swap, is required here) - every existing
   component rule in this file that already reads
   var(--surface-primary)/var(--text-primary)/etc. repaints correctly
   for free, no per-component change needed.

   CLAUDE-P40-VW6 root cause: VW3's own Tinted implementation never did
   this - it only ever swapped ONE element's own `background` per
   surface to the existing --surface-secondary (Limestone/beige) token,
   never redefined the surface's token SCOPE. Every other element
   inside that surface reading --surface-primary/--border/--text-
   primary directly (finding cards, blank states, form controls, ...)
   kept rendering untinted Light values underneath - the product-owner
   observation "some surfaces become tinted and others do not" was
   exactly this coverage gap, and "the current tint is beige/gold" was
   --surface-secondary's own real, correct, UNRELATED meaning (Limestone
   - "nav surfaces, grouped controls") being reused as if it were a
   dedicated Tinted palette, which it never was. Tinted now uses its own
   --tint-* family (tokens.css - a light navy-blue, further lightened
   by CLAUDE-P40-VW8-QA - see that file's own header comment), the
   exact same redefinition shape Dark already used correctly.

   Menu (.workspace-topbar) is a configurable surface (CLAUDE-P40-VW3) -
   it has no background of its own in its base rule (just a border-
   bottom sitting on --canvas), so this combined rule (below, near
   .chat-region's own rules - deliberately AFTER every one of the five
   surfaces' own base component rules, so a test doing a plain textual
   "first rule whose selector contains .app-main" search still finds
   each surface's real base rule) sets one explicitly for Menu, same as
   it already did for Dark. */
.launcher-identity {
    padding: 0.6rem 0.7rem 1rem;
    border-top: 1px solid var(--border);
    flex-shrink: 0;
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
}
.launcher-identity-name {
    font-family: var(--font-body);
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--text-primary);
    margin: 0 0 0.3rem 0.55rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.app-main {
    flex: 1;
    min-width: 0;
    min-height: 0;
    display: flex;
    flex-direction: column;
    /* CLAUDE-P40-VW8-QA: a pure layout wrapper now - <main> below is the
       one actual scroll region, so this never becomes a second
       scrolling context of its own (no "duplicate page-level
       scrollbars" - Section 10). */
    overflow: hidden;
}

/* CLAUDE-P40-VW8-QA, Section 10: <main> is the ONE scroll region for
   every page's actual content - Menu (outside .app-main entirely,
   above it in .app-shell) never moves, because it is never an
   ancestor OR a sibling of anything that scrolls; only this element
   does. flex/min-height:0/column so a Workspace page's own
   .workspace-pane-display can, in turn, stretch to fill whatever
   height is actually available (see that rule's own comment) instead
   of shrink-wrapping its content - the direct fix for the "shallow
   white rectangle" Display-division defect, which shared this exact
   root cause (nothing in the ancestor chain ever gave the Display
   grid a real height to fill). Applies to every page sharing
   base.html's shell, not just Workspace - deliberately: the "Menu
   must stay visible while content scrolls" requirement is a property
   of this ONE shared shell, not a Workspace-specific concern (Section
   10's own "avoid... where panel-level scrolling is appropriate" -
   one central rule here, not scattered per-page overrides). Placed
   BEFORE the narrow-viewport padding override directly below, so that
   override still wins on cascade order at <=720px - both are the same
   selector specificity, and only source order decides between them. */
main {
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
    overflow-y: auto;
    padding: 0 3rem;
    max-width: 1180px;
    margin: 0 auto;
    width: 100%;
    scrollbar-color: transparent transparent;
}

@media (max-width: 720px) {
    main { padding: 0 1.2rem; }
}

/* CLAUDE-MAIN-DISPLAY-SCROLL-SIMPLIFICATION-01: main's own general-
   purpose overflow-y:auto (CLAUDE-P40-VW8-QA, above - "the ONE scroll
   region for every page's actual content") becomes a REDUNDANT second
   scrolling surface specifically when a PDF Document is the active
   Display content - .document-viewer-canvas-container (mounted by
   static/js/pdf_viewer.js's own mount(), see that rule's own
   `overflow: auto` further below) already provides a real, correctly-
   bounded internal scroll region for the rendered page/canvas. "The
   document moves. The workspace does not" - the Product Owner's own
   red-scrollbar-keep/green-scrollbar-remove screenshot. Scoped
   narrowly, via :has() (already an established technique in this file
   - see .conversation-thread:has(...) and .files-folder-row:has(...)
   elsewhere), to THIS one content shape specifically, so every other
   page (Investigation/Overview/forms/Project Data Management/etc) keeps
   main's own whole-content scroll exactly as CLAUDE-P40-VW8-QA
   intended - only the PDF-viewing case loses it. A real mechanism
   removal (overflow-y:hidden), not a cosmetic scrollbar hide - main
   genuinely cannot become a second scrolling context in this state. */
main:has(.document-viewer-canvas-container) {
    overflow-y: hidden;
}

/* -- true zero-state entry shell (pre-authentication) ---------------------
   Deliberately minimal - no launcher panel, no top bar, no workspace
   controls exist pre-auth (see templates/base.html) - this is the entire
   identity footprint of that state, not a scaled-down copy of the app
   shell. The Archiosk wordmark is the deliberate exception to the
   UI-wide Arial Nova Cond rule below - restored to its original
   family/weight, never Arial Nova/Arial Narrow. */
.entry-shell-brand {
    padding: 1.2rem 1.5rem;
    flex-shrink: 0;
    display: flex;
    align-items: baseline;
    gap: 0.4rem;
    font-family: "Space Grotesk", sans-serif;
    font-weight: 700;
    font-size: 1.1rem;
    color: var(--text-primary);
}

/* CLAUDE-P40-E2B1, Section G: on narrow screens the Launcher panel
   becomes a real overlay drawer rather than a permanent column -
   exactly the same idiom the Workspace-local Toolbox drawer uses (see
   main.css's own .workspace-pane-toolbox narrow-screen rule), just
   app-wide since the panel itself is now app-wide. The same
   launcher-toggle-btn/html.launcher-hidden mechanism drives this - a
   drawer is just what "shown" looks like at this width. */
@media (max-width: 640px) {
    .launcher-panel {
        position: fixed;
        top: 0;
        left: 0;
        bottom: 0;
        width: min(85vw, 320px);
        z-index: 30;
        border-right: 1px solid var(--border);
    }
}

/* -- hero ---------------------------------------------------------------- */
.hero { padding: 2rem 0 1.5rem; max-width: 780px; }
.eyebrow {
    font-family: var(--font-body);
    text-transform: uppercase;
    letter-spacing: 0.08em;
    font-size: 0.85rem;
    color: var(--text-metadata);
    margin-bottom: 1rem;
}
.hero h1 {
    font-family: var(--font-display);
    font-size: 3rem;
    line-height: 1.12;
    margin: 0 0 1.5rem;
    font-weight: 300;
}
.hero-copy { color: var(--text-secondary); font-size: 1.05rem; max-width: 60ch; }
.hero-actions { margin-top: 2.2rem; display: flex; gap: 1rem; }
.hero-actions-center { justify-content: center; }

.upload-form { margin-top: 1.2rem; display: flex; flex-direction: column; align-items: flex-start; gap: 1rem; }
.upload-form-optional { display: flex; gap: 1rem; flex-wrap: wrap; }
/* CLAUDE-P29: Project Operating Environment selector -- deliberately
   plain (border + spacing only, no new accent color) since this is a
   structural/decision element, not a status or emphasis signal; see
   this file's own header comment on when color is/isn't appropriate. */
.operating-environment-fieldset {
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: var(--space-5) var(--space-6);
    width: 100%;
    max-width: 480px;
}
.operating-environment-option {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin: var(--space-3) 0;
    font-size: var(--text-sm);
}
.operating-environment-confirm {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-size: var(--text-sm);
    color: var(--text-secondary);
}
/* CLAUDE-CA1D-FOLDER-ESTABLISH-CLARITY-01: this whole folder-
   establishment fieldset (upload.html) had zero CSS of its own until
   now - most concretely, #folder-submit-button:disabled looked
   pixel-identical to its own enabled state (no app-wide .btn:disabled
   rule exists; every other disabled submit button in this app has its
   own explicit override, e.g. #project-open-button:disabled above).
   A live user report ("Establish Project from Folder does nothing")
   traced to exactly that: with multiple eligible top-level files (a
   real RFP folder with both a .docx/.pdf pair and a Schedule), nothing
   is auto-selected, so the button silently stays disabled until one of
   the plain, unstyled radio candidates below is clicked - a control
   that gave no visual signal it was interactive or required at all. */
#folder-submit-button:disabled { opacity: 0.5; cursor: not-allowed; }
/* CLAUDE-PROJECT-SURFACE-CONSOLIDATION-01 addendum (Storage Grammar):
   two named options (Link/Upload) inside one fieldset, same plain
   border-only treatment as .operating-environment-fieldset above (this
   is a structural choice, not a status signal). .storage-option-disabled
   covers both the permanently-disabled Link option and the entitlement-
   greyed Upload option - visually identical (--text-disabled + reduced
   opacity), same token this file uses everywhere else for "not
   available right now" (see .doc-control-btn:disabled,
   .conv-selection-btn:disabled). */
.storage-connection-fieldset {
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: var(--space-5) var(--space-6);
    width: 100%;
    max-width: 480px;
    display: flex;
    flex-direction: column;
    gap: var(--space-5);
}
.storage-option-label { margin: 0; font-size: var(--text-sm); }
.storage-option-disabled { opacity: 0.6; }
.storage-option-disabled .storage-option-label,
.storage-option-disabled > .pane-note { color: var(--text-disabled); }
#folder-selection-summary { margin: 0.8rem 0 0.4rem; color: var(--text-secondary); font-size: var(--text-sm); }
.folder-founding-candidates {
    list-style: none;
    margin: 0.6rem 0 1.2rem;
    padding: 0;
    display: grid;
    gap: 0.5rem;
    max-width: 480px;
}
.folder-founding-candidates label {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.6rem 0.9rem;
    background: var(--surface-primary);
    border: 1px solid var(--border);
    border-radius: 4px;
    cursor: pointer;
    font-size: var(--text-sm);
}
.folder-founding-candidates label:hover { border-color: var(--border-strong); }
.folder-founding-candidates label:has(input:checked) { border-color: var(--machine-blue); border-width: 2px; }
.folder-founding-candidates input[type="radio"] { accent-color: var(--machine-blue); flex-shrink: 0; }
.text-input {
    font-family: var(--font-body);
    font-size: 0.85rem;
    padding: 0.6rem 0.8rem;
    background: var(--surface-primary);
    border: 1px solid var(--border);
    border-radius: 3px;
    color: var(--text-primary);
    width: 100%;
    max-width: 320px;
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
/* CLAUDE-GATEWAY-UI-GRAMMAR-01: a real, class-based width variant -
   was a one-off inline style="max-width:480px" on upload.html's own
   project-name field (its placeholder text is meaningfully longer than
   the actor/role fields beside it, a genuine reason for a wider field,
   not arbitrary page-by-page convenience) - reusable now instead of a
   value only that one page could see or reuse. */
.text-input-wide { max-width: 480px; }
.text-input::placeholder { color: var(--text-disabled); }
.text-input:focus {
    border-color: var(--machine-blue);
    box-shadow: 0 0 0 3px rgba(44, 96, 121, 0.15);
}

/* CLAUDE-CA1D-RECEPTION-FIX-01: password reveal toggle (login only). Absolute
   positioning inside a relative wrapper so it overlays the existing
   .text-input without changing .gateway-field's grid layout. */
.password-field { position: relative; display: flex; }
.password-field .text-input { flex: 1; padding-right: 2.4rem; }
.password-toggle {
    position: absolute;
    right: 0.4rem;
    top: 50%;
    transform: translateY(-50%);
    display: flex;
    align-items: center;
    justify-content: center;
    /* CLAUDE-SIGNIN-EYE-VISIBILITY-01: 0.45rem (up from 0.3rem) so the
       clickable box is ~32x32px, closer to a practical tap target,
       without exceeding .text-input's own 2.4rem reserved padding-right
       above (right:0.4rem + this box must stay under ~2rem). */
    padding: 0.45rem;
    background: none;
    border: none;
    border-radius: 3px;
    /* CLAUDE-SIGNIN-EYE-VISIBILITY-01: was --text-metadata (this card's
       own token override makes it rgba(200,255,248,.72), already
       adequate contrast on its own) - bumped to --text-primary at rest
       so the control reads as clearly present/interactive at a glance
       rather than only becoming legible on hover, matching the Product
       Owner's "clearly visible against the password-field background"
       requirement. */
    color: var(--text-primary);
    cursor: pointer;
}
.password-toggle:hover { color: var(--machine-blue); }
.password-toggle:focus-visible {
    outline: 2px solid var(--machine-blue);
    outline-offset: 1px;
}
/* CLAUDE-SIGNIN-EYE-VISIBILITY-01: root-cause fix for the two icons
   rendering superimposed on each other ("beside/on top of each other,
   confusing" - Product Owner live report). login.js's previous approach
   toggled the SVGs' `hidden` IDL property/attribute, but `hidden` has no
   effect on SVG elements in Chromium (SVG's own UA stylesheet does not
   include a `[hidden]` rule the way html.css does for HTML elements, and
   author-origin CSS always wins over UA-origin CSS regardless of
   specificity) - both icons were always visible, permanently overlapping.
   Driving visibility from aria-pressed here instead means the single
   source of truth for "which state is this control in" is the same
   attribute assistive tech already reads, and login.js only has to keep
   toggling that one attribute (see its own updated comment). */
.password-toggle[aria-pressed="false"] .password-toggle-icon-hide,
.password-toggle[aria-pressed="true"] .password-toggle-icon-show {
    display: none;
}

/* CLAUDE-VOICE-CONSISTENCY-01: pre-authentication voice control - a
   small icon + inline status, no separate composer/text field on this
   page (see login.js's own comment on why). .voice-input-button/
   .voice-input-status reused unchanged from the in-project composer. */
.signin-voice-row { display: flex; align-items: center; gap: 0.5rem; margin-top: 0.4rem; }

.btn {
    font-family: var(--font-body);
    font-size: 0.85rem;
    padding: 0.85rem 1.5rem;
    border-radius: 3px;
    border: 1px solid var(--border);
    display: inline-block;
}
/* Frequent primary actions read through tonal contrast (a solid dark fill),
   not through color - see the color-scarcity rule. */
.btn-primary { background: var(--text-primary); color: var(--surface-primary); border-color: var(--text-primary); font-weight: 600; }
.btn-primary:hover { background: var(--text-secondary); text-decoration: none; }
.btn-ghost { color: var(--text-primary); }
.btn-ghost:hover { border-color: var(--border-strong); text-decoration: none; }

/* -- home / project entry (deliberately quiet, not a marketing hero) ---- */
.entry { padding: 1.5rem 0 1rem; max-width: 720px; }
.entry-copy { color: var(--text-secondary); font-size: 0.95rem; max-width: 56ch; margin: 0 0 1rem; }
.entry-actions { display: flex; gap: 1rem; margin-top: 0.8rem; margin-bottom: 1.2rem; }

.entry-recent { max-width: 900px; }
.entry-empty { color: var(--text-secondary); font-size: 0.85rem; }
.project-row-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.6rem; }
.project-row {
    background: var(--surface-primary);
    border: 1px solid var(--border);
    border-radius: 6px;
}
.project-row-link {
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
    padding: 0.9rem 1.1rem;
    color: var(--text-primary);
}
.project-row-link:hover { border-color: var(--border-strong); text-decoration: none; }
.project-row-name { font-family: var(--font-display); font-size: 0.95rem; }
.project-row-meta {
    font-family: var(--font-body);
    font-size: 0.85rem;
    color: var(--text-metadata);
}
.entry-flag { white-space: nowrap; }
.entry-flag-red { color: var(--failure-red); }
.entry-flag-amber { color: var(--attention-amber); }

/* -- projects directory (cards, not a developer table) ------------------- */
.projects-controls {
    display: flex;
    align-items: center;
    gap: 0.7rem;
    margin: 1rem 0 1.2rem;
    flex-wrap: wrap;
}
.projects-search { max-width: 280px; margin: 0; }
.project-card-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.7rem; }
.project-card {
    display: flex;
    align-items: center;
    gap: 0.6rem;
}
.project-card-link {
    display: flex;
    flex: 1;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
    padding: 1rem 1.2rem;
    background: var(--surface-primary);
    border: 1px solid var(--border);
    border-radius: 6px;
    color: var(--text-primary);
    min-width: 0;
}
.project-card-link:hover { border-color: var(--border-strong); text-decoration: none; }
/* CLAUDE-CA1D-RECEPTION-FIX-01 (addendum): select-then-confirm cards in
   project_chooser.html -- a <label> wrapping a radio, reusing
   .project-card-link's own box/hover treatment (cursor: pointer since
   labels don't get it by default the way anchors do). */
.project-card-selectable { cursor: pointer; }
.project-card-selectable input[type="radio"] { margin: 0 0.8rem 0 0; accent-color: var(--machine-blue); flex-shrink: 0; }
.project-card-selectable:has(input:checked) { border-color: var(--machine-blue); border-width: 2px; }
#project-open-button { margin-top: 1rem; }
#project-open-button:disabled { opacity: 0.5; cursor: not-allowed; }
.project-card-main {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    min-width: 0;
}
.project-card-name {
    font-family: var(--font-display);
    font-size: 1rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.project-card-id { font-size: var(--text-sm); color: var(--text-metadata); }
.project-card-meta {
    font-family: var(--font-body);
    font-size: 0.85rem;
    color: var(--text-metadata);
    display: flex;
    align-items: center;
    gap: 0.4rem;
    flex-shrink: 0;
    white-space: nowrap;
}
.project-card-dot { color: var(--border-strong); }
.project-card-secondary {
    font-family: var(--font-body);
    font-size: 0.85rem;
    color: var(--text-metadata);
    padding: 0 1.2rem 0 0;
    flex-shrink: 0;
}
.project-card-secondary:hover { color: var(--text-primary); }
.projects-empty { color: var(--text-secondary); font-size: 0.9rem; padding: 1.5rem 0; }
.projects-empty .btn { margin-top: 1rem; }

/* CLAUDE-P40-VW7B, Section 4: Vestibule "Current Project" / "Available
   Projects" section labels - same small-caps metadata treatment
   .thumbnails-pane-header h2 already established for a quiet section
   heading, reused rather than a new type style. */
.gateway-vestibule-current-label,
.gateway-vestibule-available-label {
    font-family: var(--font-body);
    font-size: 0.75rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-metadata);
    margin: 0 0 0.5rem;
}
.gateway-vestibule-current { margin-bottom: 1.2rem; }
/* A real border-WIDTH change (1px -> 2px), not color alone, so "this is
   the currently entered Project" reads even without color vision - the
   "Currently entered" badge text itself is the primary, always-
   sufficient cue (Section 13's "no color-only distinction"). */
.project-card-current .project-card-link {
    border-color: var(--machine-blue);
    border-width: 2px;
}
.project-card-current-badge {
    font-size: 0.72rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--machine-blue);
}

@media (max-width: 640px) {
    .project-card-link { flex-direction: column; align-items: flex-start; }
    .project-card { flex-direction: column; align-items: stretch; }
    .project-card-secondary { padding: 0 1.2rem 0.9rem; }
}

/* -- chassis step cells (hexagon-clipped) -------------------------------- */
.hive-strip { padding: 3rem 0 5rem; }
.hive-strip h2 {
    font-family: var(--font-display);
    font-size: 1.1rem;
    color: var(--text-metadata);
    text-transform: uppercase;
    letter-spacing: 0.1em;
    margin-bottom: 2rem;
}
.cell-row {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 1.5rem;
}
.cell {
    background: var(--surface-primary);
    border: 1px solid var(--border);
    padding: 1.6rem 1.4rem 2rem;
    position: relative;
    clip-path: polygon(8% 0, 92% 0, 100% 8%, 100% 92%, 92% 100%, 8% 100%, 0 92%, 0 8%);
}
.cell-index {
    font-family: var(--font-body);
    color: var(--text-secondary);
    font-size: 0.8rem;
}
.cell h3 { font-family: var(--font-display); margin: 0.6rem 0 0.5rem; font-size: 1.15rem; }
.cell p { color: var(--text-secondary); font-size: 0.92rem; margin: 0; }

@media (max-width: 900px) {
    .cell-row { grid-template-columns: 1fr 1fr; }
}

@media (max-width: 560px) {
    .cell-row { grid-template-columns: 1fr; }
    .lattice { max-width: none; }
    .dash-meta { width: 100%; justify-content: space-between; }
}

/* -- page header (dashboard, Case Workspace, Project Home - via the
   `page_header` macro in _macros.html) -------------------------------
   One shape for every page-level header: title block on the left,
   a controls slot on the right. Project Home used to have its own
   separate .project-home-header with a smaller h1 (1.5rem), top-aligned
   instead of bottom-aligned, and no divider - undocumented drift with
   no evidence it was deliberate, reconciled onto this one rule when the
   macro was introduced. */
.dash-head {
    display: flex;
    justify-content: space-between;
    align-items: flex-end;
    padding: 1.25rem 0 0.9rem;
    border-bottom: 1px solid var(--border);
    flex-wrap: wrap;
    gap: 1.5rem;
}
.dash-head h1 { font-family: var(--font-display); font-size: 1.8rem; margin: 0.3rem 0 0; font-weight: 300; }
.dash-head-actions { display: flex; gap: 0.6rem; align-items: center; }
.mono { font-family: var(--font-body); color: var(--machine-blue); font-size: 1.1rem; }
.form-error { color: var(--failure-red); }
.dash-meta { display: flex; gap: 2.2rem; }
.meta-stat { text-align: right; }
.meta-num { display: block; font-family: var(--font-display); font-size: 1.9rem; color: var(--text-primary); }
.meta-label { font-family: var(--font-body); font-size: 0.85rem; color: var(--text-metadata); text-transform: uppercase; letter-spacing: 0.06em; }

/* -- honeycomb lattice (signature element) -------------------------------- */
.lattice-wrap {
    display: flex;
    flex-wrap: wrap;
    gap: 1.5rem;
    padding: 1.1rem 0;
    align-items: flex-start;
}
.lattice {
    /* Responsive grid instead of a fixed-width flex-wrap: no magic-number
       dependency between container width / hex width / gap to keep rows
       even, and it can't overflow the page on a narrow viewport. */
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(92px, 1fr));
    gap: 10px;
    flex: 1 1 320px;
    max-width: 640px;
}
.hex {
    aspect-ratio: 92 / 80;
    clip-path: polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%);
    background: var(--surface-primary);
    border: 1px solid var(--border);
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
    font-family: var(--font-body);
    font-size: var(--text-sm);
    color: var(--text-metadata);
    padding: 0.4rem;
    position: relative;
}
.hex.done { background: var(--accepted-green-tint); border-color: var(--accepted-green); color: var(--text-primary); }
.hex.active { border-color: var(--machine-blue); color: var(--text-primary); animation: pulse 2.4s ease-in-out infinite; }
.hex.pending { color: var(--text-metadata); }

@keyframes pulse {
    0%, 100% { box-shadow: 0 0 0 0 rgba(44, 96, 121, 0.35); }
    50% { box-shadow: 0 0 0 6px rgba(44, 96, 121, 0); }
}

.legend { font-family: var(--font-body); font-size: 0.8rem; color: var(--text-metadata); min-width: 160px; }
.legend-title { text-transform: uppercase; letter-spacing: 0.08em; font-size: 0.85rem; margin-bottom: 0.8rem; }
.dot { display: inline-block; width: 10px; height: 10px; border-radius: 2px; margin-right: 0.5rem; }
.dot-done { background: var(--accepted-green); }
.dot-active { background: transparent; border: 1px solid var(--machine-blue); }
.dot-pending { background: transparent; border: 1px solid var(--border); }

/* -- registry table -------------------------------------------------------- */
.registry { padding: 0.6rem 0 1.25rem; }
.registry-filter { display: flex; gap: 0.4rem; flex-wrap: wrap; margin: 0.8rem 0 0.8rem; }
.chip {
    font-family: var(--font-body);
    font-size: 0.85rem;
    padding: 0.28rem 0.65rem;
    border: 1px solid var(--border);
    border-radius: 999px;
    color: var(--text-metadata);
    cursor: pointer;
    background: transparent;
}
.chip.active { background: var(--surface-selected); border-color: var(--border-strong); color: var(--text-primary); }

/* Bounded, internally-scrolling region - a long registry never grows the
   page unboundedly, and a fixed height (not just max-height) means
   selecting a category never changes this box's own footprint, so
   nothing below it (Audit Trail) can shift merely because a filter
   matched fewer rows. #registry-table-scroll pins the exact height;
   this shared max-height/overflow is the general "long list" treatment
   reused for any other bounded table region (e.g. Audit Trail below). */
.table-scroll { overflow-x: auto; overflow-y: auto; max-height: 360px; }
#registry-table-scroll { height: 360px; }
.registry-table { width: 100%; border-collapse: collapse; font-size: 0.92rem; }
.registry-table th {
    text-align: left;
    font-family: var(--font-body);
    font-size: 0.85rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-metadata);
    border-bottom: 1px solid var(--border);
    padding: 0.5rem 0.7rem;
    position: sticky;
    top: 0;
    background: var(--surface-primary);
}
.registry-table td { padding: 0.5rem 0.7rem; border-bottom: 1px solid var(--border); color: var(--text-primary); }
.registry-table td:last-child { font-family: var(--font-body); color: var(--machine-blue); }

/* -- governance / audit trail ---------------------------------------------- */
.governance { padding: 0.6rem 0 1.25rem; }

/* -- consistency flags ---------------------------------------------------- */
.consistency { padding: 0.6rem 0 1.25rem; }
.consistency h2 { font-family: var(--font-display); font-size: 1.05rem; margin-bottom: 0.7rem; }
.help-desk h2 { font-family: var(--font-display); font-size: 1.05rem; margin-bottom: 0.7rem; }
.consistency-note, .consistency-clean {
    font-family: var(--font-body);
    font-size: 0.85rem;
    padding: 1rem 1.2rem;
    border: 1px solid var(--border);
    border-radius: 3px;
}
.consistency-note { color: var(--text-secondary); }
.consistency-clean { color: var(--accepted-green); border-color: var(--accepted-green); }

.flag-list { display: grid; gap: 1rem; }
/* A flagged cross-requirement contradiction - genuinely the failure/
   contradiction semantic, not an amber "needs evidence" state. */
.flag-card {
    background: var(--surface-primary);
    border: 1px solid var(--failure-red);
    border-left: 4px solid var(--failure-red);
    border-radius: 3px;
    padding: 1.2rem 1.4rem;
}
.flag-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; margin-bottom: 0.8rem; }
.flag-req { font-size: var(--text-sm); color: var(--text-primary); padding: 0.6rem 0.8rem; background: var(--surface-secondary); border-radius: 3px; }
.flag-req-label {
    display: block;
    font-family: var(--font-body);
    font-size: var(--text-sm);
    color: var(--text-metadata);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    margin-bottom: 0.3rem;
}
.flag-explanation { color: var(--text-secondary); font-size: 0.9rem; margin: 0; }
.export-actions { margin-top: 1.2rem; }

/* -- gateway (login + post-login project gateway) ------------------------- */
.gateway-page { display: flex; justify-content: center; padding: 1.75rem 0; }
.gateway-card {
    width: min(480px, 100%);
    border: 1px solid var(--border);
    border-radius: 8px;
    background: var(--surface-primary);
    box-shadow: 0 24px 60px rgba(42, 36, 30, 0.14);
    overflow: hidden;
}
/* CLAUDE-CA1D-RECEPTION-FIX-01 (addendum): upper-left escape action,
   only present on pages that opt into gateway_base.html's own
   gateway_back_action block (project_chooser.html) - empty/absent
   everywhere else, so gateway.html's own layout is unaffected. */
.gateway-back-action { padding: 1.4rem 2rem 0; }
.gateway-back-action .btn { padding: 0.4rem 0.9rem; font-size: 0.85rem; }
.gateway-brand { display: flex; align-items: center; gap: 1.2rem; padding: 2rem 2rem 1.5rem; }
/* CLAUDE-LETTERMARK-PURGE-01: .gateway-brand is now a wordmark lockup
   with nothing beside it. Its history, kept because it explains why the
   rule below is only typography: it began as a flat bee-yellow badge
   carrying a literal "A" glyph, became the archiosk_mark SVG under
   CLAUDE-BOTTLENECK-ADOPTION-01, and lost the symbol entirely on
   2026-08-30 when that mark was retired for reading as a bowtie beside
   the very word it sat next to. The `gap: 1.2rem` above now has one
   child to space and is harmless; it is left in place so a future
   second element lands in the established composition rather than
   flush against the card edge. */
.gateway-brand h1 { font-family: var(--font-display); font-size: 1.8rem; font-weight: 300; margin: 0; color: var(--text-primary); }
.gateway-subtitle { margin: 0.3rem 0 0; color: var(--text-secondary); font-size: var(--text-sm); }
.gateway-divider { height: 1px; margin: 0 2rem; background: var(--border); }
.gateway-section { padding: 1.6rem 2rem 2rem; }
.gateway-section-label {
    font-family: var(--font-body);
    text-transform: uppercase;
    letter-spacing: 0.08em;
    font-size: 0.85rem;
    color: var(--text-metadata);
    margin: 0 0 0.3rem;
}
.gateway-section h2 { font-family: var(--font-display); font-size: 1.15rem; margin: 0 0 1.2rem; color: var(--text-primary); }
/* CLAUDE-VOICE-CONSISTENCY-01: the Gateway orientation composer - a
   single compact row (input + mic + Ask), deliberately not the full
   resizable in-project .conversation-dock-panel machinery (there is no
   project/case here to give it a thread) - see gateway.html's own
   template comment. .voice-input-button/.voice-input-status reused
   unchanged from the in-project composer (main.css, "CLAUDE-POSTCAMEL-
   VOICE1-PRE" above) - one visual language, not a second one. */
.gateway-orientation-form { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 1rem; flex-wrap: wrap; }
.gateway-orientation-form .text-input { flex: 1; min-width: 220px; max-width: none; }

/* CLAUDE-ESTABLISH-HELPDESK-01 (GOV-D-001): the candidate founding document the
   registry help desk may read. Deliberately quiet and deliberately NOT styled
   like the Composer's + attachment chip - that chip means "this is going into
   the record", and this means the opposite: shown for one question, filed by
   nothing but the create-project button below. Same-looking controls that mean
   different things is the drift this app is converging away from. */
/* CLAUDE-PROJECT-CODE-01: the acronym sits beside the project name because it
   IS project identity - seeing "South Regional Police Centre / SRPC" together is
   what makes a derived acronym obviously right or obviously wrong at a glance.
   Narrow by construction: a 4-character field that looks like a 4-character
   field tells the user the limit without a validation message. */
.project-code-field { display: flex; align-items: baseline; gap: .55rem; flex-wrap: wrap; margin: .5rem 0 0; }
.project-code-label { font-size: .8rem; color: var(--text-secondary); }
.project-code-input { width: 6ch; max-width: 6ch; min-width: 6ch; text-transform: uppercase; letter-spacing: .06em; }
#upload-project-code-note { flex: 1 1 16rem; margin: 0; }

.composer-candidate-doc { display: flex; align-items: center; gap: .5rem; margin: 0 0 .35rem; font-size: .85rem; color: var(--text-secondary); }
.composer-candidate-doc strong { font-weight: 500; color: var(--text-primary); }
#upload-help-candidate-note { margin: 0 0 .9rem; }

/* Application-level Developer Mode Composer: the same restrained service
   counter language as the project Composer, limited to the home page. */
.developer-home-composer { margin-top: 1.25rem; padding: 1rem; border: 1px solid var(--line, rgba(255,255,255,.14)); border-radius: .35rem; }
.developer-home-composer-heading { display: flex; align-items: center; gap: .65rem; flex-wrap: wrap; }
.developer-home-composer-heading h2 { margin: 0; font-size: 1rem; }
.developer-template-identity, .developer-template-identity-inline { color: var(--text-metadata); font-size: .76rem; }
.developer-template-identity-form, .developer-new-chat-form { margin: 0; }
.developer-template-identity-button { border: 0; background: transparent; color: inherit; font: inherit; padding: 0; cursor: pointer; }
.developer-template-identity-button:hover, .developer-template-identity-button:focus-visible { text-decoration: underline; }
.developer-home-composer-form { display: flex; align-items: center; gap: .5rem; margin-top: .65rem; flex-wrap: wrap; }
.developer-home-composer-form .text-input { flex: 1; min-width: 220px; max-width: none; }
.developer-home-selection, .developer-home-messages { margin: .5rem 0; }
.developer-home-message { margin: .25rem 0; }
.developer-home-message-role { display: inline-block; min-width: 2.25rem; font-weight: 600; }
.developer-home-context-actions { display: flex; gap: .5rem; flex-wrap: wrap; margin-top: .65rem; }
.developer-home-context-actions form { margin: 0; }
.developer-chat-history { margin: .65rem 0; border-top: 1px solid var(--line, rgba(255,255,255,.1)); }
.developer-chat-history-row { border-bottom: 1px solid var(--line, rgba(255,255,255,.08)); }
.developer-chat-history-row summary { cursor: pointer; padding: .35rem 0; color: var(--text-secondary); list-style: none; }
.developer-chat-history-row summary::-webkit-details-marker { display: none; }
.developer-delete-chat-form { display: flex; align-items: end; gap: .5rem; flex-wrap: wrap; padding: .35rem 0 .55rem; font-size: .78rem; color: var(--text-secondary); }
.developer-delete-chat-form label { display: flex; flex-direction: column; gap: .2rem; }
.gateway-orientation-reply {
    margin: -0.6rem 0 1.2rem;
    font-family: var(--font-body);
    font-size: var(--text-sm);
    color: var(--text-secondary);
}

.gateway-field { display: grid; gap: 0.35rem; margin-bottom: 1rem; }
.gateway-field label {
    font-family: var(--font-body);
    font-size: 0.85rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--text-metadata);
}

/* auto-fit, not a fixed 2 columns: one action (a read_only session, which
   only ever sees "Open an existing project") should fill the row, not
   sit half-width with dead space beside it - composition should follow
   how many actions there actually are, not assume a fixed count. */
.gateway-actions { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 0.8rem; }
.gateway-action {
    display: flex;
    align-items: center;
    gap: 0.9rem;
    min-height: 82px;
    padding: 1rem;
    text-align: left;
    text-decoration: none;
    border: 1px solid var(--border);
    border-radius: 5px;
    background: var(--surface-secondary);
    color: var(--text-primary);
    transition: border-color 120ms ease, background 120ms ease, transform 120ms ease;
}
.gateway-action:hover { border-color: var(--border-strong); transform: translateY(-1px); text-decoration: none; }
.gateway-action-primary {
    border-color: var(--border-strong);
    background: var(--surface-selected);
}
.gateway-action-primary:hover { border-color: var(--border-strong); }
.gateway-action-icon {
    display: grid;
    place-items: center;
    flex: 0 0 38px;
    width: 38px;
    height: 38px;
    border: 1px solid var(--border);
    border-radius: 4px;
    background: var(--surface-primary);
    color: var(--text-primary);
    font-size: 1.1rem;
}
.gateway-action strong { display: block; margin-bottom: 0.25rem; font-size: 0.85rem; color: var(--text-primary); }
.gateway-action small { display: block; color: var(--text-secondary); font-size: 0.85rem; line-height: 1.4; }

/* CLAUDE-CA1D-PROJECT-GATEWAY-LABELS-01: two project-context groups
   (Client/Owner, Design-Builder/Proponent), each containing its own
   small .gateway-actions row (New Project / Open Existing Project) --
   reuses that existing auto-fit grid unchanged for the inner row, this
   only stacks the two context groups themselves. */
.gateway-context-groups { display: grid; gap: 1.6rem; }
.gateway-context-description { margin: 0 0 0.8rem; color: var(--text-secondary); font-size: 0.85rem; line-height: 1.4; }

/* -- gateway login card (compact) -----------------------------------------
   Geometry/spacing from the archiosk-explorer (port 5173) welcome/sign-in
   card, scoped to .gateway-card-compact so the post-login .gateway-card
   (gateway.html) keeps its original spacious layout untouched.

   CLAUDE-CA1D-SIGNIN-VISUAL-CONTINUITY-01: the card's own former light
   "sand" panel is replaced with a dark, translucent glass treatment that
   sits naturally on the new deep-ocean background (auth_shell.html).
   Every color inside this card (.text-input, .gateway-field label,
   .btn-primary, .form-error, links) was already driven by this app's
   shared design tokens (--text-primary/--surface-primary/--border/etc.)
   -- rather than overriding each individual rule, this redefines those
   SAME custom properties scoped to .gateway-card-compact only, so every
   descendant re-themes correctly with no other edits, including
   .btn-primary's already-inverted `background: var(--text-primary);
   color: var(--surface-primary);` relationship, which self-corrects
   into a still-legible light button once those two tokens swap roles.
   Nothing outside this one scoped card is affected -- the rest of the
   authenticated app keeps its existing light theme exactly as before. */
.gateway-card-compact {
    --text-primary: rgb(230, 244, 255);
    --text-secondary: rgba(228, 243, 255, .82);
    --text-metadata: rgba(200, 230, 255, .72);
    --text-disabled: rgba(200, 230, 255, .4);
    --surface-primary: rgba(10, 35, 42, .55);
    --border: rgba(176, 219, 255, .28);
    --border-strong: rgba(176, 219, 255, .5);
    --machine-blue: rgba(200, 230, 255, .8);
    --failure-red: rgba(255, 170, 150, .95);
    background-color: rgba(8, 28, 36, .72);
    -webkit-backdrop-filter: blur(14px);
    backdrop-filter: blur(14px);
    border: 1px solid rgba(176, 219, 255, .22);
    box-shadow: 0 24px 70px rgba(1, 8, 14, .5);
}
/* The card and footer must paint above the fixed, z-index:0 ocean
   canvas beneath them -- same explicit position/z-index treatment
   landing.css's own .landing-content already uses for the identical
   reason, scoped here to only this shell (gateway.html/project_chooser
   .html's own .gateway-card/.gateway-card-wide, which never sit inside
   .auth-shell-page, are unaffected). */
.auth-shell-page .gateway-card,
.auth-shell-page .auth-shell-footer {
    position: relative;
    z-index: 2;
}
/* CLAUDE-GATEWAY-UI-GRAMMAR-01: raw px values below converted to the
   app's own rem scale (same visual sizes - 16px=1rem/32px=2rem/12px=
   0.75rem/etc. at the 16px root font-size every other rule in this file
   assumes) - a Product Owner consistency report named exactly this kind
   of stray px value (lifted wholesale from a different reference design
   per this block's own header comment above) as part of the drift.
   Values themselves are UNCHANGED, only the unit. */
.gateway-card-compact .gateway-brand {
    gap: 0.6rem;
    padding: 1rem 2rem 0.75rem;
    align-items: flex-end;
}
.gateway-card-compact .gateway-brand h1 { line-height: 1; }
.gateway-card-compact .gateway-section { padding: 0.8rem 2rem 1rem; }
.gateway-card-compact .gateway-section-label { margin-bottom: 1.2rem; }
.gateway-card-compact .gateway-field { gap: 0.1875rem; margin-bottom: 0.875rem; }
.gateway-card-compact .text-input { max-width: none; }
.gateway-card-compact form .form-error { margin: 0; }
/* CLAUDE-GATEWAY-UI-GRAMMAR-01: was its own padding/border-radius/font-
   weight (10px/16px, 0 radius, 700 weight) - a literal square-cornered,
   differently-padded, bolder button matching neither .btn-primary's
   shared 0.85rem/1.5rem padding and 3px radius nor any other primary
   button anywhere else in the app (this block's own header comment
   already named the whole compact-card geometry as lifted from a
   different reference design; this was the one piece of that geometry
   that visibly read as a different button system, not just a smaller
   card). Only the block/full-width/margin behavior - genuinely specific
   to this compact card, not expressible via .btn alone - is kept;
   padding/border-radius/font-weight now inherit .btn/.btn-primary's
   shared values unchanged. */
.gateway-card-compact .gateway-signin-button {
    display: block;
    width: 100%;
    box-sizing: border-box;
    text-align: center;
    margin-top: 0.875rem;
}

.gateway-footer {
    display: flex;
    justify-content: space-between;
    gap: 1rem;
    padding: 0.8rem 2rem;
    border-top: 1px solid var(--border);
    background: var(--surface-secondary);
    color: var(--text-metadata);
    font-family: var(--font-body);
    font-size: 0.8rem;
    letter-spacing: 0.02em;
}

/* CLAUDE-P40-VW5: templates/gateway_shell.html's own top-level wrapper -
   a real top bar (.workspace-topbar, reused as-is) above the centered
   action-picker card, structurally never inside .app-shell/.app-main
   (no Lists/Toolbox/Display siblings exist on this page at all - see
   that template's own comment on why). .gateway-page keeps its
   existing centering rule; flex: 1 here just lets it claim the full
   height below the top bar instead of collapsing to content height. */
.gateway-shell {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
}
.gateway-shell > .gateway-page {
    flex: 1;
    align-items: center;
}
/* Wider than the shared 480px .gateway-card default (Section 2: "must
   use the available width and remain visually centred") - scoped to
   this modifier only, so login/forgot-password/reset-password's own
   .gateway-card-compact (auth_shell.html) is completely untouched. */
.gateway-card-wide { width: min(920px, 100%); }

/* CLAUDE-CA1D-GATEWAY-VISUAL-CONTINUITY-01: same deep-ocean background
   as Explore/Sign In, reusing the SAME token-redefinition technique
   .gateway-card-compact already established above -- .gateway-shell
   (templates/gateway_shell.html's own exclusive wrapper class, confirmed
   via `grep -rln "gateway-shell" templates/` to appear nowhere else)
   redefines the shared design tokens once, cascading automatically into
   every descendant that already consumes them: .workspace-topbar-brand's
   --brand-gold, the account-menu dropdown's --surface-primary/--border
   (.workspace-user-options, .workspace-overflow-item), and .gateway-card
   (via .gateway-card-wide, which sits inside this shell and has no own
   background/border rule to override). base.html's own separate,
   app-wide .workspace-topbar usage is never nested inside .gateway-shell,
   so it keeps its existing light theme untouched. */
/* CLAUDE-DEEP-OCEAN-HUE-CORRECTION-01: same hue correction as tokens.css's
   own --ocean-* family (see that file's own comment for the full
   before/after/contrast-verification record) - every value below must
   stay byte-identical to tokens.css's own --ocean-* values
   (test_appearance_simplify_01_global_theme.py's own literal-copy parity
   test enforces this), so this is the SAME fix applied at its other
   required location, not a second, independent correction. */
.gateway-shell {
    --text-primary: rgb(230, 244, 255);
    --text-secondary: rgba(228, 243, 255, .82);
    --text-metadata: rgba(200, 230, 255, .72);
    --text-disabled: rgba(200, 230, 255, .4);
    --surface-primary: rgba(10, 35, 42, .55);
    --surface-secondary: rgba(14, 45, 54, .5);
    --surface-hover: rgba(176, 219, 255, .12);
    --surface-selected: rgba(176, 219, 255, .18);
    --border: rgba(176, 219, 255, .28);
    --border-strong: rgba(176, 219, 255, .5);
    --divider-strong: rgba(176, 219, 255, .32);
    --brand-gold: var(--bee-yellow);
    --machine-blue: rgba(200, 230, 255, .8);
    --failure-red: rgba(255, 170, 150, .95);
}
.gateway-shell .gateway-card-wide {
    background-color: rgba(8, 28, 36, .72);
    -webkit-backdrop-filter: blur(14px);
    backdrop-filter: blur(14px);
    border: 1px solid rgba(176, 219, 255, .22);
    box-shadow: 0 24px 70px rgba(1, 8, 14, .5);
}
/* The card and flash messages must paint above the fixed, z-index:0
   ocean canvas beneath them -- same pattern as the .auth-shell-page
   rule above. .workspace-topbar already carries its own
   position:relative/z-index:31 (unrelated, pre-existing reasons) which
   is more than enough to also clear the canvas once .gateway-shell
   becomes the isolated stacking context via its landing-page class, so
   it deliberately isn't repeated here. */
.gateway-shell .gateway-page,
.gateway-shell > .flash-stack {
    position: relative;
    z-index: 2;
}

/* CLAUDE-P40-D1: the standalone authentication shell (auth_shell.html
   - /login, /forgot-password, /reset-password). Reuses .gateway-card
   and its children as-is for visual consistency with the post-login
   .gateway-page (gateway.html) - only the OUTER container differs,
   since this one is never inside .app-shell/.app-main (no sidebar
   column exists to center against, so it centers in the full
   viewport itself instead of relying on a flex sibling for height). */
.auth-shell-page {
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 1rem;
    padding: 1.75rem 1rem;
}
.auth-shell-footer {
    /* CLAUDE-CA1D-SIGNIN-VISUAL-CONTINUITY-01: not var(--text-metadata)
       -- that token is scoped to .gateway-card-compact's own re-theme
       above, and this footer sits outside the card, directly on the
       new dark ocean background, so it needs its own explicit light
       color rather than inheriting the (still light-theme) global
       token value. */
    color: rgba(190, 235, 230, .5);
    font-family: var(--font-body);
    font-size: 0.78rem;
    letter-spacing: 0.02em;
}

@media (max-width: 560px) {
    /* .gateway-actions no longer needs an override here - auto-fit with
       a minmax floor already collapses to one column once 240px no
       longer fits two side by side. */
    .gateway-footer { flex-direction: column; gap: 0.3rem; }
    .flag-pair { grid-template-columns: 1fr; }
}

.error-page { padding: 3rem 0; text-align: center; }
.error-page h1 { font-family: var(--font-display); font-size: 2.2rem; font-weight: 300; }

/* -- flash messages (post/redirect/get feedback) --------------------------- */
.flash-stack { padding-top: 0.8rem; display: grid; gap: 0.5rem; }
.flash-message {
    font-family: var(--font-body);
    font-size: 0.85rem;
    padding: 0.7rem 1rem;
    border-radius: 3px;
    border: 1px solid var(--border);
}
.flash-error { color: var(--failure-red); border-color: var(--failure-red); background: var(--failure-red-tint); }
.flash-success { color: var(--accepted-green); border-color: var(--accepted-green); background: var(--accepted-green-tint); }

.workspace-link { margin-top: 0.8rem; font-size: var(--text-sm); padding: 0.5rem 1rem; }

/* ============================================================
   Workspace top bar (CLAUDE-P40-E2B, Section A)
   ============================================================ */
/* Deliberately restrained - one row, plain text identity/breadcrumb,
   a handful of small buttons. Never a second "brand card": no
   background fill, no border-radius box, no shadow - the bottom
   border is the only separation from the grid below it, same
   restrained-divider language as .workspace-pane.

   CLAUDE-P40-VW8-QA (panel-border hierarchy correction): this line
   spans the ENTIRE application width, unlike every other "ordinary"
   divider in this file - product-owner report named "the divider below
   the top Menu bar" among the distracting areas. var(--border) is
   already this file's general "quiet divider" token, but at full
   viewport width even the quiet token read as too present. Switched to
   var(--divider-strong) instead - NOT because it's stronger (it isn't;
   it's the same fixed, mode-invariant, deliberately understated color
   Chat's own already-approved resize-handle divider uses, see
   .conversation-dock-resize-handle::before's own comment) but because
   it is the one token in this file proven to read calmly at every
   theme's own background rather than tracking whichever surface
   happens to render it - the same reasoning that token's own original
   comment gives, applied here to a second full-width seam instead of
   only the workspace/Chat one. */
/* CLAUDE-P40-VW7B-QA3 (real-Chromium/Playwright-verified correction):
   QA2's flex-shrink:0 fix (below, on .workspace-topbar-identity) was
   real and still correct as far as it went, but a clean-session
   real-browser reproduction showed the Project-name link still failed
   to navigate. Genuine browser evidence (Playwright against the live
   local app - getBoundingClientRect/elementFromPoint/elementsFromPoint/
   a real .click(), swept across viewport widths from 1920px down to
   600px) found the ACTUAL cause at narrow widths: both
   .launcher-panel (Lists) and .workspace-right-column (Toolbox+Eye)
   become `position: fixed; top: 0; ... z-index: 30` overlay drawers
   below the 640px breakpoint (see each one's own @media rule) - and
   since .workspace-topbar itself was plain `position: static` with no
   z-index, EITHER drawer paints over the ENTIRE topbar, including this
   link, because both drawers start at top:0 rather than below the
   topbar's own height. document.elementFromPoint at the visible
   "Project name" text returned `.tree-leaf.launcher-link.current-
   project` (Lists' own current-Project row) at 600px, not the topbar
   anchor at all - confirming the visible topbar text was being
   REACHED by clicks, just not by the element a user would expect,
   exactly the class of bug QA3 asked to rule out ("whether the visible
   text is rendered by the anchor or another overlapping element").
   Fix: .workspace-topbar gets its own stacking context
   (position:relative) and z-index:31 - one step above the drawers'
   ceiling of 30 (the same "stack higher than anything it could ever
   appear over" idiom this file already uses for
   .conv-selection-toolbar/z-index:70 relative to the 60-ceiling
   Appearance popup) - so the topbar, which is this app's "one shell
   every authenticated page shares" (this rule's own original comment,
   above), can never again be occluded by a drawer at any width,
   without touching either drawer's own top:0/z-index:30 (both still
   correctly cover Display/Chat beneath the topbar, which is the
   intended drawer behavior) and without depending on the topbar's own
   height (which varies - two-line wrap when Document controls are
   present, see QA2's own comment - so an offset like `top:
   <topbar-height>` on the drawers would have been fragile in exactly
   the way a structural fix must not be). Verified with a real
   .click(): fails at 600px before this fix (Timeout, drawer link
   receives it instead), succeeds at every width from 600px through
   1920px after it. */
.workspace-topbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: 0.6rem 0;
    border-bottom: 1px solid var(--divider-strong);
    margin-bottom: 1rem;
    flex-wrap: wrap;
    position: relative;
    z-index: 31;
}
/* CLAUDE-P40-VW7B-QA2: flex-shrink:0 added - a real-browser check found
   that clicking the Foreground Project's own name (menu.context.switch-
   project) did not open the Vestibule. Root cause: this was the ONLY
   one of .workspace-topbar's three flex children without flex-shrink:0
   (.workspace-topbar-controls, the mirror-image region on the right,
   already has it) - #workspace-document-controls (the middle region,
   flex:1 1 auto, actively GROWS to fill space whenever Document
   controls are visible) could squeeze this region's own rendered width
   toward its min-width:0 floor, shrinking the Project-name link's
   actual clickable area toward nothing even though its DOM/href/
   aria-label were always correct - not a JS interception bug, not a
   missing anchor, a pure flex-distribution asymmetry. Now protected at
   its natural content width, exactly like .workspace-topbar-controls
   already is; .workspace-topbar's own flex-wrap:wrap is the safe
   fallback if all three regions' combined natural width ever exceeds
   the viewport (a two-line header, still fully clickable) rather than
   a one-line header with an invisible-width link. .workspace-topbar-
   context's own nested overflow:hidden/text-overflow:ellipsis (below)
   still truncates an unusually long Project+Document name WITHIN this
   now-guaranteed width, unaffected by this change. */
/* CLAUDE-APP-MENU-01: this region now also holds .workspace-menubar (8
   top-level menu buttons, a real min-content width of its own) ahead of
   the breadcrumb - at a narrow viewport their combined natural width can
   exceed the whole .workspace-topbar, and this block's own overflow:
   hidden was clipping relative to ITS box, not the viewport, so the
   breadcrumb (including the Project-name link, menu.context.switch-
   project) could render past the visible edge entirely invisible/
   unclickable rather than merely truncated. flex-wrap:wrap lets the
   breadcrumb drop to its own line below the menu bar instead - the
   SAME "two-line header, still fully clickable" fallback QA2's own
   comment above already established for .workspace-topbar itself.

   CLAUDE-ARCHIOSK-IDENTITY-ACTIVITY-INDICATOR-01 (live-browser catch):
   overflow:hidden REMOVED entirely, not just narrowed - it clipped every
   menu dropdown's own .workspace-menubar-panel (position:absolute,
   intentionally extending below this box) invisible, on every menu, on
   every page, the instant a real click opened one - a live-only defect
   pytest's own DOM-only tests never render CSS layout/paint for and so
   could never have caught. .workspace-topbar-context (the breadcrumb)
   already carries its own independent overflow:hidden/text-overflow:
   ellipsis for truncation, below - nothing here still depended on this
   parent's own copy. */
.workspace-topbar-identity {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 0.6rem;
    min-width: 0;
    max-width: 100%;
    flex-shrink: 0;
}
/* CLAUDE-P40-BRAND1 (product-owner correction): "Archiosk" used to be
   0.85rem/--text-metadata (a MUTED, secondary color) - smaller and
   dimmer than menu.context's own breadcrumb (0.88rem/--text-primary,
   below), so the application's own identity read as LESS prominent
   than a Project/Document breadcrumb, backwards from what identity
   branding should be. Now a real flex row (mark + wordmark, sharing
   ONE link/tab-stop/accessible-name - see the template's own comment)
   at 1.2rem/600 ("Archiosk", within the suggested 18-20px semibold
   range) - genuinely larger and richer than menu.context, which keeps
   its own EXISTING 0.88rem/normal-weight unchanged (already within the
   suggested 14-16px/normal-weight secondary range - real repository
   evidence, not a value invented for this correction). */
.workspace-topbar-brand {
    display: flex;
    align-items: baseline;
    gap: 0.4rem;
    color: var(--brand-gold);
    flex-shrink: 0;
    text-decoration: none;
}
.workspace-topbar-brand:hover { text-decoration: underline; }
.workspace-topbar-brand:focus-visible {
    outline: 2px solid var(--machine-blue);
    outline-offset: 2px;
    border-radius: 3px;
}
.workspace-topbar-brand-text {
    font-family: var(--font-display);
    font-size: 1.2rem;
    font-weight: 600;
}
/* CLAUDE-P40-BRAND1: no per-Appearance override rule here on purpose -
   .workspace-topbar is already one of the combined "owned surface
   roots" further down this file that locally redefines the standard
   token names per Appearance (the same block --text-primary/the
   semantic accents already live in); --brand-gold is redefined there
   too, so this rule's own `color: var(--brand-gold)` above already
   repaints correctly per Appearance for free, the same way every
   other token-driven rule in this file does. */
.workspace-topbar-context {
    /* CLAUDE-P40-BRAND1: deliberately kept as its own literal value, not
       var(--text-sm) - this rule's own size is verified elsewhere
       (test_p40brand1_brand_mark.py) to sit in a specific relationship
       to the brand text's own size and the 14-16px secondary-text
       range; consolidating it into the shared token would be a genuine
       (if tiny) value change to a pairing already deliberately tuned,
       not a no-op rename like the CLAUDE-POSTCAMEL-P02-ST1 consolidation
       elsewhere in this file. */
    font-size: 0.88rem;
    color: var(--text-primary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
/* CLAUDE-P40-VW7B, Section 5: the Foreground Project's own breadcrumb
   segment is now a real link into the Project Vestibule (see the
   template's own comment) - same color as the plain text it replaces
   (inherits .workspace-topbar-context's --text-primary; a link here
   must not read as a DIFFERENT, more/less important piece of text than
   its neighbors), no underline at rest, real hover/focus-visible
   affordance so it still reads as interactive on inspection. */
.workspace-topbar-project {
    color: inherit;
    text-decoration: none;
}
.workspace-topbar-project:hover { text-decoration: underline; }
.workspace-topbar-project:focus-visible {
    outline: 2px solid var(--machine-blue);
    outline-offset: 2px;
    border-radius: 2px;
}
/* CLAUDE-POSTCAMEL-TOPMENU-FIX1: the link above deliberately keeps the
   SAME text color/weight as its plain-text neighbors (see that rule's
   own comment) - this chevron is the one visual signal that it's a
   control, not a second color decision. var(--text-secondary) is the
   same "less prominent than the primary reading" token .workspace-
   topbar-sep/.workspace-topbar-doc already use immediately below, not a
   new one; sized down and given breathing room so it reads as a small
   indicator, never competing with the project name itself for
   attention. */
.workspace-topbar-project-chevron {
    display: inline-block;
    margin-left: 0.3rem;
    font-size: 0.65em;
    color: var(--text-secondary);
    vertical-align: middle;
}
.workspace-topbar-sep { color: var(--text-secondary); margin: 0 0.3rem; }
.workspace-topbar-doc { color: var(--text-secondary); }
.workspace-topbar-badge {
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--attention-amber);
    margin-left: 0.4rem;
}
/* CLAUDE-RFP-BOUNDARY-01: the persistent workspace-side indicator
   (Section 11) - --machine-blue (tokens.css: "machine/system/reference"),
   never --accepted-green/--failure-red, since Owner vs Proponent is an
   identity fact, not a good/bad status judgment; both sides get the
   IDENTICAL treatment for the same reason - color-differentiating them
   would wrongly imply one side is a "better" state than the other. A
   plain bordered pill (no fill), same restrained "reserve stronger
   accent color for meaningful states" discipline .current-project's own
   border-only treatment already established nearby. */
.workspace-side-indicator {
    display: inline-flex;
    align-items: center;
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--machine-blue);
    border: 1px solid var(--machine-blue);
    border-radius: 3px;
    padding: 0.05rem 0.4rem;
    margin-left: 0.5rem;
    white-space: nowrap;
    cursor: default;
}
/* CLAUDE-DEVELOPER-MODE-COCKPIT-01, Addendum E: reuses --machine-blue
   (system/reference, not a new 7th semantic color - tokens.css's own
   "add a new semantic color only when a new kind of information
   genuinely needs distinguishing" discipline) but FILLED rather than
   .workspace-side-indicator's outline, so the two never read as the
   same kind of badge at a glance - one is project-environment state,
   this one is reviewer-session state, and they can legitimately both
   be visible at once. */
.workspace-developer-mode-badge {
    display: inline-flex;
    align-items: center;
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--surface-primary);
    background: var(--machine-blue);
    border-radius: 3px;
    padding: 0.1rem 0.45rem;
    margin-left: 0.5rem;
    white-space: nowrap;
    cursor: default;
}

/* CLAUDE-DEVELOPER-MENU-01: the badge's own exit.

   Inherits the badge's colour rather than declaring its own, so the exit can
   never drift out of contrast with the surface it sits on - the badge already
   pairs --surface-primary on --machine-blue and that pairing was chosen
   deliberately. A second colour here would be a second thing to verify. */
.workspace-developer-mode-badge-text { pointer-events: none; }
.workspace-developer-mode-exit-form { display: inline-flex; margin: 0 0 0 0.35rem; }
.workspace-developer-mode-exit {
    border: 0;
    background: transparent;
    color: inherit;
    font: inherit;
    line-height: 1;
    padding: 0 0.15rem;
    cursor: pointer;
    opacity: 0.75;
    border-radius: 2px;
}
.workspace-developer-mode-exit:hover,
.workspace-developer-mode-exit:focus-visible { opacity: 1; }
/* CLAUDE-LIVE-VERIFICATION-ACCOUNT-MECHANISM-01: same filled-badge shape
   as .workspace-developer-mode-badge immediately above, deliberately a
   DIFFERENT semantic color so the two are never mistakable for one
   another at a glance - reuses --attention-amber ("needs evidence /
   pending attention", tokens.css's own grammar) rather than adding an
   8th semantic color: a live-verification session genuinely IS a
   pending-attention state (temporary, should be ended when done), not
   decoration. Contrast verified: --surface-primary on --attention-amber
   is 7.08:1, comfortably above the 4.5:1 floor. */
.workspace-verification-session-badge {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--surface-primary);
    background: var(--attention-amber);
    border-radius: 3px;
    padding: 0.1rem 0.45rem;
    margin-left: 0.5rem;
    white-space: nowrap;
    cursor: default;
}
.workspace-verification-session-badge form {
    display: inline;
}
.workspace-verification-session-badge button {
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--surface-primary);
    background: transparent;
    border: 1px solid var(--surface-primary);
    border-radius: 2px;
    padding: 0 0.3rem;
    cursor: pointer;
}
/* CLAUDE-RFP-BOUNDARY-01: the Owner-side Publish Procurement Package
   selection list - plain, restrained rows, same 0.85rem/border-bottom
   idiom this file already uses for compact list surfaces (see
   .finding-review-actions/.review-btn nearby). */
.publish-source-list {
    list-style: none;
    margin: 0.6rem 0;
    padding: 0;
    display: flex;
    flex-direction: column;
}
.publish-source-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    font-size: 0.85rem;
    padding: 0.35rem 0;
    border-bottom: 1px solid var(--border);
}
.publish-source-item:last-child { border-bottom: none; }
.publish-source-include { display: flex; align-items: center; gap: 0.4rem; flex: 1 1 auto; min-width: 0; }
.publish-source-founding { display: flex; align-items: center; gap: 0.3rem; flex-shrink: 0; color: var(--text-secondary); font-size: 0.8rem; }
/* CLAUDE-P40-VW7A-QA (Move Document Controls into the Top Application
   Menu): the central region between the breadcrumb and Display Layout/
   Appearance/Account - flex:1 so it claims whatever space is left
   between those two (which don't grow), and centers its own content
   within that space. Deliberately styled to "read as part of the
   application, not a bright toolbar pasted over it" - no per-button
   borders (unlike .workspace-topbar-btn above), quiet text color,
   background only on hover/focus, the same restrained language
   .conv-selection-btn already established for a comparable dense
   inline-action row. */
.workspace-topbar-document-controls {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
}
.workspace-topbar-document-controls[hidden] { display: none; }
.doc-controls-group {
    display: flex;
    align-items: center;
    gap: 0.15rem;
    min-width: 0;
}
.doc-control-btn {
    font-family: var(--font-body);
    font-size: 0.8rem;
    color: var(--text-secondary);
    background: none;
    border: none;
    border-radius: 3px;
    padding: 0.3rem 0.45rem;
    cursor: pointer;
    line-height: 1;
    text-decoration: none;
    display: inline-flex;
    align-items: center;
    white-space: nowrap;
}
.doc-control-btn:hover:not(:disabled) { background: var(--surface-hover); color: var(--text-primary); text-decoration: none; }
.doc-control-btn:focus-visible { outline: 2px solid var(--machine-blue); outline-offset: 1px; }
.doc-control-btn:disabled { color: var(--text-disabled); cursor: default; }
.doc-page-nav {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    font-size: 0.8rem;
    color: var(--text-secondary);
}
.doc-page-input {
    width: 2.6em;
    text-align: center;
    font-family: var(--font-body);
    font-size: 0.8rem;
    padding: 0.2rem 0.3rem;
    border: 1px solid var(--border);
    border-radius: 3px;
    background: var(--surface-primary);
    color: var(--text-primary);
}
.doc-page-total { color: var(--text-metadata); }
.doc-page-total:not(:empty)::before { content: "/ "; }
.doc-zoom-level {
    font-size: 0.78rem;
    color: var(--text-metadata);
    min-width: 3em;
    text-align: center;
    font-variant-numeric: tabular-nums;
}
.doc-controls-divider { width: 1px; height: 1.1em; background: var(--border); margin: 0 0.2rem; flex-shrink: 0; }
.doc-search-input {
    width: 9rem;
    max-width: 30vw;
    font-family: var(--font-body);
    font-size: 0.8rem;
    padding: 0.25rem 0.4rem;
    border: 1px solid var(--border);
    border-radius: 3px;
    background: var(--surface-primary);
    color: var(--text-primary);
}
.doc-search-count { font-size: 0.75rem; color: var(--text-metadata); min-width: 2.5em; }
/* CLAUDE-P40-VW7A-QA, Section "Responsive behavior": hidden until
   static/js/pdf_viewer.js's own matchMedia(900px) listener marks it
   active (narrow viewport) and re-parents .doc-controls-secondary's
   real DOM node into #doc-controls-overflow-panel below - not a
   second, cloned copy of the same controls. */
.doc-controls-overflow { position: relative; display: none; }
.doc-controls-overflow.doc-controls-overflow-active { display: inline-flex; }
.doc-controls-overflow-summary { list-style: none; cursor: pointer; }
.doc-controls-overflow-summary::-webkit-details-marker { display: none; }
.doc-controls-overflow-panel {
    position: absolute;
    top: calc(100% + 0.3rem);
    right: 0;
    background: var(--surface-primary);
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: 0.4rem;
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 0.15rem;
    z-index: 60;
    min-width: 170px;
    box-shadow: none;
}
.doc-controls-overflow-panel .doc-control-btn { justify-content: flex-start; }
.doc-controls-overflow-panel .doc-controls-divider { width: auto; height: 1px; margin: 0.2rem 0; }
.doc-controls-overflow-panel .doc-search-input { width: 100%; max-width: none; }

.workspace-topbar-controls {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-shrink: 0;
}
/* CLAUDE-MENU-DEBOXING-01: the permanent 1px border here used to render
   at REST too - every one of the 8 top-level menu triggers (plus
   Display Layout/Appearance/Account, which share this same class) sat
   inside its own always-visible box, reading as a row of button tiles
   rather than a restrained command surface (Product Owner, explicit:
   "the top-menu icons do not need individual visible frames/boxes").
   border-color: transparent (not border: none) keeps the exact same
   1px box model at rest, so hover/focus/open never shifts layout by a
   pixel - only the color changes, VS-Code-like "icon + spacing +
   subtle response" rather than "icon enclosed permanently in a visible
   frame." The hover/expanded rule below is unchanged in EFFECT (same
   border-strong reveal) - see app_menu.js's own new aria-expanded
   sync, added alongside this change, for why [aria-expanded="true"]
   now actually matches something (it was dead CSS before - native
   <details>/<summary> never set that attribute on its own), making
   the open-menu frame a genuine, truthful state signal rather than a
   decorative one. */
.workspace-topbar-btn {
    font-family: var(--font-body);
    font-size: var(--text-sm);
    color: var(--text-secondary);
    background: none;
    border: 1px solid transparent;
    border-radius: 3px;
    padding: 0.35rem 0.65rem;
    cursor: pointer;
    list-style: none;
    display: inline-block;
}
.workspace-topbar-btn::-webkit-details-marker { display: none; }
.workspace-topbar-btn:hover,
.workspace-topbar-btn[aria-expanded="true"] {
    color: var(--text-primary);
    border-color: var(--border-strong);
}
/* Keyboard focus must stay visible even though the frame itself is now
   quiet at rest - the browser's own default focus outline already
   provides this (untouched, never suppressed), but a subtle border
   reveal on :focus-visible too keeps the same "which control has focus"
   signal sighted keyboard users get from hover, matching Section 5's
   own "preserve... keyboard focus visibility" requirement explicitly,
   not merely by omission. */
.workspace-topbar-btn:focus-visible {
    border-color: var(--border-strong);
}
/* CLAUDE-APP-MENU-01: the application menu bar - Archiosk | File | Edit |
   View | Document | Tools | Window | Help, very top-left. "Archiosk" is a
   plain <details><summary class="workspace-topbar-btn"> exactly like its
   seven neighbors (no border/background/size distinction) - the Product
   Owner's own explicit "not a separate logo" requirement. Reuses the SAME
   popover idiom .workspace-layout-options/.workspace-appearance-options/
   .workspace-user-options already established (position:absolute,
   top:calc(100% + 0.3rem), solid --surface-primary, z-index 60) rather
   than inventing a second one - this rule intentionally does not touch
   those three's own tested selector group above; it duplicates the same
   properties for the menu bar's own panels instead. */
/* Section 2/3/4: the three-dot working indicator. Hidden by default
   (Section 3/7 - idle is quiet, nothing rendered at rest, not merely
   dim) - static/js/case_workspace.js's own Composer submit handler
   removes [hidden] and adds .working the instant a real request is in
   flight; the next full page load starts hidden again on its own,
   matching #dock-composer-execution-status's own established self-
   clearing discipline one element over. --machine-blue (tokens.css's
   own "machine/system/reference" semantic) is the only color used -
   this is motion, not a state change, so no second color is ever
   introduced merely to animate. */
.workspace-app-activity {
    display: inline-flex;
    flex-direction: column;
    justify-content: center;
    align-self: center;
    gap: 2px;
    width: 8px;
    height: 16px;
    margin-right: 0.15rem;
}
.workspace-app-activity[hidden] {
    display: none;
}
.workspace-app-activity-dot {
    width: 4px;
    height: 4px;
    border-radius: 50%;
    background: var(--machine-blue);
    opacity: 0.25;
}
.workspace-app-activity.working .workspace-app-activity-dot {
    animation: workspace-app-activity-fall 1.35s ease-in-out infinite;
}
.workspace-app-activity.working .workspace-app-activity-dot:nth-child(2) {
    animation-delay: 0.3s;
}
.workspace-app-activity.working .workspace-app-activity-dot:nth-child(3) {
    animation-delay: 0.6s;
}
/* Section 3: "avoid aggressive flashing, bouncing, novelty animation,
   excessive speed... subtle enough for continuous use in a professional
   instrument." A dim baseline (0.25 opacity, above) with each dot
   briefly reaching full opacity in top->middle->bottom sequence, then a
   real pause (55%-100%) before repeating - a restrained pulse, not a
   spinner. */
@keyframes workspace-app-activity-fall {
    0%, 55%, 100% { opacity: 0.25; }
    15%, 30% { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
    .workspace-app-activity.working .workspace-app-activity-dot {
        animation: none;
        opacity: 0.7;
    }
}
.workspace-menubar {
    display: flex;
    align-items: center;
    gap: 0.3rem;
    flex-shrink: 0;
}
.workspace-menubar-menu {
    position: relative;
}
.workspace-menubar-panel {
    position: absolute;
    top: calc(100% + 0.3rem);
    left: 0;
    background: var(--surface-primary);
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: 0.3rem;
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
    z-index: 60;
    min-width: 220px;
    max-width: min(320px, 94vw);
}
.workspace-menubar-item {
    display: block;
    width: 100%;
    text-align: left;
    font-family: var(--font-body);
    font-size: var(--text-sm);
    color: var(--text-primary);
    background: none;
    border: none;
    border-radius: 3px;
    padding: 0.35rem 0.5rem;
    cursor: pointer;
    text-decoration: none;
    list-style: none;
}
.workspace-menubar-item::-webkit-details-marker { display: none; }

/* CLAUDE-MENU-SHORTCUT-SLOTS-01: the two-column menu row -
   [icon] Label .......... Accelerator

   OPT-IN VIA :has(), NOT a blanket display change. The base rule above stays
   `display: block`, and an item only becomes a flex row once it actually
   contains an icon or a shortcut. That matters because this change could not be
   verified in a browser: a global block->flex switch would restyle every menu
   item in the app on the strength of reasoning alone, whereas this restyles
   only items that do not exist yet. A browser without :has() support simply
   keeps today's appearance, so the failure mode is "no change", not "wrong".

   :has() is already an established technique in this file (see
   .conversation-thread:has(...) and .files-folder-row:has(...)), not a new
   dependency introduced here.

   NO ACCELERATOR IS RENDERED YET, deliberately. There is no Ctrl/Alt keybinding
   anywhere in static/js - the only modifier usage is shiftKey for shift-Enter -
   so a <kbd> printed today would promise a keystroke that does nothing. The
   slot exists; it stays empty until a real binding fills it. */
.workspace-menubar-item:has(.menu-item-icon),
.workspace-menubar-item:has(.menu-shortcut) {
    display: flex;
    align-items: center;
    gap: var(--space-2);
}
.menu-item-icon {
    flex: 0 0 auto;
    width: 1em;
    height: 1em;
    /* Decorative by default - the label carries the meaning. Mark the SVG
       aria-hidden at the call site rather than relying on this. */
}
.menu-shortcut {
    /* Pushed to the trailing edge without a fixed column width, so a long
       label never collides with its accelerator. */
    margin-left: auto;
    padding-left: var(--space-2);
    font-family: var(--font-body);
    font-size: var(--text-xs);
    /* --text-metadata, not --text-disabled: an accelerator is a quiet LABEL,
       and --text-disabled carries the distinct meaning "this control does not
       work". Using it here would make every shortcut read as broken. */
    color: var(--text-metadata);
    background: none;
    border: none;
    white-space: nowrap;
}
.workspace-menubar-item:hover,
.workspace-menubar-item:focus-visible {
    background: var(--surface-hover);
    color: var(--text-primary);
}
/* Icon Intelligence: a real, functionally-disabled item (not merely
   dimmed) reads identically to every other disabled control in this app
   - color: var(--text-disabled); cursor: default - see .doc-control-btn:
   disabled's own established precedent. Buttons use the native `disabled`
   attribute (blocks the click handler entirely, static/js/app_menu.js
   never has to guard against a disabled click firing); a plain <span>
   placeholder (no real command to disable) uses aria-disabled instead,
   since there is no control underneath to disable. */
.workspace-menubar-item:disabled,
.workspace-menubar-item[aria-disabled="true"],
.workspace-menubar-item-disabled {
    color: var(--text-disabled);
    cursor: default;
}
.workspace-menubar-item:disabled:hover,
.workspace-menubar-item[aria-disabled="true"]:hover {
    background: none;
    color: var(--text-disabled);
}
/* A checked/active command (Compare Documents, Fullscreen) gets the SAME
   restrained color+weight treatment every other aria-pressed control in
   this app already uses (.eye-send-btn[aria-pressed="true"] etc.) - never
   a new visual meaning. */
.workspace-menubar-item[aria-pressed="true"] {
    color: var(--machine-blue);
    font-weight: 700;
}
.workspace-menubar-separator {
    border: none;
    border-top: 1px solid var(--border);
    margin: 0.25rem 0.1rem;
}
/* Submenus (Admin/Appearance/Display Layout inside Archiosk; Export inside
   File) - a nested <details> reusing the exact same panel idiom, offset
   to the right of its own parent panel rather than stacking below it, so
   a submenu never visually collides with its own trigger item. */
.workspace-menubar-submenu {
    position: relative;
}
.workspace-menubar-subpanel {
    position: absolute;
    top: 0;
    left: calc(100% + 0.2rem);
    background: var(--surface-primary);
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: 0.4rem;
    z-index: 61;
    min-width: 200px;
    max-width: min(320px, 94vw);
}
/* CLAUDE-POST-SIGNIN-GATEWAY-SIMPLIFICATION-01, Addendum G: File > Open
   Project's own lightweight chooser - the same .workspace-menubar-
   subpanel popover idiom above, widened slightly (project names run
   longer than the other submenu items sharing that base rule) and
   capped in height so a reviewer with many accessible projects gets an
   internally-scrolling list, never a popover that grows off-screen. */
.workspace-open-project-panel {
    min-width: 260px;
    max-width: min(360px, 94vw);
}
.workspace-open-project-search {
    width: 100%;
    margin-bottom: 0.3rem;
    font-size: 0.85rem;
}
.workspace-open-project-list {
    list-style: none;
    margin: 0;
    padding: 0;
    max-height: min(320px, 60vh);
    overflow-y: auto;
}
.workspace-open-project-item {
    display: block;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
/* CLAUDE-CA1D-INSTRUMENT-RAIL-01: the "quiet global machine fact" line
   (Plan-Mode report, Section D.2) - deliberately NOT a peer of
   .workspace-topbar-btn above: no border/background at rest, matching
   the de-boxing discipline's "ending-line / side separation rather than
   top-and-bottom boxing" idiom already established for
   .conv-operational-actions .review-btn - a single underline appears on
   hover/focus, quiet metadata color at rest, same font-size as
   .doc-zoom-level's own plain status text. Only ever rendered for an
   admin-relevant anomaly (see base.html), so at rest this adds nothing
   to an already-busy top bar. */
.workspace-topbar-status {
    font-family: var(--font-body);
    font-size: 0.78rem;
    color: var(--text-metadata);
    text-decoration: none;
    border-bottom: 1px solid transparent;
    padding-bottom: 0.1rem;
}
.workspace-topbar-status:hover,
.workspace-topbar-status:focus-visible {
    color: var(--text-primary);
    border-bottom-color: currentColor;
}
/* The top-bar dropdown menus (Display Layout, Appearance, Account) are
   real interactive control surfaces, not decorative Display background
   - the same exemption Section E already grants the composer/focus
   states/warnings; a bordered popover here is functional chrome, not
   the thing Section E asks to de-tint. CLAUDE-P40-E3A: generalized from
   the old two-menu (.workspace-layout-menu/.workspace-topbar-overflow)
   set to three (.workspace-layout-menu/.workspace-appearance-menu/
   .workspace-user-menu), all sharing the same popover idiom. */
.workspace-layout-menu,
.workspace-appearance-menu,
.workspace-user-menu {
    position: relative;
}
.workspace-layout-options,
.workspace-appearance-options,
.workspace-user-options {
    position: absolute;
    top: calc(100% + 0.3rem);
    right: 0;
    /* CLAUDE-P40-VW6: a solid token color (never rgba/translucent) - the
       "text beneath the Appearance popup remains visible through it"
       report was, per this stage's own diagnosis, primarily the popup
       sitting BELOW the Display context menu's z-index (40) with no
       comfortable margin (was 20) rather than a transparency defect;
       raised well above every other overlay in this file (see the
       z-index audit note - nothing else on the page exceeds 40) so this
       menu reliably paints over Toolbox/Display/Lists content
       regardless of what else is open. */
    background: var(--surface-primary);
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: 0.4rem;
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    z-index: 60;
    /* CLAUDE-LEFT-RAIL-01: widened from 170px so "Project Data
       Management" (the longest item now relocated here) fits on one
       line without wrapping. */
    min-width: 220px;
}
/* CLAUDE-MENU-SUBMENU-FLYOUT-01: Appearance and Display Layout fly out to
   the RIGHT, like every other nested submenu.

   Both panels kept the geometry they had as TOP-LEVEL topbar dropdowns
   (top: calc(100% + 0.3rem); right: 0) when CLAUDE-APP-MENU-01 relocated
   them inside the Archiosk menu as nested <details>. A downward-anchored
   panel inside an already-open dropdown reads as the parent menu growing
   vertically, not as a submenu - the Product Owner's own report.

   Deliberately a separate, later rule rather than an edit to the shared
   3-selector group above: .workspace-user-options (Account) is still a
   real top-level topbar dropdown and its downward anchor is correct, and
   five tests assert that group's exact selector string. This overrides
   ONLY the anchoring; background/border/padding/flex/z-index still come
   from the shared rule, so the two idioms cannot drift apart visually.

   Values match .workspace-menubar-subpanel exactly - one flyout geometry
   for every nested submenu, not a second set of numbers. Scoped to
   `.workspace-menubar-submenu >` so either panel reverts to the dropdown
   anchor if it is ever hosted top-level again. The phone breakpoint
   flattens all three panel classes to position:static and wins on
   specificity, so the drawer is unaffected. */
.workspace-menubar-submenu > .workspace-appearance-options,
.workspace-menubar-submenu > .workspace-layout-options {
    top: 0;
    right: auto;
    left: calc(100% + 0.2rem);
}
/* CLAUDE-POSTCAMEL-PROJECT-CONTEXT-01: the Project Context control joins
   the SAME popover idiom as the three menus above, but is kept as its
   own separate rule (duplicating, not extending, the shared selector
   group above) rather than folding it into that exact list - two
   existing tests (tests/test_p40e3a_qa_reconciliation.py,
   tests/test_p40vw6_theme_correction.py) assert against that precise
   3-selector string verbatim (z-index/stacking and "never rgba"
   audits), and widening it to 4 would break both for a reason
   unrelated to what either test actually checks. Same properties,
   genuinely a duplicate rule, not a divergent one. */
.workspace-project-context-menu {
    position: relative;
}
.workspace-project-context-options {
    position: absolute;
    top: calc(100% + 0.3rem);
    right: 0;
    background: var(--surface-primary);
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: 0.4rem;
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    z-index: 60;
    min-width: 170px;
}
/* CLAUDE-P40-VW3: the Light/Dark/Tinted-per-surface matrix - real
   tabular data (row = surface, column = mode), not decoration, so a
   real <table> with row/col headers plus each radio's own aria-label
   (belt-and-braces: correct under table navigation AND under a plain
   linear read). Sits inside .workspace-appearance-options above, which
   already supplies this popover's own background/border/padding - this
   only lays out the grid itself, compact enough that 5 rows x 3 modes
   doesn't turn the popover into its own scrolling panel.

   CLAUDE-P40-VW8-QA (Approved Theme Set): a 4th column (Deep Forest)
   added two-word header text ("Midnight Blue") wide enough to grow the
   popover past the viewport on a narrow screen if left unconstrained -
   .workspace-appearance-options below now caps its own width, and
   header cells are left free to wrap (no white-space:nowrap - only the
   row-header/surface-name column has that, unchanged) so the table
   itself is squeezed by the popover's max-width rather than pushing it
   wider. */
.workspace-appearance-options {
    max-width: min(320px, 94vw);
}
/* CLAUDE-POSTCAMEL-PROJECT-CONTEXT-01: same shared popover shell as
   Display Layout/Appearance above, just wider (holds prose + a
   textarea, not a compact matrix) and capped in height with its own
   scroll - a long History list must never grow the popover off-screen. */
.workspace-project-context-options {
    max-width: min(360px, 94vw);
    max-height: min(480px, 80vh);
    overflow-y: auto;
}
.workspace-project-context-text {
    font-size: var(--text-sm);
    color: var(--text-primary);
    white-space: pre-wrap;
    margin: 0 0 0.2rem;
}
.workspace-project-context-subsection {
    border-top: 1px solid var(--border);
    padding-top: 0.4rem;
    margin-top: 0.3rem;
}
.workspace-project-context-subsection summary {
    cursor: pointer;
    font-size: var(--text-sm);
    color: var(--machine-blue);
}
.workspace-project-context-history {
    list-style: none;
    margin: 0.4rem 0 0;
    padding: 0;
    display: grid;
    gap: 0.5rem;
}
.workspace-project-context-history li {
    border-bottom: 1px solid var(--border);
    padding-bottom: 0.4rem;
    font-size: var(--text-sm);
}
.workspace-project-context-history li p:first-child { margin: 0 0 0.15rem; white-space: pre-wrap; }
/* CLAUDE-POSTCAMEL-PROJECT-CONTEXT-01: Document Context - a plain
   in-flow <details>, same idiom as the Files folder-row actions
   disclosure above, just not absolutely-positioned (this one sits
   directly in the Display document pane, not a narrow sidebar row). */
.document-context-panel { margin: 0.6rem 0; }
.document-context-panel summary { cursor: pointer; font-size: var(--text-sm); color: var(--machine-blue); }
.document-context-text { font-size: var(--text-sm); color: var(--text-primary); white-space: pre-wrap; margin: 0.3rem 0; }
/* Bounded GO QA/QC pass: Admin Document Mode - same restrained <details>
   shape as .document-context-panel above, admin-only. Gauge colors reuse
   tokens.css's own existing semantic accepted-green/attention-amber/
   failure-red trio (already used the same way by the MM6 relationship-
   river status badges) rather than minting new ones - Good=confirmed-
   like, Review=needs-attention-like, Weak=failure-like. */
.admin-document-mode-panel { margin: 0.6rem 0; }
.admin-document-mode-panel summary { cursor: pointer; font-size: var(--text-sm); color: var(--machine-blue); }
.admin-qac-gauge { font-size: var(--text-sm); margin: 0.3rem 0; }
.admin-qac-gauge-good strong { color: var(--accepted-green); }
.admin-qac-gauge-review strong { color: var(--attention-amber); }
.admin-qac-gauge-weak strong { color: var(--failure-red); }
.admin-qac-cause { font-size: var(--text-xs); color: var(--attention-amber); margin: 0.2rem 0 0.5rem; }
.admin-qac-claims-list { list-style: none; margin: 0.5rem 0 0; padding: 0; display: flex; flex-direction: column; gap: 0.5rem; }
.admin-qac-claim { border-top: 1px solid var(--border); padding-top: 0.4rem; }
.admin-qac-claim-field { font-size: var(--text-sm); font-weight: 600; margin: 0 0 0.2rem; }
.admin-qac-claim-statement { font-size: var(--text-sm); color: var(--text-primary); margin: 0; }
.admin-qac-phase-outcome { font-size: var(--text-sm); margin: 0 0 0.4rem; }
/* CLAUDE-APPEARANCE-SIMPLIFY-01: retires the old .appearance-matrix
   table (per-surface Menu/Lists/Display/Toolbox/Chat rows, a "mixed"
   state note) entirely - Appearance is now ONE global choice, so there
   is nothing left to disagree and nothing to display as "mixed."
   .appearance-global-options reuses the same plain-list, no-card
   density every other compact popover control in this file already
   uses (see .operating-environment-option). */
.appearance-global-options {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}
.appearance-global-option {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-size: var(--text-sm);
    cursor: pointer;
}
.appearance-global-option input[type="radio"] {
    margin: 0;
    cursor: pointer;
}
/* Standard visually-hidden pattern: present to assistive tech and
   keyboard navigation, removed from normal visual flow only - never
   `display: none`, which would also remove it from the accessibility
   tree. */
.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}
.workspace-overflow-item {
    font-family: var(--font-body);
    font-size: var(--text-sm);
    color: var(--text-primary);
    background: none;
    border: none;
    text-align: left;
    padding: 0.35rem 0.5rem;
    border-radius: 3px;
    cursor: pointer;
    text-decoration: none;
    display: block;
    width: 100%;
}
.workspace-overflow-item:hover {
    background: var(--surface-hover);
}
/* CLAUDE-LEFT-RAIL-01: a quiet, non-interactive section label - the
   same restrained "structural landmark, not a second control" idiom
   .current-project's own border-only treatment already established
   (no fill, no new accent color), distinguishing the relocated
   Admin-only items from the account-level items below them without
   adding a divider rule or any decorative chrome. */
.workspace-overflow-label {
    font-family: var(--font-body);
    font-size: var(--text-xs);
    font-weight: 600;
    color: var(--text-secondary);
    text-transform: uppercase;
    letter-spacing: 0.03em;
    padding: 0.35rem 0.5rem 0.15rem;
}
/* CLAUDE-P40-VW7A: the UI Reference Mode toggle is a <label> wrapping a
   checkbox, not a plain <a>/<button> like its .workspace-overflow-item
   siblings - this override keeps it visually consistent with them
   (same padding/hover/radius) while letting the checkbox sit inline. */
.workspace-overflow-checkbox {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    cursor: pointer;
}
.workspace-overflow-checkbox input { margin: 0; }
.workspace-user-name {
    font-weight: 600;
    margin-right: 0.2rem;
}

/* Display Layout menu internals (CLAUDE-P40-E3A, Section 6; CLAUDE-P40-
   VW4 replaced the old either/or orientation choice with two independent
   numeric steppers, one per axis - the old .workspace-layout-orientation/
   .workspace-layout-option radiogroup this comment used to describe is
   retired, no markup references it anymore): a numeric stepper (never a
   bare text field), Apply - nothing here re-renders on every keystroke,
   only on the explicit Apply click. The same controls are reused inside
   the per-division right-click context menu (#display-context-menu)
   below. */
.workspace-layout-quantity-label {
    display: block;
    font-size: var(--text-xs);
    color: var(--text-metadata);
    margin-bottom: 0.15rem;
}
.workspace-layout-limit-note {
    font-size: var(--text-xs);
    color: var(--text-metadata);
    margin: 0.3rem 0;
}
.workspace-layout-quantity {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.6rem;
    padding: 0.3rem 0;
}
.workspace-layout-stepper {
    font-family: var(--font-body);
    font-size: 0.95rem;
    width: 1.8rem;
    height: 1.8rem;
    border: 1px solid var(--border);
    border-radius: 3px;
    background: var(--surface-primary);
    color: var(--text-primary);
    cursor: pointer;
}
.workspace-layout-stepper:hover { background: var(--surface-hover); border-color: var(--border-strong); }
/* CLAUDE-P40-VW4, Requirement 5/6: the relevant stepper is disabled the
   moment incrementing it would exceed six Displays (or decrementing
   below one) - token-based, not a hardcoded gray, so it stays legible
   under every VW3 appearance mode. */
.workspace-layout-stepper:disabled {
    color: var(--text-disabled);
    border-color: var(--border);
    background: var(--surface-primary);
    cursor: not-allowed;
}
.workspace-layout-stepper:disabled:hover { background: var(--surface-primary); border-color: var(--border); }
.workspace-layout-quantity-value {
    font-family: var(--font-body);
    font-variant-numeric: tabular-nums;
    font-size: 0.9rem;
    min-width: 1.2rem;
    text-align: center;
}
.workspace-layout-apply {
    font-family: var(--font-body);
    font-size: var(--text-sm);
    width: 100%;
    padding: 0.4rem 0.5rem;
    margin-top: 0.2rem;
    border: 1px solid var(--border-strong);
    border-radius: 3px;
    background: transparent;
    color: var(--text-primary);
    cursor: pointer;
}
.workspace-layout-apply:hover { background: var(--surface-hover); }

/* Appearance menu (Section 10): plain checkbox rows, one per region -
   applies the SAME existing plain/tinted presentation choice
   independently to Lists/Display/Toolbox/Chat, never a new color. */
.workspace-appearance-option {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-family: var(--font-body);
    font-size: var(--text-sm);
    color: var(--text-primary);
    padding: 0.3rem 0.5rem;
    cursor: pointer;
}

@media (max-width: 640px) {
    .workspace-topbar-context { max-width: 40vw; }
}

/* ============================================================
   Workspace — Lists | Display | Toolbox, Chat full-width beneath
   ============================================================ */
/* CLAUDE-P40-E3A: the old .case-workspace grid (Display|Toolbox columns,
   Chat confined beneath Display only) is retired. Lists, Display and
   Toolbox are now three flex siblings inside base.html's own
   .app-shell-body row (.launcher-panel/.app-main/.workspace-pane-toolbox
   - see that rule above); Chat is a fourth, full-width flex row
   beneath .app-shell-body, inside .app-shell itself (Section 9). Flexbox
   rather than a named-area grid was chosen specifically because it makes
   "closing a Display causes the remaining Displays to expand" (Section
   6) and "Toolbox collapsing lets Display expand into the released
   space" (Section 7) automatic consequences of flex:1 on .app-main and
   display:none on a hidden sibling, rather than something that needs a
   grid-template-areas rewrite on every state change.

   Geometry reasoned from CSS's specified behavior, not confirmed against
   a real rendered browser - no browser/screenshot tool exists in this
   environment. Treat as reasoned, not proven, until someone resizes a
   real window through each state (see this stage's own completion
   report for the browser-validation limitation this carries forward). */
/* CLAUDE-P40-EYE1, Section 3: was html.toolbox-hidden .workspace-pane-
   toolbox - now targets .workspace-right-column, the new full-height
   column containing BOTH Toolbox and Eye, so hiding it removes both
   panes AND the divider between them (a descendant, no separate rule
   needed) with no leftover empty panel/gutter/reserved width, exactly
   the same display:none mechanism as before, just retargeted to the
   new outer column. */
html.toolbox-hidden .workspace-right-column {
    display: none;
}
/* CLAUDE-P40-E2B, Section E, carried forward: an explicit plain/neutral
   surface (--surface-primary, near-white), deliberately NOT the app's
   own warmer --canvas background - so an embedded Document's own
   natural white page background sits flush against Display without a
   visible color seam. No border/shadow/radius here - a plain fill only.
   CLAUDE-P40-E3A, Section 10: this IS "Default Display is plain white" -
   nothing to change here, --surface-primary already was and remains the
   untinted default; .appearance-tinted (below) is the only thing that
   ever swaps it. */
.workspace-pane-display {
    min-width: 0;
    min-height: 100%;
    flex: 1;
    display: flex;
    flex-direction: column;
    background: var(--surface-primary);
}
.app-main.appearance-tinted .workspace-pane-display,
.app-main.appearance-dark .workspace-pane-display {
    background: var(--surface-primary);
    color: var(--text-primary);
}

/* CLAUDE-P40-E3A, Section 6; CLAUDE-P40-VW4 replaced the single either/
   or orientation + one shared quantity with a genuine two-axis grid -
   Vertical divisions (--display-v, columns) and Horizontal divisions
   (--display-h, rows), independently 1-6, applied via Apply (never a
   fixed preset menu, the old [data-layout="single/side-by-side/stacked/
   grid"] system this already replaced once). A true V x H rectangle has
   14 valid combinations with V*H<=6 (not a linear 1-6 range), too many
   to enumerate as static [data-orientation][data-count] attribute
   selectors the way the single-axis version did - static/js/
   case_workspace.js's own applyLayout sets --display-v/--display-h as
   inline custom properties instead, and grid-template-columns/rows
   below reads them directly. Mobile-first: the base rule always stacks
   divisions in one column regardless of the applied V/H - the real
   multi-column/multi-row geometry only applies above the 900px
   breakpoint further down, where there's room for it (Section 11:
   Display remains usable, never forced into unreadably narrow slices).
   Divisions are separated only by a restrained 1px divider line
   (Section 5 - "no cards, shadows, rounded containers"), never a
   bordered/shadowed box per division - a `gap` the same color as
   --border, with each division painting its own --surface-primary
   background over it, is the simplest way to get a real hairline on
   BOTH axes of a true 2D grid (the old border-swaps-side-per-
   orientation technique didn't generalize past one axis). */
.display-divisions {
    display: grid;
    grid-template-columns: 1fr;
    /* CLAUDE-P40-VW8-QA: an explicit row template even at the base
       (mobile-first, single-column) width, not just at the 900px+
       breakpoint below - a 1fr row track only ever distributes real
       space if the GRID CONTAINER itself has a defined height to
       divide among them, which flex:1/min-height:100% (this rule)
       plus .workspace-pane-display's own flex:1 (above) now provide.
       Without a real height here, "1fr" silently degrades to
       content's own natural size regardless of what the row template
       says - the actual root cause of the "shallow white rectangle"/
       "small patches" Display-division defects, not a missing grid
       declaration on its own. */
    grid-template-rows: repeat(var(--display-h, 1), 1fr);
    grid-auto-flow: row;
    gap: 1px;
    background: var(--border);
    flex: 1;
    min-height: 100%;
}
.display-division {
    min-width: 0;
    min-height: 0;
    display: flex;
    flex-direction: column;
    background: var(--surface-primary);
    /* Uniform on every division, including index 0 - a per-edge "no
       padding on whichever side touches the pane's own edge" rule
       doesn't generalize cleanly across an arbitrary V x H grid (which
       divisions sit on the top/left edge depends on both axes at once,
       not just "is this the first one"), and VW4's own scope is the
       Vertical/Horizontal split itself, not a pixel-level padding
       reconciliation. */
    padding: 1.25rem;
}
/* Only the first [data-count] divisions (always 0..count-1, in document
   order) are ever shown - divisions beyond the applied count stay in
   the DOM (their picker/content state is never lost) but are not
   displayed, same as the old [data-layout="single"] behaviour.
   [data-count] is still vertical*horizontal (set alongside --display-v/
   --display-h by the same applyLayout call) - this show/hide table
   itself is unchanged from before VW4, since which INDIVIDUAL divisions
   are visible only ever depended on the total, never on the
   arrangement. */
.display-divisions[data-count="1"] [data-division]:not([data-division="0"]) { display: none; }
.display-divisions[data-count="2"] [data-division="2"],
.display-divisions[data-count="2"] [data-division="3"],
.display-divisions[data-count="2"] [data-division="4"],
.display-divisions[data-count="2"] [data-division="5"] { display: none; }
.display-divisions[data-count="3"] [data-division="3"],
.display-divisions[data-count="3"] [data-division="4"],
.display-divisions[data-count="3"] [data-division="5"] { display: none; }
.display-divisions[data-count="4"] [data-division="4"],
.display-divisions[data-count="4"] [data-division="5"] { display: none; }
.display-divisions[data-count="5"] [data-division="5"] { display: none; }

@media (min-width: 900px) {
    .display-divisions {
        grid-template-columns: repeat(var(--display-v, 1), 1fr);
        grid-template-rows: repeat(var(--display-h, 1), 1fr);
    }
}

.display-division-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    margin-bottom: 0.5rem;
    font-size: 0.78rem;
    color: var(--text-metadata);
    /* CLAUDE-P40-VW8-QA: .display-division is now a flex column (header
       + empty-state/content) - the header keeps its own natural size,
       never stretched or shrunk, so .display-division-content/.
       display-division-empty (both flex:1 below) get the entire
       remaining, real height to fill. */
    flex-shrink: 0;
}
.display-division-header-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* CLAUDE-P40-E3A, Section 6: "one Display is always the active target" -
   a dashed outline (functional state, not decorative framing - the same
   exemption already granted to focus rings/selected nav rows elsewhere
   in this file) marks which division the next Lists Document click will
   populate. Division 0 has no such outline: it's the default target and
   is never ambiguous about that. */
.display-division.active:not(.display-division-primary) {
    outline: 1px dashed var(--machine-blue);
    outline-offset: 4px;
}
.display-division.active .display-division-header-name {
    color: var(--machine-blue);
    font-weight: 600;
}
.display-division-close {
    font-family: var(--font-body);
    font-size: 0.9rem;
    color: var(--text-secondary);
    background: none;
    border: none;
    cursor: pointer;
    flex-shrink: 0;
    line-height: 1;
}
.display-division-close:hover { color: var(--failure-red); }

/* CLAUDE-P40-DTAB1: the Document tab strip - Documents only (Section 2's
   own scope), Division-0-only, a persistent utility above whatever
   content is currently in Display. Compact: no unnecessary height,
   .document-tab-list is the one scroll region (horizontal overflow,
   never wraps/shrinks labels below a readable width), .document-tabs-
   overflow (the "All Tabs" control) stays fixed at the strip's own
   trailing edge regardless of scroll position. */
.document-tab-strip {
    display: flex;
    align-items: flex-end;
    gap: 0.3rem;
    margin-bottom: 0.6rem;
    border-bottom: 1px solid var(--border);
    flex-shrink: 0;
    min-width: 0;
}
.document-tab-strip[hidden] { display: none; }
.document-tab-list {
    display: flex;
    align-items: flex-end;
    gap: 0.2rem;
    flex: 1 1 auto;
    min-width: 0;
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-color: transparent transparent;
}
/* CLAUDE-PANEL-CALM-02: this rail's own WebKit scrollbar rules moved
   into the shared, hover-gated block further below (with .attention-
   strip-list, which uses the identical pattern) - see that block's own
   comment. */
/* CLAUDE-P40-DTAB1, Section 3/13: a real, keyboard-operable tab, never a
   decorative div - color (data-tab-color, below) is an accent stripe
   only; the ACTIVE tab's own non-color cues (background, bottom-border
   weight/color, font-weight) are what Section 8 requires ("must also
   use shape/underline/border/weight... not color alone"). */
.document-tab {
    display: flex;
    align-items: center;
    gap: 0.3rem;
    max-width: 190px;
    min-width: 96px;
    padding: 0.4rem 0.55rem;
    border: 1px solid transparent;
    border-bottom: none;
    border-radius: 4px 4px 0 0;
    background: none;
    color: var(--text-secondary);
    font-family: var(--font-body);
    font-size: 0.8rem;
    text-decoration: none;
    cursor: pointer;
    flex-shrink: 0;
    position: relative;
    /* the curated color accent - a 3px top stripe, present only when
       data-tab-color is set (the CSS var is undefined -> no visible
       border otherwise, no JS branching needed to omit it). */
    border-top: 3px solid var(--document-tab-accent, transparent);
}
.document-tab:hover {
    background: var(--surface-hover);
    color: var(--text-primary);
    text-decoration: none;
}
.document-tab:focus-visible {
    outline: 2px solid var(--machine-blue);
    outline-offset: -2px;
}
.document-tab[aria-selected="true"] {
    background: var(--surface-primary);
    color: var(--text-primary);
    font-weight: 600;
    border-color: var(--border);
    border-bottom: 2px solid var(--machine-blue);
    margin-bottom: -1px;
}
.document-tab[data-tab-color="gold"] { --document-tab-accent: var(--tabcolor-gold); }
.document-tab[data-tab-color="turquoise"] { --document-tab-accent: var(--tabcolor-turquoise); }
.document-tab[data-tab-color="lapis"] { --document-tab-accent: var(--tabcolor-lapis); }
.document-tab[data-tab-color="terracotta"] { --document-tab-accent: var(--tabcolor-terracotta); }
.document-tab[data-tab-color="green"] { --document-tab-accent: var(--tabcolor-green); }
.document-tab[data-tab-color="purple"] { --document-tab-accent: var(--tabcolor-purple); }
.document-tab-label {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
}
/* CLAUDE-P40-DTAB1, Section 4: "distinguish the temporary preview tab
   visually without relying on color alone" - italic label text + a
   dashed (not solid) selected-state underline together, neither of
   which is a color cue. */
.document-tab.document-tab-preview .document-tab-label { font-style: italic; }
.document-tab.document-tab-preview[aria-selected="true"] { border-bottom-style: dashed; }
.document-tab-close {
    flex-shrink: 0;
    background: none;
    border: none;
    color: var(--text-metadata);
    cursor: pointer;
    font-size: 0.85rem;
    line-height: 1;
    padding: 0.15rem 0.3rem;
    border-radius: 3px;
}
.document-tab-close:hover { background: var(--surface-hover); color: var(--failure-red); }
.document-tab-close:focus-visible {
    outline: 2px solid var(--machine-blue);
    outline-offset: 1px;
}
.document-tab-menu-btn {
    flex-shrink: 0;
    background: none;
    border: none;
    color: var(--text-metadata);
    cursor: pointer;
    font-size: 0.7rem;
    line-height: 1;
    padding: 0.15rem 0.2rem;
    border-radius: 3px;
}
.document-tab-menu-btn:hover { background: var(--surface-hover); color: var(--text-primary); }
.document-tab-menu-btn:focus-visible {
    outline: 2px solid var(--machine-blue);
    outline-offset: 1px;
}
/* The per-tab context menu and the All-Tabs/Hidden-Tabs overflow panel
   share the same restrained popover idiom the top-bar menus already
   established (.workspace-layout-options etc.) - a bordered, real
   interactive control surface, not decorative Display background. */
.document-tab-menu,
.document-tabs-overflow-panel {
    position: absolute;
    z-index: 40;
    background: var(--surface-primary);
    border: 1px solid var(--border-strong);
    border-radius: 6px;
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.22);
    padding: 0.4rem;
    min-width: 180px;
    font-size: var(--text-sm);
}
.document-tab-menu-item,
.document-tab-color-option,
.document-hidden-tab-item {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    width: 100%;
    text-align: left;
    background: none;
    border: none;
    color: var(--text-primary);
    font-family: var(--font-body);
    font-size: var(--text-sm);
    padding: 0.35rem 0.5rem;
    border-radius: 4px;
    cursor: pointer;
}
.document-tab-menu-item:hover,
.document-tab-color-option:hover,
.document-hidden-tab-item:hover,
.document-tab-menu-item:focus-visible,
.document-tab-color-option:focus-visible,
.document-hidden-tab-item:focus-visible {
    background: var(--surface-hover);
}
.document-tab-color-swatch {
    display: inline-block;
    width: 0.75rem;
    height: 0.75rem;
    border-radius: 50%;
    border: 1px solid var(--border-strong);
    flex-shrink: 0;
}
.document-tab-color-swatch[data-tab-color="gold"] { background: var(--tabcolor-gold); }
.document-tab-color-swatch[data-tab-color="turquoise"] { background: var(--tabcolor-turquoise); }
.document-tab-color-swatch[data-tab-color="lapis"] { background: var(--tabcolor-lapis); }
.document-tab-color-swatch[data-tab-color="terracotta"] { background: var(--tabcolor-terracotta); }
.document-tab-color-swatch[data-tab-color="green"] { background: var(--tabcolor-green); }
.document-tab-color-swatch[data-tab-color="purple"] { background: var(--tabcolor-purple); }
.document-tab-color-swatch[data-tab-color="none"] { background: transparent; }
.document-tab-menu-divider {
    height: 1px;
    background: var(--border);
    margin: 0.3rem 0.2rem;
}
.document-tabs-overflow { position: relative; }
.document-tabs-overflow-summary {
    list-style: none;
    background: none;
    border: 1px solid var(--border);
    border-radius: 4px;
    color: var(--text-secondary);
    cursor: pointer;
    padding: 0.3rem 0.45rem;
    flex-shrink: 0;
    font-size: 0.75rem;
}
.document-tabs-overflow-summary::-webkit-details-marker { display: none; }
.document-tabs-overflow-summary:hover,
.document-tabs-overflow[open] .document-tabs-overflow-summary {
    color: var(--text-primary);
    border-color: var(--border-strong);
}
.document-tabs-overflow-panel {
    top: calc(100% + 0.3rem);
    right: 0;
    max-height: 60vh;
    overflow-y: auto;
    scrollbar-color: transparent transparent;
}
.document-hidden-tabs-heading {
    font-family: var(--font-body);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    font-size: 0.7rem;
    color: var(--text-metadata);
    padding: 0.3rem 0.5rem 0.1rem;
}
.document-hidden-tab-item-original-name {
    color: var(--text-metadata);
    font-size: 0.72rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.document-tabs-empty-note {
    color: var(--text-metadata);
    font-size: 0.78rem;
    padding: 0.35rem 0.5rem;
}

/* CLAUDE-P40-VW7B, Section 8: the Investigation Attention strip - same
   compact, restrained tab-strip idiom as .document-tab-strip
   immediately above (reused deliberately, "do not introduce a
   separate visual language" - Section 13), a genuinely distinct
   control family (Investigations, not Documents) rather than a shared
   selector, since the two are never meant to merge or be
   interchangeable. Hidden entirely when attention is empty - "compact,
   visually restrained," never a permanent empty bar. */
.attention-strip {
    display: flex;
    align-items: flex-end;
    gap: 0.3rem;
    margin-bottom: 0.6rem;
    border-bottom: 1px solid var(--border);
    flex-shrink: 0;
    min-width: 0;
}
.attention-strip[hidden] { display: none; }
.attention-strip-list {
    display: flex;
    align-items: flex-end;
    gap: 0.2rem;
    flex: 1 1 auto;
    min-width: 0;
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-color: transparent transparent;
}
/* CLAUDE-PANEL-CALM-02: this rail's own WebKit scrollbar rules moved
   into the shared, hover-gated block further below (with .document-
   tab-list, which uses the identical pattern) - see that block's own
   comment. */
/* CLAUDE-INVESTIGATION-ATTENTION-02: deliberately NOT position:fixed/
   role=dialog like .attention-capacity-dialog below - this sits in
   normal document flow, right where the attention strip would have
   shown a fifth position, and never steals focus or blocks the page
   underneath it. Neutral/informational, not --failure-red or
   --attention-amber (tokens.css's own grammar: those mean a real
   contradiction/pending-evidence state, and "you have more saved work
   than fits in Attention" is neither). */
.attention-overflow-notice {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.6rem;
    font-size: var(--text-sm);
    color: var(--text-secondary);
    background: var(--surface-secondary);
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 0.5rem 0.7rem;
    margin-bottom: 0.6rem;
}
.attention-overflow-notice[hidden] { display: none; }
.attention-overflow-notice #attention-overflow-notice-text { flex: 1 1 auto; }
.attention-overflow-notice .btn { padding: 0.3rem 0.6rem; font-size: var(--text-xs); flex-shrink: 0; }
.attention-overflow-notice-dismiss {
    background: transparent;
    border: none;
    color: var(--text-metadata);
    font-size: 1rem;
    line-height: 1;
    padding: 0.2rem 0.4rem;
    cursor: pointer;
    flex-shrink: 0;
}
/* Section 6: "selected-page indication that does not rely on color
   alone" applied here too - the focused position's real, visible
   "Focused" text tag (rendered by static/js/investigation_attention.js)
   is the non-color cue; background/border-color below are the
   additional, non-exclusive color cue on top of it. */
.attention-position {
    display: flex;
    align-items: center;
    gap: 0.3rem;
    max-width: 210px;
    min-width: 110px;
    padding: 0.4rem 0.55rem;
    border: 1px solid transparent;
    border-bottom: none;
    border-radius: 4px 4px 0 0;
    background: none;
    color: var(--text-secondary);
    font-family: var(--font-body);
    font-size: 0.8rem;
    text-decoration: none;
    cursor: pointer;
    flex-shrink: 0;
    position: relative;
}
.attention-position:hover {
    background: var(--surface-hover);
    color: var(--text-primary);
    text-decoration: none;
}
.attention-position:focus-visible {
    outline: 2px solid var(--machine-blue);
    outline-offset: -2px;
}
.attention-position-focused {
    background: var(--surface-primary);
    color: var(--text-primary);
    font-weight: 600;
    border-color: var(--border);
    border-bottom: 2px solid var(--machine-blue);
    margin-bottom: -1px;
}
.attention-position-label {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
}
.attention-position-focused-tag {
    flex-shrink: 0;
    font-size: 0.7rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--machine-blue);
}
/* Real text ("Archived"), not a color-only dot - a frozen Investigation
   still held in attention must read as such without relying on the
   reviewer noticing a color change. */
.attention-position-archived-tag {
    flex-shrink: 0;
    font-size: 0.7rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-metadata);
}
.attention-position-release {
    flex-shrink: 0;
    background: none;
    border: none;
    color: var(--text-metadata);
    cursor: pointer;
    font-size: 0.85rem;
    line-height: 1;
    padding: 0.15rem 0.3rem;
    border-radius: 3px;
}
.attention-position-release:hover { background: var(--surface-hover); color: var(--failure-red); }
.attention-position-release:focus-visible {
    outline: 2px solid var(--machine-blue);
    outline-offset: 1px;
}

/* CLAUDE-P40-VW7B, Section 9: the fifth-Investigation capacity dialog's
   own list of currently-attended positions - reuses the SAME
   .attention-capacity-dialog* family (itself the renamed, reused
   CLAUDE-P40-VW8 Project-switching dialog CSS - see that rule's own
   comment) for the dialog shell; this is only the per-position row
   inside .attention-capacity-dialog-list. */
.attention-capacity-dialog-list {
    list-style: none;
    margin: 0 0 0.7rem;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    max-height: 40vh;
    overflow-y: auto;
}
.attention-capacity-dialog-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    padding: 0.4rem 0.5rem;
    background: var(--surface-secondary);
    border-radius: 4px;
}
.attention-capacity-dialog-item-name {
    font-size: var(--text-sm);
    color: var(--text-primary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
}
.attention-capacity-dialog-item .btn { padding: 0.3rem 0.6rem; font-size: 0.78rem; flex-shrink: 0; }
.attention-capacity-conclude-form { display: none; }

/* CLAUDE-P40-E3A, Section 6: the shared right-click context menu -
   repositioned (left/top set inline by JS) to whichever division was
   clicked, the same popover idiom as the top-bar menus above. */
.display-context-menu {
    position: fixed;
    background: var(--surface-primary);
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: 0.4rem;
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    z-index: 40;
    min-width: 190px;
}
/* CLAUDE-P40-VW1: an author-origin class selector of equal specificity
   to the UA stylesheet's `[hidden] { display: none }` always wins the
   cascade regardless of source order, so `display: flex` above silently
   defeated the JS-toggled `hidden` attribute and left the menu
   permanently visible. The top-bar menus (.workspace-layout-menu etc.)
   never hit this because they're native <details> disclosure widgets,
   not a plain hidden-attribute toggle. */
.display-context-menu[hidden] {
    display: none;
}
.display-context-item {
    font-family: var(--font-body);
    font-size: var(--text-sm);
    color: var(--text-primary);
    background: none;
    border: none;
    text-align: left;
    padding: 0.35rem 0.5rem;
    border-radius: 3px;
    cursor: pointer;
    display: block;
    width: 100%;
}
.display-context-item:hover { background: var(--surface-hover); }
.display-context-divide {
    padding: 0.3rem 0.5rem;
    border-top: 1px solid var(--border);
    margin-top: 0.2rem;
    padding-top: 0.5rem;
}

/* CLAUDE-P40-VW8-QA, Section 9: "empty-state content is positioned
   coherently inside the division" - centered within the division's
   own real, now-full-height area (flex:1 on the empty state itself,
   inside .display-division's own flex column) rather than left
   stranded at the top of a tall, mostly-blank box. */
.display-division-empty {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    gap: 0.4rem;
    padding: 1.5rem 0;
    color: var(--text-secondary);
    font-size: 0.85rem;
}
.display-division-picker {
    width: 100%;
    max-width: 320px;
    margin-top: 0.5rem;
}
.display-division.display-division-populated .display-division-empty { display: none; }
/* CLAUDE-P40-VW8-QA: fills the division's real available height
   (flex:1 within .display-division's own column) instead of shrink-
   wrapping to nothing when empty, or a content-blind height:50vh
   guess when populated - both were instances of the same "shallow
   rectangle" root cause the container-level fix above addresses. */
.display-division-content {
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
}
.display-division-content iframe,
.display-division-content img {
    width: 100%;
    border: 1px solid var(--border);
}
.display-division-content iframe { flex: 1; min-height: 0; height: 100%; background: var(--surface-primary); }
.display-division-content img { max-width: 100%; max-height: 100%; display: block; margin: 0 auto; object-fit: contain; }

/* CLAUDE-P40-EYE1, Section 1: the full-height right column itself -
   Toolbox (upper) and Eye (lower), mirroring .launcher-panel's own role
   on the left (CLAUDE-P40-VW7A-QA2's Lists/Thumbnails split). Owns the
   width/background/flex-shrink that used to live directly on
   .workspace-pane-toolbox (below) - a sibling of .launcher-panel AND
   .workspace-main-column inside .app-shell-body, so it spans the SAME
   full vertical extent Chat's row occupies (never stopping at the
   Display/Chat divider - Section 1's own explicit requirement), the
   same "one shared painted background, transparent panes on top of it"
   split .lists-pane/.thumbnails-pane already established. */
/* CLAUDE-P40-EYE1 (product-owner browser correction): width is now a
   real, draggable dimension (--right-column-width, set by the new
   toolbox-divider drag logic in base.html), not a fixed value - the
   min(340px, 30vw) default becomes the STARTING value instead
   (fallback for the var()), so an unvisited browser/reset state still
   renders identically to before this correction. */
.workspace-right-column {
    width: var(--right-column-width, min(340px, 30vw));
    flex-shrink: 0;
    display: flex;
    flex-direction: column;
    height: 100%;
    overflow: hidden;
    background: var(--surface-primary);
}
/* CLAUDE-P40-E2: the Toolbox itself - a flex stack of .workspace-pane
   sections (Investigation/Document/Project tools, contextual; Removed
   Items and admin-only Project Data Management, always visible - see
   case_workspace.html), not a single pane of its own.
   CLAUDE-P40-EYE1: no longer owns width/flex-shrink/background of its
   own (moved to .workspace-right-column, above) - just the upper pane
   inside that column, sized by --toolbox-height (the Toolbox/Eye
   divider's own live value) and independently scrollable, the same
   "outer column owns sizing, inner pane owns its own scroll" split
   .lists-pane already established. */
.workspace-pane-toolbox {
    flex: 0 0 var(--toolbox-height, 60%);
    display: flex;
    flex-direction: column;
    gap: 1rem;
    min-width: 0;
    min-height: 0;
    overflow-y: auto;
    scrollbar-color: transparent transparent;
    /* CLAUDE-P40-EYE1: positioning context for .toolbox-maximize-btn
       (below), which floats over this pane's own top-right corner
       rather than a dedicated header row. */
    position: relative;
    padding-top: 2.2rem;
}
/* CLAUDE-P40-EYE1, Section 2: "clear collapse and restore controls" for
   Toolbox - floats rather than a new header row, since case_workspace.
   html's own {% block toolbox %} already renders a real Toolbox
   heading as its first child; padding-top on the pane itself (above)
   reserves room so this never overlaps that heading's own text. */
.toolbox-maximize-btn {
    position: absolute;
    top: 0.4rem;
    right: 0.4rem;
    background: none;
    border: none;
    cursor: pointer;
    color: var(--text-secondary);
    font-family: var(--font-body);
    font-size: 0.72rem;
    padding: 0.2rem 0.4rem;
    border-radius: 4px;
    z-index: 2;
}
.toolbox-maximize-btn:hover, .toolbox-maximize-btn:focus-visible {
    background: var(--surface-hover);
    color: var(--text-primary);
}
.toolbox-maximize-btn[aria-pressed="true"] { color: var(--machine-blue); }
/* CLAUDE-EYE-COMPARE-01: same floating-over-the-pane-corner treatment
   as .toolbox-maximize-btn (immediately above) - offset further left so
   the two compact controls sit side by side without overlapping, both
   inside the padding-top:2.2rem the pane already reserves. */
.toolbox-compare-btn {
    position: absolute;
    top: 0.4rem;
    right: 2.6rem;
    background: none;
    border: 1px solid var(--border);
    cursor: pointer;
    color: var(--text-secondary);
    font-family: var(--font-body);
    font-size: 0.72rem;
    padding: 0.2rem 0.5rem;
    border-radius: 4px;
    z-index: 2;
}
.toolbox-compare-btn:hover, .toolbox-compare-btn:focus-visible {
    background: var(--surface-hover);
    color: var(--text-primary);
}
.toolbox-compare-btn[aria-pressed="true"] {
    color: var(--machine-blue);
    border-color: var(--machine-blue);
}
/* CLAUDE-P40-VW6: the old .workspace-pane-toolbox.appearance-tinted
   single-property rule that used to live here is gone - Toolbox's own
   root used to BE its painted surface (unlike Display/Chat, which paint
   a descendant), so the combined .appearance-tinted/.appearance-dark
   rule below (near .chat-region's own rules) already covered it
   directly via its own background/color declarations, the same way
   .appearance-dark always did.
   CLAUDE-P40-EYE1: that combined rule's own selector now names
   .workspace-right-column instead - the new full-height right column
   (Toolbox above Eye) is the painted root today, so BOTH panes inherit
   the theme via ordinary CSS custom-property cascade; .workspace-pane-
   toolbox itself paints nothing of its own anymore (mirrors .lists-pane
   inside .launcher-panel). */
.workspace-pane-toolbox > h2 {
    font-family: var(--font-display);
    font-size: 0.95rem;
    margin: 0;
    color: var(--text-primary);
}

/* Narrow: Toolbox becomes a real overlay drawer rather than a
   permanent column competing with Display for width - Display is the
   one thing always on screen without having to scroll past it. The
   same toolbox-divider/html.toolbox-hidden class drives this (Section
   7) - a drawer is just what "shown" looks like at this width, not a
   second mechanism. Closing is via the same divider (already reachable,
   already keyboard-operable) or Escape (base.html's own script) -
   deliberately no separate click-outside-to-close backdrop element,
   which would need its own focus-trap/hit-testing machinery this pass
   doesn't otherwise need. The Launcher panel's own equivalent narrow-
   screen drawer lives in base.html's own media query, alongside the
   panel itself - "only one overlay should obstruct Display at a time"
   (Section 11) is enforced in that script, not here. */
/* CLAUDE-P40-EYE1: was .workspace-pane-toolbox - now the WHOLE right
   column becomes the overlay drawer (Toolbox and Eye both, stacked
   exactly as they are at desktop widths via the base .workspace-right-
   column rule's own display:flex/flex-direction:column, unchanged
   here), not Toolbox alone. .workspace-pane-toolbox's own padding:1rem
   moves to this element too - it used to double as both the drawer's
   outer padding AND (implicitly) Toolbox's own content inset, since
   Toolbox was the only thing in it; now .eye-pane needs the same
   drawer-edge breathing room, so the padding lives one level up. */
@media (max-width: 640px) {
    .workspace-right-column {
        position: fixed;
        top: 0;
        bottom: 0;
        right: 0;
        width: min(85vw, 320px);
        z-index: 30;
        padding: 1rem;
        border: 1px solid var(--border);
    }
}

/* CLAUDE-P40-EYE1, Section 2: draggable divider between Toolbox and
   Eye - same visual/interaction grammar as .lists-thumbnails-divider
   (CLAUDE-P40-VW7A-QA2): quiet resting line (var(--border), a
   descendant of .workspace-right-column so it inherits that column's
   own theme directly, never the shell fallback), --machine-blue accent
   on hover/focus/active-drag only, transparent hit-target background
   inheriting the column's own (no white/beige gutter in dark modes),
   thin line with a comfortable invisible hit target. Never [hidden] -
   unlike Lists/Thumbnails, Eye is a permanent pane from this stage on
   (Section 4), only its SIZE is adjustable. */
.toolbox-eye-divider {
    flex-shrink: 0;
    width: 100%;
    height: 9px;
    border: none;
    padding: 0;
    cursor: row-resize;
    background: var(--surface-primary);
    position: relative;
    z-index: 1;
}
/* CODEX-EYE-TOOLBOX-DIVIDER-01: this is the persistent structural
   boundary between two functional zones, not a hover-only affordance.
   Keep the established one-pixel border-token line visible at rest;
   hover/focus/drag still promotes that same line to the interaction
   accent without introducing a second border. */
.toolbox-eye-divider::before {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    top: 4px;
    height: 1px;
    background: var(--border);
    opacity: 1;
    transition: opacity 0.15s ease;
}
.toolbox-eye-divider:hover::before,
.toolbox-eye-divider:focus-visible::before,
.toolbox-eye-divider.dragging::before {
    background: var(--machine-blue);
    height: 2px;
    top: 3.5px;
    opacity: 1;
}
/* Locked state deliberately does NOT reveal this line - "a tiny
   persistent lock-state cue is acceptable, never a full-width bright
   line merely to indicate locked state" (Product Owner). The lock glyph
   itself (.divider-lock-btn[aria-pressed="true"] below) is that cue. */
.toolbox-eye-divider:focus-visible {
    outline: 2px solid var(--machine-blue);
    outline-offset: 1px;
}
/* CLAUDE-EYE-TOOLBOX-LAYOUT-01: the compact lock/unlock control living
   INSIDE the divider's own thin strip ("keep the control compact... do
   not add a large lock button elsewhere... lives in the gap"). Hidden at
   rest, revealed the same way the divider's own accent line is
   (hover/focus-within), except when locked - then it stays visible as
   the persistent "tiny lock-state cue" the divider's own line
   deliberately does not become.
   CLAUDE-EYE-TOOLBOX-LAYOUT-01-CORRECTION-01: .divider-link-btn (new)
   shares this exact same compact/hidden-at-rest/persistent-when-active
   treatment - "keep controls extremely compact... do not add large
   toolbar buttons" (Product Owner) - sitting immediately beside the lock
   button in the same strip, not a second control elsewhere. */
.divider-lock-btn,
.divider-link-btn {
    position: absolute;
    top: 50%;
    z-index: 2;
    width: 18px;
    height: 18px;
    padding: 0;
    border: none;
    border-radius: 3px;
    background: var(--surface-secondary);
    color: var(--text-secondary);
    font-size: 0.7rem;
    line-height: 18px;
    text-align: center;
    cursor: pointer;
    opacity: 0;
    transition: opacity 0.15s ease;
}
.divider-lock-btn {
    left: calc(50% + 12px);
    transform: translate(-50%, -50%);
}
.divider-link-btn {
    left: calc(50% - 12px);
    transform: translate(-50%, -50%);
}
.toolbox-eye-divider:hover .divider-lock-btn,
.toolbox-eye-divider:focus-within .divider-lock-btn,
.toolbox-eye-divider:hover .divider-link-btn,
.toolbox-eye-divider:focus-within .divider-link-btn,
.divider-lock-btn:focus-visible,
.divider-link-btn:focus-visible,
.divider-lock-btn[aria-pressed="true"],
.divider-link-btn[aria-pressed="true"] {
    opacity: 1;
}
.divider-lock-btn:hover,
.divider-link-btn:hover {
    background: var(--surface-hover);
    color: var(--text-primary);
}
.divider-lock-btn[aria-pressed="true"],
.divider-link-btn[aria-pressed="true"] {
    color: var(--machine-blue);
}
.divider-lock-btn:focus-visible,
.divider-link-btn:focus-visible {
    outline: 2px solid var(--machine-blue);
    outline-offset: 1px;
}

/* CLAUDE-P40-EYE1, Section 4: the Eye pane itself - a structural
   scaffold, not a completed tool. Takes whatever height Toolbox's own
   flex-basis (--toolbox-height) doesn't use, the same "remaining space"
   idiom .thumbnails-pane already established relative to .lists-pane.
   CLAUDE-EYE-TOOLBOX-LAYOUT-01: the old border-top here duplicated the
   .toolbox-eye-divider immediately adjacent to it - a full-width
   separator line with no interaction purpose of its own ("a divider
   line should appear only if it has an actual interaction purpose" -
   Product Owner). Removed; the divider itself is the one real boundary
   now, on every Appearance. */
.eye-pane {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
    overflow: hidden;
}
.eye-pane-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0.5rem 0.7rem;
    flex-shrink: 0;
}
.eye-maximize-btn {
    background: none;
    border: none;
    cursor: pointer;
    color: var(--text-secondary);
    font-family: var(--font-body);
    font-size: 0.72rem;
    padding: 0.2rem 0.4rem;
    border-radius: 4px;
}
.eye-maximize-btn:hover, .eye-maximize-btn:focus-visible {
    background: var(--surface-hover);
    color: var(--text-primary);
}
.eye-maximize-btn[aria-pressed="true"] { color: var(--machine-blue); }
.eye-pane-header h2 {
    font-family: var(--font-display);
    font-size: 0.95rem;
    margin: 0;
    color: var(--text-primary);
}
/* CLAUDE-DUAL-DOCUMENT-FOCUS-01, Part 4: "a restrained visual
   indication of which viewing surface currently owns the toolbar...
   not a heavy border or bright selection box" - a subtle weight/color
   shift on the surface's own existing title, nothing added. Applied to
   whichever of .workspace-display-panel's title
   (.display-division-header-name) or Eye's own (.eye-pane-header h2)
   belongs to the currently-focused surface (static/js/pdf_viewer.js's
   own applyFocusIndication toggles the .surface-focused class). */
.eye-pane.surface-focused .eye-pane-header h2 {
    color: var(--machine-blue);
    font-weight: 700;
}
.workspace-pane-display.surface-focused .display-division-header-name {
    color: var(--machine-blue);
    font-weight: 700;
}
/* CLAUDE-DUAL-DOCUMENT-FOCUS-01 addendum A: "Eye is not a permanent
   right-hand panel... no comparison = Toolbox owns the right column."
   static/js/eye_pane.js's own refreshEyeLayout() toggles .eye-inactive
   on .workspace-right-column whenever Eye holds no image/document and
   Compare is off (and Eye is not detached to a second window) - Eye
   collapses and Toolbox (which already has an explicit flex-basis,
   .workspace-pane-toolbox above) takes the available column height. */
/* CODEX-EYE-TOOLBOX-DIVIDER-01: an empty Eye may collapse, but its
   structural boundary remains in the column. This keeps Eye/Toolbox
   legible as distinct zones and leaves the existing splitter controls
   reachable without changing Toolbox's fill behavior. */
.workspace-right-column.eye-inactive .eye-pane {
    display: none;
}
.workspace-right-column.eye-inactive .workspace-pane-toolbox {
    flex: 1 1 auto;
}
/* CLAUDE-DUAL-DOCUMENT-FOCUS-01, Part 2/5/7: Toolbox's own ordinary
   content and Eye's page-thumbnails share one slot, one visible at a
   time (base.html's own #toolbox-normal-content/#toolbox-eye-
   thumbnails-panel) - the thumbnails panel reuses .thumbnails-list/
   .thumbnails-empty-state's own established styling verbatim (same
   class names, a different pane), not a second set of rules. */
.toolbox-eye-thumbnails-panel {
    display: flex;
    flex-direction: column;
    min-height: 0;
    flex: 1 1 auto;
}
/* An author `display` declaration on this selector otherwise beats the
   UA stylesheet's own `[hidden] { display: none }` (equal specificity,
   author rule comes later in cascade order) - explicit here so the
   `hidden` attribute base.html renders by default, and
   pdf_viewer.js's own thumbnailsPanelVisibility() toggles, actually
   takes effect instead of the panel always rendering. */
.toolbox-eye-thumbnails-panel[hidden] {
    display: none;
}
.toolbox-eye-thumbnails-panel > h2 {
    font-family: var(--font-display);
    font-size: 0.95rem;
    margin: 0 0 0.4rem;
    color: var(--text-primary);
}
/* Addendum: a compact, floating status/return control for a DETACHED
   Eye window - same floating-corner idiom as .toolbox-compare-btn
   immediately above it in the markup, offset further left so all
   three compact controls (Maximize/Compare/Eye-detached-status) sit in
   a row without overlapping. */
.eye-detached-status-btn {
    position: absolute;
    top: 0.4rem;
    right: 6.4rem;
    background: none;
    border: 1px solid var(--border);
    cursor: pointer;
    color: var(--text-secondary);
    font-family: var(--font-body);
    font-size: 0.72rem;
    padding: 0.2rem 0.5rem;
    border-radius: 4px;
    z-index: 2;
}
.eye-detached-status-return {
    color: var(--machine-blue);
    text-decoration: underline;
}
.eye-pane-body {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    padding: 0 0.7rem 0.7rem;
    scrollbar-color: transparent transparent;
}
/* CLAUDE-P40-EYE1, Section 4: a real (not decorative) drop target.
   CLAUDE-EYE-SURFACE-CORRECTION-01 (Product Owner: "Eye is not an
   image-drop utility... remove the oversized dashed drop-zone treatment
   from the normal empty state"): the dashed border + --surface-secondary
   fill used to be PERMANENT, dominating the panel even at rest - moved
   to the :focus-visible/.eye-drop-target-active states only (a real
   keyboard focus, or an actual dragover in progress), so the quiet
   default reads as part of the pane, not as a standing "drop things
   here" box. Paste/drop/click-to-focus remain fully functional either
   way - this is a visual change only, not a capability change. */
.eye-drop-target {
    flex: 1 1 auto;
    min-height: 120px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    text-align: center;
    padding: 1rem;
    border: 2px solid transparent;
    border-radius: 6px;
    cursor: pointer;
}
.eye-drop-target:focus-visible {
    outline: 2px solid var(--machine-blue);
    outline-offset: 2px;
}
.eye-drop-target.eye-drop-target-active {
    border: 2px dashed var(--machine-blue);
    background: var(--surface-hover);
}
.eye-drop-target-empty {
    color: var(--text-secondary);
    font-size: 0.85rem;
    margin: 0;
}
.eye-drop-target-note {
    color: var(--text-metadata);
    font-size: 0.72rem;
    margin: 0;
    max-width: 24rem;
}
.eye-drop-target-error {
    color: var(--failure-red);
    font-size: 0.78rem;
    margin: 0;
}
/* CLAUDE-P40-EYE1 (product-owner browser correction), Section 3: a real
   responsive viewing canvas replacing the old small fixed-size preview
   - align-self:stretch/flex:1 override .eye-drop-target's own
   align-items:center/justify-content:center (correct for the EMPTY
   state's centered message, wrong for a canvas that should fill the
   available area) so the canvas genuinely uses the pane's available
   space instead of shrink-wrapping to the image's own natural size. */
.eye-canvas {
    align-self: stretch;
    flex: 1 1 auto;
    min-height: 0;
    width: 100%;
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
}
.eye-canvas[hidden] { display: none; }
.eye-canvas-toolbar {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-wrap: wrap;
    gap: 0.3rem;
    flex-shrink: 0;
}
.eye-canvas-btn {
    background: none;
    border: none;
    cursor: pointer;
    color: var(--text-secondary);
    font-family: var(--font-body);
    font-size: 0.78rem;
    padding: 0.3rem 0.55rem;
    border-radius: 4px;
}
.eye-canvas-btn:hover, .eye-canvas-btn:focus-visible {
    background: var(--surface-hover);
    color: var(--text-primary);
}
.eye-canvas-zoom-level {
    font-size: 0.75rem;
    color: var(--text-metadata);
    font-variant-numeric: tabular-nums;
    min-width: 3.2em;
    text-align: center;
}
.eye-canvas-divider {
    width: 1px;
    height: 1.1rem;
    background: var(--border);
}
/* CLAUDE-P40-EYE1 (product-owner browser correction), Section 3: native
   scroll IS the pan mechanism - #eye-canvas-image is sized to its real
   scaled pixel dimensions (static/js/eye_pane.js sets width/height
   directly from naturalWidth/naturalHeight * scale, never CSS max-
   width/height percentages, so there is no stretching or distortion at
   any zoom level), and overflow:auto lets the browser's own scrollbars/
   touch/trackpad panning take over once the image exceeds the viewport
   - real keyboard accessibility (Page Up/Down, arrow keys) and touch
   support for free, no hand-rolled pointer-drag panning needed.
   display:flex + align-items/justify-content:center is what keeps a
   smaller-than-viewport image centered rather than pinned top-left. */
.eye-canvas-viewport {
    flex: 1 1 auto;
    min-height: 0;
    overflow: auto;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--surface-primary);
    border-radius: 4px;
    scrollbar-color: transparent transparent;
}
.eye-canvas-viewport:focus-visible {
    outline: 2px solid var(--machine-blue);
    outline-offset: -2px;
}
.eye-canvas-image {
    display: block;
    flex-shrink: 0;
}
/* CLAUDE-MM5: orientation status, save bar, and the post-save handoff
   view - additive to CLAUDE-P40-EYE1's own canvas layout above, same
   visual language (--text-secondary/--text-metadata, .eye-canvas-btn's
   own button styling reused for #eye-save-btn). */
.eye-orientation-status {
    font-size: 0.72rem;
    color: var(--text-metadata);
    text-align: center;
    flex-shrink: 0;
}
.eye-save-bar {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
    flex-shrink: 0;
    padding-top: 0.3rem;
    border-top: 1px solid var(--border);
}
.eye-save-status {
    font-size: 0.78rem;
    color: var(--text-secondary);
    flex: 1 1 auto;
}
.eye-save-description {
    flex: 1 1 12rem;
    min-width: 8rem;
    font-family: var(--font-body);
    font-size: 0.78rem;
    padding: 0.3rem 0.5rem;
    border: 1px solid var(--border);
    border-radius: 4px;
    background: var(--surface-primary);
    color: var(--text-primary);
}
/* Reuses .btn-primary's own neutral (--text-primary-on-surface) emphasis
   convention rather than introducing a new blue-background button -
   --machine-blue is reserved for machine/system/reference accents
   (focus rings, links - tokens.css's own header comment), not primary
   actions. */
.eye-save-btn {
    background: var(--text-primary);
    color: var(--surface-primary);
    font-weight: 600;
}
.eye-save-btn:hover, .eye-save-btn:focus-visible {
    background: var(--text-secondary);
}
.eye-saved-view {
    align-self: stretch;
    flex: 1 1 auto;
    min-height: 0;
    width: 100%;
}
.eye-saved-view[hidden] { display: none; }

/* CLAUDE-EYE-COMPARE-01: Eye's second real content state - a genuine
   Project Document (PDF/image/honest-fallback), reusing the exact same
   visual language as the image-canvas state above (.eye-canvas-btn,
   --text-secondary/--text-metadata, .eye-canvas-viewport's own
   centered/scrollable treatment) rather than inventing a second look. */
.eye-document-view {
    align-self: stretch;
    flex: 1 1 auto;
    min-height: 0;
    width: 100%;
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
}
.eye-document-view[hidden] { display: none; }
.eye-document-header {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    flex-shrink: 0;
    flex-wrap: wrap;
}
.eye-document-label {
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--text-metadata);
    flex-shrink: 0;
}
.eye-document-name {
    font-size: 0.82rem;
    color: var(--text-primary);
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.eye-document-body {
    flex: 1 1 auto;
    min-height: 0;
    overflow: auto;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    background: var(--surface-primary);
    border-radius: 4px;
    scrollbar-color: transparent transparent;
}
.eye-document-image {
    display: block;
    max-width: 100%;
    max-height: 100%;
}
.eye-document-pdf {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.4rem;
    width: 100%;
    height: 100%;
}
.eye-document-pdf-toolbar {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.3rem;
    flex-shrink: 0;
}
.eye-document-pdf-canvas {
    max-width: 100%;
    display: block;
}

/* CLAUDE-P40-EYE1 (product-owner browser correction): "one nested
   scrollbar in the upper-right Toolbox/Findings region remains
   unthemed" - every scroll container above already had `scrollbar-
   color` (the standards-track property, Firefox always, Chromium only
   since v121/Jan-2024) but NOTHING using the older, still very widely
   deployed WebKit/Chromium `::-webkit-scrollbar` pseudo-element API -
   on any Chromium build older than 121, `scrollbar-color` is silently
   ignored entirely and the browser falls back to its own default
   white-track/gray-thumb rendering, exactly matching the report. Both
   are now declared together on every real scroll container this app
   has (Lists, Thumbnails, Toolbox, Eye's own body AND its image-canvas
   viewport, Display's <main> AND its PDF canvas container, Chat's
   message thread) - not just the one specifically reported, per this
   correction's own "check all nested... scroll containers" instruction.
   background-clip:padding-box + a transparent border is what gives the
   thumb its visible inset from the track edges without a hard-coded
   pixel gap. :hover/:active on the thumb are WebKit's own equivalent of
   "visible hover/active scrolling states" - :focus has no dedicated
   scrollbar-part pseudo-class, so the CONTAINER's own :focus-visible
   outline (already present on the keyboard-focusable ones, e.g.
   .eye-canvas-viewport) is what covers focus instead. */
/* CLAUDE-PANEL-CALM-02: every panel-scale scroll container (Lists,
   Thumbnails, Toolbox, Eye's own body and its image-canvas viewport,
   Display's <main>, its PDF canvas container, Chat's message thread)
   shares ONE quiet-by-default/hover-reveal treatment now -
   CLAUDE-LEFTPANEL-CALM-01 built this first for Lists alone after a
   real Product Owner report named it "too agitated"; the same report
   then confirmed that fix and asked for it everywhere. Width/height
   stay unconditional (always reserved, regardless of hover) - that is
   what guarantees no layout shift when track/thumb color fades in or
   out. See the separate combined block further below for the two
   horizontal overflow rails (document/attention tab strips), which
   share this exact pattern but their own, smaller 8px sizing. */
.lists-pane::-webkit-scrollbar,
.thumbnails-list::-webkit-scrollbar,
.workspace-pane-toolbox::-webkit-scrollbar,
.eye-pane-body::-webkit-scrollbar,
.eye-canvas-viewport::-webkit-scrollbar,
main::-webkit-scrollbar,
.document-viewer-canvas-container::-webkit-scrollbar,
.conversation-thread::-webkit-scrollbar {
    width: 10px;
    height: 10px;
}
.lists-pane::-webkit-scrollbar-track,
.thumbnails-list::-webkit-scrollbar-track,
.workspace-pane-toolbox::-webkit-scrollbar-track,
.eye-pane-body::-webkit-scrollbar-track,
.eye-canvas-viewport::-webkit-scrollbar-track,
main::-webkit-scrollbar-track,
.document-viewer-canvas-container::-webkit-scrollbar-track,
.conversation-thread::-webkit-scrollbar-track {
    background: transparent;
}
.lists-pane::-webkit-scrollbar-thumb,
.thumbnails-list::-webkit-scrollbar-thumb,
.workspace-pane-toolbox::-webkit-scrollbar-thumb,
.eye-pane-body::-webkit-scrollbar-thumb,
.eye-canvas-viewport::-webkit-scrollbar-thumb,
main::-webkit-scrollbar-thumb,
.document-viewer-canvas-container::-webkit-scrollbar-thumb,
.conversation-thread::-webkit-scrollbar-thumb {
    background-color: transparent;
    border-radius: 6px;
    border: 2px solid transparent;
    background-clip: padding-box;
    transition: background-color 0.15s ease;
}
.lists-pane:hover::-webkit-scrollbar-track,
.thumbnails-list:hover::-webkit-scrollbar-track,
.workspace-pane-toolbox:hover::-webkit-scrollbar-track,
.eye-pane-body:hover::-webkit-scrollbar-track,
.eye-canvas-viewport:hover::-webkit-scrollbar-track,
main:hover::-webkit-scrollbar-track,
.document-viewer-canvas-container:hover::-webkit-scrollbar-track,
.conversation-thread:hover::-webkit-scrollbar-track {
    background: var(--surface-primary);
}
.lists-pane:hover::-webkit-scrollbar-thumb,
.thumbnails-list:hover::-webkit-scrollbar-thumb,
.workspace-pane-toolbox:hover::-webkit-scrollbar-thumb,
.eye-pane-body:hover::-webkit-scrollbar-thumb,
.eye-canvas-viewport:hover::-webkit-scrollbar-thumb,
main:hover::-webkit-scrollbar-thumb,
.document-viewer-canvas-container:hover::-webkit-scrollbar-thumb,
.conversation-thread:hover::-webkit-scrollbar-thumb {
    background-color: var(--border-strong);
}
.lists-pane::-webkit-scrollbar-thumb:hover,
.thumbnails-list::-webkit-scrollbar-thumb:hover,
.workspace-pane-toolbox::-webkit-scrollbar-thumb:hover,
.eye-pane-body::-webkit-scrollbar-thumb:hover,
.eye-canvas-viewport::-webkit-scrollbar-thumb:hover,
main::-webkit-scrollbar-thumb:hover,
.document-viewer-canvas-container::-webkit-scrollbar-thumb:hover,
.conversation-thread::-webkit-scrollbar-thumb:hover,
.lists-pane::-webkit-scrollbar-thumb:active,
.thumbnails-list::-webkit-scrollbar-thumb:active,
.workspace-pane-toolbox::-webkit-scrollbar-thumb:active,
.eye-pane-body::-webkit-scrollbar-thumb:active,
.eye-canvas-viewport::-webkit-scrollbar-thumb:active,
main::-webkit-scrollbar-thumb:active,
.document-viewer-canvas-container::-webkit-scrollbar-thumb:active,
.conversation-thread::-webkit-scrollbar-thumb:active {
    background-color: var(--machine-blue);
    background-clip: padding-box;
}
.lists-pane::-webkit-scrollbar-corner,
.thumbnails-list::-webkit-scrollbar-corner,
.workspace-pane-toolbox::-webkit-scrollbar-corner,
.eye-pane-body::-webkit-scrollbar-corner,
.eye-canvas-viewport::-webkit-scrollbar-corner,
main::-webkit-scrollbar-corner,
.document-viewer-canvas-container::-webkit-scrollbar-corner,
.conversation-thread::-webkit-scrollbar-corner {
    background: transparent;
}
/* CLAUDE-PANEL-CALM-02: the standards-track scrollbar-color property
   (Firefox) gets the identical hover-reveal treatment, for every
   container above PLUS the two horizontal overflow rails (their own
   WebKit sizing stays separate, below, but scrollbar-color doesn't
   carry a size - one shared rule covers all of them). */
.lists-pane:hover,
.thumbnails-list:hover,
.workspace-pane-toolbox:hover,
.eye-pane-body:hover,
.eye-canvas-viewport:hover,
main:hover,
.document-viewer-canvas-container:hover,
.conversation-thread:hover,
.document-tab-list:hover,
.attention-strip-list:hover,
.document-tabs-overflow-panel:hover {
    scrollbar-color: var(--border-strong) var(--surface-primary);
}
/* CLAUDE-PANEL-CALM-02: the two horizontal overflow rails (document/
   attention tab strips) - same quiet-by-default/hover-reveal pattern as
   the panel group above, kept in their own block because their WebKit
   sizing (8px height, 1px thumb inset) has always been smaller/thinner
   than the 10px/2px panel scrollbars, not because the underlying
   behavior differs. */
.document-tab-list::-webkit-scrollbar,
.attention-strip-list::-webkit-scrollbar {
    height: 8px;
}
.document-tab-list::-webkit-scrollbar-track,
.attention-strip-list::-webkit-scrollbar-track {
    background: transparent;
}
.document-tab-list::-webkit-scrollbar-thumb,
.attention-strip-list::-webkit-scrollbar-thumb {
    background-color: transparent;
    border-radius: 6px;
    border: 1px solid transparent;
    background-clip: padding-box;
    transition: background-color 0.15s ease;
}
.document-tab-list:hover::-webkit-scrollbar-track,
.attention-strip-list:hover::-webkit-scrollbar-track {
    background: var(--surface-primary);
}
.document-tab-list:hover::-webkit-scrollbar-thumb,
.attention-strip-list:hover::-webkit-scrollbar-thumb {
    background-color: var(--border-strong);
}
.document-tab-list::-webkit-scrollbar-thumb:hover,
.attention-strip-list::-webkit-scrollbar-thumb:hover,
.document-tab-list::-webkit-scrollbar-thumb:active,
.attention-strip-list::-webkit-scrollbar-thumb:active {
    background-color: var(--machine-blue);
    background-clip: padding-box;
}

/* CLAUDE-P40-E1A-VISUAL: was a filled/bordered/rounded "card"
   (background + border + border-radius + padding) around every major
   section - Project information, an Investigation's own content, the
   Findings area, an opened document, Project Briefing, Project State.
   Visual de-boxing addendum: decorative containers removed: sections
   now separate from each other with a restrained bottom divider and
   spacing only - hierarchy comes from headings/.workspace-pane-label
   typography and whitespace, not a filled box. Functional boundaries
   (the composer, real controls, focus states, selected nav rows,
   warnings, confirmations - .delegation-choice/.rfi-preview/
   .revision-notice/.finding-card, all still fully bordered/filled)
   are deliberately untouched - this rule only ever provided decorative
   framing, never a functional one. */
.workspace-pane {
    padding-bottom: 1.5rem;
    margin-bottom: 1.5rem;
    border-bottom: 1px solid var(--border);
}
.workspace-pane-display > .workspace-pane:last-child,
.workspace-pane-toolbox > .workspace-pane:last-child {
    border-bottom: none;
    margin-bottom: 0;
    padding-bottom: 0;
}
.workspace-pane h2,
.workspace-pane h3 {
    font-family: var(--font-display);
    font-size: 0.95rem;
    margin: 0 0 0.6rem;
    color: var(--text-primary);
}
.workspace-pane-label {
    font-family: var(--font-body);
    text-transform: uppercase;
    letter-spacing: 0.1em;
    font-size: var(--text-xs);
    color: var(--text-metadata);
    margin: 0 0 var(--space-2);
}

/* CLAUDE-P40-E: an opened document/drawing (?source=), inside the
   Workspace pane rather than a new tab. A generic best-effort embed
   (browser-native PDF/text rendering via iframe, or a plain image) -
   no per-format viewer of this project's own; see the pane-note this
   section itself already renders about page/clause navigation not
   being available.
   CLAUDE-P40-E2B, Section E: was background: var(--surface-secondary)
   (Limestone, the same tinted/beige surface token main.css's own
   header comment reserves for nav/grouped-control surfaces, never the
   Display) plus border-radius - a rounded, filled "card" around the
   embed. Now a plain, neutral surface (--surface-primary, the same
   near-white token the rest of the de-boxed Workspace already uses for
   functional-not-decorative backgrounds) with square corners - no
   rounded container. The 1px border is kept, deliberately: a
   functional boundary marking where the embedded content starts (the
   same "real controls/boundaries stay bordered" exemption the P40-
   E1A-VISUAL de-boxing pass already established), not decorative
   framing - without it a white-background PDF embedded against this
   same near-white page would have no visible edge at all. */
.document-viewer-frame {
    width: 100%;
    height: 70vh;
    border: 1px solid var(--border);
    background: var(--surface-primary);
}
.document-viewer-image {
    max-width: 100%;
    border: 1px solid var(--border);
    display: block;
}
/* CLAUDE-CA1D-RIVER-PO-02 CONSOLIDATION (Section B, "internal-first
   document opening"): a format with no in-app renderer (.docx/.txt/
   anything without its own branch above) used to fall back to the
   SAME .document-viewer-frame iframe as PDF/XLSX - which those two
   formats' own browser-native handling can actually render, but this
   catch-all case cannot, so the iframe silently triggered an OS
   download the instant the Source was selected. This calm card
   replaces that: the same bordered "real boundary" idiom as
   .document-viewer-frame (a genuine functional edge, not decoration),
   holding an honest explanation plus the two explicit secondary
   actions that are now the ONLY way this reaches OS-level open/
   download behavior. */
.document-unavailable-preview {
    border: 1px solid var(--border);
    background: var(--surface-primary);
    border-radius: 4px;
    padding: 1.2rem;
}
.document-unavailable-preview-actions {
    display: flex;
    gap: 0.6rem;
    margin-top: 0.8rem;
}
/* CLAUDE-P40-VW7A-QA (Move Document Controls into the Top Application
   Menu): PDF Sources render here instead of .document-viewer-frame -
   same box (width/height/border/background all unchanged from the old
   iframe's own), but with NO competing native browser toolbar chrome
   inside it, since a <canvas> has none - the "reclaimed vertical
   height" this stage's own requirement asks for happens automatically
   inside this same 70vh box, not by growing the box itself. Centers
   the rendered page horizontally when it's narrower than the
   container (fit-page/a low zoom level) and scrolls when taller
   (a tall page at fit-width, or zoomed in). */
.document-viewer-canvas-container {
    width: 100%;
    height: 70vh;
    border: 1px solid var(--border);
    background: var(--surface-primary);
    overflow: auto;
    display: flex;
    justify-content: center;
    align-items: flex-start;
    scrollbar-color: transparent transparent;
}
.document-viewer-canvas { display: block; }
.document-viewer-load-error {
    margin: auto;
    padding: 1rem;
    max-width: 28rem;
    text-align: center;
    color: var(--failure-red);
    font-size: 0.85rem;
}
/* CLAUDE-P40-VW7A-QA2, Section 4: the page canvas and its transparent
   annotation overlay are stacked in one positioning wrapper - the wrap
   sizes to the page canvas (the only child in normal flow; the overlay
   is position:absolute and contributes no size of its own), so this
   never changes the rendered box size .document-viewer-canvas-container
   already centers/scrolls, only adds a second painted layer on top. */
.document-viewer-page-wrap {
    position: relative;
    display: inline-block;
}
.document-viewer-annotation-layer {
    position: absolute;
    top: 0;
    left: 0;
}
/* CLAUDE-P40-VW7A-QA2, Section 4: the one text-annotation input surface -
   a real, focusable <input> positioned at the click point (static/js/
   pdf_viewer.js's own openTextAnnotationInput), not a canvas-drawn fake
   text box - so typing, IME, and screen readers all work exactly like
   any other form field, right up until Enter/blur commits it into the
   canvas-drawn annotation and removes this element. */
.document-viewer-annotation-text-input {
    position: absolute;
    z-index: 5;
    font-size: 13px;
    padding: 2px 4px;
    border: 1px solid var(--machine-blue);
    border-radius: 2px;
    background: var(--surface-primary);
    color: var(--text-primary);
    min-width: 120px;
}
/* CLAUDE-MM5: the one marker-note input drawing_image_viewer.js creates -
   same shape as .document-viewer-annotation-text-input above, kept as
   its own class since it lives inside .drawing-region-overlay's own
   pointer-events:none layer (needs pointer-events re-enabled locally). */
.drawing-marker-note-input {
    position: absolute;
    z-index: 5;
    font-size: 13px;
    padding: 2px 4px;
    border: 1px solid var(--machine-blue);
    border-radius: 2px;
    background: var(--surface-primary);
    color: var(--text-primary);
    min-width: 140px;
    pointer-events: auto;
}
.doc-annotation-tool[aria-pressed="true"] {
    background: var(--surface-selected);
    color: var(--machine-blue);
}
.doc-annotation-status {
    font-size: 0.72rem;
    color: var(--text-metadata);
    white-space: nowrap;
}
/* CLAUDE-MM4: the in-pane drawing toolbar/viewport - lives DIRECTLY in
   the document pane (case_workspace.html's own kind == 'drawing' branch,
   and every 'source'-kind Display division static/js/case_workspace.js
   inserts), never the top application menu bar, so it renders
   identically for the primary document AND any comparison Display
   (Section 8) with no dependency on #workspace-document-controls. */
.drawing-viewer {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
}
.drawing-viewer-toolbar {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.15rem;
    padding: 0.25rem 0.4rem;
    border: 1px solid var(--border);
    border-radius: 4px;
    background: var(--surface-secondary);
}
.doc-orientation-status,
.doc-region-status,
.doc-snapshot-status {
    font-size: 0.72rem;
    color: var(--text-metadata);
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
}
.drawing-viewport {
    position: relative;
    width: 100%;
    height: 70vh;
    border: 1px solid var(--border);
    background: var(--surface-primary);
    overflow: hidden;
    touch-action: none;
    display: flex;
    align-items: center;
    justify-content: center;
}
.drawing-pan-zoom {
    /* JS (drawing_image_viewer.js's own applyPanZoomTransform) sets
       `transform: translate(panX,panY) scale(zoom)` here - flexbox
       centering above gives pan=0/zoom=1 a sensible starting position,
       panning/zooming is layered on top of that via the transform. */
    display: flex;
    align-items: center;
    justify-content: center;
}
.drawing-pan-zoom .document-viewer-image {
    display: block;
    max-width: none;
    max-height: 68vh;
    border: none;
    /* transform-origin defaults to "center" - never overwritten by JS
       (only .style.transform is set, never .style.transformOrigin) - so
       every rotate()/scaleX()/scaleY() JS applies always pivots around
       the drawing's own visual center, matching this stage's own
       transform_point_to_display/original convention. */
}
.drawing-region-overlay {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
}
.drawing-region-box {
    position: absolute;
    border: 2px dashed var(--machine-blue);
    background: rgba(79, 169, 162, 0.15);
}
/* Bounded drawing-interaction-integrity pass: a committed region (this
   session only - see drawing_image_viewer.js's own placedRegions
   comment) reads as settled/quiet, distinct from the active dragging
   box's own more prominent dashed treatment above - solid border,
   lighter fill, so a reviewer isn't drawn to N old boxes while trying
   to read the one currently being dragged. */
.drawing-region-box-placed {
    border: 1.5px solid var(--machine-blue);
    background: rgba(79, 169, 162, 0.08);
}
/* The marker/pin glyph placed at commitMarker's success point (same
   pass as above) - "Do not leave the toolbar icon visually selectable
   while functionally inert": this is the missing visual confirmation a
   marker actually landed. transform:translate(-50%,-100%) makes the
   glyph's own visual tip (not its box center) sit exactly at the
   anchored point, the standard map-pin convention; pointer-events:auto
   re-enables interaction locally since the overlay itself is
   pointer-events:none (same local re-enable .drawing-marker-note-input
   already uses). title carries the note text as the "identify it
   afterward" affordance (Section 2.6) - no separate click-to-reveal UI
   invented for this bounded pass. */
.drawing-marker-pin {
    position: absolute;
    transform: translate(-50%, -100%);
    font-size: 1.1rem;
    line-height: 1;
    pointer-events: auto;
    cursor: default;
}
.drawing-sheet-metadata {
    padding: 0.5rem 0.6rem;
    border: 1px solid var(--border);
    border-radius: 4px;
    background: var(--surface-secondary);
    font-size: 0.78rem;
}
.drawing-sheet-metadata-list {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 0.15rem 0.6rem;
    margin: 0 0 0.4rem;
}
.drawing-sheet-metadata-list dt { color: var(--text-metadata); }
.drawing-sheet-metadata-list dd { margin: 0; color: var(--text-primary); }
.drawing-sheet-metadata-unavailable { color: var(--text-disabled); font-style: italic; }
.drawing-scale-warning {
    margin: 0;
    color: var(--attention-amber);
    font-size: 0.74rem;
}

/* -- CLAUDE-MM6: bounded river relationship viewer -------------------------
   Section 19's own "NOT a large free-form graph canvas" - a small panel
   attached to the existing evidence surface (drawing_image_viewer.js),
   listing an evidence item's own relationships, its Trustworthy Answer
   Contract summary, and a bounded create-relationship form. Status colors
   reuse tokens.css's own existing semantic meanings rather than minting
   new ones: confirmed=accepted-green, proposed/stale=attention-amber
   (pending/needs re-verification), disputed/rejected/broken=failure-red
   (a concluded contradiction or a broken anchor), superseded/unresolved
   stay tonal - neither a success nor a failure, just historical/unknown. */
.relationship-river-panel {
    padding: 0.5rem 0.6rem;
    border: 1px solid var(--border);
    border-radius: 4px;
    background: var(--surface-secondary);
    font-size: 0.78rem;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}
.relationship-river-panel h4 { margin: 0; font-size: var(--text-sm); color: var(--text-primary); }
.relationship-trust-summary { margin: 0; color: var(--text-secondary); }
.relationship-river-empty { margin: 0; color: var(--text-disabled); font-style: italic; }
.relationship-river-list { display: flex; flex-direction: column; gap: 0.4rem; }
.relationship-river-row {
    padding: 0.35rem 0.5rem;
    border: 1px solid var(--border);
    border-radius: 4px;
    background: var(--surface-primary);
}
.relationship-river-row-head { display: flex; align-items: center; gap: 0.4rem; color: var(--text-primary); }
.relationship-river-reason { margin: 0.2rem 0 0; color: var(--text-metadata); }
.relationship-river-row-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 0.25rem; margin-top: 0.3rem; }
.relationship-river-claim-case-input {
    width: 6rem;
    padding: 0.15rem 0.3rem;
    border: 1px solid var(--border);
    border-radius: 3px;
    font-size: 0.76rem;
}
.relationship-status-badge {
    display: inline-block;
    padding: 0.05rem 0.4rem;
    border-radius: 3px;
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.02em;
}
.relationship-status-proposed,
.relationship-status-stale { background: var(--attention-amber-tint); color: var(--attention-amber); }
.relationship-status-confirmed { background: var(--accepted-green-tint); color: var(--accepted-green); }
.relationship-status-disputed,
.relationship-status-rejected,
.relationship-status-broken { background: var(--failure-red-tint); color: var(--failure-red); }
.relationship-status-superseded,
.relationship-status-unresolved { background: var(--surface-hover); color: var(--text-metadata); }
.relationship-river-create {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.3rem;
    padding-top: 0.4rem;
    border-top: 1px solid var(--border);
}
.relationship-river-create input[type="text"] {
    flex: 1 1 8rem;
    min-width: 6rem;
    padding: 0.15rem 0.3rem;
    border: 1px solid var(--border);
    border-radius: 3px;
    font-size: 0.76rem;
}
.relationship-river-create select {
    padding: 0.15rem 0.3rem;
    border: 1px solid var(--border);
    border-radius: 3px;
    font-size: 0.76rem;
}
.relationship-river-create-status { font-size: 0.72rem; color: var(--text-metadata); }

/* -- shared text utilities ------------------------------------------------
   Consolidates inline style="" patterns that had drifted into
   near-duplicates across templates (font-size:0.85rem vs 0.82rem vs
   0.88rem all meaning "small secondary text", margin -0.6rem vs -0.4rem
   both meaning "pull this note up under the section title") - now one
   canonical value each, referencing tokens.css. Prefer these classes
   over a new inline style="" for anything this general; keep inline
   styles for genuinely one-off, non-reusable positioning only. */
.text-muted { color: var(--text-secondary); }
.pane-note {
    font-size: var(--text-sm);
    color: var(--text-secondary);
    margin: calc(-1 * var(--space-5)) 0 var(--space-6);
}
.history-entry {
    font-size: var(--text-sm);
    color: var(--text-secondary);
    margin: var(--space-3) 0;
    border-left: 3px solid var(--border-strong);
    padding-left: var(--space-6);
}
.spin-history-item {
    border-bottom: 1px solid var(--border);
}
.spin-history-summary {
    display: flex;
    align-items: baseline;
    gap: var(--space-4);
    cursor: pointer;
    list-style: none;
    padding: var(--space-4) 0;
}
.spin-history-summary::-webkit-details-marker { display: none; }
.spin-history-chevron {
    display: inline-block;
    min-width: 1rem;
    font-size: var(--text-lg);
    line-height: 1;
}
.spin-history-item[open] .spin-history-chevron { transform: rotate(90deg); }
.spin-history-summary:focus-visible {
    outline: 2px solid var(--machine-blue);
    outline-offset: 2px;
}
.spin-run-detail { padding: 0 0 var(--space-6) var(--space-7); }

/* Visual pressure - see tokens.css's own comment on --pressure-quiet-text
   for the token-level rule. Apply only to neutral text nodes (a
   statement, a metadata line) - NEVER to a .review-state-badge or any
   other semantic-colored element, and never via `opacity` on a shared
   ancestor (opacity composites the whole subtree, including badges,
   toward the background - it cannot be selectively "restored" on a
   child). This is a color swap on plain text only, so a badge sitting
   next to quieted text is completely unaffected by construction. */
.pressure-quiet-text { color: var(--pressure-quiet-text); }

/* -- left: project navigation ------------------------------------------------ */
.lifecycle-strip { display: flex; flex-wrap: wrap; gap: 0.3rem; margin-bottom: 0.8rem; }
.lifecycle-stage {
    font-family: var(--font-body);
    font-size: 0.8rem;
    color: var(--text-metadata);
    padding: 0.2rem 0.45rem;
    border: 1px solid var(--border);
    border-radius: 999px;
}
/* Deliberately the one place ordinary list/nav "selected" tone (Warm
   Stone) is replaced with real color: a project's current lifecycle
   position is a singular, rare fact worth a dedicated highlight, not
   just another selected-row state - see --highlight-orange above. */
.lifecycle-stage.current {
    color: var(--surface-primary);
    background: var(--highlight-orange);
    border-color: var(--highlight-orange);
    font-weight: 600;
}

/* CLAUDE-P40-E1A-VISUAL: was a filled beige (--surface-secondary,
   Limestone) card per row, bordered and rounded - a "box around
   Investigation/Source listings" the de-boxing addendum specifically
   names. Now a plain row, separated by a restrained divider
   (border-bottom, last row excepted) instead of a filled card -
   selection/hover are still real, visible state changes (addendum:
   "retain boundaries for... selected navigation rows"), just via a
   background tint on that one row rather than every row getting a
   permanent box. */
.source-list, .case-list { list-style: none; margin: 0 0 0.7rem; padding: 0; display: grid; }
.source-item, .case-item {
    display: block;
    font-size: var(--text-sm);
    padding: 0.45rem 0.1rem;
    color: var(--text-primary);
    border-bottom: 1px solid var(--border);
}
.source-list li:last-child .source-item,
.case-list li:last-child .case-item { border-bottom: none; }
.case-item:hover { color: var(--machine-blue); text-decoration: none; }
.case-item.active { background: var(--surface-selected); border-radius: 4px; padding-left: 0.5rem; }
.source-kind-badge, .case-status-badge {
    font-family: var(--font-body);
    font-size: 0.8rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-metadata);
    display: block;
    margin-bottom: 0.2rem;
}

.workspace-form { display: grid; gap: 0.5rem; margin-top: 0.5rem; }
.workspace-form input[type="text"],
.workspace-form select,
.workspace-form textarea {
    font-family: var(--font-body);
    font-size: 0.8rem;
    padding: 0.5rem 0.6rem;
    background: var(--surface-primary);
    border: 1px solid var(--border);
    border-radius: 3px;
    color: var(--text-primary);
    width: 100%;
}
.workspace-form button {
    font-family: var(--font-body);
    font-size: var(--text-sm);
    padding: 0.5rem 0.8rem;
    border-radius: 3px;
    border: 1px solid var(--border-strong);
    background: transparent;
    color: var(--text-primary);
    cursor: pointer;
}
.workspace-form button:hover { background: var(--surface-hover); }

/* -- the ONE physical conversation dock, now full application width
   (CLAUDE-P40-E3A, Section 9 - was CLAUDE-P40-E2B, Section C, before
   that CLAUDE-P40-E1A, Section A) ---------------------------------------
   #conversation-dock is the single html_id macros.conversation_dock
   always renders, whether it's targeting the Project conversation or
   an open Investigation's own - the two are mutually exclusive Jinja
   branches (see case_workspace.html), never both on screen, so one id/
   one set of rules is genuinely one dock, not a shared style applied
   to two different elements.

   CLAUDE-P40-E3A: Chat is no longer grid-area: chat inside the retired
   .case-workspace grid, confined to the Display column - it's now
   base.html's own .chat-region, a full-width flex row beneath
   .app-shell-body (Lists|Display|Toolbox), sticky at the viewport
   bottom itself (see .chat-region below) so THIS panel no longer needs
   its own position:sticky/bottom/z-index - one sticky ancestor is
   enough, and stacking two independently sticky boxes here would only
   reintroduce the exact Lists/Chat overlap problem .launcher-panel's
   own :has(> .chat-region) rule above was written to avoid. height:
   100% fills .chat-region's own --chat-height; a flex column
   internally, so the resize handle/header/note stay a fixed size,
   .conversation-thread (history) takes the remaining space and scrolls
   internally, and the composer stays pinned at the panel's OWN bottom
   edge via flex. */
/* CLAUDE-P40-VW7A-QA: ONE shared horizontal inset, applied to every
   piece of Chat content (message text/role-labels via .conversation-
   thread's own padding below, and the composer via .conversation-
   input-form's own padding) rather than the PREVIOUS scheme's
   per-role, per-side asymmetric margins (.conversation-message.human
   had margin-left:1.5rem/margin-right:0, .system had the opposite) -
   product-owner report: "inconsistent horizontal alignment... left
   edges must align... right margin must visually balance the left
   margin." Declared once here (a local custom property, inherited by
   every descendant) so both consuming rules reference the SAME value
   instead of two separately hardcoded 1.5rem literals that could drift
   apart. Deliberately NOT applied to .conversation-dock-resize-handle
   (a direct sibling-in-spirit, not a padded-content child) - that
   divider line must stay full-width, unaffected by this inset. */
.conversation-dock-panel {
    height: 100%;
    width: 100%;
    max-width: 100%;
    min-width: 0;
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    background: var(--surface-primary);
    min-height: 0;
    --conversation-inset: 1.5rem;
    /* CLAUDE-P40-E3A-QA, Section 10 fix: .accordion-section (a class this
       element also carries - see the macro's own comment on reusing its
       visual treatment) sets border-top/margin-top/padding-top by
       default. CLAUDE-P40-E2B already intended the resize handle's own
       divider line to be THE one divider ("the handle IS the divider,
       not a separate invisible hit-target floating over a decorative
       one") but never actually zeroed these out here, leaving a second,
       redundant border+gap stacked directly above the handle's own -
       exactly the "visually heavy separators" the product owner flagged.
       Zeroed here, not by editing .accordion-section itself (still
       correct for its many other, actually-nested, callers). */
    border-top: none;
    margin-top: 0;
    padding-top: 0;
}
/* CLAUDE-P40-E3A, Section 9: the shell-level Chat row itself - "remains
   at the bottom... can be raised or lowered" (the existing resize
   handle/presets, unchanged internally). Sticky at the viewport bottom
   so it "remains reachable" while scrolling a long Document/
   Investigation in Display, exactly the guarantee the old
   .conversation-dock-panel sticky rule provided before Chat became a
   full-width row. 220px matches case_workspace.js's own
   COMPACT_HEIGHT default (see .launcher-panel's own note above on why).
   CLAUDE-P40-VW7A-QA2 (product-owner browser correction): this no
   longer spans the entire application width - a real-browser check
   found Chat extending underneath the full-height Lists column, and an
   earlier fix attempt (margin-left, since reverted) offsetting Chat's
   own box instead of moving it left a strip beneath Lists unpainted by
   anything, reading as a light rectangle in dark Appearance modes. The
   actual, structural fix: .chat-region is now nested inside
   templates/base.html's own .workspace-main-column, alongside .app-main
   (Display), both children of a column that is itself Lists' own
   sibling inside .app-shell-body - so Chat's box starts at the correct
   horizontal position automatically (never needing a margin-left kept
   in sync with Lists' own width/collapsed state), while Lists' own
   height:100% now genuinely covers the same vertical extent Chat's row
   occupies. CLAUDE-P40-EYE1 later moved Toolbox out of this column
   entirely (into its own .workspace-right-column, alongside a new Eye
   pane) - Chat's own width here is therefore Display's alone, not
   Display+Toolbox as it briefly was for one stage. */
.chat-region {
    flex-shrink: 0;
    position: sticky;
    bottom: 0;
    z-index: 10;
    height: var(--chat-height, 220px);
    background: var(--surface-primary);
}
/* CLAUDE-P40-E3A-QA, Section 10 fix: no border-top here - product-owner
   browser observation named "visually heavy separators", and this rule
   was stacking a second divider line directly above
   .conversation-dock-resize-handle's own (its ::before below is already
   documented, since CLAUDE-P40-E2B, as THE one visual divider handle
   reviewers see - "the handle IS the divider"). One line, not two. */
/* CLAUDE-P40-VW3/VW6: the shared Dark- and Tinted-mode rules,
   positioned here deliberately - AFTER every one of the five surfaces'
   own base component rules (.launcher-panel, .app-main,
   .workspace-topbar, .workspace-pane-toolbox, and .chat-region's own
   rule directly above), so a plain textual "first rule whose selector
   contains .app-main" search (an existing test-helper convention - see
   tests/test_p40e2b_flexible_workspace_frame.py's own _rule_body)
   still finds each surface's real base rule first, not this one, even
   though this rule's own selector list also names each of them as
   part of a compound selector. See the Appearance-menu comment above
   (near .launcher-count) for the full VW6 rationale - BOTH rules
   REDEFINE the standard token names locally on whichever surface's own
   root carries the class, so every existing rule in this file already
   written as var(--surface-primary)/var(--text-primary)/etc. repaints
   correctly for free. Tinted uses its own --tint-* family (tokens.css)
   - a real light navy-blue palette, not --surface-secondary borrowed
   from its own unrelated "grouped content" meaning (VW3's own defect,
   corrected here). */
.app-shell.appearance-dark,
.workspace-topbar.appearance-dark,
.launcher-panel.appearance-dark,
.app-main.appearance-dark,
.workspace-right-column.appearance-dark,
.chat-region.appearance-dark {
    --canvas: var(--dark-canvas);
    --surface-primary: var(--dark-surface-primary);
    --surface-secondary: var(--dark-surface-secondary);
    --surface-hover: var(--dark-surface-hover);
    --surface-selected: var(--dark-surface-selected);
    --border: var(--dark-border);
    --border-strong: var(--dark-border-strong);
    --text-primary: var(--dark-text-primary);
    --text-secondary: var(--dark-text-secondary);
    --text-metadata: var(--dark-text-metadata);
    --text-disabled: var(--dark-text-disabled);
    --seal-red: var(--dark-seal-red);
    --seal-red-tint: var(--dark-seal-red-tint);
    --machine-blue: var(--dark-machine-blue);
    --machine-blue-tint: var(--dark-machine-blue-tint);
    --highlight-orange: var(--dark-highlight-orange);
    --highlight-orange-tint: var(--dark-highlight-orange-tint);
    --accepted-green: var(--dark-accepted-green);
    --accepted-green-tint: var(--dark-accepted-green-tint);
    --attention-amber: var(--dark-attention-amber);
    --attention-amber-tint: var(--dark-attention-amber-tint);
    --failure-red: var(--dark-failure-red);
    --failure-red-tint: var(--dark-failure-red-tint);
    --risk-red: var(--dark-risk-red);
    --risk-red-tint: var(--dark-risk-red-tint);
    --brand-gold: var(--dark-brand-gold);
    background: var(--surface-primary);
    color: var(--text-primary);
}
.app-shell.appearance-tinted,
.workspace-topbar.appearance-tinted,
.launcher-panel.appearance-tinted,
.app-main.appearance-tinted,
.workspace-right-column.appearance-tinted,
.chat-region.appearance-tinted {
    --canvas: var(--tint-canvas);
    --surface-primary: var(--tint-surface-primary);
    --surface-secondary: var(--tint-surface-secondary);
    --surface-hover: var(--tint-surface-hover);
    --surface-selected: var(--tint-surface-selected);
    --border: var(--tint-border);
    --border-strong: var(--tint-border-strong);
    --text-primary: var(--tint-text-primary);
    --text-secondary: var(--tint-text-secondary);
    --text-metadata: var(--tint-text-metadata);
    --text-disabled: var(--tint-text-disabled);
    --seal-red: var(--tint-seal-red);
    --seal-red-tint: var(--tint-seal-red-tint);
    --machine-blue: var(--tint-machine-blue);
    --machine-blue-tint: var(--tint-machine-blue-tint);
    --highlight-orange: var(--tint-highlight-orange);
    --highlight-orange-tint: var(--tint-highlight-orange-tint);
    --accepted-green: var(--tint-accepted-green);
    --accepted-green-tint: var(--tint-accepted-green-tint);
    --attention-amber: var(--tint-attention-amber);
    --attention-amber-tint: var(--tint-attention-amber-tint);
    --failure-red: var(--tint-failure-red);
    --failure-red-tint: var(--tint-failure-red-tint);
    --risk-red: var(--tint-risk-red);
    --risk-red-tint: var(--tint-risk-red-tint);
    --brand-gold: var(--tint-brand-gold);
    background: var(--surface-primary);
    color: var(--text-primary);
}
/* "Deep Forest" (CLAUDE-P40-VW8-QA, Approved Theme Set) - third dark
   appearance choice, same redefinition pattern as .appearance-dark
   ("Graphite") and .appearance-tinted ("Midnight Blue") directly
   above, reading from the new --forest-* token family
   (static/css/tokens.css's own comment there has the full derivation
   and contrast-verification story). */
.app-shell.appearance-deep-forest,
.workspace-topbar.appearance-deep-forest,
.launcher-panel.appearance-deep-forest,
.app-main.appearance-deep-forest,
.workspace-right-column.appearance-deep-forest,
.chat-region.appearance-deep-forest {
    --canvas: var(--forest-canvas);
    --surface-primary: var(--forest-surface-primary);
    --surface-secondary: var(--forest-surface-secondary);
    --surface-hover: var(--forest-surface-hover);
    --surface-selected: var(--forest-surface-selected);
    --border: var(--forest-border);
    --border-strong: var(--forest-border-strong);
    --text-primary: var(--forest-text-primary);
    --text-secondary: var(--forest-text-secondary);
    --text-metadata: var(--forest-text-metadata);
    --text-disabled: var(--forest-text-disabled);
    --seal-red: var(--forest-seal-red);
    --seal-red-tint: var(--forest-seal-red-tint);
    --machine-blue: var(--forest-machine-blue);
    --machine-blue-tint: var(--forest-machine-blue-tint);
    --highlight-orange: var(--forest-highlight-orange);
    --highlight-orange-tint: var(--forest-highlight-orange-tint);
    --accepted-green: var(--forest-accepted-green);
    --accepted-green-tint: var(--forest-accepted-green-tint);
    --attention-amber: var(--forest-attention-amber);
    --attention-amber-tint: var(--forest-attention-amber-tint);
    --failure-red: var(--forest-failure-red);
    --failure-red-tint: var(--forest-failure-red-tint);
    --risk-red: var(--forest-risk-red);
    --risk-red-tint: var(--forest-risk-red-tint);
    --brand-gold: var(--forest-brand-gold);
    background: var(--surface-primary);
    color: var(--text-primary);
}
/* "Deep Ocean" (CLAUDE-APPEARANCE-SIMPLIFY-01) - fifth global appearance
   choice, same redefinition pattern as .appearance-dark/.appearance-
   tinted/.appearance-deep-forest above, reading from the new --ocean-*
   token family (tokens.css's own comment there has the full derivation
   and source-tracing story - traced from the landing/sign-in shell's
   own deep-ocean background, not invented). */
.app-shell.appearance-deep-ocean,
.workspace-topbar.appearance-deep-ocean,
.launcher-panel.appearance-deep-ocean,
.app-main.appearance-deep-ocean,
.workspace-right-column.appearance-deep-ocean,
.chat-region.appearance-deep-ocean {
    --canvas: var(--ocean-canvas);
    --surface-primary: var(--ocean-surface-primary);
    --surface-secondary: var(--ocean-surface-secondary);
    --surface-hover: var(--ocean-surface-hover);
    --surface-selected: var(--ocean-surface-selected);
    --border: var(--ocean-border);
    --border-strong: var(--ocean-border-strong);
    --text-primary: var(--ocean-text-primary);
    --text-secondary: var(--ocean-text-secondary);
    --text-metadata: var(--ocean-text-metadata);
    --text-disabled: var(--ocean-text-disabled);
    --seal-red: var(--ocean-seal-red);
    --seal-red-tint: var(--ocean-seal-red-tint);
    --machine-blue: var(--ocean-machine-blue);
    --machine-blue-tint: var(--ocean-machine-blue-tint);
    --highlight-orange: var(--ocean-highlight-orange);
    --highlight-orange-tint: var(--ocean-highlight-orange-tint);
    --accepted-green: var(--ocean-accepted-green);
    --accepted-green-tint: var(--ocean-accepted-green-tint);
    --attention-amber: var(--ocean-attention-amber);
    --attention-amber-tint: var(--ocean-attention-amber-tint);
    --failure-red: var(--ocean-failure-red);
    --failure-red-tint: var(--ocean-failure-red-tint);
    --risk-red: var(--ocean-risk-red);
    --risk-red-tint: var(--ocean-risk-red-tint);
    --brand-gold: var(--ocean-brand-gold);
    background: var(--surface-primary);
    color: var(--text-primary);
}
.chat-region.appearance-tinted,
.chat-region.appearance-tinted .conversation-dock-panel,
.chat-region.appearance-dark,
.chat-region.appearance-dark .conversation-dock-panel,
.chat-region.appearance-deep-forest,
.chat-region.appearance-deep-forest .conversation-dock-panel,
.chat-region.appearance-deep-ocean,
.chat-region.appearance-deep-ocean .conversation-dock-panel {
    background: var(--surface-primary);
    color: var(--text-primary);
}

/* CLAUDE-DEEP-OCEAN-VISUAL-PARITY-01: the glass material itself.
   tokens.css's own --ocean-* family was, until this stage, color-only
   (surface/border/text) - the redefinition blocks above already cascade
   those correctly into every Deep-Ocean-scoped surface (that part was
   never broken), but nothing ever carried over the backdrop-filter
   blur + box-shadow glow that make .gateway-shell .gateway-card-wide
   (this repository's own already-shipped, already-approved reference -
   see tokens.css's own derivation comment above --ocean-glass-blur/
   --ocean-glow) actually READ as translucent glass rather than plain
   flat dark navy. "Dark navy background + turquoise accent" was
   Product Owner's own named anti-pattern for what NOT to reduce Deep
   Ocean to.

   Scoped to the genuinely floating/overlay surfaces - the menu bar's
   own dropdown panels (this session's own CLAUDE-APP-MENU-01 work) and
   the pre-existing Display Layout/Appearance/Account popovers - the
   structural analog of a card floating over a canvas backdrop, exactly
   like the reference. Layered on top of the translucent background
   these surfaces already inherit from --surface-primary via the
   .workspace-topbar.appearance-deep-ocean redefinition above - never a
   second background definition, never touching any other appearance
   mode. z-index 60/61 (already established, well above the narrow
   popup-shadowing regression CLAUDE-P40-VW6's own comment on the
   selector group below once diagnosed and fixed) remains untouched -
   this only adds visual material, never repositions anything.

   CLAUDE-MENU-FOREGROUND-LAYERING-01: background-color added below.
   Previously these floating overlays only ADDED blur+glow on top of
   the --surface-primary background they inherit from the .workspace-
   topbar.appearance-deep-ocean redefinition above - the same .55-
   alpha density every passive background tray (project list, document
   rail, toolbox) also uses, since those panels sit on the same
   redefinition's cascade. Blurred-but-still-.55-alpha let the tray
   shape/color bleed through an open menu instead of the menu owning
   the foreground. --ocean-glass-foreground (tokens.css) is a literal
   copy of .gateway-card-wide's own denser .72-alpha background - a
   real floating-card-over-live-background precedent that never drew a
   bleed-through report - so a genuinely foreground overlay now reads
   as denser glass than the passive panel underneath it, without
   inventing a new color or touching any other appearance mode. */
.workspace-topbar.appearance-deep-ocean .workspace-menubar-panel,
.workspace-topbar.appearance-deep-ocean .workspace-menubar-subpanel,
.workspace-topbar.appearance-deep-ocean .workspace-layout-options,
.workspace-topbar.appearance-deep-ocean .workspace-appearance-options,
.workspace-topbar.appearance-deep-ocean .workspace-user-options {
    background-color: var(--ocean-glass-foreground);
    -webkit-backdrop-filter: blur(var(--ocean-glass-blur));
    backdrop-filter: blur(var(--ocean-glass-blur));
    box-shadow: var(--ocean-glow);
}
/* The Composer dock is a real, bounded panel (not a floating overlay),
   so it gets the restrained half of the material - glow only, no blur
   (Section 4's own "avoid... excessive blur" - blurring content behind
   a panel that never floats over anything would be a pure decorative
   gimmick, not a structural analog of the reference card). */
.chat-region.appearance-deep-ocean .conversation-dock-panel {
    box-shadow: var(--ocean-glow);
}

/* CLAUDE-P40-E2B: the drag/keyboard-operable divider between Display
   and Chat - a real ARIA separator (draggable via
   static/js/case_workspace.js's pointer handlers, and keyboard-
   operable via ArrowUp/ArrowDown/Home/End when focused), not a purely
   decorative line. The visual accordion-section top border moves here
   (from the panel itself) so the handle IS the divider reviewers see,
   not a separate invisible hit-target floating over a decorative one. */
.conversation-dock-resize-handle {
    flex-shrink: 0;
    height: 10px;
    margin: -0.6rem 0 0.2rem;
    cursor: ns-resize;
    position: relative;
    /* CLAUDE-P40-VW7A-QA2 (product-owner browser correction): was
       unset/transparent - a descendant of .chat-region, so a real
       background here (rather than relying on whatever shows through)
       merges this splitter's own channel with Chat's own theme, the
       same "Appearance All must govern... the surrounding channel"
       requirement every other divider now satisfies. */
    background: var(--surface-primary);
}
.conversation-dock-resize-handle::before {
    content: "";
    position: absolute;
    top: 4px;
    left: 0;
    right: 0;
    height: 1px;
    /* CLAUDE-P40-VW6: --divider-strong (tokens.css), not --border - the
       upper workspace and Chat are independently-themed surfaces (VW3),
       so --border here would resolve to whichever mode CHAT ITSELF is
       in, with no guarantee the workspace surface directly above is
       different enough to still show a line (Dark workspace above Dark
       Chat was exactly the reported "the divider... disappears" case -
       two very-low-contrast dark-on-dark borders, technically present,
       not perceptible). --divider-strong is fixed and never redefined
       by .appearance-dark/.appearance-tinted, so it stays visible
       against light, dark, AND tinted alike, on either side, in any
       combination.
       CLAUDE-EYE-TOOLBOX-LAYOUT-01: quiet (opacity 0) at rest, revealed
       on hover/focus/drag - same generalized-from-Deep-Ocean treatment
       as .panel-divider/.toolbox-eye-divider above. --divider-strong is
       still the right color CHOICE for the moment it IS revealed (still
       needs to read against either side in any combination); opacity is
       what makes it quiet at rest now, not a weaker color. */
    background: var(--divider-strong);
    opacity: 0;
    transition: opacity 0.15s ease;
}
.conversation-dock-resize-handle:hover::before,
.conversation-dock-resize-handle:focus-visible::before,
.conversation-dock-resize-handle.dragging::before {
    background: var(--machine-blue);
    opacity: 1;
}
/* Locked state deliberately does NOT reveal this line - same rule as
   .toolbox-eye-divider's own locked state above; the lock glyph itself
   is the persistent cue, not a full-width line. */
.conversation-dock-resize-handle:focus-visible {
    outline: 2px solid var(--machine-blue);
    outline-offset: 2px;
}
.conversation-dock-resize-handle .divider-lock-btn {
    top: 4px;
    transform: translate(-50%, -50%);
}
.conversation-dock-resize-handle:hover .divider-lock-btn,
.conversation-dock-resize-handle:focus-within .divider-lock-btn {
    opacity: 1;
}

/* ===========================================================================
   Deep Ocean "Visual Source of Truth" (CLAUDE-APPEARANCE-SIMPLIFY-01
   addendum) - Product Owner correction, twice, after live review: a
   first attempt here (a flat, opaque, luminance-matched hex the same
   way Midnight Blue/Deep Forest were derived) read "close to midnight
   blue instead of being like gateway page," followed by the explicit
   instruction "adapt Gateway page for deepocean appearance." Every
   --ocean-* token (tokens.css) is now a literal copy of THIS FILE's own
   .gateway-shell rule (above - templates/gateway_shell.html's already-
   shipped, already-visually-approved deep-ocean treatment), so the
   standard shared token-redefinition mechanism every other theme
   already uses (background: var(--surface-primary) etc., in the
   combined selector above) already produces the correct translucent
   glass fill/text/border for every one of the 5 former surfaces
   (including Display now - a plain opaque patch there would read as a
   surface this theme forgot, not a deliberate exception) with zero
   extra rules. This block is only what genuinely can't flow through
   that token mechanism: the literal background gradient (.app-shell
   has no --canvas equivalent to redefine into, since Gateway's own
   background is a real multi-stop gradient, not a flat color) and
   backdrop-filter (not expressible as a custom property). Placed AFTER
   every base rule it touches (.panel-divider, .toolbox-eye-divider,
   .conversation-dock-resize-handle above) so a plain textual/regex
   search for one of those base selectors still finds the base rule
   first, not this override. ============================================ */

/* The literal .landing-page gradient (static/css/landing.css), reused
   verbatim in place of a flat canvas color - .app-shell has no
   translucency of its own to blur (it IS the base layer everything
   else floats above), so it gets the real multi-stop background
   directly rather than going through --canvas. */
.app-shell.appearance-deep-ocean {
    background:
        radial-gradient(circle at 50% 10%, rgba(33, 112, 132, .14), transparent 30%),
        radial-gradient(circle at 50% 45%, rgba(8, 55, 68, .12), transparent 42%),
        linear-gradient(180deg, rgb(2, 8, 18) 0%, rgb(3, 23, 37) 46%, rgb(1, 5, 10) 100%);
}
/* backdrop-filter itself has no custom-property equivalent, so this is
   the one thing every former surface still needs a dedicated rule for -
   the actual glass FILL/text/border colors already flow through the
   standard --surface-primary/--text-primary/--border mechanism (the
   combined selector above), now that those tokens are themselves
   translucent rgba() values reused from .gateway-shell. */
.workspace-topbar.appearance-deep-ocean,
.launcher-panel.appearance-deep-ocean,
.app-main.appearance-deep-ocean,
.workspace-right-column.appearance-deep-ocean,
.chat-region.appearance-deep-ocean {
    -webkit-backdrop-filter: blur(14px);
    backdrop-filter: blur(14px);
}
/* Menu's own bottom border (border-bottom: 1px solid var(--divider-
   strong) in its base rule) is functionally meaningful - separates
   persistent chrome from scrolling content below it ("borders only
   where functionally necessary"). --divider-strong is deliberately
   mode-invariant (see tokens.css's own comment - it must stay legible
   regardless of which theme is active), so it is never itself
   redefined here; this scoped override recolors just this ONE border
   to var(--border) (now the same translucent glass tone every other
   ocean border already reads) instead, a deliberate Deep-Ocean-only
   exception for internal consistency, not a change to the shared
   token. */
.workspace-topbar.appearance-deep-ocean {
    border-bottom-color: var(--border);
}
/* CLAUDE-DEEP-OCEAN-COLOR-CORRECTION-01 (Product Owner: "the current
   live Deep Ocean workspace has drifted into visibly green/teal panel
   blocks... one Deep Ocean instrument, not multiple greenish boxes").
   Root cause: .display-divisions's own base rule (above) paints
   background: var(--border) behind its division(s), with each
   .display-division painting --surface-primary OVER it and only a 1px
   `gap` left to show the border color through - a real, working
   hairline technique for every OPAQUE theme (Titanium/Black/Midnight
   Blue/Deep Forest), where the division's own opaque fill fully
   occludes everything except that 1px sliver. Deep Ocean's own
   --surface-primary is deliberately TRANSLUCENT (rgba(10, 35, 42, .55)
   - the glass effect), so it never fully occludes what's behind it:
   the bright, saturated --ocean-border (rgba(176, 219, 255, .28) - a
   cyan/mint tone, correct for hover/focus accents, wrong for a static
   fill) shows through across the WHOLE division, not just the gap,
   compositing into exactly the visible green/teal panel block reported
   above Display. Scoped to Deep Ocean only - every other Appearance's
   hairline technique already works correctly and is untouched.
   --ocean-canvas (tokens.css) is the same dark navy as
   --ocean-surface-primary - using it here means the "gap" now reads as
   the SAME tone the division's own translucent fill already produces,
   consistent with this correction's own "distinguish regions through
   dark negative space and subtle tonal variation, not unrelated panel
   colors," rather than inventing a new dedicated gap color. */
.app-shell.appearance-deep-ocean .display-divisions {
    background: var(--ocean-canvas);
}
/* CLAUDE-DEEP-OCEAN-MIDDLE-SURFACE-CORRECTION-01 (Product Owner: "the
   main middle panel has an additional lighter background shade that
   stops short of the left and right structural edges... creates a
   floating-panel effect and makes the middle workspace visually
   narrower than the chat/composer area below").
   Root cause, confirmed live via computed styles: .workspace-pane-
   display (nested inside .app-main's own <main>) paints its OWN
   background: var(--surface-primary) - for Deep Ocean, the SAME
   translucent rgba(10, 35, 42, .55) .app-main (its ancestor) already
   painted. Stacking that translucent fill a SECOND time compounds the
   alpha compositing into a more opaque, visibly lighter box than the
   single-layer surrounding area - exactly the "additional lighter
   shade." Every other Appearance's fill is opaque, so painting it
   twice is visually identical to painting it once (no seam) - this
   is genuinely Deep-Ocean-only, not a shared defect. Fix: let Deep
   Ocean's own Display content show the SAME single layer of
   translucency .app-main already provides - transparent here, not a
   second color (Part: "do not introduce a replacement panel color").
   The unrelated bare `main` rule (this file, ~line 1105) and its
   3rem/1180px padding/max-width are UNTOUCHED - those apply globally,
   to every Appearance and every non-Workspace page (gateway, docs,
   etc.), and are explicitly out of scope ("DO NOT CHANGE... general
   workspace dimensions"). The override below narrows scope to
   Workspace's own <main>, Deep Ocean only, reducing (not zeroing) the
   inset so Display's own left/right content edges land close to
   Chat/Composer's own (measured live: Composer's own effective inset
   from the shared column edge is roughly 1.5rem, not 3rem) - "preserve
   reasonable small internal padding so text does not literally touch
   a divider," not full edge-to-edge text. max-width: none removes the
   1180px reading-width cap for this one case, since Chat/Composer has
   no equivalent cap either - the two must expand and contract
   together, not diverge on a wide viewport. */
.app-main.appearance-deep-ocean .workspace-pane-display {
    background: transparent;
}
.app-main.appearance-deep-ocean main {
    padding: 0 1.5rem;
    max-width: none;
}
/* CLAUDE-EYE-TOOLBOX-LAYOUT-01: the "splitter/lock controls reveal on
   hover/focus" treatment that used to live here, scoped to
   .appearance-deep-ocean only, is now the SHARED base behavior for
   .panel-divider/.toolbox-eye-divider/.conversation-dock-resize-handle
   (see each one's own base rule above) - "Deep Ocean currently has no
   visible line between panels; other themes still show separator
   lines... do not make Deep Ocean the exception; do not leave old
   theme-specific borders active elsewhere" (Product Owner). Removed
   from here entirely rather than leaving a now-redundant duplicate:
   Deep Ocean gets the identical quiet-gap/hover-reveal behavior every
   other Appearance now gets, from the same rules, not a second copy of
   it. */

/* CLAUDE-P40-VW8-QA: was justify-content:space-between, spreading the
   old heading text (removed - see _macros.html's own comment) and this
   toggle to opposite ends. With only the toggle left, flex-end keeps it
   in its prior top-right position rather than letting a single flex
   item collapse to flex-start under the old rule. */
.conversation-dock-header {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 0.6rem;
    font-family: var(--font-body);
    text-transform: uppercase;
    letter-spacing: 0.1em;
    font-size: 0.8rem;
    color: var(--text-metadata);
    flex-shrink: 0;
    margin-bottom: 0.5rem;
}
/* CLAUDE-P40-E3A-QA, Section 10: one size toggle, replacing the old
   two-button .conversation-dock-presets/.conversation-preset-btn pair -
   its own label already names the action ("Expand"/"Compact"), so no
   aria-pressed-driven color distinction between two buttons is needed
   anymore. */
.conversation-size-toggle {
    font-family: var(--font-body);
    font-size: 0.78rem;
    text-transform: none;
    letter-spacing: normal;
    color: var(--text-secondary);
    background: none;
    border: 1px solid var(--border);
    border-radius: 3px;
    padding: 0.15rem 0.5rem;
    cursor: pointer;
}
.conversation-size-toggle:hover {
    color: var(--text-primary);
    border-color: var(--border-strong);
}

/* -- center: conversation ----------------------------------------------------- */
.conversation-thread {
    display: grid;
    align-content: start;
    gap: 0.7rem;
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    margin-bottom: 1rem;
    scrollbar-color: transparent transparent;
    /* CLAUDE-P40-VW7A-QA: the shared --conversation-inset (declared on
       .conversation-dock-panel above) on both sides, superseding the
       old right-only 0.3rem scrollbar-clearance padding - comfortably
       larger than a scrollbar needs, so that purpose is still served
       for free. */
    padding-left: var(--conversation-inset);
    padding-right: var(--conversation-inset);
    scroll-behavior: smooth;
}
/* CLAUDE-P40-E1A-VISUAL: was a filled beige bubble (human) and a
   bordered card (system) per message - the de-boxing addendum names
   this directly ("remove borders and filled cards around ordinary
   user and Archiosk messages"). CLAUDE-P40-VW7A-QA removed the
   per-role asymmetric indent (.human had margin-left:1.5rem/.system
   had margin-right:1.5rem instead - two different left edges,
   "inconsistent horizontal alignment" per that stage's own report) -
   role is now distinguished by the existing .role-label alone (already
   uppercase/letter-spaced/colored differently per role), not
   indentation; every message now starts at the SAME left edge, set by
   .conversation-thread's own shared inset above. */
.conversation-message {
    padding: 0.3rem 0;
    font-size: var(--text-sm);
    line-height: 1.45;
    color: var(--text-primary);
}
/* CLAUDE-POSTCAMEL-CA1C (Section 6/7): a real ConversationMessage reply
   can now contain deliberate embedded line breaks (a vertical, copy-
   paste-ready structure proposal) - the default browser white-space
   behavior collapses them to a single line, which would otherwise
   silently defeat the entire point of proposing a structure "vertically
   and hierarchically, not buried in prose". pre-wrap preserves both
   line breaks and indentation while still wrapping long lines (never
   forcing horizontal scroll) - every existing single-line reply is
   visually unaffected. */
.conv-message-text {
    white-space: pre-wrap;
}
.conversation-message .role-label {
    display: block;
    font-family: var(--font-body);
    font-size: 0.8rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--text-metadata);
    margin-bottom: 0.3rem;
}
.conversation-message.system .role-label { color: var(--machine-blue); }
.conversation-empty { color: var(--text-secondary); font-size: 0.85rem; }
/* CLAUDE-P40-E3A-QA, Section 10: product-owner browser observation named
   an "oversized empty-history area" - .conversation-thread is flex:1
   (fills whatever Chat height is currently set, by design, so continuous
   resize has somewhere to put the extra space), which pinned a single
   empty-state line to the top and left a large bare gap beneath it.
   Centering only the genuinely-empty case (the message is the thread's
   only child) fills that same space on purpose instead of leaving it
   looking like an accident - a real conversation with messages keeps
   align-content: start (top-packed, scrollable) unaffected. */
.conversation-thread:has(> .conversation-empty:only-child) {
    align-content: center;
    justify-items: center;
}
.conversation-thread .conversation-empty { text-align: center; }

/* CLAUDE-P40-VW7A-QA: padding-left/right use the SAME --conversation-
   inset .conversation-thread uses above (declared once, on their
   shared ancestor .conversation-dock-panel) - the composer's own left/
   right edges now line up with the message thread's, and the input
   itself (flex:1) simply fills whatever width remains inside that
   inset, which grew once the old Chat (N) footer label was removed
   (CLAUDE-P40-VW7A-QA) - nothing here needs to know that label ever
   existed; it just fills available space, same as before. */
.conversation-input-form {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.6rem;
    flex-shrink: 0;
    padding-left: var(--conversation-inset);
    padding-right: var(--conversation-inset);
    /* CLAUDE-P40-VW7A-QA2 (product-owner browser correction): a real-
       browser check found the composer row (input + Send) touching the
       application frame/viewport edge - this element had left/right
       inset (above) but no bottom spacing at all, since it is the last
       child of .conversation-dock-panel, itself the last child of
       .chat-region (position:sticky; bottom:0). The SAME
       --conversation-inset token used for left/right, reused rather
       than a new one-off value, so left/right/bottom read as one
       consistent, balanced inset - not page overflow or an external
       blank strip, just intrinsic padding on a flex-shrink:0 item, so
       it survives every --chat-height value (resize/collapse/expand)
       and the narrow-viewport layout unchanged. */
    padding-bottom: var(--conversation-inset);
}
/* CLAUDE-P40-E1: the "Re: <anchored object>" badge a "Discuss this X"
   click (macros.aperture) sets on the ONE dock composer - flex-basis
   100% forces it onto its own line above the input/button when shown,
   rather than cramming into the same row. */
.dock-composer-anchor-label {
    flex-basis: 100%;
    font-family: var(--font-body);
    font-size: 0.78rem;
    color: var(--text-metadata);
    background: var(--surface-secondary);
    border-radius: 4px;
    padding: 0.25rem 0.5rem;
    width: fit-content;
}
.conversation-input-form input[type="text"],
.conversation-input-form textarea {
    flex: 1;
    font-family: var(--font-body);
    font-size: 0.9rem;
    /* CLAUDE-P40-VW7A-QA: back to symmetric internal padding - the
       form's own padding-left (above) is what now aligns the input's
       text with the message thread's own left edge, so the input no
       longer needs its own asymmetric compensation (CLAUDE-P40-VW8-QA's
       left:1.5rem special case is superseded, not layered on top of,
       the new shared inset - two overlapping fixes for the same
       alignment would just be a different inconsistency).

       CLAUDE-CA1D-RIVER-PO-02 (Section 18, "composer visual chrome"):
       a full border+radius+filled-surface box beside a filled Send
       button read as one heavy search-bar widget, not "a disciplined
       lane." Replaced the all-around border with a single bottom rule
       - "input area as a disciplined lane, not a boxed widget," the
       same parallel-horizontal-structure principle as the operational-
       action controls above, applied to the one remaining boxed
       control in this row. Background stays transparent (the panel's
       own surface shows through) rather than a separate lighter/darker
       fill, so there is nothing left reading as an enclosure. The
       clear-editable-region requirement is met by the bottom rule plus
       the existing global input:focus-visible outline (unchanged) -
       both survive dark/tinted/forest since --border already carries
       per-theme contrast the same as every other divider in this
       file.

       CLAUDE-CA1D-COMPOSER-LINE-01: a live product-owner refinement -
       the lane's top and bottom rules now reuse --machine-blue (the
       SAME token already used, conditionally, for this exact input's
       own :focus state below, and permanently for this same Chat
       surface's .conversation-message.system .role-label - i.e.
       already ARCHIOSK's chat/machine identity color, not a new one).
       Only these two hairlines change color; surface stays
       transparent, placeholder/mic/Send are untouched, so this reads
       as the lane's own quiet identity marker, never a tinted
       composer. Kept thin (1px, unchanged weight) and theme-aware for
       free, since --machine-blue is already redefined per Appearance
       mode (dark/tinted/forest) elsewhere in this file.

       CLAUDE-CA1D-COMPOSER-CONTEXT-LABEL-01: the top rule moved OFF this
       element onto .composer-context-rule directly below (an <input>
       cannot contain the embedded work-context label's child markup) -
       this input keeps only its own bottom rule, completely unchanged. */
    padding: 0.7rem 0.2rem;
    background: transparent;
    border: none;
    border-bottom: 1px solid var(--machine-blue);
    border-radius: 0;
    color: var(--text-primary);
}
/* CLAUDE-CA1D-COMPOSER-CONTEXT-LABEL-01: "blue upper rule -> embedded
   context label -> blue upper rule" - the composer's own upper line,
   now hosting the repositioned work-context indicator (formerly a
   separate `context_indicator` macro/div rendered above the whole
   composer). Two flex:1 line segments carry the actual --machine-blue
   rule; with no current_context they sit directly adjacent (gap: 0),
   reading as ONE continuous line - the exact same "uninterrupted when
   empty" behavior the lower rule already has, just applied to the
   upper one now that it can host content. */
.composer-context-rule {
    display: flex;
    align-items: center;
    gap: 0;
}
.composer-context-rule-line {
    flex: 1 1 auto;
    height: 0;
    border-top: 1px solid var(--machine-blue);
}
/* The label itself: text only, --attention-amber (ARCHIOSK's existing
   restrained amber/orange accent - "needs evidence / pending attention"
   per tokens.css's own semantic grammar, reused here for "this is the
   active work lane," not a new color). Deliberately no background/fill/
   radius - reads as a tab/folder name cut directly into the rule, not a
   pill or badge; its own left/right padding is what pushes the two rule
   segments apart, so the "gap" is real spacing, not a background-
   masking trick that would need to match every theme's own surface
   color to look right. */
.composer-context-label {
    display: flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0 0.5rem;
    min-width: 0;
    flex: 0 1 auto;
    font-family: var(--font-body);
    font-size: 0.72rem;
    color: var(--attention-amber);
    background: transparent;
    white-space: nowrap;
}
.composer-context-label-text {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 220px;
}
.composer-context-label-text strong {
    font-weight: 600;
}
/* A real, focusable, keyboard-operable <button> (not a link styled to
   look like one) - reuses the existing supported clear-context route
   via `formaction`, no new switching/navigation mechanism introduced.
   Deliberately quiet/subordinate to the label text itself. */
.composer-context-clear {
    font-family: var(--font-body);
    font-size: 0.85rem;
    line-height: 1;
    color: var(--attention-amber);
    background: transparent;
    border: none;
    padding: 0 0.1rem;
    cursor: pointer;
    opacity: 0.75;
}
.composer-context-clear:hover,
.composer-context-clear:focus-visible {
    opacity: 1;
}
.composer-context-clear:focus-visible {
    outline: 2px solid var(--machine-blue);
    outline-offset: 1px;
}
.conversation-input-form button {
    font-family: var(--font-body);
    font-size: var(--text-sm);
    padding: 0.7rem 1.1rem;
    border-radius: 4px;
    border: 1px solid var(--text-primary);
    background: var(--text-primary);
    color: var(--surface-primary);
    font-weight: 600;
    cursor: pointer;
}
/* CLAUDE-POSTCAMEL-VOICE1-PRE: a secondary, visually distinct control
   from Send (outline, not filled) - it never submits the form, so it
   must never look like the primary action. voice-input-listening is
   the ONLY visual state change while capturing - no separate "recording
   timer" or waveform UI, matching this stage's own "no audio permanence,
   no new conversation system" minimalism.
   CLAUDE-GATEWAY-UI-GRAMMAR-01: font-family/font-size/border/border-
   radius/font-weight/cursor added directly here (previously only ever
   supplied by the parent `.conversation-input-form button` rule above,
   since this control only ever appeared inside that composer) - the
   two newer instances (Sign-In, Project Gateway) render this same class
   without that parent, and were silently falling back to unstyled
   native button chrome (no border/radius/font at all) instead of
   matching the original. Same values as `.conversation-input-form
   button`, so the in-project instance's appearance is unchanged (this
   rule is strictly more specific for the properties it repeats, all
   pixel-identical to that rule's own values). */
.voice-input-button {
    font-family: var(--font-body);
    font-size: var(--text-sm);
    border-radius: 4px;
    border: 1px solid var(--text-primary);
    font-weight: 600;
    cursor: pointer;
    background: transparent !important;
    color: var(--text-primary) !important;
    padding: 0.7rem 0.9rem !important;
}
.voice-input-button.voice-input-listening {
    background: var(--attention-amber) !important;
    color: var(--surface-primary) !important;
    border-color: var(--attention-amber) !important;
}
/* CLAUDE-VOICE1-LIVE-FIX-01: the one visible outcome surface for voice
   input - flex-basis:100% (same pattern as .dock-composer-anchor-label
   above) drops it onto its own line below input/mic/Send rather than
   cramming into that row or reflowing it. Empty and invisible via :empty
   whenever voice input is idle - never a permanent reserved-space line. */
.voice-input-status {
    flex-basis: 100%;
    font-family: var(--font-body);
    font-size: 0.78rem;
    color: var(--text-metadata);
}
.voice-input-status:empty {
    display: none;
}
/* CLAUDE-CA1D-INSTRUMENT-RAIL-01: the composer-adjacent execution strip
   proof (Plan-Mode report, Section D.3) - identical shape to
   .voice-input-status directly above (same flex-basis/font/color/:empty
   idiom), deliberately not a new visual pattern. static/js/case_workspace.js
   sets this text right before the composer's own classic form-POST fires
   and never clears it client-side - the next page load starts empty, so
   :empty below hides it again with no extra code. */
.composer-execution-status {
    flex-basis: 100%;
    font-family: var(--font-body);
    font-size: 0.78rem;
    color: var(--text-metadata);
}
.composer-execution-status:empty {
    display: none;
}
/* --failure-red per tokens.css's own grammar: "contradiction / failure" -
   a denied permission, missing microphone, or unavailable recognition
   service are real failures, not neutral status text like "Listening…". */
.voice-input-status[data-state="error"] {
    color: var(--failure-red);
}

/* CLAUDE-P40-VW7: OneNote-style selection toolbar for Project
   Conversation text -> Tags/Highlights/Tasks (static/js/case_workspace.js
   positions/shows/hides this; hidden by default via the [hidden]
   attribute, never a permanent floating toolbar - Section 10's own
   explicit "avoid adding" instruction). position:fixed because
   positionToolbar() computes against getBoundingClientRect(), viewport-
   relative coordinates. Solid token background (never rgba/translucent)
   and a z-index above every other overlay in this file, same fix and
   same reasoning as the Appearance popup's own comment above
   (.workspace-appearance-options, z-index 60) - "text beneath the popup
   remains visible through it" is a stacking defect, not a transparency
   one, so the fix here is identical: paint solid, stack higher than
   anything it could ever appear over (70, one step above the existing
   ceiling of 60). */
.conv-selection-toolbar {
    position: fixed;
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 0.05rem;
    background: var(--surface-primary);
    border: 1px solid var(--border-strong);
    border-radius: 5px;
    padding: 0.25rem;
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.18);
    z-index: 70;
}
/* The [hidden] attribute alone does not hide the toolbar: this class
   selector's own `display: flex` and the UA default `[hidden]{display:
   none}` are equal specificity (0,1,0 each), and this rule is declared
   later in the cascade, so it silently won - the toolbar rendered
   permanently regardless of `toolbar.hidden` being set correctly by
   JS. Same bug CLASS as the tokens.css comment-boundary regression
   (CLAUDE-P40-VW8-QA-R3): logic was correct, a cascade/specificity
   detail made it invisible in its effect. */
.conv-selection-toolbar[hidden] {
    display: none;
}
.conv-selection-btn {
    font-family: var(--font-body);
    font-size: 0.76rem;
    padding: 0.35rem 0.55rem;
    border-radius: 4px;
    border: none;
    background: none;
    color: var(--text-primary);
    cursor: pointer;
    white-space: nowrap;
    text-align: left;
}
.conv-selection-btn:hover:not(:disabled) { background: var(--surface-hover); }
.conv-selection-btn:disabled { color: var(--text-disabled); cursor: default; }
.conv-selection-status {
    position: fixed;
    z-index: 70;
    font-size: var(--text-xs);
    color: var(--text-metadata);
    background: var(--surface-primary);
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: 0.3rem 0.5rem;
    bottom: 1rem;
    right: 1rem;
    max-width: 320px;
}
/* CLAUDE-P40-VW8-QA (reversibility correction): the short-lived Undo
   affordance for a Tag/Highlight/Important/Question removal - sits
   immediately left of .conv-selection-status (same fixed bottom-right
   corner, same z-index/solid-background/border treatment) so the
   result message and the way to reverse it read as one unit. Written
   with its own [hidden] override from the start (learned directly from
   .conv-selection-toolbar's own bug above - display declared on the
   base rule always needs one, not just when a report catches its
   absence). */
.conv-selection-undo {
    position: fixed;
    z-index: 70;
    font-family: var(--font-body);
    font-size: var(--text-xs);
    font-weight: 600;
    color: var(--machine-blue);
    background: var(--surface-primary);
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: 0.3rem 0.6rem;
    bottom: 1rem;
    right: 340px;
    cursor: pointer;
}
.conv-selection-undo[hidden] { display: none; }
.conv-selection-undo:hover { border-color: var(--border-strong); }
.conv-selection-undo:focus-visible { outline: 2px solid var(--machine-blue); outline-offset: 2px; }

/* The Remove Tag dialog's own applied-Tags list - reuses .conv-dialog's
   shared modal chrome (background/border/z-index/centering, above),
   this only lays out the rows themselves. Each row identifies its Tag
   by NAME (text), not the color swatch alone - "do not rely on color
   alone" for state identification. */
.conv-remove-tag-list {
    list-style: none;
    margin: 0.5rem 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
}
.conv-remove-tag-row {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.3rem 0;
}
.conv-remove-tag-name {
    flex: 1;
    font-size: 0.85rem;
    color: var(--text-primary);
}

/* Add Tag / Make Task - small, compact dialogs (Section 10: "don't
   cover most of the selection", "avoid heavy shadows/filled cards"), a
   fixed-position centered card rather than a full-screen modal takeover
   - the conversation stays visible around it. Same solid-background/
   raised-z-index treatment as the selection toolbar above, one step
   higher again (80) so a dialog always paints over a still-visible
   toolbar rather than the two fighting for stacking order. */
.conv-dialog {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    z-index: 80;
    width: min(360px, calc(100vw - 2rem));
    max-height: calc(100vh - 2rem);
    overflow-y: auto;
    background: var(--surface-primary);
    border: 1px solid var(--border-strong);
    border-radius: 6px;
    padding: 0.9rem;
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.22);
}
.conv-dialog form { display: flex; flex-direction: column; gap: 0.5rem; }
.conv-dialog-heading {
    font-family: var(--font-body);
    font-weight: 600;
    font-size: 1rem;
    margin: 0 0 0.2rem;
    color: var(--text-primary);
}
.conv-dialog-error {
    font-size: var(--text-sm);
    color: var(--failure-red);
    background: var(--failure-red-tint);
    border-radius: 4px;
    padding: 0.35rem 0.5rem;
    margin: 0;
}
.conv-dialog-quote {
    font-size: var(--text-sm);
    color: var(--text-secondary);
    font-style: italic;
    border-left: 3px solid var(--border-strong);
    padding-left: 0.6rem;
    margin: 0 0 0.3rem;
}
.conv-dialog-or {
    font-size: var(--text-xs);
    color: var(--text-metadata);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    margin: 0.2rem 0 0;
}
.conv-dialog label {
    font-size: var(--text-xs);
    color: var(--text-metadata);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.conv-dialog select,
.conv-dialog input[type="text"] {
    font-family: var(--font-body);
    font-size: 0.9rem;
    padding: 0.5rem 0.6rem;
    background: var(--surface-primary);
    border: 1px solid var(--border);
    border-radius: 4px;
    color: var(--text-primary);
}
.conv-tag-color-picker {
    border: none;
    padding: 0;
    margin: 0.2rem 0 0;
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
}
.conv-tag-color-picker legend {
    font-size: var(--text-xs);
    color: var(--text-metadata);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    padding: 0;
    width: 100%;
}
.conv-tag-color-option {
    display: flex;
    align-items: center;
    gap: 0.3rem;
    font-size: var(--text-xs);
    text-transform: capitalize;
    color: var(--text-secondary);
    cursor: pointer;
}
.conv-tag-color-swatch {
    display: inline-block;
    width: 0.85rem;
    height: 0.85rem;
    border-radius: 50%;
    border: 1px solid var(--border-strong);
}
.conv-dialog-actions {
    display: flex;
    gap: 0.5rem;
    margin-top: 0.3rem;
}
.conv-dialog-actions .btn { padding: 0.5rem 1rem; font-size: var(--text-sm); }

/* CLAUDE-P40-VW8, renamed and reused CLAUDE-P40-VW7B: the same fixed/
   centered/solid-background/high-z-index dialog idiom .conv-dialog
   already established (CLAUDE-P40-VW7) - this used to be the Project-
   switching interruption dialog, retired in CLAUDE-P40-VW7B once its
   only trigger (a different Project's own Lists row, clickable while
   a Project was open) stopped existing at all (Section 3's own
   removal of the portfolio from the opened workspace). Renamed rather
   than deleted-then-recreated for the NEW fifth-Investigation capacity
   dialog (Section 9) - "do not introduce a separate visual language"
   (Section 13) - same markup shape (heading/body/actions/note),
   different content and trigger. */
.attention-capacity-dialog {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    z-index: 80;
    width: min(380px, calc(100vw - 2rem));
    max-height: calc(100vh - 2rem);
    overflow-y: auto;
    background: var(--surface-primary);
    border: 1px solid var(--border-strong);
    border-radius: 6px;
    padding: 0.9rem;
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.22);
}
.attention-capacity-dialog-heading {
    font-family: var(--font-body);
    font-weight: 600;
    font-size: 1rem;
    margin: 0 0 0.4rem;
    color: var(--text-primary);
}
.attention-capacity-dialog-body {
    font-size: var(--text-sm);
    color: var(--text-secondary);
    margin: 0 0 0.7rem;
}
.attention-capacity-dialog-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
}
.attention-capacity-dialog-actions .btn { padding: 0.5rem 0.8rem; font-size: var(--text-sm); }
.attention-capacity-dialog-note {
    font-size: var(--text-xs);
    color: var(--attention-amber);
    margin: 0.6rem 0 0;
}

/* Navigate-to-source (Section 4): a temporary, animated flash - never a
   permanent border/fill (CLAUDE-P40-E1A-VISUAL's own "remove borders and
   filled cards around ordinary...messages" de-boxing rule above still
   applies to a message's RESTING appearance; this is time-boxed feedback
   that JS removes 2.5s after applying it, not a redesign of that
   resting state). Built on --highlight-orange - already means "the one
   current/active position in a sequence," a genuine semantic match for
   "this is what you navigated to," not a repurposing.

   CLAUDE-CA1D-HIGHLIGHT-CONTRAST-02 (Product Owner visual refinement,
   "translucent focus, not opaque highlight"): originally an opaque flat
   --highlight-orange-tint wash - readable, but a solid block that hid
   the surrounding cockpit rather than sitting as a layer behind it, and
   visually indistinguishable in KIND from .tag-highlight-inline's own
   PERSISTENT state above (same flat fill, just a different hue) even
   though the two mean different things: this one is a few seconds of
   "you just arrived here," that one is "the user chose to mark this."
   Replaced with an actual glow - a translucent color-mix() background
   wash plus a soft blurred box-shadow aura, both fading to nothing -
   using the same low-alpha reasoning verified above, so legibility
   holds at every animation frame, not just at rest.

   CLAUDE-CA1D-HIGHLIGHT-CONTRAST-03 (Product Owner visual refinement,
   "reduce highlight strength" - explicitly "temporary source-return
   focus should fade quickly and never resemble a persistent selected
   block"): the original 22%/35% peak still read as a block on a real
   multi-line message, the opposite of what a glancing "you arrived
   here" cue should feel like. Lowered the background peak to 10% (below
   even the persistent-highlight's own 12%, since this state should
   read as the quieter of the two - a flash, not a mark) and moved most
   of the visible effect onto the blurred box-shadow (18% peak, spread
   over a wide 24px blur so no single pixel is anywhere near that
   opacity) - an edge glow around the message rather than a fill across
   it, matching "text -> faint translucent tint -> subtle edge/glow."
   Both keyframe stops are far below the already-verified 22% case, so
   contrast only improves further. Reduced-motion keeps a single static
   low-alpha wash (no shadow, no keyframes) rather than a hard opaque
   block, so the "soft, restrained" character survives even without
   animation. */
.conv-source-flash {
    animation: conv-source-flash-fade 2.5s ease-out;
    border-radius: 6px;
}
@keyframes conv-source-flash-fade {
    0% {
        background: color-mix(in srgb, var(--highlight-orange) 10%, transparent);
        box-shadow: 0 0 24px 0 color-mix(in srgb, var(--highlight-orange) 18%, transparent);
    }
    100% {
        background: transparent;
        box-shadow: 0 0 24px 0 transparent;
    }
}
@media (prefers-reduced-motion: reduce) {
    .conv-source-flash {
        animation: none;
        background: color-mix(in srgb, var(--highlight-orange) 8%, transparent);
        box-shadow: none;
    }
}

/* -- disclosure grammar (application-wide) ---------------------------------
   ONE mechanism for every collapse/expand control, whatever <details> it
   decorates - a bare "+"-prefixed nested affordance (.add-source-details)
   or a labeled section header (.accordion-summary). Never a native
   OS-drawn triangle, never a mix of unrelated arrow glyphs - the shape
   and motion always mean "this expands," full stop. */
.add-source-details { margin-top: 0.6rem; }
.add-source-details summary {
    display: flex;
    align-items: center;
    font-family: var(--font-body);
    font-size: 0.85rem;
    color: var(--text-metadata);
    cursor: pointer;
    margin-bottom: 0.4rem;
    list-style: none;
}
.add-source-details summary::-webkit-details-marker { display: none; }
.add-source-details summary::before {
    content: "\25B8";
    display: inline-block;
    margin-right: 0.5rem;
    color: var(--text-secondary);
    font-size: 0.72rem;
    flex-shrink: 0;
    transition: transform 0.12s ease;
}
.add-source-details[open] > summary::before { transform: rotate(90deg); }

/* CLAUDE-CA1D-RIVER-PO-01 (River Action Stack): each item reuses
   .add-source-details itself (subdisclosure, above) for its own
   independent expand/collapse - the SAME rotating-triangle disclosure
   grammar every other collapsible surface in this app already uses, per
   that rule's own "never a mix of unrelated arrow glyphs" comment. This
   block only styles the list container and the item's own expanded
   content, never a second disclosure mechanism. */
.conv-river-action-stack {
    list-style: none;
    margin: 0.5rem 0 0;
    padding: 0;
}
.conv-river-action + .conv-river-action { margin-top: 0.4rem; }
.conv-river-action-detail {
    padding-left: 1.2rem;
    font-size: var(--text-sm);
    color: var(--text-secondary);
}
.conv-river-action-detail p { margin: 0.3rem 0; }
.conv-river-action-uncertainty { color: var(--attention-amber); }
.conv-river-action-evidence-label {
    font-size: var(--text-xs);
    color: var(--text-metadata);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

/* -- Case Workspace accordion/focus sections ------------------------------- */
.accordion-section {
    border-top: 1px solid var(--border);
    margin-top: 0.6rem;
    padding-top: 0.6rem;
}
.workspace-pane > .accordion-section:first-of-type,
.workspace-pane > h2 + .accordion-section {
    border-top: none;
    margin-top: 0;
    padding-top: 0;
}
.accordion-summary {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.6rem;
    font-family: var(--font-body);
    text-transform: uppercase;
    letter-spacing: 0.1em;
    font-size: 0.8rem;
    color: var(--text-metadata);
    cursor: pointer;
    list-style: none;
}
.accordion-summary::-webkit-details-marker { display: none; }
.accordion-summary::before {
    content: "\25B8";
    display: inline-block;
    margin-right: 0.5rem;
    color: var(--text-secondary);
    font-size: 0.72rem;
    transition: transform 0.12s ease;
}
.accordion-section[open] > .accordion-summary::before { transform: rotate(90deg); }
.accordion-summary-text { flex: 1; }
.accordion-summary-status {
    color: var(--text-metadata);
    font-size: 0.8rem;
    text-transform: none;
    letter-spacing: normal;
    white-space: nowrap;
}
.accordion-section[open] > .accordion-summary { margin-bottom: 0.5rem; }
.collapse-all-btn {
    font-family: var(--font-body);
    font-size: var(--text-sm);
    color: var(--text-metadata);
    background: transparent;
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: 0.35rem 0.65rem;
    cursor: pointer;
}
.collapse-all-btn:hover { color: var(--text-primary); border-color: var(--border-strong); }

/* -- Project Home (calm, single-purpose project entry - Prompt 3) --------- */
/* CLAUDE-P40-E2B: rendered inside .workspace-pane-display now (that
   wrapper carries the plain/neutral Display surface - see its own
   comment above), inside division 0's own .display-division-content. */
.project-home { max-width: 760px; padding: 0.25rem 0 1.5rem; }
.project-home-back { font-size: var(--text-sm); color: var(--text-secondary); margin: 0 0 0.6rem; }
.project-home-back a { color: var(--text-secondary); }
.project-home-back a:hover { color: var(--text-primary); }

/* CLAUDE-P40-VW8-QA (New Investigation Action in Lists): the focused
   create-form panel projected into Display - reuses .project-home's own
   max-width/padding (class list is "project-home new-case-form-panel"),
   these two rules are the only genuinely new geometry needed. */
.new-case-form-heading { font-family: var(--font-display); font-size: 1.3rem; font-weight: 500; margin: 0 0 0.8rem; }
.new-case-form-actions { display: flex; gap: 0.6rem; align-items: center; margin-top: 0.3rem; }

/* CLAUDE-POSTCAMEL-INVESTIGATION-AR1: "Continue from Archive" chooser -
   reuses .project-home/.new-case-form-panel/.source-list/.source-item/
   .pane-note/.link-button/.btn wholesale (same panel this stage's
   "focused form/chooser, never a management page" precedent already
   established for New Investigation); only the row's own internal
   layout (header + trailing actions + an initially-hidden Snapshot
   output) is genuinely new geometry. */
.archive-chooser-row { display: grid; gap: 0.35rem; }
.archive-chooser-row-header { display: flex; align-items: baseline; gap: 0.5rem; flex-wrap: wrap; }
.archive-chooser-row-actions { display: flex; align-items: center; gap: 0.8rem; }
.archive-chooser-continue-form { display: inline; }
.archive-chooser-snapshot { border-left: 2px solid var(--border); padding-left: 0.6rem; margin-top: 0.2rem; }

/* CLAUDE-P40-VW9 (Governed Files Display and Project File Architecture):
   the two governed sibling roots read as different GOVERNANCE SPACES
   without inventing a new color distinction (this file's own header
   comment: color is used rarely, only for the fixed semantic meanings
   above - Data Room/Design-Builder are not one of them). The
   differentiation is structural/neutral instead: Data Room gets the
   quieter --surface-secondary + --border-strong treatment ("controlled,
   structural"); Design-Builder Workspace gets --surface-primary + the
   ordinary --border ("open, editable") - the same two-tier surface
   language main.css already uses elsewhere (nav vs. panel), reused, not
   a new one invented for this stage. .files-surface widens past
   .project-home's own 760px max-width since two side-by-side roots need
   more room; narrows to one stacked column under the existing 900px
   breakpoint (see main.css's own responsive rules), never a second,
   parallel breakpoint system. */
.files-surface { max-width: 1100px; }
.files-roots { display: grid; grid-template-columns: 1fr 1fr; gap: 1.2rem; margin-top: 1rem; }
@media (max-width: 900px) {
    .files-roots { grid-template-columns: 1fr; }
}
.files-root {
    border-radius: 6px;
    padding: 1rem 1.1rem;
}
.files-root-data-room { background: var(--surface-secondary); border: 1px solid var(--border-strong); }
.files-root-design-builder { background: var(--surface-primary); border: 1px solid var(--border); }
.files-breadcrumb { font-size: 0.85rem; color: var(--text-secondary); margin: 0.6rem 0; }
.files-breadcrumb a { color: var(--text-secondary); }
.files-breadcrumb a:hover { color: var(--text-primary); }
.files-folder-list { list-style: none; margin: 0.6rem 0 0; padding: 0; display: grid; gap: 0.25rem; }
.files-folder-row {
    display: flex; align-items: center; justify-content: space-between; gap: 0.5rem;
    padding: 0.35rem 0.5rem; border-radius: 4px;
}
.files-folder-row:hover { background: var(--surface-hover); }
.files-folder-link { color: var(--text-primary); font-size: 0.92rem; }
.files-folder-actions { position: relative; flex-shrink: 0; }
.files-folder-actions summary {
    list-style: none; cursor: pointer; color: var(--text-metadata);
    padding: 0.1rem 0.4rem; border-radius: 4px;
}
.files-folder-actions summary::-webkit-details-marker { display: none; }
.files-folder-actions summary:hover { color: var(--text-primary); background: var(--surface-hover); }
/* CLAUDE-P40-VW9A (Files Cockpit Close-Out, A1/A3): the panel renders
   IN-FLOW, never as a floating absolute overlay. It used to be
   position:absolute/right:0 against .files-folder-actions itself (the
   small "..." trigger near the row's own right edge) - real testing
   this stage found the SAME underlying problem at two different
   widths: at the realistic ~300-400px a multi-Display division
   actually renders at (A3's own ask), the panel genuinely extended
   past the iframe body's own left edge; and, independently, at
   ORDINARY full desktop width, a panel tall enough to hold three
   stacked forms could visually cover several folder rows below it,
   taking THEIR triggers physically out of reach too - not a
   narrow-viewport-only problem after all (confirmed directly by a
   10-folder real-browser regression test, not assumed). :has()
   (already used elsewhere in this file - see
   .conversation-thread:has(...) above) switches the ROW itself to a
   column layout only while its OWN menu is open, so the expanding
   panel pushes later rows down instead of floating over them -
   applied unconditionally rather than gated behind a width
   breakpoint, since the reachability problem it fixes was never
   actually width-specific. A bounded, in-flow variant reusing
   already-tested styling, not a redesign of the page. */
.files-folder-row:has(.files-folder-actions[open]) { flex-direction: column; align-items: stretch; }
.files-folder-actions-panel {
    background: var(--surface-primary); border: 1px solid var(--border-strong);
    border-radius: 6px; padding: 0.6rem; display: grid; gap: 0.5rem;
    margin-top: 0.4rem; width: 100%; box-sizing: border-box;
}
.files-folder-actions-panel form { display: flex; gap: 0.35rem; align-items: center; flex-wrap: wrap; }
@media (max-width: 480px) {
    /* The realistic ~300-400px multi-Display division case: even a
       wrapped horizontal form (select + button) can feel cramped -
       stack its own fields vertically too, at that width only. */
    .files-folder-actions-panel form { flex-direction: column; align-items: stretch; }
}

/* Project Home's title/actions row is now the shared .dash-head (see
   above) instead of its own parallel rule. */
.project-home-subtitle { color: var(--text-secondary); font-size: 0.85rem; margin: 0.35rem 0 0; max-width: 56ch; }

.project-home-controls { display: flex; align-items: center; gap: 0.4rem; flex-shrink: 0; }
.project-home-icon-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    border-radius: 4px;
    border: 1px solid var(--border);
    background: transparent;
    color: var(--text-metadata);
    cursor: pointer;
    font-size: 1rem;
    list-style: none;
}
.project-home-icon-btn::-webkit-details-marker { display: none; }
.project-home-icon-btn:hover { color: var(--text-primary); border-color: var(--border-strong); }
/* Star is a personal bookmark, not a governance or identity signal - tonal
   only, never bee-yellow (reserved) or seal-red (not consequential). */
.project-home-icon-btn.active { color: var(--text-primary); border-color: var(--border-strong); background: var(--surface-selected); }
.project-home-overflow { position: relative; }
.project-home-overflow-panel {
    margin-top: 0.6rem;
    padding: 0.9rem;
    background: var(--surface-primary);
    border: 1px solid var(--border);
    border-radius: 6px;
    max-width: 320px;
}

.project-home-composer { margin: 0 0 0.5rem; }
.composer-plus { margin-top: 0.5rem; }
.composer-plus summary {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.6rem;
    height: 1.6rem;
    border-radius: 4px;
    border: 1px solid var(--border);
    color: var(--text-metadata);
    cursor: pointer;
    font-size: 0.9rem;
    list-style: none;
}
.composer-plus summary::-webkit-details-marker { display: none; }
.composer-plus summary:hover { color: var(--text-primary); border-color: var(--border-strong); }
.composer-plus-links { font-size: 0.85rem; color: var(--text-secondary); margin: 0.5rem 0 0; display: flex; gap: 0.9rem; flex-wrap: wrap; }
.composer-plus-links a { color: var(--machine-blue); }

.project-home-state-message {
    color: var(--text-secondary);
    font-size: 0.8rem;
    line-height: 1.5;
    max-width: 62ch;
    margin: 0.3rem 0 1rem;
}

/* -- left: unpromoted (machine-extracted) Requirements ---------------------
   Same signal as .finding-card's left border below - this is
   machine-produced, provisional-by-nature content too, and previously had
   no visual distinction from ordinary governed text at all (visual
   language review finding: the one concrete gap on a near-empty Project
   Home). Lighter-weight than a full card since this sits inside an
   already-nested accordion, not the main Artifact Workspace list - same
   signal, proportionate to context. */
.unpromoted-requirement {
    border-left: 3px solid var(--machine-blue);
    padding: 0.3rem 0 0.3rem 0.6rem;
    margin: 0.3rem 0;
}
.unpromoted-requirement small { color: var(--machine-blue); }

/* -- right: artifact workspace -------------------------------------------- */
.finding-list { display: grid; gap: 1rem; }
/* The left border marks this as a machine-produced Finding - a genuine,
   restrained use of the machine/reference signal (Prompt: BEEHIVE Visual
   Color System #6), not decoration. */
.finding-card {
    background: var(--surface-secondary);
    border: 1px solid var(--border);
    border-left: 4px solid var(--machine-blue);
    border-radius: 4px;
    padding: 1rem 1.1rem;
}
.finding-card.focused { border-left-color: var(--border-strong); box-shadow: 0 0 0 1px var(--border-strong); }
.finding-statement { font-size: 0.9rem; color: var(--text-primary); margin: 0 0 0.5rem; }
.finding-confidence {
    font-family: var(--font-body);
    font-size: var(--text-sm);
    color: var(--machine-blue);
    text-transform: uppercase;
    letter-spacing: 0.05em;
}

/* CLAUDE-BAUHAUS-CONSTRUCTIVIST-UI-01: the Spin State Report's own
   finding composition - consequence expressed through scale, spacing,
   disclosure state, and left-accent-bar structure (reusing .finding-
   card's own established "left border marks machine-produced" idiom
   above), never through a rainbow of badge colors. See routes/
   workspace.py's own _SPIN_CLASSIFICATION_TREATMENT for the full
   per-classification reasoning (Product Owner constraint: "severity
   should gain visual consequence through composition, scale, position,
   density, spacing, interruption, or structural treatment - not simply
   different coloured badges"). No background fill, no border-radius, no
   box-shadow - continuing this app's own existing de-boxing direction
   (Section: "no cards, shadows, rounded containers"); the accent bar
   itself (border-left width/style/color) is the one structural device
   carrying identity, set inline per-finding since its exact px/color/
   style is real per-finding data, not one of a small fixed set of
   classes. */
.spin-finding {
    padding-top: var(--space-8);
    padding-bottom: var(--space-8);
    padding-left: var(--space-9);
}
.spin-finding > summary {
    list-style: none;
    cursor: pointer;
}
.spin-finding > summary::-webkit-details-marker { display: none; }
.spin-finding-tag {
    font-family: var(--font-display);
    display: block;
}
/* Attention tier (new / new_verification_gap): maximum scale, generous
   isolation (Section 10's own "negative space is active material" -
   isolate consequential information) - rendered pre-expanded (the
   `open` attribute is set per-finding in the template, based on the
   same classification-driven weight, never decoratively) so the
   PM sees the full concern without an extra click. */
.spin-weight-max {
    margin-bottom: var(--space-12);
}
.spin-weight-max .spin-finding-tag {
    font-size: var(--text-xl);
    font-weight: 300;
    letter-spacing: 0.01em;
}
/* Elevated tier (strengthened / indeterminate): real but lesser scale
   contrast than max - also pre-expanded. */
.spin-weight-high {
    margin-bottom: var(--space-11);
}
.spin-weight-high .spin-finding-tag {
    font-size: var(--text-lg);
    font-weight: 500;
}
/* Base tier (weakened / superseded): ordinary body-control scale,
   collapsed by default (a deliberate click away, not hidden). */
.spin-weight-base {
    margin-bottom: var(--space-9);
}
.spin-weight-base .spin-finding-tag {
    font-size: var(--text-md);
    font-weight: 500;
}
/* Quiet/settled tier (resolved / unchanged): the receding end of the
   composition (Product Owner: "let unchanged information recede... stay
   inspectable without competing") - smaller scale, tight vertical
   density, collapsed, secondary text color rather than primary. */
.spin-weight-quiet {
    padding-top: var(--space-5);
    padding-bottom: var(--space-5);
    margin-bottom: var(--space-4);
}
.spin-weight-quiet .spin-finding-tag {
    font-size: var(--text-sm);
    font-weight: 500;
    color: var(--text-secondary);
}
/* The report's own headline (kind + world + status) - real typographic
   authority at the entry point to the whole report, the same "light
   weight where large text stays comfortably readable" rule tokens.css's
   own header already establishes for every other large heading in the
   app (Hero, Gateway brand, Dashboard head) - Spin's own report simply
   never used it before now. */
.spin-report-headline {
    font-family: var(--font-display);
    font-size: var(--text-2xl);
    font-weight: 300;
    margin: 0 0 var(--space-6);
}

.finding-artifact-thumb {
    display: block;
    max-width: 100%;
    border-radius: 3px;
    margin: 0.7rem 0;
    border: 1px solid var(--machine-blue);
}

.finding-provenance {
    font-family: var(--font-mono);
    font-size: var(--text-sm);
    color: var(--text-metadata);
    background: var(--surface-primary);
    border: 1px solid var(--border);
    border-radius: 3px;
    padding: 0.5rem 0.6rem;
    margin-bottom: 0.7rem;
    line-height: 1.6;
    word-break: break-all;
}
.finding-provenance span { color: var(--text-metadata); }
.finding-provenance strong { color: var(--text-primary); font-weight: 500; }

.snapshot-detail {
    background: var(--surface-secondary);
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: 0.7rem 0.9rem;
    margin: 0.6rem 0;
}
.snapshot-detail p:first-child { margin-top: 0; }
.snapshot-detail .source-list { margin-top: 0.4rem; }

.finding-review-actions { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 0.6rem; }
.review-btn {
    font-family: var(--font-body);
    font-size: 0.85rem;
    padding: 0.4rem 0.7rem;
    border-radius: 999px;
    cursor: pointer;
    background: transparent;
}
.review-btn.accept { border: 1px solid var(--accepted-green); color: var(--accepted-green); }
.review-btn.accept:hover { background: var(--accepted-green-tint); }
.review-btn.reject { border: 1px solid var(--failure-red); color: var(--failure-red); }
.review-btn.reject:hover { background: var(--failure-red-tint); }
.review-btn.needs_evidence { border: 1px solid var(--attention-amber); color: var(--attention-amber); }
.review-btn.needs_evidence:hover { background: var(--attention-amber-tint); }
.review-btn.correction { border: 1px solid var(--text-metadata); color: var(--text-metadata); }
.review-btn.correction:hover { border-color: var(--text-primary); color: var(--text-primary); }
/* Human authority / deliberate commitment (Approval Gate "Yes") - the
   sealing-wax accent, used only here, extremely sparingly. */
.review-btn.seal { border: 1px solid var(--seal-red); color: var(--seal-red); }
.review-btn.seal:hover { background: var(--seal-red-tint); }
/* CLAUDE-CA1D-RIVER-01 (the "fourth beat"): each real action is its own
   <form> (create_task_route/add_tag_occurrence_route both require a
   real POST, unlike .conv-next-steps' plain <a> navigation links) - a
   compact horizontal row, same idiom as this stage's own Section 3
   example ("Prepare deadlines · Make tasks · Check conflict"), not a
   stacked list. */
.conv-operational-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-top: 0.5rem;
}
.conv-operational-action-form { display: inline-flex; }
.conv-operational-action-form button[disabled] { opacity: 0.6; cursor: default; }
/* CLAUDE-CA1D-RIVER-PO-02 (Section 17, "action control visual chrome"):
   these two buttons shared .review-btn.needs_evidence wholesale with
   Finding review's accept/reject/needs_evidence/correction/seal pills -
   fine for a deliberate review decision, but the Product Owner found it
   read as a full enclosed capsule for what is really a light, casual
   offer ("Make a Task from this"). Scoped to THIS container only (base
   .review-btn is untouched - Finding review keeps its own pill) so the
   correction cannot bleed into an unrelated, already-working control.
   "Ending-line / side separation rather than top-and-bottom boxing":
   no border/background/radius at rest, a single underline that appears
   on hover/focus (interactivity is still obvious, just not a standing
   box), and a hairline left-rule between adjacent actions instead of
   gap alone - separation without a second enclosure per action. Padding
   keeps the click/tap target close to .review-btn's own original
   height even though the visible box is gone. */
.conv-operational-actions .review-btn {
    border: none;
    border-radius: 0;
    background: transparent;
    padding: 0.35rem 0.1rem;
    border-bottom: 1px solid transparent;
}
.conv-operational-actions .review-btn:hover {
    background: transparent;
    border-bottom-color: currentColor;
}
.conv-operational-action-form:not(:first-child) {
    border-left: 1px solid var(--border);
    padding-left: 0.5rem;
}
/* CLAUDE-CA1D-RIVER-03: a quiet, one-time teaching line, not a second
   toolbar - deliberately plain text (no border/background/icon), the
   same restraint .conv-next-steps's own plain <a> row already uses.
   [hidden] (native, not a class) is the actual on/off switch - this
   rule only styles it for the one time it IS shown. */
.conv-selection-hint {
    font-size: var(--text-xs);
    color: var(--text-metadata);
    font-style: italic;
    margin: 0.3rem 0 0;
}

.review-decision-badge {
    display: inline-block;
    font-family: var(--font-body);
    font-size: 0.8rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    padding: 0.25rem 0.55rem;
    border-radius: 999px;
    margin-top: 0.5rem;
}
.review-decision-badge.accept { color: var(--accepted-green); border: 1px solid var(--accepted-green); background: var(--accepted-green-tint); }
.review-decision-badge.reject { color: var(--failure-red); border: 1px solid var(--failure-red); background: var(--failure-red-tint); }
.review-decision-badge.needs_evidence { color: var(--attention-amber); border: 1px solid var(--attention-amber); background: var(--attention-amber-tint); }
.review-decision-badge.correction { color: var(--text-metadata); border: 1px solid var(--text-metadata); }

.applied-badge {
    display: inline-block;
    font-family: var(--font-body);
    font-size: 0.8rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    padding: 0.3rem 0.6rem;
    border-radius: 3px;
    color: var(--surface-primary);
    background: var(--accepted-green);
    font-weight: 700;
}

.correction-note {
    font-size: var(--text-sm);
    color: var(--text-primary);
    background: var(--surface-secondary);
    border-left: 3px solid var(--text-metadata);
    padding: 0.5rem 0.7rem;
    margin-top: 0.5rem;
}

.apply-bar {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 1rem;
    margin-top: 1.4rem;
    padding-top: 1.2rem;
    border-top: 1px solid var(--border);
}
.apply-bar p { margin: 0; font-size: 0.8rem; color: var(--text-secondary); }
/* Apply Confirmed Findings / Issue RFI: a consequential, approval-gated,
   irreversible commitment - the moment BEEHIVE's own vocabulary calls
   sealing/formal decision. See the color-scarcity rule: this is one of
   the few buttons in the whole application that earns seal-red. */
.apply-button {
    font-family: var(--font-body);
    font-size: 0.85rem;
    padding: 0.7rem 1.2rem;
    border-radius: 4px;
    border: 1px solid var(--seal-red);
    background: var(--seal-red);
    color: var(--surface-primary);
    font-weight: 700;
    cursor: pointer;
    white-space: nowrap;
}
.apply-button:disabled { background: transparent; color: var(--text-disabled); border-color: var(--border); cursor: not-allowed; }

.findings-empty { color: var(--text-secondary); font-size: var(--text-sm); padding: 2rem 0; text-align: center; }

/* -- Reviewer Validation / Disposition / review_state badges --------------
   Three separate concepts, three separate badge families - never merged
   into one color/label so the distinction stays visible in the UI, not
   just in the data model. */
.review-state-badge {
    font-family: var(--font-body);
    font-size: 0.8rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    padding: 0.15rem 0.45rem;
    border-radius: 999px;
    border: 1px solid var(--border);
    color: var(--text-metadata);
}
.review-state-badge.review-state-verified { color: var(--accepted-green); border-color: var(--accepted-green); }
.review-state-badge.review-state-not-verified { color: var(--failure-red); border-color: var(--failure-red); }
.review-state-badge.review-state-unverified { color: var(--text-metadata); border-color: var(--border); }
/* TemporalObligation's derived condition (evaluate_temporal_condition) -
   only the two states someone actually needs to notice get color;
   not-yet-due and the terminal statuses (completed/cancelled/superseded)
   stay the neutral default pill, same "color is rare, only where it
   carries real information" rule as everywhere else in this file. */
.review-state-badge.review-state-overdue { color: var(--failure-red); border-color: var(--failure-red); }
.review-state-badge.review-state-due, .review-state-badge.review-state-due-soon { color: var(--attention-amber); border-color: var(--attention-amber); }
/* CLAUDE-P13: PerspectiveAssessment polarity, reusing the same badge
   component - "risk"/"opportunity" are just two more states in the same
   vocabulary as verified/overdue/etc., not a new visual component. */
.review-state-badge.review-state-risk { color: var(--risk-red); border-color: var(--risk-red); }
.review-state-badge.review-state-opportunity { color: var(--accepted-green); border-color: var(--accepted-green); }

/* CLAUDE-P13: navigation-membrane layer toggle. Layer annotations
   (currently just the risk/opportunity badge below) render into the
   page unconditionally - cheap, they're small badges - and are shown or
   hidden by one class on <html>, exactly like launcher-hidden above. No
   server round-trip to toggle; nothing changes until a reviewer
   explicitly turns a layer on, and the governed content underneath
   (the Requirement's own text, its Perspective disclosure) is completely
   unaffected either way - the map, not the terrain. */
[data-layer] { display: none; }
.layer-risk-active [data-layer="risk"] { display: inline; }

/* CLAUDE-P38 (OBS-12): History defaults to a compact summary; "Show
   full history" flips one class on <html>, same client-side-only
   pattern as the risk layer toggle above - the complete audit record
   (services/governance.py's GovernanceLog) is unaffected either way,
   only what's rendered by default changes. */
.history-full { display: none; }
.history-full-active .history-full { display: block; }
.history-full-active .history-summary { display: none; }

/* Requirement-evidence explainability (Connect Accepted Knowledge to
   Requirement Impact tranche) - maps the existing Relationship.
   relationship_type vocabulary onto the same semantic language already
   used above, not a new truth-status vocabulary. */
.evidence-badge {
    font-family: var(--font-body);
    font-size: 0.8rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    padding: 0.15rem 0.45rem;
    border-radius: 999px;
    border: 1px solid var(--border);
    color: var(--text-metadata);
}
.evidence-badge.evidence-green { color: var(--accepted-green); border-color: var(--accepted-green); }
.evidence-badge.evidence-red { color: var(--failure-red); border-color: var(--failure-red); }
.evidence-badge.evidence-amber { color: var(--attention-amber); border-color: var(--attention-amber); }
.evidence-badge.evidence-cyan { color: var(--machine-blue); border-color: var(--machine-blue); }

.review-decision-badge.validation-correct { color: var(--accepted-green); border: 1px solid var(--accepted-green); background: var(--accepted-green-tint); }
.review-decision-badge.validation-incorrect { color: var(--failure-red); border: 1px solid var(--failure-red); background: var(--failure-red-tint); }
.review-decision-badge.validation-partial,
.review-decision-badge.validation-needs_evidence { color: var(--attention-amber); border: 1px solid var(--attention-amber); background: var(--attention-amber-tint); }
.review-decision-badge.validation-not_applicable { color: var(--text-metadata); border: 1px solid var(--text-metadata); }

.review-decision-badge.disposition-confirmed,
.review-decision-badge.disposition-known_accepted { color: var(--accepted-green); border: 1px solid var(--accepted-green); background: var(--accepted-green-tint); }
.review-decision-badge.disposition-rejected { color: var(--failure-red); border: 1px solid var(--failure-red); background: var(--failure-red-tint); }
.review-decision-badge.disposition-deferred,
.review-decision-badge.disposition-known_pending_acceptance { color: var(--attention-amber); border: 1px solid var(--attention-amber); background: var(--attention-amber-tint); }

/* -- Revision-awareness ---------------------------------------------------- */
.revision-notice {
    background: var(--attention-amber-tint);
    border: 1px solid var(--attention-amber);
    border-left: 4px solid var(--attention-amber);
    border-radius: 4px;
    padding: 0.9rem 1.1rem;
    margin-bottom: 1rem;
}
.revision-notice strong { color: var(--attention-amber); text-transform: uppercase; font-family: var(--font-body); font-size: var(--text-sm); letter-spacing: 0.05em; }
.revision-notice p { color: var(--text-primary); font-size: 0.85rem; margin: 0.4rem 0 0.6rem; }
.region-status { font-family: var(--font-mono); }
.region-status-unchanged { color: var(--accepted-green); }
.region-status-changed { color: var(--failure-red); }
.region-status-unable_to_determine { color: var(--text-metadata); }

/* -- Delegation Choice / RFI preview & draft -------------------------------- */
.delegation-choice, .rfi-preview, .rfi-draft-card {
    background: var(--surface-secondary);
    border: 1px solid var(--machine-blue);
    border-radius: 4px;
    padding: 0.9rem 1.1rem;
    margin: 1rem 0;
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
}
.delegation-choice form, .rfi-preview form, .rfi-draft-card form { display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; }

/* -- Confirm page family (templates/confirm_base.html) -----------------------
   The one-decision interrupt: Approval Gate actions and project deletion
   both need a single centered card, no page header, no nav-adjacent chrome
   - a real, named geometry now, not two copies of an inline-styled
   .case-workspace grid override repurposed for one column. */
.confirm-page { max-width: 560px; margin: 3rem auto; padding: 0 1rem; }

/* -- Approval Gate ----------------------------------------------------------- */
.approval-gate h2 { font-family: var(--font-display); font-size: 1.1rem; margin: 0.4rem 0 1rem; }
.approval-gate-actions { display: flex; flex-direction: column; gap: 0.6rem; margin-top: 1.2rem; align-items: flex-start; }
.approval-gate-actions button { width: 100%; text-align: left; padding: 0.7rem 1rem; }

/* CLAUDE-P40-VW7A: UI Reference Mode - a developer/QA overlay only (the
   "UI Reference Mode" checkbox in the Account menu), never rendered by
   default. Every [data-ui-ref] element gets a small monospace badge via
   ::after and attr(data-ui-ref) - zero JS beyond the toggle itself needed
   to read the value, since CSS's own attr() function renders it
   directly. position:relative is scoped to when the mode is ACTIVE
   only (never applied at rest) so this has zero layout effect on the
   ordinary rendered page - the entire point of a reference registry is
   that consulting it never changes what a reviewer normally sees.
   High z-index (100, one step above every other overlay this app
   defines - see .conv-dialog's own 80) so the badge is never hidden
   behind a popup/dialog while inspecting one. pointer-events:none so
   the badge itself is never a click target, and pre-wrap/monospace so
   a longer dot-path id never gets silently clipped.

   Bounded UI pass (ID badge correction): the badge is a shrink-to-fit
   absolutely-positioned box (no explicit width), so its old symmetric
   0.05rem/0.3rem padding was pure box inflation - it never centered
   anything (there was no extra space to center within) and only ate
   into how much of the available rail width a long dot-path id got
   before crowding its neighbor. Padding removed outright and
   text-align:center added (harmless no-op at today's shrink-to-fit
   width, but correct if this ever gets an explicit width) so the
   badge now reads as a compact identifier chip that hugs its own
   text, not a padded input. */
.ui-reference-mode-active [data-ui-ref] {
    position: relative;
    outline: 1px dashed var(--machine-blue);
    outline-offset: 1px;
}
.ui-reference-mode-active [data-ui-ref]::after {
    content: attr(data-ui-ref);
    position: absolute;
    top: -0.05rem;
    left: -0.05rem;
    transform: translateY(-100%);
    z-index: 100;
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    line-height: 1.3;
    text-align: center;
    color: var(--surface-primary);
    background: var(--machine-blue);
    padding: 0;
    border-radius: 2px;
    white-space: pre;
    pointer-events: none;
}

/* =========================================================================
   CLAUDE-SPIN-00A - reusable right-panel tool-control grammar
   ---------------------------------------------------------------------
   Introduced for the Spin container comparison exercise, but written as
   genuinely reusable primitives (no Spin-only assumptions baked in) so a
   future right-panel tool can compose from these instead of writing new
   page-specific markup/CSS - the Product Owner's own "choose approved
   container -> compose approved components -> configure content" goal.
   Every value below is a token reference, no new hex colors introduced.
   ========================================================================= */

/* -- tool-control-row: label + a small trailing control area. Compact,
   professional, repeated-use density - NOT a consumer settings-list row
   (no icon, no chevron, no full-row hover-highlight ceremony). */
.tool-control-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-5);
    padding: var(--space-3) 0;
    border-bottom: 1px solid var(--border);
}
.tool-control-row:last-child { border-bottom: none; }
.tool-control-row-label {
    font-family: var(--font-body);
    font-size: var(--text-sm);
    color: var(--text-primary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* -- tool-toggle: a compact ON/OFF control for professional, repeated
   use - deliberately a small rectangular button (role="switch" via
   aria-pressed), not an iOS-style pill/thumb switch (PO: "avoid large
   consumer-style switches"). Same tonal-contrast-on-press idiom as
   .btn-primary, just much smaller. */
.tool-toggle {
    font-family: var(--font-body);
    font-size: var(--text-2xs);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    line-height: 1;
    padding: 0.3rem 0.55rem;
    border-radius: 3px;
    border: 1px solid var(--border-strong);
    background: transparent;
    color: var(--text-secondary);
    cursor: pointer;
    flex-shrink: 0;
    min-width: 3.2rem;
    text-align: center;
}
.tool-toggle:hover { border-color: var(--text-primary); color: var(--text-primary); }
.tool-toggle[aria-pressed="true"] {
    background: var(--text-primary);
    border-color: var(--text-primary);
    color: var(--surface-primary);
    font-weight: 700;
}

/* -- active-set-summary: reuses .workspace-pane-label's own small-caps
   metadata typography for the label half, plain body text for the
   value - no new type scale introduced. */
.active-set-summary { margin: var(--space-4) 0; }
.active-set-summary-label {
    font-family: var(--font-body);
    text-transform: uppercase;
    letter-spacing: 0.1em;
    font-size: var(--text-xs);
    color: var(--text-metadata);
    margin: 0 0 var(--space-2);
}
.active-set-summary-value {
    font-family: var(--font-body);
    font-size: var(--text-sm);
    color: var(--text-primary);
    font-weight: 600;
}

/* -- gauge-slot: a neutral, restrained reservation for the future
   Project Pulse / PM Situational Gauge - explicitly NOT a real gauge
   (no fabricated score, no needle, no color-coded health value). A
   dashed border reads as "reserved," not "broken/empty by mistake". */
.gauge-slot {
    border: 1px dashed var(--border-strong);
    border-radius: 3px;
    padding: var(--space-6) var(--space-5);
    text-align: center;
    color: var(--text-metadata);
    font-size: var(--text-xs);
}
.gauge-slot-title {
    display: block;
    font-family: var(--font-display);
    font-size: var(--text-sm);
    color: var(--text-secondary);
    margin-bottom: var(--space-1);
}

/* -- Baseline / Spin Active status: two more states in the same
   .review-state-badge vocabulary already reused for risk/opportunity
   above - not a new badge component. */
.review-state-badge.review-state-baseline { color: var(--text-metadata); border-color: var(--border); }
.review-state-badge.review-state-spin-active { color: var(--accepted-green); border-color: var(--accepted-green); }

/* -- Spin canonical grammar (templates/_spin_prototype.html) -----------
   CLAUDE-SPIN-01: the Product Owner selected Alternative A (Compact
   Control Stack) as the base, plus one incorporated pairing from
   Alternative B - the status badge and active-set summary grouped into
   one header block (.spin-status-strip) so engagement state reads at a
   glance without scrolling. B's physical Engaged/Not-Engaged row
   regrouping and C's always-visible sequence display were both
   explicitly NOT adopted (see the template's own header comment) - their
   CSS (.spin-variant-switcher*, .spin-sequence-row/.spin-sequence-index,
   per-variant .spin-discipline-list/.gauge-slot overrides) is retired
   along with the markup that used it, not left as dead weight.

   CLAUDE-PROJECT-SURFACE-CONSOLIDATION-01 (Part I): .spin-global-controls
   (the "All ON"/"All OFF"/"Return to Baseline" button trio) is retired
   below in favor of .spin-trigger (one SPIN action) and the compact
   per-row .spin-selector/.spin-findings-box/.spin-row-disclosure grammar
   - same real-estate-conscious density precedent as the Appearance
   selector above (small, plain, no icon/chevron ceremony), following a live
   Product Owner review that rejected the repeated large ON/OFF buttons. */
.spin-status-strip {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: var(--space-4);
    margin-bottom: var(--space-4);
    background: var(--surface-secondary);
    border-radius: 3px;
}
.spin-trigger { margin-bottom: var(--space-4); }
.spin-discipline-list { display: flex; flex-direction: column; max-height: 15rem; overflow-y: auto; }
.spin-row {
    display: grid;
    grid-template-columns: 1fr 1.5rem 2.5rem 1.25rem;
    align-items: center;
    gap: var(--space-3);
}
/* -- spin-selector: "included in the next Spin," not a persistent power
   state - a small circle (○/●), deliberately not styled like
   .tool-toggle's rectangular ON/OFF button, so it reads as a lighter-
   weight selection affordance rather than a switch. */
.spin-selector {
    width: 0.9rem;
    height: 0.9rem;
    border-radius: 50%;
    border: 1px solid var(--border-strong);
    background: transparent;
    cursor: pointer;
    padding: 0;
    justify-self: center;
}
.spin-selector:hover { border-color: var(--text-primary); }
.spin-selector[aria-pressed="true"] { background: var(--text-primary); border-color: var(--text-primary); }
/* -- spin-findings-box: honestly blank ("[ ]") until a real investigation
   completes - this prototype layer never populates it (see
   spin_prototype.js's own header comment); never a fabricated count. */
.spin-findings-box {
    font-family: var(--font-body);
    font-size: var(--text-xs);
    color: var(--text-metadata);
    text-align: center;
}
.spin-row-disclosure {
    font-family: var(--font-body);
    font-size: var(--text-sm);
    line-height: 1;
    color: var(--text-metadata);
    background: transparent;
    border: none;
    cursor: pointer;
    padding: 0;
    justify-self: center;
}
.spin-row-disclosure:hover { color: var(--text-primary); }
.spin-row-disclosure[aria-expanded="true"] { color: var(--text-primary); }
.spin-row-detail { padding: 0 0 var(--space-3) var(--space-3); }
.spin-row-detail .pane-note { margin: 0; }

/* ============================================================
   CLAUDE-MOBILE-FRAME-02 — the mobile-first workspace frame
   ============================================================
   The Product Owner's clarified mobile architecture, and now the
   application's primary one:

        TOP     fixed compact header  - where am I, what am I working on
        MIDDLE  ONE active work tray  - where the work happens
        BOTTOM  fixed Composer        - how I talk to GO

   Everything below is PRESENTATION only. Nothing here can change
   authorization, project context, evidence, or any project fact - the
   separation of presentation state from project state is structural
   here, not a convention: a stylesheet cannot post, and
   static/js/workspace_trays.js deliberately never fetches.

   The frame is expressed as ONE state - `data-tray-focus` on <html> -
   applied at every width. An attribute holds exactly one value, so
   "one active work tray" is structurally true rather than four
   booleans that have to be kept in agreement. On a phone that state is
   always set (the frame IS the layout). On desktop it is optional: set
   it and one tray takes the work area for concentrated work, clear it
   and the ordinary LAY-5A composition returns. Same architecture,
   progressively more room - not a second application.

   Deliberately NOT rebuilt here: LAY-5A itself, the panel dividers'
   collapse mechanism (html.launcher-hidden/html.toolbox-hidden), the
   Eye/Toolbox splitter, and --chat-height's single write point in
   case_workspace.js. This block composes with all four.
   ------------------------------------------------------------ */

/* -- the top zone: function switcher -------------------------------------
   The bottom edge of the fixed header - current work surface named on the
   left, the switcher on the right. Restrained on purpose: text labels in
   the metadata tier, one hairline, no icons, and no color except the
   active state's --machine-blue (see this file's own header comment on
   when color is allowed to mean something). It reuses the application's
   OWN surface vocabulary - Lists/Display/Eye/Toolbox, the same names the
   Appearance menu already uses - so it introduces navigation, not nouns. */
.tray-switcher {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    flex-shrink: 0;
    padding: 0.25rem 1.2rem 0.35rem;
    border-bottom: 1px solid var(--border);
    background: var(--surface-primary);
    position: relative;
    z-index: 29;
}
.tray-switcher-context {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 0.72rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-metadata);
}
.tray-switcher-options {
    display: flex;
    align-items: center;
    gap: 0.15rem;
    flex-shrink: 0;
}
.tray-switcher-btn {
    appearance: none;
    background: none;
    border: none;
    border-radius: 4px;
    padding: 0.3rem 0.6rem;
    font: inherit;
    font-size: 0.78rem;
    color: var(--text-secondary);
    cursor: pointer;
}
.tray-switcher-btn:hover { color: var(--text-primary); background: var(--surface-hover); }
.tray-switcher-btn:focus-visible { outline: 2px solid var(--machine-blue); outline-offset: 1px; }
.tray-switcher-btn[aria-pressed="true"] { color: var(--machine-blue); }

/* -- the frame: one tray owns the work area ------------------------------
   Non-active trays keep their DOM - a Composer draft, a scroll position, a
   selected Document, an unsaved Eye capture all survive a switch and a
   rotation. They surrender their box, never their content; display:none is
   the same mechanism the existing collapse controls already use for
   exactly that reason.

   The Composer is the one thing focus never hides. It is a ZONE, not a
   tray: there is no "switch to Composer", which is precisely why the
   future photo flow needs no "Open in Composer" handoff. It is lifted out
   of .workspace-main-column's column flow to span the frame - by position,
   not by moving it in the DOM, so every existing script that reaches into
   #chat-region by id keeps working untouched. */
/* DESKTOP focus mode only. The phone gets the same anchoring unconditionally,
   from the media query below - see this file's own CLAUDE-MOBILE-FRAME-PIN-01
   block. Splitting them is deliberate: on a phone the frame is the layout and
   must not depend on a script having run, whereas on desktop "focused" is
   genuinely an application state. */
html[data-tray-focus] .chat-region {
    position: fixed;
    left: 0;
    right: 0;
    /* --kb-inset is published by workspace_trays.js from visualViewport:
       0 everywhere the software keyboard resizes the layout viewport
       properly, and the keyboard's real height on iOS, which does not.
       This is what keeps the Composer usable with the keyboard open. */
    bottom: var(--kb-inset, 0px);
    height: var(--chat-height, 220px);
    border-top: 1px solid var(--border);
    z-index: 26;
}
/* :has(), not a bare selector: the Composer is gated on project_id/workspace,
   so a project-less page has no .chat-region at all and reserving its height
   there would leave a dead strip below the work area with nothing in it. The
   technique is already established in this file (see .conversation-thread:has()
   and .files-folder-row:has()). */
html[data-tray-focus] .app-shell-body:has(.chat-region) {
    padding-bottom: var(--chat-height, 220px);
}
/* The dividers are collapse controls for a multi-column composition that
   is not on screen in this state. */
html[data-tray-focus] .app-shell-body > .panel-divider { display: none; }

/* The column holding Display+Composer must stay rendered even when Display
   is not the active tray, or the Composer inside it goes with it - so it
   collapses to zero width rather than to display:none, and stops clipping,
   since its fixed child now paints outside it. */
html[data-tray-focus]:not([data-tray-focus="display"]) .workspace-main-column {
    flex: 0 0 0;
    min-width: 0;
    overflow: visible;
}
html[data-tray-focus]:not([data-tray-focus="display"]) .app-main { display: none; }

/* A tray that is active is a tray that is shown - focus overrides a
   collapse the reviewer set earlier, rather than presenting them with an
   empty frame and no way to read what happened. */
html[data-tray-focus="lists"] .launcher-panel,
html[data-tray-focus="toolbox"] .workspace-right-column {
    display: flex;
    position: static;
    width: auto;
    max-width: none;
    flex: 1 1 auto;
    min-width: 0;
}
html[data-tray-focus="display"] .launcher-panel,
html[data-tray-focus="toolbox"] .launcher-panel { display: none; }
html[data-tray-focus="display"] .workspace-right-column,
html[data-tray-focus="lists"] .workspace-right-column { display: none; }

/* Eye and Toolbox share one column, split by the Eye/Toolbox divider.
   Focusing Toolbox gives it the whole column; the divider has nothing left
   to divide, so it goes too, and the splitter's own persisted proportion is
   untouched and returns intact when focus is cleared. Eye has no equivalent
   rule here because Eye is no longer a tray at all - it is the foreground
   LAYER (CLAUDE-MOBILE-Q-TRIAL-01 Section 4, at the end of this file). */
html[data-tray-focus="toolbox"] .eye-pane,
html[data-tray-focus="toolbox"] .toolbox-eye-divider { display: none; }
html[data-tray-focus="toolbox"] .workspace-pane-toolbox {
    height: auto;
    max-height: none;
    flex: 1 1 auto;
    min-height: 0;
}

/* -- the work/Composer boundary, at thumb size ---------------------------
   The desktop handle for this same boundary
   (#conversation-dock-resize-handle) is a 9px pointer-precision strip
   carrying two micro-buttons: correct for a mouse, unusable with a thumb.
   This is that boundary sized for touch. It is not a second sizing system -
   every value it produces goes through window.__chatSplitter.setValue(),
   the one existing write point for --chat-height. */
.tray-composer-grabber { display: none; }
.tray-composer-grabber-bar {
    display: block;
    width: 42px;
    height: 4px;
    border-radius: 2px;
    background: var(--border-strong);
}

.mobile-nav-toggle {
    appearance: none;
    background: none;
    border: 1px solid var(--border);
    border-radius: 4px;
    /* Touch target first, glyph second. */
    min-width: 40px;
    min-height: 36px;
    align-items: center;
    justify-content: center;
    font-size: 1rem;
    line-height: 1;
    color: var(--text-secondary);
    cursor: pointer;
    flex-shrink: 0;
}
.mobile-nav-toggle:focus-visible { outline: 2px solid var(--machine-blue); outline-offset: 1px; }
.mobile-nav-toggle[aria-expanded="true"] { color: var(--machine-blue); border-color: var(--machine-blue); }

/* -- phone widths --------------------------------------------------------
   Portrait is the primary operating posture. Nothing below asks the
   reviewer to rotate the device to reach anything. */
@media (max-width: 640px) {
    /* 100dvh, not 100vh: on iOS Safari 100vh is the height the viewport
       has with browser chrome RETRACTED, so a 100vh shell is taller than
       the screen and a bottom-anchored Composer starts life below the
       fold. dvh tracks the viewport that is actually visible. The vh line
       stays first as the fallback for anything without dvh. */
    .app-shell { height: 100vh; height: 100dvh; }

    /* The eight-menu desktop menubar wrapped into two or three full-width
       rows of competing labels here and ate the workspace - the Product
       Owner's own iPhone screenshot. All three of the usual escapes are
       ruled out (compressed row, horizontal scroll, truncated labels), so
       the same nav becomes a drawer behind one control. No second mobile
       menu, no duplicate command implementations. */
    .mobile-nav-toggle { display: inline-flex; }
    .workspace-menubar { display: none; }
    html.mobile-nav-open .workspace-menubar {
        display: flex;
        flex-direction: column;
        align-items: stretch;
        gap: 0;
        position: fixed;
        top: 0;
        left: 0;
        bottom: 0;
        width: min(86vw, 320px);
        z-index: 40;
        overflow-y: auto;
        padding: 0.6rem;
        background: var(--surface-primary);
        border-right: 1px solid var(--border-strong);
    }
    html.mobile-nav-open .workspace-menubar-menu { position: static; }
    html.mobile-nav-open .workspace-menubar-panel {
        position: static;
        box-shadow: none;
        border: none;
        min-width: 0;
    }

    /* CLAUDE-MOBILE-SUBMENU-REPAIR-01 - live iPhone report: "the menu on phone
       is not stable."

       The drawer flattened the TOP-LEVEL menus (position: static, above) but
       never the NESTED ones. .workspace-menubar-subpanel keeps its desktop
       flyout geometry - position: absolute, left: calc(100% + 0.2rem),
       min-width: 200px - inside a drawer that is min(86vw, 320px) wide. On a
       390px phone that opens a 200px panel starting ~320px from the left edge:
       entirely off-screen.

       So tapping Admin appeared to do nothing at all. The menu was not
       unstable, it was opening something the phone could never show - which is
       indistinguishable from broken, and worse, because the tap registered.

       Same treatment the parents already get, for the same reason: a drawer is
       a vertical list, and a flyout has nothing to fly out over. Indented so
       the nesting is still legible once flattened. Desktop is untouched. */
    html.mobile-nav-open .workspace-menubar-submenu { position: static; }
    /* All THREE nested panel classes, not just the shared one.
       View > Appearance and Display Layout are also
       .workspace-menubar-submenu, but their panels carry their own classes
       (.workspace-appearance-options / .workspace-layout-options) and are
       absolutely positioned too - top: calc(100% + 0.3rem).
       
       Flattening only the shared class would have been worse than doing
       nothing for those two: removing position:relative from the submenu above
       takes away the positioning context they were anchored to, so they would
       have detached and resolved against the fixed drawer instead. A test
       counts submenus against panels so a fourth variant cannot be missed
       silently. */
    html.mobile-nav-open .workspace-menubar-subpanel,
    html.mobile-nav-open .workspace-appearance-options,
    html.mobile-nav-open .workspace-layout-options {
        position: static;
        top: auto;
        left: auto;
        min-width: 0;
        box-shadow: none;
        border: none;
        padding-left: 0.8rem;
    }

    /* CLAUDE-MOBILE-MENU-REPAIR-01 - live iPhone report. The drawer above
       already worked (toggle, Escape, close-on-choose, outside-tap dismiss);
       what it contained was the desktop menu transplanted. Three bounded
       corrections, all mobile-only. Desktop is untouched.

       1. TOUCH SIZE. The items kept their desktop height, so a menu built for a
          cursor was being driven with a thumb. 44px is this shell's own
          established target - composer attach, capture review and the landing
          sound toggle all use it - not a new number invented here. */
    html.mobile-nav-open .workspace-menubar-item {
        min-height: 44px;
        display: flex;
        align-items: center;
    }

    /* 2. THE LEFTOVER STRIP READ AS BROKEN. The drawer is min(86vw, 320px), so
          on a 390px phone roughly 70px of Composer stayed visible beside it.
          That strip was ALREADY a dismiss target - workspace_trays.js closes on
          any outside tap - but nothing said so, and an inert-looking sliver of
          the work you were mid-way through reads as a rendering fault rather
          than an invitation. The scrim makes existing behaviour visible; it adds
          no behaviour.

          A pseudo-element, so there is no new DOM node to keep in sync: it is
          not an event target, so the tap still lands on <body> and the existing
          outside-click handler still closes the drawer. z-index sits below the
          drawer's own 40. */
    html.mobile-nav-open body::after {
        content: "";
        position: fixed;
        inset: 0;
        z-index: 39;
        background: rgba(0, 0, 0, 0.45);
    }

    /* CLAUDE-MOBILE-DRAWER-STACKING-01 - live report: "the desktop top menu
       works but not on the phone."

       The drawer sets z-index: 40 and the scrim above sets 39, which reads as
       correct and is not, because z-index only orders siblings WITHIN a
       stacking context. .workspace-menubar lives inside .workspace-topbar,
       which is position: relative with z-index: 31 and therefore creates its
       own context. The drawer's 40 is scoped inside it, so the whole topbar
       subtree stacks at 31 - and the scrim, a child of body at 39, paints over
       all of it.

       The result was not a menu that looked wrong. It was a menu that LOOKED
       CORRECT and did not respond: every drawer item hit-tested to BODY, and a
       synthetic tap on any of them timed out because the element could never
       receive the event. The first tap worked only because the scrim does not
       exist until the drawer opens. Desktop was never affected - there is no
       scrim there, which is exactly why it worked and the phone did not.

       Raising the TOPBAR is the fix, not raising the drawer: the drawer cannot
       escape a context it is nested in, so its own z-index can never win.
       Lifting the topbar above the scrim keeps the intended order - content
       dimmed, drawer above it - and leaves the hamburger reachable to close
       what it opened. Mobile-only; desktop stacking is untouched. */
    html.mobile-nav-open .workspace-topbar {
        z-index: 41;
    }

    /* 3. DISABLED ITEMS ARE NOISE HERE, NOT INFORMATION. Thirteen of the
          drawer's sixty-one items are disabled in a given context, and each
          explains itself through a title attribute ("Admin only", "Open a
          Project first") that a touch device cannot surface - there is no
          hover. On a phone they occupy scarce space while being both unusable
          AND unexplainable.

          This hides a control that already cannot act. It removes no
          capability, changes no authority, and is not a decision about which
          menus belong on a phone - that question is left open deliberately. On
          desktop they stay, where the tooltip works and a greyed row genuinely
          teaches what exists. */
    html.mobile-nav-open .workspace-menubar-item-disabled,
    html.mobile-nav-open .workspace-menubar-item[aria-disabled="true"] {
        display: none;
    }

    /* CLAUDE-DEVELOPER-MENU-01: the only way out of Developer Mode must be
       hittable with a thumb - same 44px floor as the menu repair. A miss on a
       small target leaves the person stuck in the mode. */
    .workspace-developer-mode-exit { min-width: 44px; min-height: 44px; }

    /* CLAUDE-MOBILE-DOCUMENT-CONVERGENCE-01 - when a document is open on a
       phone, the evidence becomes the job.

       CA1 Section AC: one primary job per phone screen. Opening a drawing and
       then having to scroll past three restatements of its filename to reach it
       is the opposite of that.

       VERIFIED, NOT ASSUMED: .workspace-pane-document is nested INSIDE the same
       .display-division-primary that carries .display-division-header, so on a
       phone the filename renders in the division header and again as this
       pane's own <h2>, one above the other, on the same screen. The document tab
       strip carries it a third time.

       Nothing is removed and no capability is hidden. Every control below is
       still present, still visible and still tappable - they stop occupying
       three stacked rows above the evidence. Desktop is untouched: all of this
       is inside the phone breakpoint. */

    /* 1. ONE IDENTITY, NOT THREE. The division header immediately above this
          pane already states the filename, so this heading repeats it with no
          new information. The pane's own kind/status label below it is KEPT -
          "PDF, superseded by a later revision" is information the header does
          not carry. */
    .workspace-pane-document > h2 {
        display: none;
    }

    /* 2. DOCUMENT MANAGEMENT STOPS STACKING. Download / Replace / Remove wrapped
          onto two or three full-width rows at phone width. They remain fully
          visible and reachable - a governed action must never be hidden merely
          to tidy a screen - but they now sit on ONE horizontally scrollable
          row, which is the difference between secondary and buried.

          scrollbar-width: none because a visible scrollbar on a three-button
          strip reads as breakage; the row is short enough to reach by dragging,
          and nothing is off-screen at typical phone widths. */
    .workspace-pane-document .document-primary-actions {
        display: flex;
        flex-wrap: nowrap;
        gap: 0.4rem;
        overflow-x: auto;
        scrollbar-width: none;
        -webkit-overflow-scrolling: touch;
        padding-bottom: 0.15rem;
    }
    .workspace-pane-document .document-primary-actions > * {
        flex: 0 0 auto;
    }
    .workspace-pane-document .document-primary-actions::-webkit-scrollbar {
        display: none;
    }

    /* 3. THE EVIDENCE TAKES THE RECLAIMED SPACE. Without a floor the viewer is
          only as tall as its content wants to be, so a page rendered small stays
          small while chrome keeps its full height above it. 55vh is a floor, not
          a fixed height - the viewer still grows, and its own internal scroll
          region (main:has(.document-viewer-canvas-container) removes the outer
          one) still owns the gesture. */
    .workspace-pane-document .document-viewer-canvas-container,
    .workspace-pane-document .document-viewer-image,
    .workspace-pane-document iframe {
        min-height: 55vh;
    }

    .workspace-topbar {
        gap: 0.4rem;
        padding: 0.35rem 0;
        margin-bottom: 0;
        flex-wrap: nowrap;
    }
    .workspace-topbar-identity { flex-wrap: nowrap; gap: 0.4rem; min-width: 0; }
    /* Project identity is the one breadcrumb element that must survive at
       this width - "which project am I in". It keeps its own ellipsis
       rather than being dropped. */
    .workspace-topbar-context { max-width: none; flex: 1 1 auto; min-width: 0; }

    .tray-switcher { padding: 0.2rem 0.6rem 0.3rem; }
    /* The active-surface readout is already the aria-live announcement and
       is duplicated by the pressed button beside it; at 320px the four
       switch targets are worth more than the label. */
    .tray-switcher-context { display: none; }
    .tray-switcher-options { flex: 1 1 auto; gap: 0.25rem; }
    .tray-switcher-btn {
        flex: 1 1 0;
        min-width: 0;
        /* A real touch target rather than a text-sized one. */
        min-height: 40px;
        padding: 0.3rem 0.2rem;
        border: 1px solid var(--border);
    }
    .tray-switcher-btn[aria-pressed="true"] {
        border-color: var(--machine-blue);
        background: var(--machine-blue-tint);
    }

    /* CLAUDE-MOBILE-FRAME-PIN-01 (Product Owner, live phone use: "the
       chat.composer is not fixed at the bottom... the page menu is not fixed
       on top").

       Un-gated on purpose. These used to apply only under
       html[data-tray-focus], a JS-set attribute - so any page or moment
       without it (a project-less page, an unresolvable stored tray key, a
       blocked script, a stale cached stylesheet) dropped the Composer back to
       the desktop `position: sticky` rule, which anchors to a column rather
       than to the viewport. At this width the frame is the layout, so it is a
       property of the viewport, not of application state. */
    .chat-region {
        position: fixed;
        left: 0;
        right: 0;
        bottom: var(--kb-inset, 0px);
        height: var(--chat-height, 220px);
        border-top: 1px solid var(--border);
        z-index: 26;
    }

    /* The header stays put because .app-shell is a fixed-height flex column
       with overflow:hidden - so every child that must not move needs a shrink
       guard. .tray-switcher already had one; .workspace-topbar never did, and
       it is precisely the child a flex container compresses first because it
       is the one that wraps (eight menus plus a breadcrumb). */
    .workspace-topbar,
    .tray-switcher {
        flex-shrink: 0;
    }

    /* body resolves height:100% against the LAYOUT viewport, which on iOS is
       the URL-bar-retracted height - taller than what is actually visible, so
       the shell and the viewport disagreed about where "the bottom" is. */
    html,
    body {
        height: 100vh;
        height: 100dvh;
    }

    /* CLAUDE-MOBILE-FRAME-PIN-02 (Product Owner: "when I move the upper part
       of the page up and down the full page moves up and down... when I move
       the composer it moves up and down as well").

       overflow:hidden above is NOT enough on iOS. Safari still rubber-bands
       the whole document on a touch-drag over any non-scrollable area, and
       while the document is displaced everything positioned against the
       viewport travels with it - so the "unpinned" header and the "moving"
       Composer were one bug wearing two faces, which no amount of pinning
       could have fixed. Taking the document out of flow is the difference
       between "this does not scroll" and "there is nothing here to scroll". */
    body {
        position: fixed;
        inset: 0;
        width: 100%;
    }
    html,
    body {
        overscroll-behavior: none;
    }

    /* CLAUDE-MOBILE-FRAME-PIN-03: "I can shift the screen left a little bit.
       That must be fixed like any other mobile application in the world."

       overscroll-behavior above governs what happens at the END of a
       scrollable axis; it does nothing about a horizontal axis that exists
       because something inside is genuinely too wide. .app-shell-body already
       clipped its own overflow, but the topbar above it and the fixed Composer
       below are outside that container entirely, so neither was covered.

       touch-action: pan-y refuses the browser's horizontal pan gesture
       outright - it never starts, rather than starting and springing back,
       which is the difference between feeling native and feeling like a web
       page. Deliberately NOT applied to inner scroll regions: a wide table, a
       drawing or a PDF page must still scroll sideways inside its own box.
       This stops the SCREEN moving, not the content. */
    html,
    body,
    .app-shell {
        max-width: 100%;
        overflow-x: hidden;
        touch-action: pan-y;
        overscroll-behavior-x: none;
    }
    .workspace-topbar,
    .tray-switcher,
    .chat-region {
        max-width: 100%;
        overflow-x: hidden;
    }
    /* The blueprint backdrop is fixed and inset:0, but it paints a repeating
       background - if anything ever gives it a width larger than the viewport
       it becomes a silent horizontal extent of its own. */
    .blueprint-grid {
        max-width: 100vw;
        overflow: hidden;
    }

    /* Scroll chaining is the default: reach the end of an inner region, keep
       dragging, and the gesture is handed to the document. That is precisely
       the Composer case - the conversation thread lives inside it, so running
       out of thread used to drag the page. Every real scroll region in this
       shell ends the gesture it owns. */
    main,
    .lists-pane,
    .thumbnails-list,
    .workspace-pane-toolbox,
    .eye-pane-body,
    .eye-canvas-viewport,
    .eye-document-body,
    .document-viewer-canvas-container,
    .conversation-thread,
    html.mobile-nav-open .workspace-menubar {
        overscroll-behavior: contain;
    }

    /* "Leave a margin at the bottom and top because there are visual
       cluster." Content used to butt straight against the fixed header above
       and the fixed Composer below. The gap goes on the CONTENT, never on the
       Composer itself - a margin there would open a transparent strip with
       the scrolling work showing through underneath it. */
    main {
        padding-top: 0.75rem;
        padding-bottom: 0.75rem;
    }
    .lists-pane,
    .workspace-pane-toolbox,
    .eye-pane-body {
        padding-top: 0.5rem;
    }
    .app-shell-body:has(.chat-region) {
        padding-bottom: calc(var(--chat-height, 220px) + 0.5rem);
    }

    /* The 320px floor below exists to stop Lists and the right column
       squeezing Display on a DESKTOP. At 320px of actual viewport it is
       the thing that guarantees horizontal overflow, and in the frame
       there is no second column left to squeeze Display anyway. */
    .workspace-main-column { min-width: 0; }
    .app-shell-body { overflow-x: hidden; }

    .tray-composer-grabber {
        display: flex;
        align-items: center;
        justify-content: center;
        min-height: 28px;
        padding: 0.5rem 0;
        margin: 0;
        cursor: row-resize;
        touch-action: none;
        background: var(--surface-primary);
    }
    .tray-composer-grabber:focus-visible {
        outline: 2px solid var(--machine-blue);
        outline-offset: -2px;
    }
    .tray-composer-grabber.dragging .tray-composer-grabber-bar,
    .tray-composer-grabber:focus-visible .tray-composer-grabber-bar {
        background: var(--machine-blue);
    }
    /* The desktop splitter strip and its lock/link micro-buttons are
       pointer-precision controls; the grabber above replaces them here. */
    .conversation-dock-resize-handle { display: none; }

    /* With the keyboard up the frame is mostly keyboard. Give the tray a
       floor so it stays a work surface rather than a sliver, and let the
       Composer have the rest. */
    html.keyboard-open .app-shell-body { min-height: 96px; }
}

/* Above the phone breakpoint the drawer control has nothing to do - the
   real menubar is on screen. */
@media (min-width: 641px) {
    .mobile-nav-toggle { display: none; }
}

/* CLAUDE-MOBILE-Q-TRIAL-01, Section 6 — mic discoverability, silently.

   The automatic spoken welcome is retired (it mispronounced the brand, and
   "wrong pronunciation creates distrust"), but the mic still has to be
   findable. This is the whole of that affordance: one thin ring that breathes
   outward twice and extinguishes itself, once per reviewer.

   READY and LISTENING are required to be unmistakably different states, so
   they share no property:

     READY (this)      transparent button, thin --machine-blue ring, expands
                       and fades, self-extinguishing, never returns.
     LISTENING (above) solid --attention-amber fill and border, no motion,
                       persists exactly as long as the key is held.

   The cancel rule below makes that structural rather than a convention - the
   ring cannot render while listening even if both classes were somehow set at
   once. Nothing here requests microphone permission; it is a class and a
   keyframe. */
.voice-input-button.voice-input-available-cue {
    position: relative;
}
.voice-input-button.voice-input-available-cue::after {
    content: "";
    position: absolute;
    /* Outside the button's own border so the ring reads as emanating FROM the
       control rather than as a second border on it. */
    inset: -3px;
    border-radius: 6px;
    border: 1px solid var(--machine-blue);
    opacity: 0;
    pointer-events: none;
    animation: voice-available-breath 2.2s ease-out 2;
}
/* Two states must never be on screen together. */
.voice-input-button.voice-input-listening::after {
    display: none;
}
@keyframes voice-available-breath {
    0%   { opacity: 0;    transform: scale(0.94); }
    35%  { opacity: 0.5;  transform: scale(1.02); }
    100% { opacity: 0;    transform: scale(1.14); }
}
/* This codebase's established convention: skip non-essential ambient effects
   outright rather than slowing them down. The mic is still visible, still
   labelled, and still works - only the ripple is withheld. */
@media (prefers-reduced-motion: reduce) {
    .voice-input-button.voice-input-available-cue::after { animation: none; }
}

/* ============================================================
   CLAUDE-MOBILE-Q-TRIAL-01, Section 4 — the foreground layer
   ============================================================
   "While working on the main panel, open a drawing/document/photo directly
   from the current screen, bring it to the foreground, work with it, then
   shovel it back to reveal the main panel exactly where it was." A drawing
   sheet laid on a desk and slid off again.

   A TRAY replaces what is in the work area. A LAYER covers it. That is the
   whole distinction, and it is what makes "exactly where it was" structural
   rather than a promise: nothing beneath the layer is hidden, resized or
   re-attributed, so there is no state to restore - no scroll to recapture,
   no selection to re-apply, no Composer draft to preserve, because none of
   it was ever taken away.

   Eye is the sheet. It is already the drawing/photo surface (rotate, mirror,
   markers, crop), so this adds a POSITION for an existing pane rather than a
   new surface, and adds no new switcher entry.

   Composer stays below the layer, never covered - the reviewer must be able
   to ask GO about the drawing they are looking at without putting it down.
   That is the same reason there is no "Open in Composer" handoff.
   ------------------------------------------------------------ */

/* The sheet is positioned inside .app-shell-body rather than against the
   viewport, deliberately. .app-shell-body already occupies exactly the space
   between the header and the bottom of the shell, so "below the header" comes
   out of the existing layout for free - no measuring of a topbar whose height
   changes with viewport width, project name length and menu wrapping, and no
   custom property that would have to be recomputed every time any of those
   moved. */
html[data-tray-layer] .app-shell-body {
    position: relative;
}

/* Eye lives inside .workspace-right-column, which the frame may have
   collapsed or hidden - and a positioned child of a display:none ancestor
   does not render at all. So the column is kept rendered at zero width, the
   same technique .workspace-main-column already uses to keep the fixed
   Composer alive when Display is not the active tray. Its own position must
   be static, or the sheet would resolve against the column instead of
   .app-shell-body. */
html[data-tray-layer="eye"] .workspace-right-column {
    display: flex;
    position: static;
    flex: 0 0 0;
    width: 0;
    min-width: 0;
    overflow: visible;
}
html[data-tray-layer="eye"] .toolbox-eye-divider,
html[data-tray-layer="eye"] .workspace-pane-toolbox {
    display: none;
}
html[data-tray-layer="eye"] #eye-pane {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    /* Stops short of the Composer, and tracks it: --chat-height is the live
       boundary the grabber and the desktop handle both write, so moving that
       boundary resizes the sheet with it rather than leaving it overlapping. */
    bottom: calc(var(--chat-height, 220px) + var(--kb-inset, 0px));
    height: auto;
    max-height: none;
    z-index: 27;
    background: var(--surface-primary);
    border-top: 1px solid var(--border-strong);
    /* A real edge, so the sheet reads as lying ON the work rather than as the
       page having navigated somewhere else. */
    box-shadow: 0 -6px 18px rgba(0, 0, 0, 0.18);
}
/* A project-less page cannot render a Composer, so there is nothing to stop
   short of and the sheet reaches the bottom. */
html[data-tray-layer="eye"] .app-shell-body:not(:has(.chat-region)) #eye-pane {
    bottom: 0;
}

/* The dismiss control exists only while the sheet is up - hidden rather than
   disabled the rest of the time, so it is never a control sitting beside
   Maximize with nothing to do. */
.eye-layer-dismiss { display: none; }
html[data-tray-layer="eye"] .eye-layer-dismiss {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    appearance: none;
    background: none;
    border: 1px solid var(--border);
    border-radius: 4px;
    /* Touch-sized: this is the control that puts the drawing down, and it is
       reached with a thumb while holding the phone. */
    min-width: 40px;
    min-height: 36px;
    margin-right: 0.3rem;
    font-size: 1rem;
    line-height: 1;
    color: var(--text-secondary);
    cursor: pointer;
}
.eye-layer-dismiss:hover { color: var(--text-primary); }
.eye-layer-dismiss:focus-visible {
    outline: 2px solid var(--machine-blue);
    outline-offset: 1px;
}

/* Maximize divides the right COLUMN between Eye and Toolbox. With the sheet
   in front of the whole work area that question is not on screen at all, and
   the control would silently do nothing. */
html[data-tray-layer="eye"] .eye-maximize-btn { display: none; }

/* ============================================================
   CLAUDE-GO-COMPOSER-CAPTURE-01 / -LIFECYCLE-01
   The Composer's "+" attachment, and conversation lifecycle
   ============================================================
   "add a '+' beside the Composer so I can add an image by my phone camera...
   That is the entry point to the application." So it is sized and placed as an
   entry point: a real thumb target sitting immediately beside the input, not a
   small icon tucked into a corner.

   Restrained per this file's own colour grammar - the control is neutral at
   rest and only takes --machine-blue on focus, which is the interaction accent
   already in use everywhere else in the shell. Nothing here is a status. */
.composer-attach {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    /* Comfortably past the touch-target guidance: this is the most-pressed
       control in the field workflow. */
    min-width: 44px;
    min-height: 44px;
    border: 1px solid var(--border);
    border-radius: 6px;
    font-size: 1.35rem;
    line-height: 1;
    color: var(--text-secondary);
    cursor: pointer;
    background: none;
}
.composer-attach:hover { color: var(--text-primary); border-color: var(--border-strong); }
/* The visible control is a <label>, so the focus ring has to be driven from
   the input it owns - a hidden input can hold focus but shows nothing. */
#dock-composer-image:focus-visible + .composer-attach,
.composer-attach:focus-visible {
    outline: 2px solid var(--machine-blue);
    outline-offset: 1px;
    color: var(--machine-blue);
}

/* The attached photo, shown before sending - a reviewer must be able to see
   WHICH photo is about to go, and take it back, before it is transmitted. */
.composer-attach-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    flex-basis: 100%;
    order: 99;
    min-width: 0;
    margin-top: 0.35rem;
    padding: 0.3rem 0.4rem;
    border: 1px solid var(--border);
    border-radius: 6px;
    font-size: 0.78rem;
    color: var(--text-secondary);
}
.composer-attach-chip[hidden] { display: none; }
.composer-attach-thumb {
    width: 34px;
    height: 34px;
    object-fit: cover;
    border-radius: 4px;
    flex-shrink: 0;
}
.composer-attach-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
}
.composer-attach-clear {
    appearance: none;
    background: none;
    border: none;
    margin-left: auto;
    flex-shrink: 0;
    min-width: 32px;
    min-height: 32px;
    font-size: 1rem;
    color: var(--text-secondary);
    cursor: pointer;
}
.composer-attach-clear:hover { color: var(--text-primary); }
.composer-attach-clear:focus-visible { outline: 2px solid var(--machine-blue); outline-offset: 1px; }

/* Start a new conversation, or conclude this one, without leaving the
   Composer. Quiet by default - these sit in the dock header beside the size
   toggle and must not compete with the conversation itself. */
.conversation-lifecycle-form { display: inline; margin: 0; }
.conversation-lifecycle-btn {
    appearance: none;
    display: inline-flex;
    align-items: center;
    background: none;
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: 0.25rem 0.55rem;
    margin-right: 0.3rem;
    font: inherit;
    font-size: 0.74rem;
    color: var(--text-secondary);
    text-decoration: none;
    cursor: pointer;
}
.conversation-lifecycle-btn:hover { color: var(--text-primary); border-color: var(--border-strong); }
.conversation-lifecycle-btn:focus-visible { outline: 2px solid var(--machine-blue); outline-offset: 1px; }

@media (max-width: 640px) {
    /* Real thumb targets on the surface the Product Owner actually works on. */
    .conversation-lifecycle-btn {
        min-height: 36px;
        padding: 0.3rem 0.7rem;
        font-size: 0.8rem;
    }
}

/* CLAUDE-GO-CONVERSATION-HISTORY-TRAY-01 — conversations, under the Composer.
   "Since the phone screen is small put the chat histories list under the
   Composer in a tray."

   Closed by default and quiet when closed: one line of height, so the work
   keeps the screen. Native <details>, no script, no state of its own. */
.conversation-history-tray {
    flex-basis: 100%;
    order: 100;
    border-top: 1px solid var(--border);
    font-size: 0.78rem;
}
.conversation-history-summary {
    cursor: pointer;
    list-style: none;
    padding: 0.5rem 0.2rem;
    /* A real touch target, not a text-sized one — this is reached with a
       thumb while holding the phone. */
    min-height: 40px;
    display: flex;
    align-items: center;
    gap: 0.4rem;
    color: var(--text-secondary);
    letter-spacing: 0.04em;
    text-transform: uppercase;
}
.conversation-history-summary::-webkit-details-marker { display: none; }
.conversation-history-summary::before {
    content: "\25B8";
    transition: transform 0.15s ease;
}
.conversation-history-tray[open] .conversation-history-summary::before {
    transform: rotate(90deg);
}
.conversation-history-summary:focus-visible {
    outline: 2px solid var(--machine-blue);
    outline-offset: -2px;
}
.conversation-history-list {
    display: flex;
    flex-direction: column;
    /* The list is the one thing here allowed to grow — capped so it can never
       push the composer's own input off a small screen, which is the failure
       this tray exists to avoid rather than cause. */
    max-height: 40vh;
    overflow-y: auto;
    overscroll-behavior: contain;
    padding-bottom: 0.3rem;
}
.conversation-history-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    min-height: 40px;
    padding: 0.35rem 0.4rem;
    border-radius: 4px;
    color: var(--text-secondary);
    text-decoration: none;
}
.conversation-history-item:hover { background: var(--surface-hover); color: var(--text-primary); }
.conversation-history-item:focus-visible { outline: 2px solid var(--machine-blue); outline-offset: -2px; }
/* Where you are now — the one place colour means something here. */
.conversation-history-current { color: var(--machine-blue); }
.conversation-history-count {
    flex-shrink: 0;
    color: var(--text-metadata);
    font-variant-numeric: tabular-nums;
}

/* CLAUDE-GO-COMPOSER-CAPTURE-03 — "the next step is not clear."
   "Make a new Q" was a phrase invented in conversation and written nowhere in
   the product, so the one action this entry point exists for was discoverable
   only by having been told it. Shown with the photo, gone with the photo. */
.composer-attach-next {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-basis: 100%;
    order: 100;
    margin-top: 0.35rem;
}
.composer-attach-next[hidden] { display: none; }
.composer-attach-action {
    appearance: none;
    /* The one primary-weight control in this row: it IS the next step, and a
       quiet one would reproduce the problem it exists to solve. */
    background: var(--machine-blue);
    color: var(--surface-primary);
    border: 1px solid var(--machine-blue);
    border-radius: 6px;
    padding: 0.45rem 0.9rem;
    min-height: 40px;
    font: inherit;
    font-size: 0.82rem;
    font-weight: 600;
    cursor: pointer;
}
.composer-attach-action:focus-visible {
    outline: 2px solid var(--machine-blue);
    outline-offset: 2px;
}
/* Says the button is not the only option, so the reviewer does not read it as
   the only thing a photo can do. */
.composer-attach-or {
    font-size: 0.76rem;
    color: var(--text-metadata);
}

/* ============================================================
   CLAUDE-MOBILE-FRAME-PIN-04 — lock the shell at every width
   ============================================================
   "Right now there is a little left and right drift. Lock the main app shell."

   Two halves, because refusing the gesture was never going to be enough on its
   own: remove the overflow that actually existed, and then hold the frame.
   ------------------------------------------------------------ */

/* THE CAUSE. A flex item's automatic minimum size is its CONTENT size, not
   zero - so `flex: 1` without `min-width: 0` refuses to shrink below its
   intrinsic width and pushes its whole row wider than the container. The
   composer row already held a voice button and Send; the "+" and the
   "Make a new Q" row were added to it, and the drift became visible. The
   button was not the bug, it was the straw. This is the most common cause of
   unexplained horizontal overflow in a flex layout. */
.conversation-input-form,
.conversation-dock-composer {
    max-width: 100%;
    min-width: 0;
}
.conversation-input-form textarea,
.conversation-input-form > * {
    min-width: 0;
}
.conversation-input-form textarea {
    /* Belt and braces: even at its minimum, never wider than the row. */
    max-width: 100%;
}

/* THE LOCK, at every width - "the main app shell" is not a mobile-only
   statement, and a narrowed desktop window had the same drift available to it.

   overflow-x: clip rather than hidden where supported: `hidden` makes the
   element a scroll CONTAINER, which can still be scrolled programmatically and
   by some assistive interactions. `clip` refuses outright. `hidden` stays
   first as the fallback for anything without clip. */
html,
body,
.app-shell {
    width: 100%;
    max-width: 100%;
    overflow-x: hidden;
    overflow-x: clip;
}

/* The fixed regions are outside .app-shell's own clipping, so each holds its
   own edge. Inner scroll regions are deliberately NOT listed - a wide table, a
   drawing or a PDF page must still scroll sideways inside its own box. This
   stops the SHELL moving, not the content. */
.workspace-topbar,
.tray-switcher,
.chat-region,
.app-shell-body {
    max-width: 100%;
    min-width: 0;
}

/* CLAUDE-GO-DOCUMENT-EXPORT-01 — File > Export rows.
   One row per content kind, three formats on it. A flat list of twelve
   entries would be the same information arranged to be unusable. */
.workspace-menubar-export-row {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.3rem 0.6rem;
    white-space: nowrap;
}
.workspace-menubar-export-label {
    flex: 1 1 auto;
    min-width: 0;
    color: var(--text-secondary);
    font-size: 0.82rem;
}
.workspace-menubar-export-format {
    flex-shrink: 0;
    padding: 0.2rem 0.45rem;
    border: 1px solid var(--border);
    border-radius: 4px;
    color: var(--text-secondary);
    text-decoration: none;
    font-size: 0.74rem;
}
.workspace-menubar-export-format:hover {
    color: var(--machine-blue);
    border-color: var(--machine-blue);
}
.workspace-menubar-export-format:focus-visible {
    outline: 2px solid var(--machine-blue);
    outline-offset: 1px;
}

/* ============================================================
   CLAUDE-MOBILE-PRIMARY-RESET-01 — fix the composition
   ============================================================
   "The live phone experience is now judged chaotic. Do not add features to
   solve this. Reduce and reorganize."

   Six persistent bands stacked before any work was visible: topbar, tray
   switcher, grabber, dock header, composer, history tray. Only the topbar and
   the composer earn permanent space on a phone. What follows removes or
   demotes the other four and leaves one context bar, one scrolling page, one
   Composer.

   Nothing governed is deleted. Lists, Toolbox, Eye, Archive and the size
   toggle all still exist and still work — they are simply no longer standing
   in front of the work. ------------------------------------------------- */

/* The context bar: project, current conversation, and the way to every other
   conversation — one band answering "where am I", "what else is there",
   "start fresh". */
.context-identity { min-width: 0; flex: 1 1 auto; position: relative; }
.context-identity-summary {
    display: flex;
    align-items: baseline;
    gap: 0.3rem;
    min-width: 0;
    min-height: 36px;
    padding: 0.2rem 0;
    cursor: pointer;
    list-style: none;
    font-size: 0.82rem;
    color: var(--text-secondary);
}
.context-identity-summary::-webkit-details-marker { display: none; }
.context-identity-summary::after {
    content: "\25BE";
    margin-left: 0.15rem;
    color: var(--text-metadata);
}
.context-identity-summary:focus-visible { outline: 2px solid var(--machine-blue); outline-offset: 2px; }
.context-identity-project {
    font-weight: 600;
    color: var(--text-primary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 45%;
}
.context-identity-sep { color: var(--text-metadata); flex-shrink: 0; }
.context-identity-current {
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
/* Opens over the page rather than pushing it down: a disclosure that reflows
   the whole frame is the reason the old history tray felt like another band. */
.context-identity-list {
    position: absolute;
    top: calc(100% + 0.3rem);
    left: 0;
    z-index: 32;
    min-width: min(88vw, 340px);
    max-height: 55vh;
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: 0.3rem;
    background: var(--surface-primary);
    border: 1px solid var(--border-strong);
    border-radius: 6px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);
}
.context-identity-item {
    display: block;
    min-height: 40px;
    padding: 0.55rem 0.5rem;
    border-radius: 4px;
    color: var(--text-secondary);
    text-decoration: none;
    font-size: 0.84rem;
}
.context-identity-item:hover { background: var(--surface-hover); color: var(--text-primary); }
.context-identity-item:focus-visible { outline: 2px solid var(--machine-blue); outline-offset: -2px; }
.context-identity-current-item { color: var(--machine-blue); }

.context-new-form { display: inline; margin: 0; flex-shrink: 0; }
.context-new-btn {
    appearance: none;
    background: none;
    border: 1px solid var(--border);
    border-radius: 4px;
    min-height: 36px;
    padding: 0.3rem 0.7rem;
    font: inherit;
    font-size: 0.78rem;
    color: var(--text-secondary);
    cursor: pointer;
}
.context-new-btn:hover { color: var(--text-primary); border-color: var(--border-strong); }
.context-new-btn:focus-visible { outline: 2px solid var(--machine-blue); outline-offset: 1px; }

@media (max-width: 640px) {
    /* REMOVED — four competing permanent panes is the compressed-desktop
       pattern that was rejected. The same controls now live in View > Panels,
       deliberately requested instead of permanently occupying the frame. */
    .tray-switcher-options { display: none; }

    /* REMOVED — "New" moved up into the context bar; the size toggle is
       redundant with the grabber sitting two pixels above it. */
    .conversation-dock-header { display: none; }

    /* Everything the top bar carries that is not identity is secondary on a
       phone: Display Layout, Appearance and Account all live in the ☰ drawer
       already, and the working indicator is not worth a band of its own. */
    .workspace-topbar-controls,
    .workspace-app-activity,
    .workspace-topbar-document-controls { display: none; }

    /* The grabber keeps its touch target and loses its padding — it was
       costing more vertical space than the control needed. */
    .tray-composer-grabber { min-height: 22px; padding: 0.3rem 0; }
}

/* ============================================================
   CLAUDE-MOBILE-SHELL-STABILITY-01 — every shell, not just one
   ============================================================
   Product Owner standing rule: "no whole-page horizontal drift; no accidental
   lateral movement of the application shell… the outer application shell
   itself remains anchored."

   CLAUDE-MOBILE-FRAME-PIN-04 locked `html, body, .app-shell`. But `.app-shell`
   exists in exactly ONE of five shells. The landing page renders through
   landing_shell.html (`.landing-page`), and Gateway, Sign-In and the embedded
   panel each have their own root. Four of five were never locked — including
   the landing page, which is the first thing anyone sees.

   Listed explicitly rather than applied to a universal selector: a shell root
   is a specific, small, knowable set, and `*` would also catch the drawing and
   table viewers that legitimately pan. ------------------------------------ */
.landing-page,
.gateway-shell,
.auth-shell-page,
.app-main {
    max-width: 100%;
    overflow-x: hidden;
    overflow-x: clip;
}

@media (max-width: 640px) {
    /* The same gesture refusal .app-shell already carries. Horizontal panning
       remains available INSIDE the bounded viewers that need it — the drawing
       canvas, wide tables, large images — because those set their own
       touch-action and are not shell roots. */
    /* .auth-shell-page also carries .landing-page, so Sign-In is covered
       twice over - listed anyway so the set reads as the complete list of
       shell roots rather than relying on a coincidence of class stacking. */
    .landing-page,
    .gateway-shell,
    .auth-shell-page {
        touch-action: pan-y;
        overscroll-behavior-x: none;
    }

    /* 100vh on iOS is the URL-bar-retracted height, so a 100vh landing page is
       taller than the screen and invites the rubber-band this rule exists to
       stop. Same fallback ordering as .app-shell. */
    .landing-page {
        min-height: 100vh;
        min-height: 100dvh;
    }
}

/* The background canvas is positioned against the viewport and is therefore
   outside every shell root's own clipping. It holds its own edge. Its pixel
   size is no longer written from window.innerWidth at all — see
   static/js/ocean_field.js — so this is a floor, not the fix. */
.landing-field-canvas {
    max-width: 100vw;
    max-height: 100dvh;
}

/* ============================================================
   CLAUDE-MOBILE-SHELL-STABILITY-02 — nothing outside the screen,
   nothing white behind it
   ============================================================
   Product Owner: "Make sure no text ever extends outside phone screen keeping
   a minimum margins on all sides and there is no white area showing behind the
   phone screen."

   Three separate causes, and they are easy to mistake for one.
   ------------------------------------------------------------ */

/* 1. NOTHING WHITE BEHIND. viewport-fit=cover (added to every shell's viewport
      meta) lets the page paint into the notch and home-indicator areas; without
      an explicit background on the document itself, what shows there is the
      browser's own white. Painted on html AND body because the overscroll area
      takes its colour from the root element, not from any app container. */
html {
    background-color: var(--canvas);
}
body {
    background-color: var(--canvas);
}

/* 2. MINIMUM MARGIN ON ALL SIDES. env() resolves to 0 where there is no inset,
      so max() gives a real gutter on every phone and a larger one only where
      the hardware needs it. Applied to the shell roots rather than to text
      elements: a margin owned by the container cannot be forgotten by the next
      component someone adds inside it. */
@media (max-width: 640px) {
    .landing-page,
    .gateway-shell,
    .auth-shell-page,
    .app-shell {
        padding-left: max(0.85rem, env(safe-area-inset-left));
        padding-right: max(0.85rem, env(safe-area-inset-right));
    }
    /* The fixed regions sit outside the shell's padding box, so each needs its
       own inset or it would run under the rounded corners. */
    .workspace-topbar,
    .tray-switcher {
        padding-left: max(0.6rem, env(safe-area-inset-left));
        padding-right: max(0.6rem, env(safe-area-inset-right));
    }
    .chat-region {
        padding-left: max(0.5rem, env(safe-area-inset-left));
        padding-right: max(0.5rem, env(safe-area-inset-right));
        padding-bottom: env(safe-area-inset-bottom);
    }

    /* 3. NO TEXT OUTSIDE THE SCREEN. A long unbroken token - a URL, a file
          name, a drawing number - has no break opportunity, so it pushes its
          container wider than the viewport and takes the whole shell with it.
          This is a genuine width source, not a cosmetic one, and it is the
          same class of fault as the canvas: something inside is really wider
          than the screen.

          Newly relevant: CLAUDE-AIRLOCK-WEB-RESEARCH-01 prints full source
          URLs in its citations, which are exactly this shape. */
    .conv-message-text,
    .conversation-message,
    .conversation-thread,
    .context-identity-item,
    .conversation-history-item,
    main,
    .pane-note {
        overflow-wrap: anywhere;
        word-break: break-word;
    }
}

/* ============================================================
   CLAUDE-MOBILE-ICON-01 / CLAUDE-MOBILE-PWA-01
   ============================================================ */

/* The landing brand lives in landing.css, not here: landing_shell.html
   loads landing.css ALONE - no tokens.css, no main.css - so a .landing-*
   rule written here would be dead on the only page that uses it.

   The update notice below is duplicated there for the same reason. */

/* The update notice. An OFFER, never an automatic reload: a page that reloads
   itself can discard a half-typed Composer message, and on a phone the
   reviewer may be standing on site with one hand. Sits above the Composer and
   clears the home indicator. */
.update-notice {
    position: fixed;
    left: max(0.6rem, env(safe-area-inset-left));
    right: max(0.6rem, env(safe-area-inset-right));
    bottom: calc(env(safe-area-inset-bottom) + 0.6rem);
    z-index: 60;
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.6rem 0.75rem;
    border: 1px solid var(--border-strong);
    border-radius: 8px;
    background: var(--surface-primary);
    box-shadow: 0 6px 20px rgba(0, 0, 0, 0.25);
    font-size: 0.82rem;
    color: var(--text-secondary);
}
.update-notice-text { flex: 1 1 auto; min-width: 0; }
.update-notice-action {
    flex-shrink: 0;
    appearance: none;
    min-height: 36px;
    padding: 0.35rem 0.8rem;
    border: 1px solid var(--machine-blue);
    border-radius: 6px;
    background: var(--machine-blue);
    color: var(--surface-primary);
    font: inherit;
    font-size: 0.8rem;
    font-weight: 600;
    cursor: pointer;
}
.update-notice-dismiss {
    flex-shrink: 0;
    appearance: none;
    background: none;
    border: none;
    min-width: 32px;
    min-height: 32px;
    font-size: 1.1rem;
    color: var(--text-metadata);
    cursor: pointer;
}
.update-notice-action:focus-visible,
.update-notice-dismiss:focus-visible {
    outline: 2px solid var(--machine-blue);
    outline-offset: 2px;
}

/* ============================================================
   CLAUDE-COMPOSER-DRAFT-ASSIST-01 — the pen
   ============================================================ */

/* Sits in the composer row, revealed by draft_assist.js only when the draft
   actually has text. Same weight as the mic beside it: an offer, not a
   competitor to Send. */
.composer-pen {
    flex-shrink: 0;
    appearance: none;
    min-width: 40px;
    min-height: 40px;
    border: 1px solid var(--border-subtle);
    border-radius: 8px;
    background: none;
    color: var(--text-secondary);
    font-size: 1rem;
    cursor: pointer;
}
.composer-pen:hover { color: var(--text-primary); border-color: var(--border-strong); }
.composer-pen:focus-visible { outline: 2px solid var(--machine-blue); outline-offset: 2px; }

/* ONE component, two form factors. Desktop: a popover above the Composer.
   Phone (below): a bottom sheet. CSS decides, so there is no second
   implementation to drift out of step. */
.composer-pen-sheet {
    position: absolute;
    bottom: calc(100% + 0.5rem);
    left: 0.75rem;
    right: 0.75rem;
    z-index: 40;
    max-height: 60vh;
    overflow-y: auto;
    padding: 0.75rem;
    border: 1px solid var(--border-strong);
    border-radius: 10px;
    background: var(--surface-primary);
    box-shadow: 0 8px 28px rgba(0, 0, 0, 0.28);
}
.composer-pen-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
}
.composer-pen-action {
    appearance: none;
    min-height: 36px;
    padding: 0.3rem 0.7rem;
    border: 1px solid var(--border-subtle);
    border-radius: 999px;
    background: none;
    color: var(--text-secondary);
    font: inherit;
    font-size: 0.8rem;
    cursor: pointer;
}
.composer-pen-action:hover { color: var(--text-primary); border-color: var(--border-strong); }
.composer-pen-action:focus-visible { outline: 2px solid var(--machine-blue); outline-offset: 2px; }

.composer-pen-status {
    margin: 0.5rem 0 0;
    font-size: 0.8rem;
    color: var(--text-metadata);
}

/* The comparison. Side by side where there is room, stacked where there is
   not - the point is seeing both at once, which is the whole safety mechanism
   for RFI and observation wording. */
.composer-pen-compare {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.75rem;
    margin-top: 0.6rem;
}
.composer-pen-side { min-width: 0; }
.composer-pen-side-title {
    margin: 0 0 0.25rem;
    font-size: 0.72rem;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--text-metadata);
}
.composer-pen-original,
.composer-pen-proposal {
    margin: 0;
    padding: 0.5rem;
    border-radius: 6px;
    background: var(--surface-secondary);
    font-size: 0.85rem;
    line-height: 1.45;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}
/* The original is deliberately quieter than the proposal, but still fully
   legible: it is the thing being compared against, not a placeholder. */
.composer-pen-original { color: var(--text-secondary); }
.composer-pen-proposal { color: var(--text-primary); }
.composer-pen-note {
    margin: 0.4rem 0 0;
    font-size: 0.78rem;
    color: var(--attention-amber);
}
.composer-pen-decide {
    grid-column: 1 / -1;
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
}
.composer-pen-decide-btn {
    appearance: none;
    min-height: 38px;
    padding: 0.35rem 0.9rem;
    border: 1px solid var(--machine-blue);
    border-radius: 6px;
    background: none;
    color: var(--machine-blue);
    font: inherit;
    font-size: 0.82rem;
    font-weight: 600;
    cursor: pointer;
}
.composer-pen-discard { border-color: var(--border-subtle); color: var(--text-metadata); }
.composer-pen-decide-btn:focus-visible { outline: 2px solid var(--machine-blue); outline-offset: 2px; }

@media (max-width: 640px) {
    /* A bottom sheet, and the comparison stacks: two columns of professional
       prose on a phone is two unreadable columns. */
    .composer-pen-sheet {
        position: fixed;
        left: 0;
        right: 0;
        bottom: 0;
        border-radius: 12px 12px 0 0;
        max-height: 70vh;
        padding-bottom: calc(env(safe-area-inset-bottom) + 0.75rem);
    }
    .composer-pen-compare { grid-template-columns: 1fr; }
}

/* ============================================================
   CLAUDE-CAPTURE-REVIEW-01 — the capture confirmation step
   ============================================================ */

/* A mobile capture confirmation, not a desktop editor. It covers the composer
   area while a photo is being looked at, because that is the only decision on
   screen at that moment - and it leaves as soon as one is made. */
.capture-review {
    position: absolute;
    left: 0.5rem;
    right: 0.5rem;
    bottom: calc(100% + 0.4rem);
    z-index: 45;
    padding: 0.6rem;
    border: 1px solid var(--border-strong);
    border-radius: 10px;
    background: var(--surface-primary);
    box-shadow: 0 8px 28px rgba(0, 0, 0, 0.3);
}
.capture-review-stage {
    position: relative;
    display: flex;
    justify-content: center;
    background: var(--surface-secondary);
    border-radius: 6px;
    overflow: hidden;
}
.capture-review-image {
    display: block;
    max-width: 100%;
    /* Bounded so a portrait phone photo cannot push the actions off screen -
       "Use photo" must stay reachable without scrolling, or the confirmation
       becomes a chore. */
    max-height: 45vh;
    object-fit: contain;
    /* The photo is the crop surface: stop the browser treating a drag as a
       scroll or an image drag. */
    touch-action: none;
    -webkit-user-drag: none;
    user-select: none;
    cursor: crosshair;
}
/* The crop rectangle. Outline plus a dimmed exterior would need a second
   element; a single bright inset border reads clearly enough at this size and
   keeps the DOM to one node. */
.capture-review-crop {
    position: absolute;
    border: 2px solid var(--machine-blue);
    box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.38);
    pointer-events: none;
}
.capture-review-hint {
    margin: 0.4rem 0 0.5rem;
    font-size: 0.78rem;
    color: var(--text-metadata);
}
.capture-review-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    align-items: center;
}
/* Use photo is the one obvious action after capture - listed first, filled,
   and given the room. Cropping is the exception, not the ritual. */
.capture-review-use {
    flex: 1 1 auto;
    appearance: none;
    min-height: 44px;
    padding: 0.4rem 1.1rem;
    border: 1px solid var(--machine-blue);
    border-radius: 8px;
    background: var(--machine-blue);
    color: var(--surface-primary);
    font: inherit;
    font-weight: 700;
    cursor: pointer;
}
.capture-review-action {
    appearance: none;
    min-height: 44px;
    padding: 0.4rem 0.85rem;
    border: 1px solid var(--border-subtle);
    border-radius: 8px;
    background: none;
    color: var(--text-secondary);
    font: inherit;
    font-size: 0.85rem;
    cursor: pointer;
}
.capture-review-discard { color: var(--text-metadata); }
.capture-review-use:focus-visible,
.capture-review-action:focus-visible {
    outline: 2px solid var(--machine-blue);
    outline-offset: 2px;
}

@media (max-width: 640px) {
    /* Full-width on a phone, anchored to the bottom, clearing the home
       indicator - the same treatment the composer's other sheets use. */
    .capture-review {
        position: fixed;
        left: 0;
        right: 0;
        bottom: 0;
        border-radius: 12px 12px 0 0;
        padding-bottom: calc(env(safe-area-inset-bottom) + 0.6rem);
    }
    /* More room for the photo itself, since the phone is where this step
       actually earns its place. */
    .capture-review-image { max-height: 52vh; }
    /* 44px targets already; this keeps them from wrapping into a cramped
       second row on a narrow screen. */
    .capture-review-action { font-size: 0.8rem; padding: 0.4rem 0.7rem; }
}

/* CLAUDE-CAPTURE-REVIEW-02: crop handles.
   Visual affordances only - hit-testing lives in JS against the photo with a
   22px grab radius, because a thumb cannot reliably land on a 10px dot and a
   handle you must hit exactly is a handle that does not work on a phone. */
.capture-review-handle {
    position: absolute;
    width: 12px;
    height: 12px;
    border: 2px solid var(--machine-blue);
    background: var(--surface-primary);
    border-radius: 2px;
    pointer-events: none;
}
.capture-review-handle-nw { left: -7px; top: -7px; }
.capture-review-handle-ne { right: -7px; top: -7px; }
.capture-review-handle-sw { left: -7px; bottom: -7px; }
.capture-review-handle-se { right: -7px; bottom: -7px; }

/* CLAUDE-RBAC-TOKENS-03 - bidding marketplace scaffold: DEFENCE IN DEPTH.

   This is NOT the mechanism that keeps the component away from people. The
   mechanism is config.ENABLE_BIDDING_PORTAL, checked server-side in
   templates/_bidding_portal.html, so while the flag is off there is no markup
   on the page at all - nothing to click, focus, read in devtools, or reveal by
   overriding a stylesheet. Hidden is not absent, and only absent is safe.

   These rules exist for the window where somebody turns the flag on before the
   feature is finished. They make the scaffold inert rather than merely quiet:
   pointer-events refuses the click, user-select refuses the drag, and the
   markup's own disabled/tabindex=-1/aria-hidden refuse the keyboard and the
   screen reader. A scaffold that could be pressed is a promise the product
   cannot keep. */
.bidding-portal {
    pointer-events: none;
    user-select: none;
    opacity: .55;
}
.bidding-portal .bidding-portal-act {
    pointer-events: none;
    cursor: not-allowed;
}
.bidding-portal-state {
    font-size: .75rem;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--text-metadata);
}
.bidding-portal-empty {
    color: var(--text-metadata);
    font-style: italic;
}

/* CLAUDE-HELP-CENTER-01 - the contextual [?] inside a Toolbox pane.
   Same shape and reasoning as .pm-help on the access panel: a real link so it
   works with a keyboard and a screen reader without script, sized to sit
   INSIDE a line of text rather than to the 44px touch minimum, which in a
   sentence would push the line apart. Opens in a new tab - the Toolbox is
   beside an open workspace, and reading a guide should not cost the state
   somebody is working in. */
.pane-help {
    display: inline-grid;
    place-items: center;
    /* 18px, not 16: the glyph inside is 11px, which is this application's
       floor for any text a person is expected to read
       (test_global_search_and_header's typography rule, which caught this at
       10px). A 16px circle around 11px text leaves no optical breathing room,
       so the ring grew rather than the text shrinking - the floor is the
       fixed thing here. */
    width: 18px;
    height: 18px;
    margin-left: 4px;
    border: 1px solid var(--border);
    border-radius: 50%;
    color: var(--text-metadata);
    font-size: 11px;
    font-weight: 700;
    line-height: 1;
    text-decoration: none;
    vertical-align: middle;
}
.pane-help:hover { color: var(--machine-blue); border-color: var(--machine-blue); }
.pane-help:focus-visible { outline: 2px solid var(--machine-blue); outline-offset: 2px; }
