/* viewer/Components/AmbientEffectLayer.razor.rz.scp.css */
.ambient-effect-layer[b-7i6h99p46h] {
    position: fixed;
    inset: 0;
    width: 100vw;
    height: 100vh;
    overflow: hidden;
    contain: strict;
    pointer-events: none;
}

.ambient-effect-layer--background[b-7i6h99p46h] { z-index: 0; }
.ambient-effect-layer--foreground[b-7i6h99p46h] { z-index: 2; }

.ambient-effect-layer[b-7i6h99p46h]  .ambient-effect-layer__canvas {
    position: absolute;
    inset: 0;
    display: block;
    width: 100% !important;
    height: 100% !important;
    max-width: 100vw;
    max-height: 100vh;
    pointer-events: none !important;
}
/* viewer/Components/AnnotationHoverPreview.razor.rz.scp.css */
/* The card itself starts hidden; FormattedDocumentView.razor.css's ::deep rules over the hosting
   .annotation-link/.note-annotation-link span flip opacity/visibility on hover/focus. Kept hidden-by-default
   here (not only in the parent file) so this component never paints visible chrome if it is ever reused
   outside that reveal wiring. */
.annotation-hover-card[b-g04m6okcyg] {
    position: absolute;
    inset-block-start: calc(100% + 4px);
    inset-inline-start: 0;
    z-index: 30;
    display: flex;
    flex-direction: column;
    gap: 6px;
    width: max-content;
    max-width: 260px;
    padding: 8px 10px;
    border: 1px solid var(--stroke);
    border-radius: var(--radius-overlay);
    background: var(--surface-raised);
    box-shadow: var(--shadow-overlay);
    color: var(--text-primary);
    font-size: 12px;
    font-weight: 400;
    line-height: 1.4;
    text-align: left;
    text-decoration: none;
    white-space: normal;
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition: opacity 120ms ease, visibility 0s linear 120ms;
}

/* One row per resolved annotation (see AnnotationHoverPreview.razor's own doc) — a single-annotation hover is
   one row and reads exactly as before this stacking policy existed; a stacked (overlapping-annotation) hover
   gets a quiet divider between rows instead of the card's own border, so multiple rows read as one card, not a
   pile of separate cards. */
.annotation-hover-row[b-g04m6okcyg] {
    display: flex;
    align-items: flex-start;
    gap: 8px;
}

.annotation-hover-row + .annotation-hover-row[b-g04m6okcyg] {
    padding-block-start: 6px;
    border-block-start: 1px solid var(--stroke);
}

.annotation-hover-swatch[b-g04m6okcyg] {
    flex: 0 0 auto;
    width: 10px;
    height: 10px;
    margin-block-start: 3px;
    border-radius: 50%;
    background: var(--hover-accent, var(--accent));
}

.annotation-hover-copy[b-g04m6okcyg] {
    display: grid;
    gap: 2px;
    min-width: 0;
}

.annotation-hover-name[b-g04m6okcyg] {
    overflow-wrap: anywhere;
    color: var(--text-primary);
    font-weight: 650;
}

.annotation-hover-type[b-g04m6okcyg] {
    color: var(--text-secondary);
    font-size: 10px;
    letter-spacing: .04em;
    text-transform: uppercase;
}

.annotation-hover-summary[b-g04m6okcyg] {
    overflow-wrap: anywhere;
    color: var(--text-muted);
}
/* viewer/Components/BackgroundNoteReadDetailView.razor.rz.scp.css */
/* Background Note detail is one flat Library reading surface. Heading, prose, and gallery are separated by
   spacing or hairlines; nested content cards would duplicate the route surface. */
.note-inspector[b-cmjmvke6nf] {
    box-sizing: border-box;
    width: 100%;
    max-width: 860px;
    margin: 0 auto;
    padding: 24px clamp(16px, 4vw, 32px) 56px;
}

/* Same vertical rhythm as the Scene detail beside it (see SceneReadDetailView.razor.css's own note): --sp-4 on
   BOTH sides of a hairline, --sp-1 for a label bound to what it names. The two surfaces sit one click apart in
   the same navigator, so a reader crossing between them should not meet a different set of gaps. */
.note-inspector__heading[b-cmjmvke6nf] { margin: 0 0 var(--sp-4); }
.note-inspector__eyebrow[b-cmjmvke6nf] { margin: 0 0 var(--sp-1); color: var(--accent); }
.note-inspector__heading h1[b-cmjmvke6nf] { margin: 0; font-family: var(--font-reading); line-height: 1.25; }
/* Matches the Scene detail's own folder trail exactly — same role, same tier, same colour. */
.note-inspector__folder-path[b-cmjmvke6nf] {
    margin: var(--sp-1) 0 0;
    color: var(--text-muted);
    font-size: 11px;
}

.note-inspector__content[b-cmjmvke6nf] { margin-top: var(--sp-4); border-top: 1px solid var(--stroke); padding-top: var(--sp-4); }
/* A note IS prose, so it reads on app.css's shared prose tokens rather than its own 16px/1.6 — the same column
   the Scene's Manuscript tab and Read's own surface use. */
.note-inspector__content[b-cmjmvke6nf]  .formatted-document {
    font-family: var(--font-reading);
    font-size: var(--prose-font-size);
    line-height: var(--prose-line-height);
}
.note-inspector__content[b-cmjmvke6nf]  .prose-line {
    min-height: calc(var(--prose-line-height) * 1em);
    margin: 0 0 var(--prose-paragraph-spacing);
}
.note-inspector__content[b-cmjmvke6nf]  .prose-line:last-child { margin-bottom: 0; }
.note-inspector__gallery[b-cmjmvke6nf] { display: flex; flex-wrap: wrap; gap: 10px; margin-top: var(--sp-4); padding-top: var(--sp-4); border-top: 1px solid var(--stroke); }
/* Fixed width (not max-width): the box is fully reserved at BOTH dimensions before a slow-loading image
   arrives, so neighbouring gallery items never reflow horizontally once it lands (object-fit:contain still
   letterboxes the real image inside cleanly) — same convention as TagReadDetailView's identical gallery role. */
.note-inspector__gallery img[b-cmjmvke6nf] { box-sizing: border-box; width: 320px; height: 180px; border: 1px solid var(--stroke); border-radius: 8px; padding: 6px; background: var(--surface-nav); object-fit: contain; }

@media (max-width: 720px) {
    .note-inspector[b-cmjmvke6nf] { margin: 0 auto; padding: 18px 16px 40px; }
    .note-inspector__gallery img[b-cmjmvke6nf] { width: 100%; height: auto; max-height: 260px; }
}
/* viewer/Components/BottomSheet.razor.rz.scp.css */
/* Shared bottom-sheet primitive (see BottomSheet.razor). Native popover top-layer placement supplies light
   dismissal, Escape, and trigger-focus restoration; this file owns only the sheet's own slide/scrim/chrome.
   The sheet is a narrow-tier-only primitive — every trigger that opens one is itself hidden outside
   :root[data-phone] at its call site — so this stylesheet needs no width gate of its own; it always renders
   as a bottom sheet wherever it is used. */
.bottom-sheet[b-2iy2bkdbn9] {
    position: fixed;
    inset: auto 0 0 0;
    margin: 0;
    width: 100%;
    max-width: none;
    max-height: 70dvh;
    display: flex;
    flex-direction: column;
    border: 1px solid var(--stroke);
    border-width: 1px 0 0 0;
    border-radius: var(--radius-overlay) var(--radius-overlay) 0 0;
    background: var(--surface-raised);
    box-shadow: var(--shadow-overlay);
    color: var(--text-primary);
    color-scheme: inherit;
    font-family: var(--font-interface);
    padding-bottom: max(var(--sp-3), env(safe-area-inset-bottom));
    overscroll-behavior: contain;
    transform: translateY(4%);
    opacity: 0;
    transition: transform 180ms ease, opacity 180ms ease, overlay 180ms allow-discrete, display 180ms allow-discrete;
}

.bottom-sheet:popover-open[b-2iy2bkdbn9] {
    display: flex;
    transform: translateY(0);
    opacity: 1;
}

/* Progressive enhancement only: browsers without @starting-style support just skip the slide-in and show the
   sheet immediately open — still fully functional. */
@starting-style {
    .bottom-sheet:popover-open[b-2iy2bkdbn9] { transform: translateY(4%); opacity: 0; }
}

.bottom-sheet[b-2iy2bkdbn9]::backdrop {
    background: rgb(0 0 0 / 45%);
    transition: background-color 180ms ease, overlay 180ms allow-discrete, display 180ms allow-discrete;
}

@starting-style {
    .bottom-sheet:popover-open[b-2iy2bkdbn9]::backdrop { background: rgb(0 0 0 / 0%); }
}

.bottom-sheet__handle[b-2iy2bkdbn9] {
    flex: 0 0 auto;
    align-self: center;
    width: 36px;
    height: 4px;
    margin: 10px 0 2px;
    border-radius: 2px;
    background: var(--stroke-strong);
    touch-action: none;
    cursor: grab;
}

.bottom-sheet__head[b-2iy2bkdbn9] {
    display: flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-2);
    min-height: var(--tap);
    padding: 4px var(--sp-4) 6px;
    border-bottom: 1px solid var(--stroke);
}

.bottom-sheet__close[b-2iy2bkdbn9] {
    display: grid;
    flex: 0 0 var(--tap);
    place-items: center;
    min-height: var(--tap);
    border: 0;
    border-radius: var(--radius-control);
    background: transparent;
    color: var(--text-secondary);
    cursor: pointer;
}

.bottom-sheet__close:hover[b-2iy2bkdbn9] { background: var(--surface-hover); color: var(--text-primary); }

.bottom-sheet__body[b-2iy2bkdbn9] {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: var(--sp-3) var(--sp-4);
    /* Sheet body content is TIER-2 controls (scope/axis/filter groups) — same field-label vocabulary and 44px
       row height every other compact control surface in the project uses. */
    font-size: 14px;
}

.bottom-sheet__body[b-2iy2bkdbn9]  .bottom-sheet__group + .bottom-sheet__group { margin-top: var(--sp-4); }
.bottom-sheet__body[b-2iy2bkdbn9]  .bottom-sheet__group-label {
    margin: 0 0 var(--sp-1);
    color: var(--text-muted);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: .02em;
}

.bottom-sheet__footer[b-2iy2bkdbn9] {
    flex: 0 0 auto;
    display: flex;
    justify-content: flex-end;
    gap: var(--sp-2);
    border-top: 1px solid var(--stroke);
    padding: var(--sp-2) var(--sp-4);
}

:root[data-touch] .bottom-sheet__handle[b-2iy2bkdbn9] { width: 44px; height: 5px; }
/* viewer/Components/Canvas/CameraControlPill.razor.rz.scp.css */
/* Pixel-aligned to desktop's CameraControls.axaml (Border.cam-pill / Button.cam-btn / cam-caret / cam-percent /
   cam-div): 26px buttons, 16px caret, 46px percent box, 7px pill corner radius, 5px control corner radius, and
   a solid --surface-nav fill (the single Opacity="0.92" on the whole control — not an additional translucent
   background) — desktop's Margin="0,12,12,0" placement for the pill's host. */
.camera-control-pill[b-tfqe5uciuq] {
    position: absolute;
    z-index: 5;
    inset: 12px 12px auto auto;
    display: flex;
    align-items: center;
    padding: 2px;
    border: 1px solid var(--stroke);
    border-radius: 7px;
    color: var(--text-primary);
    background: var(--surface-nav);
    box-shadow: var(--shadow-panel);
    opacity: .92;
}

.camera-control-pill:hover[b-tfqe5uciuq] { opacity: 1; }

.camera-control-pill__mobile-trigger[b-tfqe5uciuq],
.camera-control-pill__mobile-panel[b-tfqe5uciuq] { display: none; }

.camera-control-pill__button[b-tfqe5uciuq],
.camera-control-pill__caret[b-tfqe5uciuq] {
    display: grid;
    place-items: center;
    width: 26px;
    height: 26px;
    padding: 0;
    border: 0;
    border-radius: 5px;
    color: inherit;
    background: transparent;
    cursor: pointer;
}

.camera-control-pill__button:hover[b-tfqe5uciuq],
.camera-control-pill__caret:hover[b-tfqe5uciuq],
.camera-control-pill__options button:hover[b-tfqe5uciuq] {
    background: var(--surface-hover);
}

.camera-control-pill__caret[b-tfqe5uciuq] { width: 16px; }

.camera-control-pill__divider[b-tfqe5uciuq] {
    width: 1px;
    height: 18px;
    margin: 0 1px;
    background: var(--stroke);
}

.camera-control-pill__percent[b-tfqe5uciuq] {
    width: 46px;
    height: 26px;
    padding: 0;
    border: 0;
    outline: 0;
    background: transparent;
    text-align: center;
}

/* Exception (elevation-token sweep): an inset focus ring, not an elevation shadow. */
.camera-control-pill__percent:focus-visible[b-tfqe5uciuq] {
    border-radius: var(--radius-control);
    box-shadow: inset 0 0 0 1px var(--accent);
}

.camera-control-pill__options[b-tfqe5uciuq] {
    position: absolute;
    position-area: bottom span-left;
    min-width: 150px;
    margin: 6px 0;
    padding: 5px;
    border: 1px solid var(--stroke);
    border-radius: var(--radius-overlay);
    color: var(--text-primary);
    background: var(--surface-raised);
    box-shadow: var(--shadow-overlay);
    max-height: min(60dvh, 320px);
    overflow-y: auto;
    color-scheme: inherit;
    font-family: var(--font-interface);
}

.camera-control-pill__options button[b-tfqe5uciuq] {
    display: block;
    width: 100%;
    padding: 7px 9px;
    border: 0;
    border-radius: var(--radius-control);
    color: inherit;
    background: transparent;
    font: inherit;
    font-size: 12px;
    text-align: left;
    cursor: pointer;
}

.camera-control-pill__options hr[b-tfqe5uciuq] {
    height: 1px;
    margin: 4px 2px;
    border: 0;
    background: var(--stroke);
}

/* Phone camera chrome is one right-edge button until requested. The native popover owns light dismissal,
   Escape, focus return, and its implicit aria-expanded relationship; the expanded strip opens inward and
   never changes the camera or percentage state. Desktop retains the pixel-aligned control above.
   Compact tier via the shared attribute (wwwroot/js/responsiveTier.js), not a restated @media condition — see
   that file for why a breakpoint cannot be shared in CSS itself. `:root[data-phone]` also outranks the base
   rules above on specificity, so this block no longer depends on appearing after them. */
:root[data-phone] .camera-control-pill--desktop[b-tfqe5uciuq] { display: none; }

/* A content-sized percent chip, not a lone icon box (see the markup comment on the trigger). `position:
   absolute` remains the DEFAULT so a host that drops this component straight onto a canvas still gets a
   corner-anchored control with no extra CSS — but every Viewer host now re-parents it into an existing chrome run instead; those hosts override this to `position: static` so the chip joins that run
   rather than floating alone mid-canvas. The full pill radius matches .sheet-trigger-pill, the other
   glyph+label trigger this tier draws. */
:root[data-phone] .camera-control-pill__mobile-trigger[b-tfqe5uciuq] {
    position: absolute;
    z-index: 5;
    inset: 6px 6px auto auto;
    display: flex;
    min-width: var(--tap);
    min-height: var(--tap);
    align-items: center;
    justify-content: center;
    gap: 3px;
    box-sizing: border-box;
    padding: 0 10px;
    border: 1px solid var(--stroke);
    border-radius: 999px;
    color: var(--text-primary);
    background: var(--surface-nav);
    box-shadow: var(--shadow-panel);
    font-family: var(--font-interface);
    white-space: nowrap;
    cursor: pointer;
}

:root[data-phone] .camera-control-pill__mobile-trigger-percent[b-tfqe5uciuq] {
    color: inherit;
    font-variant-numeric: tabular-nums;
}

:root[data-phone] .camera-control-pill__mobile-trigger:hover[b-tfqe5uciuq],
:root[data-phone] .camera-control-pill__mobile-button:hover[b-tfqe5uciuq] { background: var(--surface-hover); }

:root[data-phone] .camera-control-pill__mobile-panel:popover-open[b-tfqe5uciuq] {
    position: fixed;
    inset: max(8px, env(safe-area-inset-top)) max(8px, env(safe-area-inset-right)) auto auto;
    display: flex;
    max-width: calc(100vw - 16px - env(safe-area-inset-left) - env(safe-area-inset-right));
    box-sizing: border-box;
    align-items: center;
    margin: 0;
    padding: 3px;
    border: 1px solid var(--stroke);
    border-radius: var(--radius-overlay);
    color: var(--text-primary);
    background: var(--surface-raised);
    box-shadow: var(--shadow-overlay);
    font-family: var(--font-interface);
}

:root[data-phone] .camera-control-pill__mobile-button[b-tfqe5uciuq] {
    display: grid;
    width: var(--tap);
    height: var(--tap);
    flex: 0 0 var(--tap);
    place-items: center;
    padding: 0;
    border: 0;
    border-radius: var(--radius-control);
    color: inherit;
    background: transparent;
    cursor: pointer;
}

:root[data-phone] .camera-control-pill__mobile-panel .camera-control-pill__divider[b-tfqe5uciuq] {
    height: 24px;
}

:root[data-phone] .camera-control-pill__mobile-panel .camera-control-pill__percent[b-tfqe5uciuq] {
    width: 54px;
    height: var(--tap);
    flex: 0 1 54px;
    font-size: 16px;
}

:root[data-phone] .camera-control-pill__mobile-close[b-tfqe5uciuq] { margin-left: 2px; }

:root[data-phone] .camera-control-pill__mobile-options[b-tfqe5uciuq] {
    position: fixed;
    inset: max(60px, calc(env(safe-area-inset-top) + 52px)) max(8px, env(safe-area-inset-right)) auto auto;
    box-sizing: border-box;
    width: max-content;
    max-width: calc(100vw - 16px - env(safe-area-inset-left) - env(safe-area-inset-right));
    margin: 0;
}

:root[data-phone] .camera-control-pill__mobile-options button[b-tfqe5uciuq] {
    min-height: var(--tap);
    padding: 9px 11px;
    font-size: 14px;
}
/* viewer/Components/ConsequenceOutcomeList.razor.rz.scp.css */
/* Extracted verbatim (values unchanged) from SceneReadDetailView.razor.css's own ".scene-inspector__outcome*"
   rules — see that file's history comment for the surface-card/surface-app pairing lesson this still follows.
   Blazor CSS isolation scopes by component, so the subject-chip "ring the chip in its own colour" look
   (TagReference.razor's own doc: "chrome stays at the call site") is duplicated here rather than shared with
   SceneReadDetailView.razor.css's unrelated POV/metric/marker/note chips — the two chip families diverged the
   moment this row moved into its own component. */
.outcome-list[b-myeeh6afya] { display: grid; gap: 4px; }
.outcome-list__title[b-myeeh6afya] { margin: 0 0 var(--sp-2); }
.outcome-list__row[b-myeeh6afya] { padding: 12px 10px; }
.outcome-list__header[b-myeeh6afya] { display: flex; flex-wrap: wrap; align-items: center; gap: 5px; }
.outcome-list__kind[b-myeeh6afya] { display: inline-flex; align-items: center; gap: 7px; margin: 0; }
.outcome-list__kind[b-myeeh6afya]  .outcome-list__glyph { font-size: 14px; line-height: 1; }
/* The authored outcome scale VALUE reads as normal text, not secondary chrome (task #22 faint-text fix). */
.outcome-list__scale[b-myeeh6afya] { min-width: 18px; justify-content: center; padding-inline: 5px; color: var(--text-primary); }
.outcome-list__subjects[b-myeeh6afya] { display: flex; flex-wrap: wrap; gap: 5px; }
.outcome-list__subject-chip[b-myeeh6afya] {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    border: 1px solid var(--semantic-color);
    border-radius: 10px;
    background: transparent;
    padding: 3px 7px;
    color: var(--text-secondary);
    cursor: pointer;
}
.outcome-list__subject-chip:hover[b-myeeh6afya],
.outcome-list__subject-chip:focus-visible[b-myeeh6afya] { border-color: var(--accent); background: var(--surface-hover); }
.outcome-list__note[b-myeeh6afya] { margin: 5px 0 0; color: var(--text-primary); font-size: 12px; line-height: 1.45; white-space: pre-wrap; }

@media (max-width: 720px) {
    .outcome-list__subject-chip[b-myeeh6afya] { min-height: var(--tap); }
}
/* viewer/Components/FormattedDocumentView.razor.rz.scp.css */
.formatted-document[b-f3wixbotg9] {
    overflow-wrap: anywhere;
    font-size: 17px;
    line-height: 1.75;
}

.prose-line[b-f3wixbotg9] {
    min-height: 1.75em;
    margin-block: 0;
    white-space: pre-wrap;
}

.document-title[b-f3wixbotg9],
.heading-one[b-f3wixbotg9],
.heading-two[b-f3wixbotg9],
.heading-three[b-f3wixbotg9] {
    margin-block: 1.3em .35em;
    font-family: inherit;
    font-weight: 650;
    line-height: inherit;
}

.document-title[b-f3wixbotg9] { font-size: 2.333em; font-weight: 400; }
.heading-one[b-f3wixbotg9] { font-size: 1.5em; }
.heading-two[b-f3wixbotg9] { font-size: 1.25em; }
.heading-three[b-f3wixbotg9] { font-size: 1.167em; }

.list-line[b-f3wixbotg9] {
    padding-inline-start: var(--list-indent, 1.5rem);
}

/* ::deep below (here and on every other inline/run-level rule in this file): the block-level rules above target
   elements written directly in this file's markup (<p>/<h1..h3>/<div>), which the Razor compiler scope-stamps
   automatically. Every inline run span, though (line-prefix, the format-* classes, the annotation-link family),
   is built by hand through RenderTreeBuilder in LineContent's @code lambda — invisible to that static-markup
   scope stamping, so the plain (unprefixed) form of these selectors compiles to `.foo[b-xxx]` and silently
   never matches a real `<span class="foo">` with no such attribute. ::deep opts the WHOLE selector out of
   scoping (valid as the very first token, not only after an already-scoped ancestor) so it matches the actual,
   unstamped DOM Blazor renders. */
[b-f3wixbotg9] .line-prefix {
    display: inline-block;
    width: 1.5rem;
    margin-inline-start: -1.5rem;
    color: var(--accent);
    font-variant-numeric: tabular-nums;
}

.scene-break[b-f3wixbotg9] {
    padding-block: 1.5rem;
    text-align: center;
    color: var(--text-muted);
    letter-spacing: .2em;
}

.screenplay-heading[b-f3wixbotg9] {
    margin-block-start: 1em;
    font-size: 1.05em;
    font-weight: 650;
    text-transform: uppercase;
}

.screenplay-character[b-f3wixbotg9] {
    width: 66%;
    margin-block-start: 1em;
    margin-inline-start: 34%;
    font-weight: 650;
    text-transform: uppercase;
}

.screenplay-dialogue[b-f3wixbotg9] {
    width: 58%;
    margin-inline-start: 22%;
}

.screenplay-parenthetical[b-f3wixbotg9] {
    width: 45%;
    margin-inline-start: 30%;
    font-style: italic;
}

.screenplay-transition[b-f3wixbotg9] {
    font-weight: 650;
    text-align: right;
    text-transform: uppercase;
}

.align-center[b-f3wixbotg9] { text-align: center; }
.align-right[b-f3wixbotg9] { text-align: right; }
.align-justify[b-f3wixbotg9] { text-align: justify; }
[b-f3wixbotg9] .format-bold { font-weight: 700; }
[b-f3wixbotg9] .format-italic { font-style: italic; }
[b-f3wixbotg9] .format-strike { text-decoration: line-through; }
[b-f3wixbotg9] .format-underline { text-decoration: underline; }
[b-f3wixbotg9] .format-strike.format-underline { text-decoration: underline line-through; }

/* Inline published annotation link (rendered by this component only). position: relative anchors the nested
   AnnotationHoverPreview card's position: absolute directly beneath the link text — see the reveal rule below. */
[b-f3wixbotg9] .annotation-link { position: relative; display: inline; margin: 0; padding: 0; border: 0; color: var(--accent); background: transparent; font: inherit; line-height: inherit; text-align: inherit; text-decoration: underline; text-decoration-color: color-mix(in srgb, var(--accent) 62%, transparent); text-underline-offset: 0.14em; cursor: pointer; }
[b-f3wixbotg9] .annotation-link:hover { text-decoration-thickness: 2px; }
[b-f3wixbotg9] .annotation-link:focus-visible { border-radius: 2px; outline: 2px solid var(--accent); outline-offset: 2px; }

/* Background-note-target annotation: same link underline/wash treatment as a Tag annotation above, but the
   ONE consistent themed note colour (--note-accent) instead of the tag accent — notes carry no colour of
   their own (presence only), never a per-note colour. */
[b-f3wixbotg9] .note-annotation-link { color: var(--note-accent); text-decoration-color: color-mix(in srgb, var(--note-accent) 62%, transparent); }
[b-f3wixbotg9] .note-annotation-link:focus-visible { outline-color: var(--note-accent); }

/* The reader's OWN marked passage: a soft wash and a hairline under the words, in the margin-note voice rather
   than the link voice. It must read as "I said something here" at a glance and disappear again while reading,
   so there is no colour block, no icon, and no border box.

   The ink is a FIXED warm honey-gold rather than the theme accent (2026-08-17, matching desktop's own
   ReaderMarkWash: the accent-tinted wash read as an off-blue that was hard to see and put a reader's mark in
   the same colour family as everything else the surface accents). It is deliberately not themed: a mark carries
   no colour of its own, and telling this layer apart from every other treatment prose wears is the hue's only
   job. Same #CFA857 both hosts, at the same weights — desktop's ExactAlpha 0x40 is this 25%. */
.formatted-document[b-f3wixbotg9] { --reader-mark-ink: #CFA857; }
[b-f3wixbotg9] .reader-mark {
    border-radius: 2px;
    background: color-mix(in srgb, var(--reader-mark-ink) 25%, transparent);
    box-shadow: inset 0 -2px 0 0 color-mix(in srgb, var(--reader-mark-ink) 70%, transparent);
    cursor: pointer;
}
[b-f3wixbotg9] .reader-mark:hover { background: color-mix(in srgb, var(--reader-mark-ink) 38%, transparent); }
[b-f3wixbotg9] .reader-mark:focus-visible { outline: 2px solid var(--reader-mark-ink); outline-offset: 1px; }

/* A passage the reader STAMPED rather than wrote about. The comment wash means "someone said something here",
   so a silent one-tap reaction must not wear it at full weight: a reader who stamps a dozen passages would
   otherwise repaint the chapter as though they had written a dozen notes. Desktop's ReaderMarkWash gives a
   stamp exactly ONE THIRD of a written note's ink (StampScale) — 8% of the same honey here, the identical law
   at the identical weight — plus the hairline and end glyph this host has always drawn, which are what keep a
   wash that faint findable while scanning a dark reading column. */
[b-f3wixbotg9] .reader-mark--stamped {
    background: color-mix(in srgb, var(--reader-mark-ink) 8%, transparent);
    box-shadow: inset 0 -1px 0 0 color-mix(in srgb, var(--reader-mark-ink) 45%, transparent);
}
[b-f3wixbotg9] .reader-mark--stamped:hover { background: color-mix(in srgb, var(--reader-mark-ink) 16%, transparent); }

/* The one mark the reader has OPEN — its card is up, or its edges are being dragged. Painted heavier in the
   same honey rather than in a second colour, exactly as desktop emphasises the mark a row opened: "this one",
   not "a different kind of one". Without it the passage under an open card dissolves into the marks around it. */
[b-f3wixbotg9] .reader-mark--open {
    background: color-mix(in srgb, var(--reader-mark-ink) 45%, transparent);
    box-shadow: inset 0 -2px 0 0 var(--reader-mark-ink);
}
[b-f3wixbotg9] .reader-mark--open:hover { background: color-mix(in srgb, var(--reader-mark-ink) 52%, transparent); }

/* The glyph is a pseudo-element on purpose: the review surface turns a browser selection back into document
   offsets by summing TEXT NODES under the nearest data-start span, so a real glyph node inside the prose would
   shift every offset after it. --reader-stamp carries the Phosphor character from the one C# glyph map. */
[b-f3wixbotg9] .reader-mark--stamp::after {
    content: var(--reader-stamp, "");
    margin-inline-start: 2px;
    color: color-mix(in srgb, var(--reader-mark-ink) 80%, var(--text-secondary));
    font-family: "Thruline Phosphor Fill";
    font-size: .8em;
    font-weight: 400;
    line-height: 1;
    vertical-align: .05em;
}

/* Hover-preview reveal: AnnotationHoverPreview's own .annotation-hover-card (a CHILD component's root element,
   in ITS OWN CSS-isolation scope) starts hidden — see its razor.css. ::deep on the whole selector reaches both
   the unstamped .annotation-link ancestor above AND past the child component's separate scope. Entering uses a
   show DELAY (a deliberate pause before the ghost appears, so passing the pointer over a link in a hurry never
   flashes a card); leaving is immediate — no delay on dismiss, just the card's own short opacity fade. Keyboard
   focus reveals the same card (the link already carries tabindex="0"/role="link"), so the preview is reachable
   without a pointer too. */
[b-f3wixbotg9] .annotation-link:hover .annotation-hover-card,
[b-f3wixbotg9] .annotation-link:focus-visible .annotation-hover-card {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transition: opacity 120ms ease 400ms, visibility 0s linear 400ms;
}

/* Compact tier via the shared attribute (wwwroot/js/responsiveTier.js), not a restated @media condition — see
   that file for why a breakpoint cannot be shared in CSS itself. `:root[data-phone]` also outranks the base
   rules above on specificity, so this block no longer depends on appearing after them. */
:root[data-phone] .screenplay-character[b-f3wixbotg9] {
    width: 72%;
    margin-inline-start: 28%;
}

:root[data-phone] .screenplay-dialogue[b-f3wixbotg9] {
    width: 76%;
    margin-inline-start: 12%;
}

:root[data-phone] .screenplay-parenthetical[b-f3wixbotg9] {
    width: 68%;
    margin-inline-start: 18%;
}
/* viewer/Components/LibraryNavigationView.razor.rz.scp.css */
/* Library tree rows. Extracted from app.css; owned by the component that renders them. */

.library-tree[b-pf35lh31po] { display: block; width: 100%; min-width: 0; min-height: 100%; margin: 0; padding: 0; }
/* Desktop tree row: icon + 10x10 colour swatch + row-title label. Hover/selection is a
   subtle wash while the icon and label swap secondary->primary (driven by the row colour). */
/* Depth indent: LibraryNavigationView.razor's DepthStyle emits --tree-depth (a bare number) as an inline custom
   property rather than a baked padding-left px value, so the per-level multiplier below can differ by tier —
   see the compact override further down. `padding: 1px 4px` supplies the vertical rhythm and the RIGHT-side
   4px; padding-left is set separately (below, at higher specificity than a shorthand's own left component since
   it is a later longhand declaration in the same rule) so the two never fight. */
.tree-row[b-pf35lh31po] {
    display: grid;
    width: 100%;
    min-width: 0;
    height: 28px;
    grid-template-columns: 18px minmax(0, 1fr);
    align-items: center;
    border-radius: var(--radius-control);
    padding: 1px 4px;
    padding-left: calc(4px + var(--tree-depth, 0) * 16px);
    color: var(--text-secondary);
    font: inherit;
}
.tree-row:not(.tree-row--static):hover[b-pf35lh31po] { background: var(--surface-hover); color: var(--text-primary); }
/* Match desktop TreeViewItem selection exactly: its SubtleSecondaryColor wash and primary row content.
   No accent-soft fill, edge bar, or shadow. */
.tree-row.selected[b-pf35lh31po] { background: var(--surface-selected); color: var(--text-primary); }
.tree-row--static[b-pf35lh31po] { color: var(--text-muted); }
.tree-toggle[b-pf35lh31po] { width: 18px; height: 24px; padding: 0; border: 0; background: transparent; color: var(--text-muted); cursor: pointer; }
.tree-toggle-spacer[b-pf35lh31po] { width: 18px; }
.tree-content[b-pf35lh31po] { display: grid; grid-template-columns: 18px auto minmax(0, 1fr); align-items: center; width: 100%; min-width: 0; height: 26px; overflow: hidden; border: 0; background: transparent; padding: 0; color: inherit; font: inherit; text-align: left; cursor: pointer; }
.tree-content:disabled[b-pf35lh31po], .tree-content--static[b-pf35lh31po] { color: inherit; cursor: default; }
.node-icon[b-pf35lh31po] { font-size: 13px; text-align: center; color: inherit; }
/* An imported icon ASSET replaces the glyph outright and occupies the same slot, so the row's text baseline
   and indent stay identical whichever tier of the ladder a folder or tag ends up on. */
.node-icon--image[b-pf35lh31po] { width: 16px; height: 16px; object-fit: contain; flex: 0 0 16px; }
.tree-label[b-pf35lh31po] { grid-column: 3; min-width: 0; overflow: hidden; padding: 4px; color: inherit; text-overflow: ellipsis; white-space: nowrap; }
.tree-empty[b-pf35lh31po] { margin: 20px 12px; text-align: center; }
/* Compact tier via the shared attribute (wwwroot/js/responsiveTier.js), not a restated @media condition — see
   that file for why a breakpoint cannot be shared in CSS itself. `:root[data-phone]` also outranks the base
   rules above on specificity, so this block no longer depends on appearing after them. */
:root[data-phone] .tree-row[b-pf35lh31po] {
    grid-template-columns: var(--tap) minmax(0, 1fr);
    height: auto;
    min-height: var(--tap);
    padding-block: 0;
    /* Owner density nit: the desktop 16px/level indent pushed a deep row's label roughly a third of the way
       across a 400px phone screen before any text started (e.g. a Character's children starting ~180px in).
       10px/level keeps hierarchy legible while giving text more of the narrow row back; touch height itself
       (min-height: var(--tap) above) is unchanged — 44px is the floor, not the thing that read as generous. */
    padding-left: calc(4px + var(--tree-depth, 0) * 10px);
}
:root[data-phone] .tree-toggle[b-pf35lh31po] { width: var(--tap); height: var(--tap); }
:root[data-phone] .tree-content[b-pf35lh31po] { height: var(--tap); }
:root[data-phone] .tree-label[b-pf35lh31po] { font-size: 14px; }
/* viewer/Components/LibraryTraitFilterPanel.razor.rz.scp.css */
/* Library trait filter: a self-contained 28px funnel trigger plus a native-popover panel, matching Read's own
   .read-settings-trigger/.read-settings-panel idiom rather than inventing a new overlay pattern. */
.library-trait-filter[b-vgxzdot5gv] { position: relative; display: contents; }

/* Self-contained trigger chrome (deliberately not depending on Home.razor.css's own scoped .tool-button class,
   which a child component's rendered markup cannot reliably select into) — same 28x28 quiet icon-button look
   as every other Library navigator-tools button. */
.library-trait-filter__trigger[b-vgxzdot5gv] {
    position: relative;
    display: grid;
    place-items: center;
    width: 28px;
    height: 28px;
    border: 0;
    border-radius: var(--radius-control);
    background: transparent;
    color: var(--text-secondary);
    cursor: pointer;
    transition: background-color 120ms ease, color 120ms ease;
}
.library-trait-filter__trigger:hover[b-vgxzdot5gv] { background: var(--surface-hover); color: var(--text-primary); }
.library-trait-filter__trigger--active[b-vgxzdot5gv] { color: var(--accent); }

.library-trait-filter__badge[b-vgxzdot5gv] {
    position: absolute;
    top: -3px;
    right: -3px;
    display: grid;
    place-items: center;
    min-width: 14px;
    height: 14px;
    border-radius: 7px;
    background: var(--accent);
    padding: 0 3px;
    color: var(--surface-app);
    font-size: 9px;
    font-weight: 600;
    line-height: 1;
    pointer-events: none;
}

.library-trait-filter__panel[b-vgxzdot5gv] {
    /* Implicit anchor (no explicit anchor-name/position-anchor pair, matching WebEntityPicker's own popover):
       a [popovertarget] button is automatically its target popover's anchor, so this positions correctly
       against WHICHEVER of this component's two rendered instances (desktop toolbar / mobile search row) was
       actually clicked, with no custom-ident collision between them. */
    position: fixed;
    position-area: block-end span-inline-end;
    position-try-fallbacks: flip-block, flip-inline;
    z-index: 10;
    margin: 4px 0 0;
    gap: 8px;
    width: min(360px, calc(100vw - var(--sp-5)));
    max-height: min(70dvh, 460px);
    overflow-y: auto;
    border: 1px solid var(--stroke);
    border-radius: var(--radius-overlay);
    background: var(--surface-raised);
    box-shadow: var(--shadow-overlay);
    padding: var(--sp-3);
    color: var(--text-secondary);
    font-size: 12px;
}
/* Display only while open: an unconditional `display` on a [popover] element would override the UA's default
   `display:none` for the closed state and leave the panel permanently visible (and pointer-event-intercepting)
   even before its trigger is ever clicked — see WebEntityPicker's own popover rule for the same convention. */
.library-trait-filter__panel:popover-open[b-vgxzdot5gv] { display: grid; }

.library-trait-filter__explainer[b-vgxzdot5gv] { margin: 0; color: var(--text-secondary); }
.library-trait-filter__empty[b-vgxzdot5gv] { margin: 0; color: var(--picker-row-meta); font-size: 11px; }

.library-trait-filter__row[b-vgxzdot5gv] { display: flex; align-items: center; gap: 6px; }
.library-trait-filter__arrow[b-vgxzdot5gv] { flex: 0 0 auto; color: var(--picker-row-meta); }

.library-trait-filter__remove[b-vgxzdot5gv],
.library-trait-filter__add[b-vgxzdot5gv],
.library-trait-filter__clear[b-vgxzdot5gv] {
    display: flex;
    align-items: center;
    gap: 6px;
    border: 0;
    border-radius: var(--radius-control);
    background: transparent;
    color: var(--text-secondary);
    cursor: pointer;
}
.library-trait-filter__remove[b-vgxzdot5gv] { flex: 0 0 auto; width: 26px; height: 26px; justify-content: center; padding: 0; }
.library-trait-filter__remove:hover[b-vgxzdot5gv] { background: var(--surface-hover); color: var(--danger); }
.library-trait-filter__add[b-vgxzdot5gv] { padding: 5px 6px; justify-self: start; }
.library-trait-filter__add:hover[b-vgxzdot5gv] { background: var(--surface-hover); color: var(--text-primary); }
.library-trait-filter__clear[b-vgxzdot5gv] {
    justify-self: end;
    padding: 4px 8px;
    border-top: 1px solid var(--stroke);
    border-radius: 0;
    padding-top: 8px;
    width: 100%;
    justify-content: flex-end;
}
.library-trait-filter__clear:hover[b-vgxzdot5gv] { color: var(--text-primary); }

:root[data-phone] .library-trait-filter__trigger[b-vgxzdot5gv] { min-width: var(--tap); min-height: var(--tap); }
:root[data-phone] .library-trait-filter__panel[b-vgxzdot5gv] {
    position-try-fallbacks: flip-inline, flip-block;
    width: min(360px, calc(100vw - var(--sp-5) - env(safe-area-inset-left) - env(safe-area-inset-right)));
    font-size: 14px;
}
/* viewer/Components/ReviewWalkthrough.razor.rz.scp.css */
/* The reader's first-run walkthrough. One centred card over a scrim, sized from its own content and clamped
   against the viewport, so the same markup serves a desktop split view and a phone held in one hand without a
   per-width layout. Compact treatment keys off the shared attribute (wwwroot/js/responsiveTier.js), never a
   restated @media condition. */

.reader-walkthrough[b-78jlc90uij] {
    position: fixed;
    inset: 0;
    z-index: 60;
    display: grid;
    place-items: center;
    padding: var(--sp-4);
}

.reader-walkthrough:focus[b-78jlc90uij] {
    outline: none;
}

.reader-walkthrough__scrim[b-78jlc90uij] {
    position: absolute;
    inset: 0;
    background: color-mix(in srgb, var(--surface-app) 78%, transparent);
    backdrop-filter: blur(2px);
}

.reader-walkthrough__card[b-78jlc90uij] {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: var(--sp-3);
    inline-size: min(30rem, 100%);
    max-block-size: min(34rem, calc(100dvh - var(--sp-6)));
    overflow-y: auto;
    padding: var(--sp-5);
    box-shadow: var(--shadow-overlay);
}

.reader-walkthrough__head[b-78jlc90uij] {
    display: flex;
    flex-direction: column;
    gap: var(--sp-1);
}

.reader-walkthrough__symbol[b-78jlc90uij] {
    display: grid;
    place-items: center;
    inline-size: 2.25rem;
    block-size: 2.25rem;
    margin-block-end: var(--sp-1);
    border-radius: var(--radius-control);
    background: var(--accent-soft);
    color: var(--accent);
    font-size: 1.15rem;
}

.reader-walkthrough__head h2[b-78jlc90uij] {
    margin: 0;
}

.reader-walkthrough__body[b-78jlc90uij] {
    margin: 0;
    color: var(--text-primary);
    line-height: 1.5;
}

/* The caveat reads as an aside, not a second paragraph of instruction: the rule carries the demotion so the
   words do not have to say "note that". */
.reader-walkthrough__aside[b-78jlc90uij] {
    margin: 0;
    padding-inline-start: var(--sp-3);
    border-inline-start: 2px solid var(--stroke-strong);
    color: var(--text-muted);
    font-size: 0.85rem;
    line-height: 1.45;
}

.reader-walkthrough__dots[b-78jlc90uij] {
    display: flex;
    gap: var(--sp-2);
    align-items: center;
}

.reader-walkthrough__dot[b-78jlc90uij] {
    inline-size: 1.75rem;
    block-size: 4px;
    padding: 0;
    border: none;
    border-radius: 2px;
    background: var(--stroke-strong);
    cursor: pointer;
}

.reader-walkthrough__dot.is-active[b-78jlc90uij] {
    background: var(--accent);
}

.reader-walkthrough__dot:focus-visible[b-78jlc90uij] {
    outline: 2px solid var(--accent);
    outline-offset: 3px;
}

.reader-walkthrough__actions[b-78jlc90uij] {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-2);
    align-items: center;
}

.reader-walkthrough__spacer[b-78jlc90uij] {
    flex: 1 1 auto;
}

/* Phones: the card takes the width it is given and its controls take a real tap target. The product guide is
   hidden outright at this tier (owner ruling 2026-07-30) because it teaches canvas sections a phone cannot
   drive; this walkthrough teaches a gesture a phone reader makes with their thumb, so it stays. */
:root[data-phone] .reader-walkthrough[b-78jlc90uij] {
    padding: var(--sp-2);
}

:root[data-phone] .reader-walkthrough__card[b-78jlc90uij] {
    inline-size: 100%;
    max-block-size: calc(100dvh - var(--sp-4));
    padding: var(--sp-4);
}

:root[data-touch] .reader-walkthrough__dot[b-78jlc90uij] {
    block-size: 6px;
    min-inline-size: 2.25rem;
}

@media (prefers-reduced-motion: reduce) {
    .reader-walkthrough__scrim[b-78jlc90uij] {
        backdrop-filter: none;
    }
}
/* viewer/Components/SceneJumpPicker.razor.rz.scp.css */
/* The shared jump-to-a-scene control owns only what the WebEntityPicker shell cannot know about: how its trigger
   label truncates, and that its Library-tree body is a filtered, scrolling region. Every other look (trigger
   chrome, popover placement, row grammar) belongs to the shell or to the call site's own ::deep overrides — see
   Read.razor.css's page-heading treatment and Review.razor.css's chrome treatment. */
.scene-jump-picker[b-ziwdw5ittx] { position: relative; min-width: 0; }

.scene-jump-picker__label[b-ziwdw5ittx] {
    overflow: hidden;
    min-width: 0;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The tree body replaces the shell's built-in list, and with it the shell's own filter box — so the tree branch
   states one here rather than leaving the picker searchless (desktop's switcher filters too). Same field metrics
   as the shell's, which is chrome the two cannot share across their separate scoped-style boundaries. */
.scene-jump-picker__search[b-ziwdw5ittx] {
    flex: 0 0 auto;
    box-sizing: border-box;
    width: 100%;
    min-height: 28px;
    border: 1px solid var(--stroke-control);
    border-radius: var(--radius-control);
    outline: none;
    background: var(--surface-input);
    padding: 3px 7px;
    color: var(--text-primary);
    font-family: var(--font-interface);
    font-size: 12px;
}
.scene-jump-picker__search:hover[b-ziwdw5ittx] { border-color: var(--accent); }
.scene-jump-picker__search:focus-visible[b-ziwdw5ittx] { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }

.scene-jump-picker__tree[b-ziwdw5ittx] {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding-top: 6px;
}
/* A separator only where there is actually something above to separate from (Read's "Full script" row). */
.scene-jump-picker__tree:not(:first-child)[b-ziwdw5ittx] { border-top: 1px solid var(--stroke); }

:root[data-touch] .scene-jump-picker__search[b-ziwdw5ittx] { min-block-size: var(--tap); }
/* viewer/Components/SceneReadDetailView.razor.rz.scp.css */
/* The Library Scene detail has a Plan | Manuscript tab strip on top (mirrors desktop SceneReadView.axaml's
   TabControl). Overview/Data stay flat (spacing and hairlines, no card fill) — desktop's own OVERVIEW/DATA
   StackPanels sit directly on the page the same way. Manuscript is the prose, reading exactly as Read's own
   column does.
   2026-08-08 desktop parity ("give the Scene read page the SAME card grammar the desktop SceneReadView has and
   the Tag read page now has" — TagReadDetailView.razor.css carries the identical comment). The page is the dark
   app ground (--surface-app), not the --surface-nav "lifted" tone the shared read-pane/peek host normally
   paints — Beats and Outcomes below are `.surface-card`s (the SAME shared app.css class the Tag article uses)
   lifted off it. A PRIOR attempt at this (see this file's own now-superseded ".scene-inspector__outcome" comment
   further down) tried the opposite pairing — page stayed --surface-nav, the nested box went --surface-app — and
   that read as "a well punched into the page" because --surface-app is DARKER than --surface-nav: a card must be
   LIGHTER than the ground it sits on to read as lifted, never darker. min-height:100% so the dark ground reaches
   the bottom of a short Scene rather than leaving --surface-nav showing through beneath it. */
.scene-inspector[b-09idnsg8za] {
    box-sizing: border-box;
    width: 100%;
    max-width: 860px;
    min-height: 100%;
    margin: 0 auto;
    padding: 24px clamp(16px, 4vw, 32px) 56px;
    background: var(--surface-app);
}

/* VERTICAL RHYTHM. Three decisions, taken from app.css's --sp-* scale, replacing the 2/3/5/6/7/8/10/12/13/16/18
   assortment this file had accumulated (app.css's own scale comment: existing page CSS migrates onto the tokens
   as it is next touched):
     --sp-4  the gap on BOTH sides of a hairline, so a rule always sits centred between what it separates,
     --sp-3  the gap that leads a new block inside a section,
     --sp-2  heading to its content; --sp-1 for a sub-heading, which binds tighter to what it labels.
   Chip padding, radii, and icon gaps are control geometry rather than rhythm and stay as authored. */
.scene-inspector__heading[b-09idnsg8za] { margin: 0 0 var(--sp-4); }
/* Sits UNDER the title now, so its margin leads rather than trails. */
.scene-inspector__folder-path[b-09idnsg8za] { margin: var(--sp-1) 0 0; color: var(--text-muted); }
.scene-inspector__section-label[b-09idnsg8za] { margin: 0 0 var(--sp-1); }
.scene-inspector__section-title[b-09idnsg8za] { margin: 0 0 var(--sp-2); }
/* --sp-2, not --sp-1: a chip row is a band of outlined pills rather than a line of text, and a 4px gap let the
   pills crowd the label they sit under (owner report on Metrics and Markers). */
.scene-inspector__subsection-title[b-09idnsg8za] { margin: 0 0 var(--sp-2); }
/* Data's state strip and optional groups share one vertical rhythm. */
.scene-inspector__metadata + .scene-inspector__fact-group[b-09idnsg8za],
.scene-inspector__fact-group + .scene-inspector__fact-group[b-09idnsg8za] { margin-top: var(--sp-3); }
.scene-inspector__heading h1[b-09idnsg8za] { margin: 0; font-family: var(--font-reading); line-height: 1.25; }
.scene-inspector__title-row[b-09idnsg8za] {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 18px;
}
/* Plan | Manuscript tab strip — sits directly under the heading, above both tabpanels. Selection reads via the
   accent underline (matching .read-mode-link's quiet, chrome-level look elsewhere in the Viewer) rather than a
   filled pill, since this strip sets the surface for everything below it rather than acting as a toolbar. */
.scene-inspector__tabs[b-09idnsg8za] {
    display: flex;
    gap: var(--sp-1);
    margin: 0;
    border-bottom: 1px solid var(--stroke);
}
/* The strip's own bottom rule already separates it from the panel, so the first section beneath must not draw
   a second one 6px below it — two hairlines that close read as a mis-set double rule rather than a boundary.
   The section keeps its top PADDING, so the rhythm above its heading is unchanged. */
[role="tabpanel"] > .scene-inspector__section:first-child[b-09idnsg8za] { border-top: 0; }
.scene-inspector__tab[b-09idnsg8za] {
    border: 0;
    border-bottom: 2px solid transparent;
    background: transparent;
    padding: 8px 2px;
    color: var(--text-secondary);
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
}
.scene-inspector__tab:hover[b-09idnsg8za] { color: var(--text-primary); }
.scene-inspector__tab.selected[b-09idnsg8za] { border-bottom-color: var(--accent); color: var(--text-primary); }
.scene-inspector__tab:focus-visible[b-09idnsg8za] { border-radius: 2px; outline: 2px solid var(--accent); outline-offset: -2px; }

/* Manuscript tab: the SAME reading treatment Read's own column uses (app.css's shared --prose-font-size/
   --prose-line-height/--prose-paragraph-spacing tokens), reached through this component's own ::deep rule
   rather than a global selector — see app.css's token comment for why a global rule can't reliably win against
   FormattedDocumentView's own scoped default. */
.scene-inspector__manuscript[b-09idnsg8za] { padding: var(--sp-4) 0 var(--sp-2); }
.scene-inspector__manuscript[b-09idnsg8za]  .formatted-document {
    font-family: var(--font-reading);
    font-size: var(--prose-font-size);
    line-height: var(--prose-line-height);
}
.scene-inspector__manuscript[b-09idnsg8za]  .prose-line { margin: 0 0 var(--prose-paragraph-spacing); }
.scene-inspector__manuscript[b-09idnsg8za]  .prose-line:last-child { margin-bottom: 0; }

.scene-inspector__section[b-09idnsg8za] { margin: 0; border-top: 1px solid var(--stroke); padding: var(--sp-4) 0; }
.scene-inspector__metadata[b-09idnsg8za],
.scene-inspector__metrics[b-09idnsg8za],
.scene-inspector__markers[b-09idnsg8za],
.scene-inspector__beat-facts[b-09idnsg8za] {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}
.scene-inspector__metrics[b-09idnsg8za],
.scene-inspector__markers[b-09idnsg8za] { gap: 8px; }

/* Hollow outlined pill — desktop's own Border.chip/chip-button shape (transparent background, 1px stroke,
   10px radius): a chip whose entity carries a REAL colour (POV, a marker, a metric, an outcome subject)
   rings itself in that colour, exactly as desktop's `Button.color-chip`/`pov-color-chip` style puts the tag
   colour on the BorderBrush rather than on a separate dot; a chip with no entity colour (on-screen, status,
   a background note) keeps the neutral stroke instead. */
.scene-inspector__chip[b-09idnsg8za],
.scene-inspector__metric[b-09idnsg8za],
.scene-inspector__marker[b-09idnsg8za],
.scene-inspector__note-chip[b-09idnsg8za] {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    min-height: 26px;
    border: 1px solid var(--stroke);
    border-radius: 10px;
    background: transparent;
    padding: 5px 10px;
    color: var(--text-secondary);
    font-size: 12px;
    line-height: 1;
}
.scene-inspector__chip--pov[b-09idnsg8za],
.scene-inspector__metric[b-09idnsg8za],
.scene-inspector__marker[b-09idnsg8za] { border-color: var(--semantic-color); }

.scene-inspector__chip > span[b-09idnsg8za] {
    color: var(--text-muted);
    font-size: 12px;
    font-weight: 400;
    line-height: 1;
    text-transform: uppercase;
}
.scene-inspector__chip strong[b-09idnsg8za],
.scene-inspector__metric strong[b-09idnsg8za],
.scene-inspector__marker strong[b-09idnsg8za] {
    color: var(--text-primary);
    font-size: 12px;
    font-weight: 600;
    line-height: 1;
}
.scene-inspector__chip[b-09idnsg8za]  .phosphor-icon {
    display: inline-flex;
    width: 12px;
    height: 12px;
    align-items: center;
    justify-content: center;
    font-size: 12px;
    line-height: 1;
}
.scene-inspector__chip--pov[b-09idnsg8za],
.scene-inspector__note-chip[b-09idnsg8za] { cursor: pointer; }
.scene-inspector__chip--pov:hover[b-09idnsg8za],
.scene-inspector__note-chip:hover[b-09idnsg8za] { border-color: var(--accent); background: var(--surface-hover); }
/* The authored VALUE reads as normal text, not secondary chrome — only the "Metrics"/"Markers" section
   labels and chip micro-labels above stay muted (task #22 faint-text fix). */
.scene-inspector__metric-value[b-09idnsg8za] {
    min-height: 18px;
    justify-content: center;
    padding: 1px 5px;
    color: var(--text-primary);
    font-size: 12px;
    line-height: 1;
}

/* Both blocks under Overview use the same heading-to-content rhythm. */
.scene-inspector__overview-block[b-09idnsg8za] { margin-top: var(--sp-3); }
.scene-inspector__summary[b-09idnsg8za] { margin: 0; font-size: 14px; line-height: 1.5; }
.scene-inspector__notes[b-09idnsg8za] { margin-top: var(--sp-3); }
.scene-inspector__notes h3[b-09idnsg8za] { margin: 0 0 var(--sp-1); }
.scene-inspector__notes p[b-09idnsg8za] {
    margin: 0;
    color: var(--text-secondary);
    font-size: 14px;
    line-height: 1.5;
    white-space: pre-wrap;
}

/* 2026-08-08: each Beat is now its own `.surface-card` (desktop parity — SceneReadView's own Beats ItemsControl
   wraps each beat in `Border Classes="group-box" Padding="14,12"`, `StackPanel Spacing="10"` between them) rather
   than a bare run separated by hairlines — desktop's own owner feedback for this exact list was "events bleed
   together" (see SceneReadView.axaml's matching comment). Padding matches desktop's card exactly. */
.scene-inspector__beats[b-09idnsg8za] { display: grid; gap: 10px; }
.scene-inspector__beat[b-09idnsg8za] { padding: 14px 12px; }
.scene-inspector__beat h3[b-09idnsg8za] { margin: 0 0 var(--sp-2); }
.scene-inspector__beat-facts[b-09idnsg8za] { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-1); margin-top: var(--sp-2); }

/* A beat's own tag is a flat navigation row, not a chip — desktop's nav-row Button around TagReference: no
   border, a quiet hover wash is the only affordance. */
.scene-inspector__tag-nav[b-09idnsg8za] {
    display: inline-flex;
    align-items: center;
    border: 0;
    border-radius: var(--radius-control);
    background: transparent;
    padding: 2px 5px;
    cursor: pointer;
}
.scene-inspector__tag-nav:hover[b-09idnsg8za],
.scene-inspector__tag-nav:focus-visible[b-09idnsg8za] { background: var(--surface-hover); }

/* Relations are flat too: no card border around the row, matching desktop's 2026-07-17 ruling ("no card
   border/fill" for a relationship row). Each endpoint is its own flat nav button; the fixed Bold arrow-right
   glyph joins them, and the beat's own valence mark (the plain-text twin of ShiftTier.Icon) trails the row. */
.scene-inspector__relation[b-09idnsg8za] { display: flex; align-items: center; gap: 4px; padding: 2px 0; }
.scene-inspector__relation-endpoint[b-09idnsg8za] {
    display: inline-flex;
    align-items: center;
    border: 0;
    border-radius: var(--radius-control);
    background: transparent;
    padding: 2px 5px;
    cursor: pointer;
}
.scene-inspector__relation-endpoint:hover[b-09idnsg8za],
.scene-inspector__relation-endpoint:focus-visible[b-09idnsg8za] { background: var(--surface-hover); }
.scene-inspector__relation[b-09idnsg8za]  .scene-inspector__relation-arrow { color: var(--text-secondary); font-size: 13px; }
.scene-inspector__relation-valence[b-09idnsg8za] { color: var(--text-muted); font-size: 12px; }

.scene-inspector__empty[b-09idnsg8za] { margin: var(--sp-2) 0; font-style: italic; }
.scene-inspector__consequences-title[b-09idnsg8za] {
    margin-top: var(--sp-4);
    border-top: 1px solid var(--stroke);
    padding-top: var(--sp-4);
}
/* 2026-08-08: the outcome ROW itself (kind glyph, subjects, scale, note) moved into the shared
   ConsequenceOutcomeList component (Components/ConsequenceOutcomeList.razor[.css]) — reused verbatim by the
   Workspace reason-card popup — so only this section's own title hairline/spacing stays here. The lesson that
   drove the row to `.surface-card` (--surface-nav, lighter than this page's --surface-app ground) lives in that
   component's own CSS comment now. */

/* BACKGROUND NOTES: leading tertiary glyph, then a wrapped row of plain (uncoloured) note chips. */
.scene-inspector__notes-row[b-09idnsg8za] { display: flex; align-items: flex-start; gap: 8px; }
.scene-inspector__notes-row[b-09idnsg8za]  .scene-inspector__notes-icon { margin-top: 4px; color: var(--text-muted); font-size: 13px; }
.scene-inspector__notes-chips[b-09idnsg8za] { display: flex; flex-wrap: wrap; gap: 6px 8px; }
.scene-inspector__section--structure[b-09idnsg8za]  .formatted-document {
    color: var(--text-primary);
    font-size: 15px;
    line-height: 1.55;
}
.scene-inspector__section--structure[b-09idnsg8za]  .prose-line {
    min-height: 1.55em;
    margin: 0 0 4px;
}
.scene-inspector__section--structure[b-09idnsg8za]  .prose-line:last-child { margin-bottom: 0; }

@media (max-width: 720px) {
    .scene-inspector[b-09idnsg8za] { margin: 0 auto; padding: 14px 16px 36px; }
    .scene-inspector__heading[b-09idnsg8za] { margin-bottom: 12px; }
    .scene-inspector__heading h1[b-09idnsg8za] { font-size: 18px; }
    .scene-inspector__title-row[b-09idnsg8za] { align-items: flex-start; flex-direction: column; gap: 8px; }
    .scene-inspector__tab[b-09idnsg8za],
    .scene-inspector__chip--pov[b-09idnsg8za],
    .scene-inspector__note-chip[b-09idnsg8za],
    .scene-inspector__tag-nav[b-09idnsg8za],
    .scene-inspector__relation-endpoint[b-09idnsg8za] { min-height: var(--tap); }
    .scene-inspector__section[b-09idnsg8za] { padding-block: 12px; }
}
/* viewer/Components/TagReadDetailView.razor.rz.scp.css */
/* 2026-08-08 desktop parity ("read surface matches the scene peek's flat/card grammar", TAGPEEK.md): the page
   is the dark app ground, not the --surface-nav "lifted" tone the shared read-pane/peek host normally paints
   for every other detail page (Scene, Background Note). min-height:100% (not just 100% of intrinsic content)
   so the dark ground still reaches the bottom of a short article rather than leaving --surface-nav showing
   through beneath it — both hosting shells (Library's .read-pane, ViewerPeekHost's .viewer-peek__body) give
   this component a definite-height box to stretch into. */
.tag-inspector[b-h0m5vn0jqe] {
    width: 100%;
    min-height: 100%;
    background: var(--surface-app);
}

.tag-identity-banner[b-h0m5vn0jqe] {
    position: relative;
    z-index: 1;
    display: flex;
    align-items: center;
    gap: 10px;
    min-height: 66px;
    width: 100%;
    max-width: 860px;
    margin: 0 auto;
    border-bottom: 1px solid var(--stroke);
    padding: 24px clamp(16px, 4vw, 32px) 18px;
    background: transparent;
    box-sizing: border-box;
}

.tag-identity-swatch[b-h0m5vn0jqe] { flex: 0 0 auto; width: 14px; height: 14px; border-radius: 50%; background: var(--semantic-color); }
.tag-profile-image[b-h0m5vn0jqe] { width: 52px; height: 52px; flex: 0 0 auto; border-radius: 7px; object-fit: cover; }
.tag-icon-image[b-h0m5vn0jqe] { width: 28px; height: 28px; margin-left: auto; flex: 0 0 auto; object-fit: contain; }
/* Leading-slot icon asset — the ladder tier below a profile portrait, so it occupies the portrait's place
   rather than the trailing badge. Same box as .tag-icon-image, minus the push to the far edge. */
.tag-identity-icon[b-h0m5vn0jqe] { width: 28px; height: 28px; flex: 0 0 auto; object-fit: contain; }
/* Authored type glyph, tinted with the tag's own colour and drawn at its authored weight. ::deep is required
   because PhosphorIcon renders its own markup, which this component's scoped attribute never reaches. */
.tag-identity-banner[b-h0m5vn0jqe]  .tag-identity-glyph { flex: 0 0 auto; color: var(--semantic-color); font-size: 26px; line-height: 1; }
.tag-identity-banner[b-h0m5vn0jqe]  .tag-icon-glyph { margin-left: auto; flex: 0 0 auto; color: var(--semantic-color); font-size: 22px; line-height: 1; }
.tag-identity-copy[b-h0m5vn0jqe] { display: grid; gap: var(--sp-1); min-width: 0; }
.tag-identity-copy h1[b-h0m5vn0jqe] { overflow: hidden; margin: 0; font-family: var(--font-reading); line-height: 1.25; text-overflow: ellipsis; }
.tag-identity-facts[b-h0m5vn0jqe] { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-1); }
.tag-identity-chip[b-h0m5vn0jqe] {
    display: inline-flex;
    align-items: center;
    min-height: 24px;
    padding: 2px var(--sp-2);
    color: var(--text-secondary);
    font-size: 11px;
    line-height: 1;
}

.tag-inspector-body[b-h0m5vn0jqe] { box-sizing: border-box; width: 100%; max-width: 860px; margin: 0 auto; padding: 0 clamp(16px, 4vw, 32px) 48px; background: transparent; }
/* Ancillary sections (Gallery, Navigation, Referenced In, Appears In, Progression) still belong to one flat
   route surface: spacing and a hairline separate them, never a card fill — only the Article below earns one. */
.tag-read-card[b-h0m5vn0jqe] { box-sizing: border-box; width: 100%; margin: 0; border: 0; border-bottom: 1px solid var(--stroke); border-radius: 0; padding: 18px 0; background: transparent; }
.tag-read-card:last-child[b-h0m5vn0jqe] { border-bottom: 0; }

/* Article: one lifted `.surface-card` PER top-level section (2026-08-08 desktop "make tag match scene" parity —
   TagArticleBlockBuilder.GroupSections' RULE, mirrored client-side in this component's own ArticleSections; see
   the .razor code-behind). --surface-nav (app.css's ".surface-card" fill) reads as an object LIFTED off
   .tag-inspector's own --surface-app page ground, the same "card lighter than the page it sits on" relationship
   desktop's Border.group-box has against its dark host — never the other way around (see app.css's own
   .surface-group-box comment for what a card reads as when that relationship is inverted). Padding 14/12 and
   10px inter-card spacing both match desktop's Border.group-box Padding="14,12" / ItemsPanel Spacing="10". */
.tag-article-sections[b-h0m5vn0jqe] { display: grid; gap: 10px; margin: 18px 0 4px; }
.tag-article-card[b-h0m5vn0jqe] { display: grid; gap: 4px; padding: 14px 12px; }
/* Indentation is the ONLY thing a nested folder heading carries beyond a top-level one — this single
   margin-left rule, shared with the field heading and body blocks it owns, is desktop's entire nesting
   mechanism (ArticleBlockMetrics.IndentFor, TagArticlePresentationBlocks.cs): a flat per-depth offset, never
   a compounding rail from nested containers. */
.tag-article-heading[b-h0m5vn0jqe], .tag-field-heading[b-h0m5vn0jqe], .tag-article-body[b-h0m5vn0jqe] { margin-left: max(0px, calc((var(--article-depth, 1) - 1) * 14px)); }
.tag-article-heading[b-h0m5vn0jqe] { margin-top: 8px; margin-bottom: 2px; }
/* The accent rule marks a TOP-LEVEL article section only (desktop's ArticleSectionHeadingBlockVm.HasAccentRule
   => Depth <= 1) — a nested subsection heading inside the same card reads through indentation and its smaller
   heading tier alone, never a second rail. Previously this border/padding sat on the bare `.tag-article-heading`
   selector and drew on every depth, which was the deep-rail parity gap (2026-08-07 desktop-canonical sweep). */
.tag-article-heading[data-depth="1"][b-h0m5vn0jqe] { margin-top: 12px; border-left: 2px solid var(--accent); padding-left: 10px; }
.tag-article-heading h2[b-h0m5vn0jqe] { margin: 0; color: var(--text-primary); font-size: 14px; line-height: 1.3; }
.tag-article-heading[data-depth="1"] h2[b-h0m5vn0jqe] { font-size: 18px; }
.tag-article-heading[data-depth="2"] h2[b-h0m5vn0jqe] { font-size: 15px; }
.tag-article-heading > span[b-h0m5vn0jqe] { display: block; width: 100%; height: 1px; margin-top: 5px; background: var(--stroke); }
.tag-field-heading[b-h0m5vn0jqe] { margin-top: 6px; margin-bottom: 0; color: var(--text-secondary); font-size: 13px; font-weight: 600; }
/* The card's own top padding already opens the leading gap a StackPanel's first item gets for free on desktop
   (no implicit leading margin there) — placed after the depth-specific margin-top rules above so it wins the
   equal-specificity tiebreak against .tag-article-heading[data-depth="1"]'s own margin-top. */
.tag-article-card > *:first-child[b-h0m5vn0jqe] { margin-top: 0; }
.tag-article-body[b-h0m5vn0jqe]  .formatted-document { color: var(--text-primary); font-family: var(--font-reading); font-size: 15px; line-height: 1.55; }
.tag-article-body[b-h0m5vn0jqe]  .prose-line { min-height: 1.55em; margin: 0 0 6px; }
.tag-article-body[b-h0m5vn0jqe]  .prose-line:last-child { margin-bottom: 0; }
.tag-image-gallery[b-h0m5vn0jqe] { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }
/* Fixed width (not max-width): the box is fully reserved at BOTH dimensions before a slow-loading image
   arrives, so neighbouring chips/gallery items never reflow horizontally once it lands (object-fit:contain
   still letterboxes the real image inside cleanly). --surface-group doesn't exist anywhere as a token
   (was silently inert, rendering transparent); --surface-nav is the same opaque image-frame fill
   BackgroundNoteReadDetailView's identical gallery role already uses. */
.tag-image-gallery img[b-h0m5vn0jqe] { box-sizing: border-box; width: 280px; height: 160px; border: 1px solid var(--stroke); border-radius: 8px; padding: 6px; background: var(--surface-nav); object-fit: contain; }
.tag-card-title[b-h0m5vn0jqe] { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); margin: 0 0 3px; }
.tag-context-toggle[b-h0m5vn0jqe] {
    flex: 0 0 auto;
    border: 0;
    padding: 1px 3px;
    background: transparent;
    color: var(--text-secondary);
    font: inherit;
    font-weight: 500;
    white-space: nowrap;
    cursor: pointer;
}
.tag-context-toggle:hover[b-h0m5vn0jqe],
.tag-context-toggle:focus-visible[b-h0m5vn0jqe] { color: var(--text-primary); text-decoration: underline; }
.tag-context-toggle[aria-pressed="true"][b-h0m5vn0jqe] { color: var(--accent); }
.tag-card-subtitle[b-h0m5vn0jqe] { margin: 8px 0 3px; }

/* Desktop's INITIAL RELATIONSHIPS row: each entry is a TagLinkButton paired with its authored starting score
   as a compact trailing badge, wrapping horizontally like desktop's HorizontalWrapItemsPanel rather than the
   vertical .tag-link-list grid the Navigation/Referenced In sections use. */
.tag-initial-relationships[b-h0m5vn0jqe] { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 14px; }
.tag-initial-relationship[b-h0m5vn0jqe] { display: inline-flex; align-items: center; gap: 4px; }
.tag-initial-relationship[b-h0m5vn0jqe]  .tag-link-button {
    display: flex;
    align-items: center;
    border: 0;
    border-radius: var(--radius-control);
    padding: var(--sp-1) var(--sp-2);
    background: transparent;
    cursor: pointer;
}
.tag-initial-relationship[b-h0m5vn0jqe]  .tag-link-button:hover,
.tag-initial-relationship[b-h0m5vn0jqe]  .tag-link-button:focus-visible { background: var(--surface-hover); }


/* Each crumb is now a bare TagReference — desktop's own BreadcrumbRow control does the same (a nav-row
   Button wrapping shared:TagReference, disabled rather than restyled for the current/non-clickable crumb),
   so the mark/name/type styling all lives in TagReference's own stylesheet rather than repeated here. */
.tag-breadcrumb-row[b-h0m5vn0jqe], .tag-breadcrumb-row button[b-h0m5vn0jqe], .tag-breadcrumb-current[b-h0m5vn0jqe] { display: flex; align-items: center; }
.tag-breadcrumb-row[b-h0m5vn0jqe] { flex-wrap: wrap; gap: 3px; }
.tag-breadcrumb-row button[b-h0m5vn0jqe], .tag-breadcrumb-current[b-h0m5vn0jqe] { border: 0; padding: 2px 3px; background: transparent; }
.tag-breadcrumb-row button[b-h0m5vn0jqe] { cursor: pointer; }
.tag-breadcrumb-row button:hover[b-h0m5vn0jqe], .tag-breadcrumb-row button:focus-visible[b-h0m5vn0jqe] { text-decoration: underline; }
.tag-breadcrumb-separator[b-h0m5vn0jqe] { color: var(--text-muted); font-size: 10px; }

.tag-reference-buckets[b-h0m5vn0jqe] {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr));
    gap: var(--sp-3);
}
.tag-reference-bucket[b-h0m5vn0jqe] { min-width: 0; padding: var(--sp-3); }
.tag-reference-bucket h3[b-h0m5vn0jqe] { margin: 0 0 var(--sp-1); color: var(--text-secondary); }
/* The link row itself is now just a hover/click surface — TagReference (its own scoped stylesheet) owns
   every bit of the mark/name/type layout that used to live here as a 3-column grid. */
.tag-link-list[b-h0m5vn0jqe] { display: grid; gap: 2px; }
.tag-link-list[b-h0m5vn0jqe]  .tag-link-button {
    display: flex;
    align-items: center;
    justify-self: stretch;
    min-width: 0;
    border: 0;
    border-radius: var(--radius-control);
    padding: var(--sp-1) var(--sp-2);
    background: transparent;
    text-align: left;
    cursor: pointer;
}
.tag-link-list[b-h0m5vn0jqe]  .tag-link-button:hover,
.tag-link-list[b-h0m5vn0jqe]  .tag-link-button:focus-visible { background: var(--surface-hover); }

.tag-appearance-grid[b-h0m5vn0jqe] {
    display: flex;
    flex-wrap: wrap;
    column-gap: 18px;
    row-gap: 1px;
}
.tag-appearance-groups[b-h0m5vn0jqe] {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr));
    gap: var(--sp-5);
}
.tag-appearance-group[b-h0m5vn0jqe] { min-width: 0; }
/* A folder heading is a SUB-label of the "Appears In" card, so it reads BELOW that card's own
   `t-header-group` title (2026-08-05, mirroring the desktop `AppearanceGroupTemplate` fix). At 13px/650 in
   primary text it out-shouted the card that owned it, and the hierarchy inverted right here: the eye read a
   flat list of chapters rather than one section of a tag. */
.tag-appearance-group-title[b-h0m5vn0jqe] {
    margin: 0 0 3px;
    color: var(--text-secondary);
    font-size: 10px;
    font-weight: 600;
}
.tag-appearance-group--other .tag-appearance-group-title[b-h0m5vn0jqe] {
    color: var(--text-muted);
    font-size: 10px;
    font-weight: 400;
}
/* Nesting is an INDENT, not a breadcrumb in the label: a subfolder is drawn inside its parent rather than
   beside it as a peer. One custom property per line carries its depth. */
.tag-appearance-folder[b-h0m5vn0jqe] { padding-left: calc(var(--folder-depth, 0) * 14px); }

/* Asymmetric spacing binds each count to its own title: tight INSIDE an entry, wide BETWEEN entries. A
   definite-width cell with the count pinned right was tried and reverted — it aligned the numbers but opened
   a gap wide enough that each read as belonging to the next scene. */
.tag-appearance-tile[b-h0m5vn0jqe] {
    display: inline-grid;
    grid-template-columns: minmax(0, auto) auto;
    align-items: center;
    width: auto;
    max-width: 100%;
    gap: 6px;
}
/* An appearance tile carries BOTH .tag-scene-row and .tag-appearance-tile, and .tag-scene-row's own
   `width: 100%` (below) has equal one-class specificity, so whichever rule is declared later in the
   file always won regardless of viewport — a "tried and reverted" full-width row at desktop width
   (see that rule's own comment). The combined-class selector outranks either plain rule on
   specificity alone (0,2,0 beats 0,1,0), so this wins everywhere, not just under the data-phone
   override further down. */
.tag-scene-row.tag-appearance-tile[b-h0m5vn0jqe] { width: auto; }
.tag-appearance-tile strong[b-h0m5vn0jqe] { font-weight: 400; }
/* Just the number, quiet — not the sentence "3 references" repeated once per scene, and not a filled badge
   either. The badge was tried first and is wrong at this list's scale: one per row across dozens of rows
   out-contrasts the scene titles it annotates, so the panel reads as a field of dots rather than as scenes.
   Tabular figures keep the numerals an even width. The sentence stays on the tile's title/aria-label. */
.tag-appearance-count[b-h0m5vn0jqe] {
    color: var(--text-secondary);
    font-size: 11px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.tag-scene-list[b-h0m5vn0jqe] { display: grid; gap: 3px; }
.tag-scene-row[b-h0m5vn0jqe] { display: grid; grid-template-columns: minmax(120px, 1fr) auto; align-items: center; gap: 8px; width: 100%; border: 0; border-radius: 4px; padding: 5px 6px; background: transparent; color: var(--text-secondary); text-align: left; cursor: pointer; }
.tag-scene-row:hover[b-h0m5vn0jqe] { background: var(--surface-hover); color: var(--text-primary); }
.tag-scene-row strong[b-h0m5vn0jqe] { overflow: hidden; color: var(--text-primary); font-size: 12px; text-overflow: ellipsis; white-space: nowrap; }
.tag-scene-row small[b-h0m5vn0jqe], .tag-scene-row em[b-h0m5vn0jqe] { overflow: hidden; color: var(--text-muted); font-size: 10px; font-weight: 400; text-overflow: ellipsis; white-space: nowrap; }
.tag-progression-row[b-h0m5vn0jqe] { grid-template-columns: minmax(110px, 1fr) auto minmax(90px, auto); }
.tag-empty-copy[b-h0m5vn0jqe] { margin: 0; font-style: italic; }

@media (max-width: 720px) {
    .tag-identity-banner[b-h0m5vn0jqe] { margin: 0 auto; padding-inline: 16px; }
    .tag-inspector-body[b-h0m5vn0jqe] { padding-inline: 16px; }
    .tag-scene-row[b-h0m5vn0jqe], .tag-progression-row[b-h0m5vn0jqe] { grid-template-columns: minmax(0, 1fr); gap: 2px; }
    .tag-image-gallery img[b-h0m5vn0jqe] { width: 100%; height: auto; max-height: 240px; }
    .tag-reference-buckets[b-h0m5vn0jqe] { grid-template-columns: minmax(0, 1fr); }
}

/* An appearance tile is ALSO a .tag-scene-row, so at narrow width the single-column collapse in the block above
   caught it too — and .tag-scene-row's own `width: 100%` had already been out-ordering .tag-appearance-tile's
   `width: auto` at every width. Together those stretched each entry to a full-width row and, under 720px,
   dropped its count onto a SECOND line beneath its own title. That is precisely the failure the
   asymmetric-spacing note further up records: the number stops reading as this scene's annotation and starts
   reading as the next scene's. Re-assert the tile's inline, content-sized, wrap-as-needed shape for BOTH
   compact orientations (portrait's narrow stack and the 850px-wide landscape phone the width block never
   reached), which also turns a 34-entry vertical ladder back into a compact field. Keyed on `data-phone`, so
   the desktop-width read pane is untouched. */
:root[data-phone] .tag-appearance-tile[b-h0m5vn0jqe] { grid-template-columns: minmax(0, auto) auto; gap: 6px; width: auto; }

/* The touch floor belongs on the TOUCH axis, not on a width sub-tier (APPEARANCE.md: "`data-touch` owns touch
   sizing only, via `--tap`, at ANY width — a touchscreen laptop wants finger-sized targets at full desktop
   layout, which is why it is a separate axis"). It used to live inside the 720px block above, so a phone held
   SIDEWAYS — 850px wide, comfortably past that ceiling, and the one compact case that is not narrow — got this
   page's desktop row metrics: every tag link, appearance tile, and scene row dropped to a ~27px tap target. */
:root[data-touch] .tag-link-list[b-h0m5vn0jqe]  .tag-link-button,
:root[data-touch] .tag-appearance-tile[b-h0m5vn0jqe],
:root[data-touch] .tag-scene-row[b-h0m5vn0jqe] { min-height: var(--tap); }
/* viewer/Components/TagReference.razor.rz.scp.css */
/* Geometry pinned to desktop's shared:TagReference template: a 16px leading slot (image | tinted glyph |
   8px colour dot), 5px to the name. The optional type identity is a hover title on the whole element
   (ShowTypeName), never printed text — the icon ladder already carries it. No border is drawn around the
   mark — a ring, when one is wanted, belongs to the chip/button the call site wraps this in. */
.tag-reference[b-kjzyb3fpw9] {
    display: inline-flex;
    min-width: 0;
    align-items: center;
    gap: 5px;
}

.tag-reference__mark[b-kjzyb3fpw9] {
    display: inline-flex;
    flex: 0 0 16px;
    align-items: center;
    justify-content: center;
    width: 16px;
    height: 16px;
}

.tag-reference__image[b-kjzyb3fpw9] { width: 16px; height: 16px; object-fit: contain; }

.tag-reference__dot[b-kjzyb3fpw9] { width: 8px; height: 8px; border-radius: 50%; background: var(--semantic-color); }

/* ::deep reaches into PhosphorIcon's own rendered markup, which this component's scoped attribute never
   touches directly — the same pattern TagReadDetailView.razor.css already uses for its identity glyph. */
.tag-reference[b-kjzyb3fpw9]  .tag-reference__glyph { font-size: 14px; line-height: 1; }

.tag-reference__name[b-kjzyb3fpw9] {
    overflow: hidden;
    color: var(--text-secondary);
    font-size: 13px;
    font-weight: 500;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* viewer/Components/TimelineForkPicker.razor.rz.scp.css */
/* 2026-07-28: the trigger/popover shell (anchor positioning, box-shadow, border, min/max-width) moved to
   WebEntityPicker's own shared chrome — only the fork/saved-route CONTENT styling stays here. Bounds this
   picker's trigger label like the old bespoke trigger did (long fork/timeline titles still truncate instead
   of growing the button unbounded, which WebEntityPicker's own generic trigger doesn't otherwise constrain). */
.timeline-fork-picker[b-sfy693hdhu]  .web-entity-picker__trigger {
    max-width: 200px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    text-align: left;
}

.timeline-fork-picker__trigger-label[b-sfy693hdhu] {
    display: inline-block;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    vertical-align: bottom;
}

.timeline-fork-picker__prompt[b-sfy693hdhu] {
    color: var(--picker-row-meta);
    font-size: 11px;
    margin: 0;
}

.timeline-fork-picker__divider[b-sfy693hdhu] {
    border: 0;
    border-top: 1px solid var(--stroke);
    margin: 0;
    width: 100%;
}

.timeline-fork-picker__fork[b-sfy693hdhu],
.timeline-fork-picker__saved[b-sfy693hdhu] { display: grid; gap: .35rem; }

.timeline-fork-picker__fork h3[b-sfy693hdhu],
.timeline-fork-picker__saved h3[b-sfy693hdhu] {
    color: var(--picker-row-meta);
    font-size: 10px;
    font-weight: 500;
    margin: 0;
}

.timeline-fork-picker__saved h3[b-sfy693hdhu] { text-transform: uppercase; }

.timeline-fork-picker__options[b-sfy693hdhu] {
    display: flex;
    flex-wrap: wrap;
    gap: 2px 4px;
}

.timeline-fork-picker__actions[b-sfy693hdhu] {
    display: flex;
    gap: 6px;
    justify-content: flex-end;
}

.timeline-fork-picker__option[b-sfy693hdhu] {
    min-height: 24px;
    padding: 3px 7px;
    background: var(--surface-input);
    border: 1px solid var(--stroke);
    border-radius: var(--radius-control);
    /* One shared picker text pair (app.css --picker-row-text/--picker-row-meta, owner ruling 2026-08-08). */
    color: var(--picker-row-text);
    cursor: pointer;
    font: inherit;
    font-size: 11px;
    max-width: 200px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.timeline-fork-picker__option:hover[b-sfy693hdhu] { border-color: var(--accent); background: var(--surface-hover); }

.timeline-fork-picker__option.selected[b-sfy693hdhu] {
    background: var(--accent-soft);
    border-color: var(--accent);
    color: var(--text-primary);
}
/* Compact tier via the shared attribute (wwwroot/js/responsiveTier.js), not a restated @media condition — see
   that file for why a breakpoint cannot be shared in CSS itself. `:root[data-phone]` also outranks the base
   rules above on specificity, so this block no longer depends on appearing after them. */
:root[data-phone] .timeline-fork-picker[b-sfy693hdhu]  .web-entity-picker__trigger {
    width: 100%;
    min-width: 0;
    min-height: var(--tap);
    max-width: 100%;
    padding-inline: 12px;
    font-size: 15px;
}

:root[data-phone] .timeline-fork-picker[b-sfy693hdhu]  .web-entity-picker__popover { overflow-y: auto; }

:root[data-phone] .timeline-fork-picker__options[b-sfy693hdhu] {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 6px;
}

:root[data-phone] .timeline-fork-picker__option[b-sfy693hdhu] {
    min-height: var(--tap);
    max-width: none;
    padding: 8px 12px;
    font-size: 14px;
    text-align: left;
}

:root[data-phone] .timeline-fork-picker__actions > button[b-sfy693hdhu] {
    min-height: var(--tap);
    padding-inline: 14px;
    font-size: 14px;
}

:root[data-phone] .timeline-fork-picker__fork h3[b-sfy693hdhu],
:root[data-phone] .timeline-fork-picker__saved h3[b-sfy693hdhu] { font-size: 13px; }
/* viewer/Components/TimelinePillRow.razor.rz.scp.css */
/* Chrome is the shared .btn kit; this file owns only the strip — that it scrolls sideways instead of growing a
   chrome bar taller, and that the SELECTED pill wears the accent outright (see the component's own comment). */
.timeline-pills[b-ipdjxfeovp] {
    display: flex;
    min-width: 0;
    align-items: center;
    gap: 4px;
    overflow-x: auto;
    scrollbar-width: none;
}
.timeline-pills[b-ipdjxfeovp]::-webkit-scrollbar { display: none; }

.timeline-pills__pill[b-ipdjxfeovp] {
    flex: 0 0 auto;
    max-width: 190px;
    overflow: hidden;
    border-radius: 999px;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.timeline-pills__pill.is-active[b-ipdjxfeovp] {
    border-color: var(--accent);
    background: var(--accent);
    color: var(--text-on-accent);
    font-weight: 600;
}
/* viewer/Components/ViewerGuidedTourHost.razor.rz.scp.css */
.viewer-guide[b-42ym00nzp3] {
    position: fixed;
    z-index: 100;
    inset: 0;
    color: var(--text-primary);
    font-family: var(--font-interface);
    pointer-events: none;
}

.viewer-guide__scrim[b-42ym00nzp3] {
    position: absolute;
    inset: 0;
    background: color-mix(in srgb, var(--surface-app) 82%, transparent);
    pointer-events: auto;
}

.viewer-guide__invitation[b-42ym00nzp3],
.viewer-guide__reference[b-42ym00nzp3],
.viewer-guide__tour-card[b-42ym00nzp3] {
    border: 1px solid var(--stroke-strong);
    background: var(--surface-nav);
    /* Was a per-theme surface-app-tinted shadow (color-mix instead of plain black) — desktop's own
       DesktopGuidedTourHost (this control's counterpart) uses flat "0 10 32 0 #60000000", so the tint was a
       web-only deviation. --shadow-overlay restores that parity as well as the token consolidation. */
    box-shadow: var(--shadow-overlay);
    pointer-events: auto;
    color: var(--text-primary);
    color-scheme: inherit;
    font-family: var(--font-interface);
}

.viewer-guide__invitation[b-42ym00nzp3] {
    position: absolute;
    top: 50%;
    left: 50%;
    display: grid;
    width: min(520px, calc(100vw - 32px));
    transform: translate(-50%, -50%);
    border-radius: var(--radius-overlay);
    padding: 34px;
    text-align: left;
}

.viewer-guide__symbol[b-42ym00nzp3] {
    display: grid;
    place-items: center;
    width: 42px;
    height: 42px;
    margin-bottom: 18px;
    border: 1px solid var(--stroke-strong);
    border-radius: var(--radius-overlay);
    background: var(--surface-raised);
    color: var(--accent);
    font-size: 22px;
}

.viewer-guide__invitation h2[b-42ym00nzp3],
.viewer-guide__reference h2[b-42ym00nzp3],
.viewer-guide__tour-card h2[b-42ym00nzp3] {
    margin: 5px 0 12px;
    font-size: 28px;
    font-weight: 620;
}

.viewer-guide__invitation > p:not(.viewer-guide__eyebrow)[b-42ym00nzp3] {
    margin: 0;
    color: var(--text-secondary);
    font-size: 15px;
    line-height: 1.65;
}

.viewer-guide__invitation > small[b-42ym00nzp3] {
    margin-top: 16px;
    color: var(--text-muted);
    line-height: 1.5;
}

.viewer-guide__invitation-actions[b-42ym00nzp3] {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 9px;
    margin-top: 28px;
}

.viewer-guide button[b-42ym00nzp3] {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 7px;
    min-height: 35px;
    border: 1px solid var(--stroke);
    border-radius: var(--radius-control);
    background: var(--surface-raised);
    padding: 7px 12px;
    color: var(--text-secondary);
    font-size: 12px;
    cursor: pointer;
}

.viewer-guide button:hover:not(:disabled)[b-42ym00nzp3] {
    border-color: var(--stroke-strong);
    background: var(--surface-hover);
    color: var(--text-primary);
}

.viewer-guide button:disabled[b-42ym00nzp3] {
    opacity: .36;
}

.viewer-guide button.viewer-guide__primary[b-42ym00nzp3] {
    border-color: var(--accent);
    background: var(--accent);
    color: var(--text-on-accent);
    font-weight: 720;
}

.viewer-guide button.viewer-guide__primary:hover:not(:disabled)[b-42ym00nzp3] {
    border-color: var(--accent-hover);
    background: var(--accent-hover);
    color: var(--text-on-accent);
}

.viewer-guide button.viewer-guide__close[b-42ym00nzp3],
.viewer-guide button.viewer-guide__skip[b-42ym00nzp3] {
    border-color: transparent;
    background: transparent;
}

.viewer-guide button.viewer-guide__close[b-42ym00nzp3] {
    width: 34px;
    min-height: 34px;
    padding: 0;
    border-radius: 50%;
}

.viewer-guide__invitation > .viewer-guide__close[b-42ym00nzp3] {
    position: absolute;
    top: 14px;
    right: 14px;
}

.viewer-guide__eyebrow[b-42ym00nzp3] {
    margin: 0;
    color: var(--text-muted);
    font-size: 11px;
    font-weight: 650;
}

.viewer-guide__reference[b-42ym00nzp3] {
    position: absolute;
    inset: clamp(16px, 5vh, 54px) clamp(16px, 6vw, 88px);
    display: grid;
    grid-template-rows: auto auto minmax(0, 1fr) auto;
    overflow: hidden;
    border-radius: var(--radius-overlay);
}

.viewer-guide__reference-header[b-42ym00nzp3] {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 20px 24px 14px;
}

.viewer-guide__reference-header h2[b-42ym00nzp3] {
    margin: 2px 0 0;
    font-size: 23px;
}

.viewer-guide__chapters[b-42ym00nzp3] {
    display: flex;
    gap: 0;
    overflow-x: auto;
    border-block: 1px solid var(--stroke);
    padding: 0 16px;
    background: var(--surface-header);
}

.viewer-guide__chapters button[b-42ym00nzp3] {
    flex: 0 0 auto;
    min-height: 39px;
    border-color: transparent;
    border-bottom-color: transparent;
    border-radius: 0;
    background: transparent;
    padding: 8px 12px;
}

.viewer-guide__chapters button.selected[b-42ym00nzp3],
.viewer-guide__card-rail button.selected[b-42ym00nzp3] {
    border-color: transparent;
    color: var(--text-primary);
}

.viewer-guide__chapters button.selected[b-42ym00nzp3] {
    border-bottom-color: var(--accent);
}

.viewer-guide__reference-body[b-42ym00nzp3] {
    display: grid;
    grid-template-columns: minmax(230px, 31%) minmax(0, 1fr);
    min-height: 0;
}

.viewer-guide__card-rail[b-42ym00nzp3] {
    min-height: 0;
    overflow-y: auto;
    border-right: 1px solid var(--stroke);
    padding: 22px 16px 28px;
    background: var(--surface-rail);
}

.viewer-guide__card-rail h3[b-42ym00nzp3] {
    margin: 0 8px 6px;
    font-size: 15px;
}

.viewer-guide__card-rail > p[b-42ym00nzp3] {
    margin: 0 8px 18px;
    color: var(--text-muted);
    font-size: 11px;
    line-height: 1.5;
}

.viewer-guide__card-rail button[b-42ym00nzp3] {
    justify-content: flex-start;
    width: 100%;
    min-height: 40px;
    margin-bottom: 5px;
    border-color: transparent;
    border-left: 2px solid transparent;
    border-radius: 0;
    background: transparent;
    text-align: left;
}

.viewer-guide__card-rail button.selected[b-42ym00nzp3] {
    border-left-color: var(--accent);
    background: var(--surface-raised);
}

.viewer-guide__reference-card[b-42ym00nzp3] {
    min-height: 0;
    overflow-y: auto;
    padding: clamp(28px, 6vh, 68px) clamp(28px, 6vw, 76px) 48px;
}

.viewer-guide__reference-card h2[b-42ym00nzp3] {
    max-width: 760px;
    font-size: clamp(30px, 4vw, 48px);
}

.viewer-guide__lead[b-42ym00nzp3] {
    max-width: 780px;
    margin: 0;
    color: var(--text-secondary);
    font-size: 17px;
    line-height: 1.65;
}

.viewer-guide__evidence[b-42ym00nzp3] {
    max-width: 780px;
    margin-top: 26px;
    border-top: 1px solid var(--stroke);
    background: transparent;
    padding: 14px 0 0;
}

.viewer-guide__evidence > span[b-42ym00nzp3] {
    color: var(--text-muted);
    font-size: 11px;
    font-weight: 650;
}

.viewer-guide__evidence p[b-42ym00nzp3] {
    margin: 5px 0 0;
    color: var(--text-secondary);
    font-size: 13px;
    line-height: 1.6;
}

.viewer-guide__try[b-42ym00nzp3] {
    display: flex;
    align-items: flex-start;
    gap: 9px;
    max-width: 780px;
    margin: 20px 0 0;
    color: var(--text-muted);
    font-size: 12px;
    line-height: 1.55;
}

.viewer-guide__try strong[b-42ym00nzp3] {
    color: var(--accent);
    font-weight: 650;
}

.viewer-guide__reference-footer[b-42ym00nzp3] {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 12px;
    border-top: 1px solid var(--stroke);
    padding: 11px 18px;
    background: var(--surface-header);
}

.viewer-guide__reference-footer > span[b-42ym00nzp3] {
    color: var(--text-muted);
    font-size: 11px;
}

.viewer-guide__reference-footer > div[b-42ym00nzp3],
.viewer-guide__tour-footer > div[b-42ym00nzp3],
.viewer-guide__tour-header > div[b-42ym00nzp3] {
    display: flex;
    align-items: center;
    gap: 7px;
}

.viewer-guide__highlight[b-42ym00nzp3] {
    position: fixed;
    top: 50%;
    left: 50%;
    width: 0;
    height: 0;
    border: 2px solid var(--accent);
    border-radius: var(--radius-control);
    /* Exception (elevation-token sweep): a spotlight cutout (the 9999px spread punches the dim scrim) plus an
       accent glow ring around the highlighted target — neither shadow layer is a drop-shadow/elevation cue, so
       neither maps onto --shadow-panel/--shadow-overlay. */
    box-shadow: 0 0 0 9999px color-mix(in srgb, var(--surface-app) 78%, transparent), 0 0 0 4px var(--accent-soft);
    opacity: 0;
    pointer-events: none;
    transition: top 180ms ease, left 180ms ease, width 180ms ease, height 180ms ease, opacity 120ms ease;
}

.viewer-guide__tour-card[b-42ym00nzp3] {
    position: fixed;
    right: 14px;
    bottom: 14px;
    display: grid;
    width: min(430px, calc(100vw - 28px));
    max-height: min(680px, calc(100vh - 28px));
    overflow-y: auto;
    border-radius: var(--radius-overlay);
    padding: 18px;
}

.viewer-guide--card-left .viewer-guide__tour-card[b-42ym00nzp3] {
    right: auto;
    left: 14px;
}

.viewer-guide__tour-header[b-42ym00nzp3] {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 12px;
}

.viewer-guide__tour-header > div > button:first-child[b-42ym00nzp3] {
    min-height: 30px;
    border-color: transparent;
    background: transparent;
    padding: 3px 6px;
}

.viewer-guide__tour-jump[b-42ym00nzp3] {
    display: flex;
    align-items: center;
    gap: 9px;
    margin-top: 13px;
    color: var(--text-muted);
    font-size: 11px;
}

.viewer-guide__tour-jump select[b-42ym00nzp3] {
    min-height: 31px;
    width: min(230px, 70%);
}

.viewer-guide__tour-card article[b-42ym00nzp3] {
    padding: 18px 2px 8px;
}

.viewer-guide__tour-card h2[b-42ym00nzp3] {
    font-size: 25px;
}

.viewer-guide__tour-card .viewer-guide__lead[b-42ym00nzp3] {
    font-size: 14px;
    line-height: 1.58;
}

.viewer-guide__tour-card .viewer-guide__evidence[b-42ym00nzp3] {
    margin-top: 18px;
    padding: 11px 0 0;
}

.viewer-guide__tour-card .viewer-guide__try[b-42ym00nzp3] {
    margin-top: 15px;
    font-size: 11px;
}

.viewer-guide__tour-footer[b-42ym00nzp3] {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 10px;
    margin-top: 9px;
    border-top: 1px solid var(--stroke);
    padding-top: 13px;
}

.viewer-guide button.viewer-guide__skip[b-42ym00nzp3] {
    padding-inline: 3px;
    color: var(--text-muted);
}

/* Compact tier via the shared attribute. Its old first clause, (max-width: 720px), was strictly INSIDE the
   820px compact threshold, so folding the two together changes nothing about which viewports match. */
:root[data-phone] .viewer-guide__tour-card[b-42ym00nzp3] {
    left: auto;
    right: max(12px, env(safe-area-inset-right));
    bottom: max(12px, env(safe-area-inset-bottom));
    width: min(430px, calc(100vw - 24px - env(safe-area-inset-left) - env(safe-area-inset-right)));
    max-height: min(65dvh, 520px);
}

:root[data-phone] .viewer-guide--card-left .viewer-guide__tour-card[b-42ym00nzp3] {
    right: auto;
    left: max(12px, env(safe-area-inset-left));
}

:root[data-phone] .viewer-guide__invitation[b-42ym00nzp3] {
    width: min(520px, calc(100vw - 24px - env(safe-area-inset-left) - env(safe-area-inset-right)));
    max-height: min(70dvh, 520px);
    overflow-y: auto;
    padding: 28px 24px;
}

:root[data-phone] .viewer-guide__reference[b-42ym00nzp3] {
    inset: calc(48px + env(safe-area-inset-top) + 12px) auto auto 50%;
    transform: translateX(-50%);
    width: min(680px, calc(100vw - 24px - env(safe-area-inset-left) - env(safe-area-inset-right)));
    height: min(70dvh, calc(100dvh - 140px - env(safe-area-inset-top) - env(safe-area-inset-bottom)));
    max-height: 620px;
}

:root[data-phone] .viewer-guide__reference-body[b-42ym00nzp3] {
    grid-template-columns: 1fr;
    grid-template-rows: auto minmax(0, 1fr);
}

:root[data-phone] .viewer-guide__card-rail[b-42ym00nzp3] {
    display: flex;
    gap: 5px;
    overflow-x: auto;
    border-right: 0;
    border-bottom: 1px solid var(--stroke);
    padding: 9px;
}

:root[data-phone] .viewer-guide__card-rail h3[b-42ym00nzp3],
:root[data-phone] .viewer-guide__card-rail > p[b-42ym00nzp3] {
    display: none;
}

:root[data-phone] .viewer-guide__card-rail button[b-42ym00nzp3] {
    flex: 0 0 min(210px, 72vw);
    margin: 0;
}

:root[data-phone] .viewer-guide__reference-card[b-42ym00nzp3] {
    padding: 28px 22px 34px;
}

:root[data-phone] .viewer-guide__reference-footer[b-42ym00nzp3] {
    align-items: flex-end;
}

:root[data-phone] .viewer-guide__reference-footer > span[b-42ym00nzp3] {
    display: none;
}

:root[data-phone] .viewer-guide button[b-42ym00nzp3] { min-height: var(--tap); font-size: 14px; }
:root[data-phone] .viewer-guide button.viewer-guide__close[b-42ym00nzp3] { width: var(--tap); min-height: var(--tap); }
:root[data-phone] .viewer-guide__tour-jump[b-42ym00nzp3] { font-size: 13px; }
:root[data-phone] .viewer-guide__tour-jump select[b-42ym00nzp3] { min-height: var(--tap); font-size: 16px; }



@media (max-width: 720px) and (orientation: portrait) {
    .viewer-guide__tour-card[b-42ym00nzp3] { bottom: calc(64px + env(safe-area-inset-bottom)); }
}

@media (max-width: 480px) {
    .viewer-guide__invitation-actions[b-42ym00nzp3] {
        grid-template-columns: 1fr;
    }

    .viewer-guide__reference-header[b-42ym00nzp3] {
        padding: 15px 16px 10px;
    }

    .viewer-guide__chapters[b-42ym00nzp3] {
        padding-inline: 8px;
    }

    .viewer-guide__chapters button[b-42ym00nzp3] { padding-inline: 8px; }

    .viewer-guide__reference-footer[b-42ym00nzp3] {
        padding-inline: 10px;
    }

    .viewer-guide__reference-footer > div[b-42ym00nzp3] {
        width: 100%;
    }

    .viewer-guide__reference-footer button.viewer-guide__primary[b-42ym00nzp3] {
        margin-left: auto;
    }
}

@media (prefers-reduced-motion: reduce) {
    .viewer-guide__highlight[b-42ym00nzp3] {
        transition: none;
    }
}

/* The guide is a desktop-only surface (owner decision 2026-07-30). At phone size it covered the very content
   it was pointing at, so it is removed outright rather than shrunk. `display:none` also takes its scrim and
   its focus trap out of the tree, so nothing of it remains interactive. Desktop is unchanged. */
/* Compact tier via the shared attribute (wwwroot/js/responsiveTier.js), not a restated @media condition — see
   that file for why a breakpoint cannot be shared in CSS itself. `:root[data-phone]` also outranks the base
   rules above on specificity, so this block no longer depends on appearing after them. */
:root[data-phone] .viewer-guide[b-42ym00nzp3] { display: none; }
/* viewer/Components/ViewerPeekHost.razor.rz.scp.css */
/* Resizable peek drawers (Tag left, Scene right) shared by graphical panes.
   Extracted from app.css; owned by the host that renders the drawers. */

.viewer-peek[b-g12pslvlzk] { position: absolute; z-index: 12; top: 0; bottom: 0; width: min(var(--peek-width), calc(100% - 32px)); display: grid; grid-template-rows: auto minmax(0, 1fr); border-color: var(--stroke-strong); background: var(--surface-nav); box-shadow: var(--shadow-overlay); }
.viewer-peek--tag[b-g12pslvlzk] { left: 0; border-right: 1px solid var(--stroke-strong); }
.viewer-peek--scene[b-g12pslvlzk] { right: 0; border-left: 1px solid var(--stroke-strong); }
.viewer-peek--shared[b-g12pslvlzk] { width: min(var(--peek-width), max(0px, calc((100% - 160px) / 2))); }
.viewer-peek__resize-handle[b-g12pslvlzk] { position: absolute; z-index: 2; top: 0; bottom: 0; width: 9px; cursor: ew-resize; touch-action: none; }
.viewer-peek__resize-handle[b-g12pslvlzk]::after { content: ""; position: absolute; top: 0; bottom: 0; left: 4px; width: 1px; background: var(--stroke); transition: background-color 120ms ease; }
.viewer-peek__resize-handle:hover[b-g12pslvlzk]::after, .viewer-peek__resize-handle--active[b-g12pslvlzk]::after { background: var(--accent); }
.viewer-peek__resize-handle--right[b-g12pslvlzk] { right: -5px; }
.viewer-peek__resize-handle--left[b-g12pslvlzk] { left: -5px; }
.viewer-peek__toolbar[b-g12pslvlzk] { display: grid; grid-template-columns: 32px 32px minmax(0, 1fr) 32px; align-items: center; gap: 5px; min-height: var(--tap); padding: 6px 8px; border-bottom: 1px solid var(--stroke); background: var(--surface-header); }
.viewer-peek__toolbar span[b-g12pslvlzk] { padding-inline: 5px; color: var(--text-muted); font-size: 12px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; }
/* Square, var(--radius-control) icon buttons — matches every other icon-button family in the app
   (.header-icon-button, the shared .btn kit's icon buttons, .tool-button, camera-control-pill buttons)
   rather than a one-off circular shape. */
.viewer-peek__toolbar button[b-g12pslvlzk] { display: grid; place-items: center; width: 30px; height: 30px; padding: 0; border: 1px solid transparent; border-radius: var(--radius-control); background: transparent; color: var(--text-secondary); cursor: pointer; }
.viewer-peek__toolbar button:not(:disabled):hover[b-g12pslvlzk] { border-color: var(--stroke); background: var(--surface-hover); color: var(--text-primary); }
.viewer-peek__body[b-g12pslvlzk] { min-height: 0; overflow-y: auto; }

/* Compact tier via the shared attribute (wwwroot/js/responsiveTier.js), not a restated @media condition — see
   that file for why a breakpoint cannot be shared in CSS itself. `:root[data-phone]` also outranks the base
   rules above on specificity, so this block no longer depends on appearing after them. */
:root[data-phone] .viewer-peek[b-g12pslvlzk] { display: none; }
/* viewer/Components/ViewerQuickOpen.razor.rz.scp.css */
/* Ctrl+P command palette. Visual language matches the rest of the app's popover family
   (WebEntityPicker/.dropdown-popover: --stroke border, --radius-overlay corners, --surface-raised fill,
   the same box-shadow) but anchored top-center over a dim scrim, like desktop's QuickOpenOverlay.axaml,
   rather than a small anchored dropdown. */

.quick-open-scrim[b-ey8z7kvvwt] {
    position: fixed;
    inset: 0;
    z-index: 30;
    display: grid;
    justify-items: center;
    align-content: start;
    padding-top: 14vh;
    background: rgb(0 0 0 / 45%);
}

.quick-open-panel[b-ey8z7kvvwt] {
    display: grid;
    grid-template-rows: auto minmax(0, 1fr);
    width: min(560px, calc(100% - 48px));
    max-height: min(480px, calc(100vh - 28vh));
    border: 1px solid var(--stroke);
    border-radius: var(--radius-overlay);
    background: var(--surface-raised);
    box-shadow: var(--shadow-overlay);
    overflow: hidden;
    color: var(--text-primary);
    color-scheme: inherit;
    font-family: var(--font-interface);
}

.quick-open-search[b-ey8z7kvvwt] {
    box-sizing: border-box;
    width: 100%;
    border: 0;
    border-bottom: 1px solid var(--stroke);
    border-radius: 0;
    background: transparent;
    padding: 14px 16px;
    color: var(--text-primary);
    font-size: 15px;
}

.quick-open-search:focus-visible[b-ey8z7kvvwt] { outline: 2px solid var(--accent); outline-offset: -2px; }

.quick-open-results[b-ey8z7kvvwt] {
    margin: 0;
    padding: 6px;
    list-style: none;
    overflow-y: auto;
}

.quick-open-result[b-ey8z7kvvwt] {
    display: flex;
    align-items: center;
    gap: 10px;
    border-radius: var(--radius-control);
    padding: 8px 10px;
    color: var(--text-secondary);
    cursor: pointer;
}

.quick-open-result:hover[b-ey8z7kvvwt],
.quick-open-result--selected[b-ey8z7kvvwt] {
    background: var(--surface-hover);
    color: var(--text-primary);
}

.quick-open-result__icon[b-ey8z7kvvwt] { color: var(--text-secondary); }
.quick-open-result--selected .quick-open-result__icon[b-ey8z7kvvwt],
.quick-open-result:hover .quick-open-result__icon[b-ey8z7kvvwt] { color: var(--accent); }

.quick-open-result__name[b-ey8z7kvvwt] {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    color: var(--text-primary);
    text-overflow: ellipsis;
    white-space: nowrap;
}

.quick-open-result__meta[b-ey8z7kvvwt] {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: 6px;
    max-width: 45%;
    font-size: 11px;
}

.quick-open-result__kind[b-ey8z7kvvwt] { color: var(--accent); }
.quick-open-result__dot[b-ey8z7kvvwt] { color: var(--text-muted); }
.quick-open-result__context[b-ey8z7kvvwt] {
    overflow: hidden;
    color: var(--text-secondary);
    text-overflow: ellipsis;
    white-space: nowrap;
}

.quick-open-empty[b-ey8z7kvvwt] {
    padding: 12px 10px;
    color: var(--text-muted);
    font-size: 12px;
    text-align: center;
}

/* Compact/touch tiers via the shared attributes (wwwroot/js/responsiveTier.js), not restated @media conditions
   — see that file for why a breakpoint cannot be shared in CSS itself. `:root[data-phone]`/`:root[data-touch]`
   also outrank the base rules above on specificity, so these blocks no longer depend on appearing after them. */
:root[data-phone] .quick-open-scrim[b-ey8z7kvvwt] {
    box-sizing: border-box;
    align-content: start;
    padding: calc(48px + env(safe-area-inset-top) + 12px) max(12px, env(safe-area-inset-right))
             calc(68px + env(safe-area-inset-bottom)) max(12px, env(safe-area-inset-left));
}
:root[data-phone] .quick-open-panel[b-ey8z7kvvwt] {
    width: min(560px, calc(100vw - 24px - env(safe-area-inset-left) - env(safe-area-inset-right)));
    height: auto;
    max-height: min(70dvh, calc(100dvh - 132px - env(safe-area-inset-top) - env(safe-area-inset-bottom)));
    border-radius: var(--radius-overlay);
}
:root[data-phone] .quick-open-search[b-ey8z7kvvwt] {
    position: sticky;
    z-index: 1;
    top: 0;
    min-height: 48px;
    padding: 12px 14px;
    background: var(--surface-raised);
    font-size: 16px;
}
:root[data-phone] .quick-open-results[b-ey8z7kvvwt] {
    padding: 4px;
    overscroll-behavior: contain;
}
:root[data-phone] .quick-open-result[b-ey8z7kvvwt] {
    box-sizing: border-box;
    min-height: var(--tap);
    gap: 8px;
    padding: 8px 10px;
}
:root[data-phone] .quick-open-result__meta[b-ey8z7kvvwt] { max-width: none; font-size: 13px; }
:root[data-phone] .quick-open-result__dot[b-ey8z7kvvwt],
:root[data-phone] .quick-open-result__context[b-ey8z7kvvwt] { display: none; }
:root[data-phone] .quick-open-empty[b-ey8z7kvvwt] {
    display: grid;
    min-height: var(--tap);
    place-items: center;
    font-size: 14px;
}

:root[data-phone] .quick-open-scrim[b-ey8z7kvvwt] {
    box-sizing: border-box;
    align-content: start;
    padding: calc(48px + env(safe-area-inset-top) + 8px) max(12px, env(safe-area-inset-right))
             max(12px, env(safe-area-inset-bottom)) max(12px, env(safe-area-inset-left));
}
:root[data-phone] .quick-open-panel[b-ey8z7kvvwt] {
    width: min(680px, calc(100vw - 24px - env(safe-area-inset-left) - env(safe-area-inset-right)));
    height: auto;
    max-height: min(70dvh, calc(100dvh - 80px - env(safe-area-inset-top) - env(safe-area-inset-bottom)));
}
:root[data-phone] .quick-open-search[b-ey8z7kvvwt] { min-height: 48px; padding-block: 11px; font-size: 16px; }
:root[data-phone] .quick-open-result[b-ey8z7kvvwt] { min-height: var(--tap); font-size: 14px; }
:root[data-phone] .quick-open-result__meta[b-ey8z7kvvwt] { font-size: 13px; }
:root[data-phone] .quick-open-empty[b-ey8z7kvvwt] { font-size: 14px; }
:root[data-touch] .quick-open-result[b-ey8z7kvvwt] { min-height: var(--tap); }
/* viewer/Components/WebEntityPicker.razor.rz.scp.css */
/* WebEntityPicker's own chrome, fully self-scoped (no ::deep needed — every element styled here is
   rendered directly by this component, unlike other picker cross-component rules).
   Visual language matches the rest of the Viewer picker family (folder scope, timeline picker) so it reads
   as native chrome rather than a second control system. */
.web-entity-picker[b-x9xdxs79v3] {
    position: relative;
    flex: 0 0 auto;
    color: var(--text-secondary);
    font-size: 11px;
}

.web-entity-picker__trigger[b-x9xdxs79v3] {
    min-height: 29px;
    box-sizing: border-box;
    border: 1px solid var(--stroke);
    border-radius: var(--radius-control);
    background: var(--surface-input);
    padding: 6px 9px;
    color: var(--text-secondary);
    font: inherit;
    white-space: nowrap;
    cursor: pointer;
}

/* One shared picker text pair (app.css --picker-row-text/--picker-row-meta, owner ruling 2026-08-08): the
   trailing count on a Multiple-mode trigger is metadata, not the control's label. */
.web-entity-picker__trigger span[b-x9xdxs79v3] { color: var(--picker-row-meta); }
.web-entity-picker__trigger:disabled[b-x9xdxs79v3] { opacity: .5; cursor: default; }
.web-entity-picker__trigger:hover:not(:disabled)[b-x9xdxs79v3] { border-color: var(--accent); background: var(--surface-hover); color: var(--text-primary); }

/* Popover on the elevated tier, matching the other Viewer pickers (folder scope, markers picker). A flex
   column of search box / bulk row / scrollable list rather than one big scrolling block, so the search box and
   All/None buttons stay pinned in place while the list underneath scrolls.
   2026-08-08: this max-height now caps the WHOLE column (search + bulk + list), not just the list — the list
   itself gets `flex: 1 1 auto; min-height: 0; overflow-y: auto` below, so it is the ONE scrolling region and the
   header row above it can never be scrolled under or overlapped by construction, regardless of how tall the
   search/bulk row happens to be. Structural flex-column shape over a fixed list max-height, per owner ruling —
   immune to header height changes rather than a number that merely happens to leave enough room today. */
.web-entity-picker__popover[b-x9xdxs79v3] {
    position: fixed;
    position-area: block-end span-inline-end;
    position-try-fallbacks: flip-block, flip-inline;
    margin-block: 4px 0;
    min-width: 220px;
    max-width: 320px;
    max-height: min(70dvh, 420px);
    gap: 6px;
    border: 1px solid var(--stroke);
    border-radius: var(--radius-overlay);
    background: var(--surface-raised);
    box-shadow: var(--shadow-overlay);
    padding: 7px;
    color: var(--text-secondary);
    color-scheme: inherit;
    font-family: var(--font-interface);
    overflow: hidden;
}

/* Display only while open: an unconditional `display` on a [popover] element would override the UA's
   default `display:none` for the closed state and leave every picker permanently expanded. */
.web-entity-picker__popover:popover-open[b-x9xdxs79v3] {
    display: flex;
    flex-direction: column;
}

.web-entity-picker__search[b-x9xdxs79v3] {
    box-sizing: border-box;
    width: 100%;
    flex: 0 0 auto;
    border: 1px solid var(--stroke);
    border-radius: var(--radius-control);
    background: var(--surface-input);
    padding: 5px 8px;
    color: var(--text-primary);
    font: inherit;
}

.web-entity-picker__bulk[b-x9xdxs79v3] {
    display: flex;
    flex: 0 0 auto;
    gap: 6px;
}

.web-entity-picker__bulk-btn[b-x9xdxs79v3] {
    border: 1px solid var(--stroke);
    border-radius: var(--radius-control);
    background: var(--surface-input);
    padding: 2px 8px;
    color: var(--text-secondary);
    font-size: 10px;
    cursor: pointer;
}

.web-entity-picker__bulk-btn:hover[b-x9xdxs79v3] { border-color: var(--accent); background: var(--surface-hover); color: var(--text-primary); }

.web-entity-picker__availability[b-x9xdxs79v3] {
    display: flex;
    flex: 0 0 auto;
    align-items: center;
    gap: 6px;
    min-height: 24px;
    color: var(--picker-row-meta);
    cursor: pointer;
}

/* The ONE scrolling region inside the popover (see the popover's own remarks above) — sized to whatever the
   column has left after the fixed-size search/bulk rows, never a fixed max-height of its own, so it cannot
   overlap or be overlapped by them regardless of their height. */
.web-entity-picker__list[b-x9xdxs79v3] {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
}

.web-entity-picker__group[b-x9xdxs79v3] {
    position: sticky;
    z-index: 1;
    top: 0;
    margin: 5px 0 1px;
    padding: 0 5px;
    background: var(--surface-raised);
    color: var(--picker-row-meta);
    font-size: 10px;
    font-weight: 600;
    letter-spacing: .04em;
    text-transform: uppercase;
}

/* A checkable group header (WebEntityPicker.GroupChecked supplied — desktop's folder-rollup rows) keeps the
   same sticky caption look but lays out as a clickable checkbox row instead of static text. */
.web-entity-picker__group--checkable[b-x9xdxs79v3] {
    display: flex;
    align-items: center;
    gap: 6px;
    cursor: pointer;
    text-transform: none;
    letter-spacing: normal;
}
/* Checkbox visuals (unchecked/checked fill+stroke, glyph) come from the shared input[type="checkbox"] skin in
   app.css — desktop-parity themed roles, not accent-color (see that rule's remarks for the full mapping). */

/* Single-mode counterpart (WebEntityPicker.GroupSelected supplied — desktop's folder-scope rows): a plain
   <button>, so it inherits every visual from the base .web-entity-picker__group rule above (sticky position,
   surface-raised background, caption type scale) automatically — author styles always beat the UA button
   stylesheet, so only the handful of properties that rule never touches need restating here. */
.web-entity-picker__group--selectable[b-x9xdxs79v3] {
    width: 100%;
    box-sizing: border-box;
    border: 0;
    font: inherit;
    text-align: left;
    text-transform: inherit;
    letter-spacing: inherit;
    cursor: pointer;
}
.web-entity-picker__group--selectable:hover[b-x9xdxs79v3] { background: var(--surface-hover); color: var(--text-primary); }

/* Folder-group disclosure caret (nested taxonomies only — a flat picker leaves GroupDepth at 0 and its groups
   still render this control, which is why it stays visually quiet rather than becoming a second heading). Same
   collapsible grammar the Viewer controls use for their own sections. */
.web-entity-picker__group-caret[b-x9xdxs79v3] {
    display: inline-grid;
    width: 18px;
    height: 18px;
    flex: 0 0 auto;
    place-items: center;
    border: 0;
    border-radius: var(--radius-control);
    background: transparent;
    padding: 0;
    color: var(--picker-row-meta);
    cursor: pointer;
}

.web-entity-picker__group-caret:hover[b-x9xdxs79v3] { background: var(--surface-hover); color: var(--text-primary); }
.web-entity-picker__group-caret[b-x9xdxs79v3]  .phosphor-icon { font-size: 11px; line-height: 1; }

/* A plain (non-checkable) caption row has to become a flex row for the caret to sit beside its text. */
.web-entity-picker__group[b-x9xdxs79v3] { display: flex; align-items: center; gap: 4px; }

:root[data-phone] .web-entity-picker__group-caret[b-x9xdxs79v3] { width: var(--tap); height: 32px; }

.web-entity-picker__row[b-x9xdxs79v3] {
    display: flex;
    width: 100%;
    box-sizing: border-box;
    align-items: center;
    gap: 7px;
    border: 0;
    border-radius: var(--radius-control);
    background: transparent;
    padding: 5px;
    /* A row's NAME is the row's content — the one semantic pair every picker family shares. It used to read
       `--text-secondary` here while the fork picker's own options read `--text-primary`. */
    color: var(--picker-row-text);
    font: inherit;
    text-align: left;
    white-space: nowrap;
    cursor: pointer;
}

.web-entity-picker__row:hover[b-x9xdxs79v3] { background: var(--surface-hover); }
.web-entity-picker__row:disabled[b-x9xdxs79v3],
.web-entity-picker__row--disabled[b-x9xdxs79v3] { opacity: .5; cursor: default; }
.web-entity-picker__row:disabled:hover[b-x9xdxs79v3],
.web-entity-picker__row--disabled:hover[b-x9xdxs79v3] { background: transparent; }
.web-entity-picker__row--clear[b-x9xdxs79v3] { color: var(--picker-row-meta); }

/* 2026-08-08 owner ruling: the optional per-entry identity mark (WebPickerEntry.Icon) — an imported image,
   an authored glyph tinted by its colour, or a plain colour swatch when only a colour is known (Markers).
   Same 16px box TagReference's own mark uses, so a Tag row here reads identically to one anywhere else. */
.web-entity-picker__row-icon[b-x9xdxs79v3] { flex: 0 0 auto; width: 16px; height: 16px; object-fit: contain; }
/* ::deep required for the glyph tier — PhosphorIcon renders its own markup, which this component's scoped
   attribute never reaches (same reason TagIdentityBanner's own ::deep glyph rules need it). */
.web-entity-picker__row[b-x9xdxs79v3]  .web-entity-picker__row-icon { flex: 0 0 auto; font-size: 14px; line-height: 1; }
.web-entity-picker__row-swatch[b-x9xdxs79v3] { flex: 0 0 auto; width: 10px; height: 10px; border-radius: 50%; background: var(--entry-color); }

.web-entity-picker__empty[b-x9xdxs79v3] {
    margin: 4px 5px;
    color: var(--picker-row-meta);
    font-size: 11px;
}
/* Compact tier via the shared attribute (wwwroot/js/responsiveTier.js), not a restated @media condition — see
   that file for why a breakpoint cannot be shared in CSS itself. `:root[data-phone]` also outranks the base
   rules above on specificity, so this block no longer depends on appearing after them. */
:root[data-phone] .web-entity-picker[b-x9xdxs79v3] { min-width: 0; font-size: 14px; }
:root[data-phone] .web-entity-picker__trigger[b-x9xdxs79v3] {
    width: 100%;
    min-width: 0;
    min-height: var(--tap);
    overflow: hidden;
    padding-inline: 12px;
    font-size: 14px;
    text-overflow: ellipsis;
}
/* SIZE TO CONTENT, then cap. This previously set `width` to the viewport cap, which made the cap the
   EFFECTIVE width: a six-item type filter rendered nearly full-screen-wide. `max-content` lets a short
   list hug its text; the search field's own intrinsic width supplies a sensible floor; the cap is now a
   genuine maximum, and `flip-inline` is tried FIRST so a horizontal overflow is corrected before a
   vertical one. */
:root[data-phone] .web-entity-picker__popover[b-x9xdxs79v3] {
    position-try-fallbacks: flip-inline, flip-block;
    box-sizing: border-box;
    width: max-content;
    min-width: 0;
    max-width: calc(100vw - 24px - env(safe-area-inset-left) - env(safe-area-inset-right));
    max-height: min(58dvh, 420px);
    overflow: hidden;
    overscroll-behavior: contain;
}
/* With a content-sized popover the rows must truncate rather than push the box wider than the cap. */
:root[data-phone] .web-entity-picker__row[b-x9xdxs79v3] { min-width: 0; }
:root[data-phone] .web-entity-picker__row > span[b-x9xdxs79v3] { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
:root[data-phone] .web-entity-picker__search[b-x9xdxs79v3] { min-height: var(--tap); font-size: 16px; }
:root[data-phone] .web-entity-picker__bulk-btn[b-x9xdxs79v3],
:root[data-phone] .web-entity-picker__availability[b-x9xdxs79v3],
:root[data-phone] .web-entity-picker__row[b-x9xdxs79v3] { min-height: var(--tap); font-size: 14px; }
:root[data-phone] .web-entity-picker__group[b-x9xdxs79v3],
:root[data-phone] .web-entity-picker__empty[b-x9xdxs79v3] { font-size: 13px; }
/* Group headers keep their size bump but drop the desktop uppercase+tracking, which at phone size
   turned every list divider into the loudest text in the popover. */
:root[data-phone] .web-entity-picker__group[b-x9xdxs79v3] { letter-spacing: 0; text-transform: none; }
:root[data-phone] .web-entity-picker__list[b-x9xdxs79v3] { min-height: 0; max-height: none; flex: 1 1 auto; overflow-y: auto; }
/* viewer/Components/WorkspaceSurface.razor.rz.scp.css */
/* Fills its positioned parent (.workspace-stage) edge-to-edge as its own edge-to-edge canvas —
   desktop parity: WorkspaceCanvasControl sits directly in its Grid cell with no inset border/mat, so this
   previously used a `height: min(70vh, 760px)` cap plus a bordered/padded stage that boxed the canvas off
   into the page's upper portion instead of filling available height (owner-reported 2026-07-28). */
.workspace-skia-surface[b-me0twdzmv9] {
    position: absolute;
    inset: 0;
    overflow: hidden;
    contain: strict;
    background: transparent;
}
.workspace-skia-surface__input[b-me0twdzmv9] {
    position: absolute;
    inset: 0;
    cursor: grab;
    touch-action: none;
    user-select: none;
}

.workspace-skia-surface__input:active[b-me0twdzmv9] { cursor: grabbing; }
.workspace-skia-surface__area-toggle[b-me0twdzmv9] {
    position: absolute;
    z-index: 3;
    top: 12px;
    left: 12px;
    display: grid;
    place-items: center;
    width: 36px;
    height: 36px;
    border: 1px solid var(--stroke);
    border-radius: var(--radius-control);
    color: var(--text-secondary);
    background: color-mix(in srgb, var(--surface-raised) 92%, transparent);
    box-shadow: var(--shadow-panel);
    cursor: pointer;
}

.workspace-skia-surface__area-toggle:hover[b-me0twdzmv9],
.workspace-skia-surface__area-toggle:focus-visible[b-me0twdzmv9] {
    color: var(--text-primary);
    background: var(--surface-hover);
}

.workspace-skia-surface[b-me0twdzmv9]  .workspace-skia-surface__canvas {
    position: absolute;
    inset: 0;
    display: block;
    width: 100% !important;
    height: 100% !important;
    max-width: 100%;
    max-height: 100%;
}

.workspace-skia-surface__loading[b-me0twdzmv9] {
    position: absolute;
    z-index: 2;
    inset: 0;
    display: grid;
    place-items: center;
    background: color-mix(in srgb, var(--surface-app) 84%, transparent);
    color: var(--text-secondary);
    font-size: 13px;
    letter-spacing: .02em;
    pointer-events: none;
}

.workspace-skia-surface__reason[b-me0twdzmv9] {
    position: absolute;
    z-index: 4;
    width: max-content;
    max-width: 320px;
    border: 1px solid var(--stroke);
    /* Desktop parity: WorkspaceCanvasControl.axaml's Border.workspace-hover-tab-accent — a 2px bar flush down
       the card's leading edge in the hovered item's own authored colour (--workspace-hover-accent, set inline
       per-tooltip by WorkspaceSurface.razor; falls back to the neutral stroke via HoverAccentCssValue). */
    border-left: 2px solid var(--workspace-hover-accent);
    border-radius: 7px;
    padding: 6px 8px;
    color: var(--text-primary);
    background: color-mix(in srgb, var(--surface-raised) 94%, transparent);
    box-shadow: var(--shadow-panel);
    font-size: 11px;
    line-height: 1.4;
    overflow-wrap: anywhere;
    pointer-events: none;
}

/* The click-pinned reason card's consequences popup (WorkspaceSurface.razor.cs's ShowConsequencePopup) — same
   accent-bar/panel chrome as .workspace-skia-surface__reason above, but wider (ConsequencePopupMaximumWidth)
   and interactive (subject chips, a close button), so unlike that pointer-events:none tooltip this one takes
   pointer input and scrolls its own overflow instead of the page. */
.workspace-skia-surface__consequence-popup[b-me0twdzmv9] {
    position: absolute;
    z-index: 5;
    width: max-content;
    max-width: 360px;
    max-height: min(70vh, 420px);
    overflow-y: auto;
    border: 1px solid var(--stroke);
    border-left: 2px solid var(--workspace-hover-accent);
    border-radius: 7px;
    padding: 10px 30px 12px 12px;
    color: var(--text-primary);
    background: var(--surface-raised);
    box-shadow: var(--shadow-panel);
    pointer-events: auto;
}
.workspace-skia-surface__consequence-popup-close[b-me0twdzmv9] {
    position: absolute;
    top: 6px;
    right: 6px;
    border: 0;
    border-radius: var(--radius-control);
    background: transparent;
    padding: 4px;
    color: var(--text-secondary);
    cursor: pointer;
}
.workspace-skia-surface__consequence-popup-close:hover[b-me0twdzmv9],
.workspace-skia-surface__consequence-popup-close:focus-visible[b-me0twdzmv9] { color: var(--text-primary); background: var(--surface-hover); }
.workspace-skia-surface__consequence-popup-reason[b-me0twdzmv9] {
    margin: 0 0 var(--sp-2);
    color: var(--text-primary);
    font-size: 13px;
    line-height: 1.4;
    overflow-wrap: anywhere;
}

@media (max-width: 720px) {
    .workspace-skia-surface__consequence-popup[b-me0twdzmv9] { max-width: min(88vw, 360px); }
}

/* The compact-tier BottomSheet's own reason paragraph — the sheet supplies its own scroll/handle/scrim/close
   chrome, so only the text treatment is needed here (mirrors .workspace-skia-surface__consequence-popup-reason
   above, minus the anchored card's own border/background). */
.workspace-consequence-sheet__reason[b-me0twdzmv9] {
    margin: 0 0 var(--sp-3);
    color: var(--text-primary);
    font-size: 14px;
    line-height: 1.45;
    overflow-wrap: anywhere;
}
/* viewer/Layout/MainLayout.razor.rz.scp.css */
/* Route-content error fallback (audit R14). Was rendering with a nonexistent ".t-button" class and no
   container chrome at all (plain unstyled paragraph + native button) — .surface-card supplies the
   border/radius/background/padding every other card-shaped surface in the app uses; this just centers
   it within .app-content and gives its paragraph/button breathing room. */
.route-error[b-gw8xvbmbb5] {
    display: grid;
    justify-items: center;
    gap: 12px;
    max-width: 420px;
    margin: 40px auto;
    text-align: center;
}

/* The fault TYPE, surfaced so a reader on a phone -- where no developer console is reachable -- can report
   what threw. Never the exception message: messages here can carry authored label text. */
.route-error__kind[b-gw8xvbmbb5] { color: var(--text-muted); font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
/* viewer/Pages/Books.razor.rz.scp.css */
/* The page is a single column of choices, so it is measured like one: a reading-width column that scrolls its own
   overflow rather than the section shell's, and the shared spacing scale throughout (it used to run 48/28/80/24/
   6/12/16 — seven numbers for one page, none of them the scale's). */
.books-page[b-12ce4hqpgf] { width: min(100%, 820px); height: 100%; margin: 0 auto; overflow-y: auto; padding: var(--sp-7) var(--sp-5) var(--sp-7); }
.books-header[b-12ce4hqpgf] { margin-bottom: var(--sp-5); }
.books-header h1[b-12ce4hqpgf] { margin: 0 0 var(--sp-1); }
.books-header p:last-child[b-12ce4hqpgf] { margin: 0; color: var(--text-secondary); }
.books-list[b-12ce4hqpgf] { display: grid; overflow: hidden; padding: 0; }
.books-row[b-12ce4hqpgf] { display: grid; grid-template-columns: 28px minmax(0, 1fr) 24px; align-items: center; gap: var(--sp-3); min-height: 56px; border: 0; border-bottom: 1px solid var(--stroke); background: transparent; padding: 10px var(--sp-4); color: var(--text-secondary); text-align: left; cursor: pointer; transition: background-color 120ms ease, color 120ms ease; }
.books-row > span[b-12ce4hqpgf] { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.books-row:last-child[b-12ce4hqpgf] { border-bottom: 0; }
.books-row:hover[b-12ce4hqpgf] { background: var(--surface-hover); color: var(--text-primary); }
.books-row--active[b-12ce4hqpgf] { background: var(--accent-soft); color: var(--text-primary); }
.books-row--active > .phosphor-icon:last-child[b-12ce4hqpgf] { color: var(--accent); }
/* An empty publication says so on the page's own measure, not flush against the shell's top-left corner. */
.books-page > .t-empty-state[b-12ce4hqpgf] { margin: 0; }
:root[data-phone] .books-page[b-12ce4hqpgf] { padding: var(--sp-5) var(--sp-3) var(--sp-6); }
:root[data-phone] .books-row[b-12ce4hqpgf] { min-height: var(--tap); }
/* viewer/Pages/Home.razor.rz.scp.css */
/* Library route shell: category rail, resizable navigator, and read pane.
   Extracted from app.css so this page owns its own presentation. Shared reading
   surfaces (formatted document, tag components) remain in app.css. */

.library-page[b-hgtizmvn0n] { display: grid; width: 100%; height: 100%; min-height: 0; }

.library-section[b-hgtizmvn0n] { display: grid; grid-template-columns: 56px auto 7px minmax(0, 1fr); min-height: 0; height: 100%; }

/* Recessed category rail: mirrors the section rail's icon+label rows
   (the shared section-button pattern) so every vertical rail in
   the app gives the same subtle --surface-hover wash on hover and desktop
   --surface-selected fill with primary content when selected. */
/* Three zones, three distinct planes, mirroring DESKTOP's actual assignment (LibraryControl.axaml):
   category rail = ControlHoverColor (--surface-hover), tree panel = ControlFillColor (--surface-raised),
   content pane = SceneColor (--surface-nav). Desktop separates zones by lifting each panel LIGHTER than the
   app base, monotonically -- content(#221c15) < tree(#2e251d) < rail(#3a322b) -- rather than recessing them.
   Previously all three sat inside a 3% band and the tree and content columns were the SAME token. */
.category-rail[b-hgtizmvn0n] { overflow-y: auto; padding: 6px 0; background: var(--surface-hover); border-right: 1px solid var(--stroke-strong); }
.rail-item[b-hgtizmvn0n] { width: 100%; min-height: 52px; display: grid; place-items: center; gap: 3px; border: 0; border-radius: var(--radius-control); background: transparent; padding: 6px 2px; color: var(--text-secondary); cursor: pointer; transition: background-color 120ms ease, color 120ms ease; }
.rail-item:hover[b-hgtizmvn0n] { background: var(--chrome-hover); color: var(--text-primary); }
.rail-item.selected[b-hgtizmvn0n] { background: var(--surface-selected); color: var(--text-primary); }
.rail-label[b-hgtizmvn0n] { color: inherit; line-height: 1.15; overflow-wrap: anywhere; }
.rail-icon[b-hgtizmvn0n] { font-size: 18px; line-height: 1; }

.library-navigator[b-hgtizmvn0n] { box-sizing: border-box; display: grid; grid-template-rows: auto minmax(0, 1fr); width: 300px; min-width: 50px; max-width: 50vw; min-height: 0; overflow: hidden; background: var(--surface-raised); }
.library-navigator.scene-catalog-active[b-hgtizmvn0n] { grid-template-rows: auto auto minmax(0, 1fr); }
.library-navigator.scene-catalog-active.scene-timeline-active[b-hgtizmvn0n] { grid-template-rows: auto auto auto minmax(0, 1fr); }
.library-navigator-divider[b-hgtizmvn0n] { position: relative; min-height: 0; cursor: col-resize; touch-action: none; background: var(--surface-raised); }
.library-navigator-divider[b-hgtizmvn0n]::after { content: ""; position: absolute; inset: 0 3px; background: var(--stroke-strong); transition: background-color 120ms ease, box-shadow 120ms ease; }
/* Exception (elevation-token sweep): a 1px accent focus/hover ring on the drag divider, not an elevation shadow. */
.library-navigator-divider:hover[b-hgtizmvn0n]::after, .library-navigator-divider:focus-visible[b-hgtizmvn0n]::after { background: var(--accent); box-shadow: 0 0 0 1px var(--accent-soft); }
.library-navigator-divider:focus-visible[b-hgtizmvn0n] { outline: 2px solid var(--accent); outline-offset: -2px; }
.navigator-tools[b-hgtizmvn0n] { display: grid; grid-template-columns: 28px 28px 28px minmax(0, 1fr) auto auto; gap: 3px; padding: 6px 5px 4px; border-bottom: 1px solid var(--stroke); align-items: center; }
.mobile-navigator-tools[b-hgtizmvn0n] { display: none; }
.scene-catalog-tabs[b-hgtizmvn0n] { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); gap: 3px; padding: 4px 5px; border-bottom: 1px solid var(--stroke); }
.scene-catalog-tab[b-hgtizmvn0n] { display: grid; place-items: center; min-width: 0; height: 30px; border: 0; border-radius: var(--radius-control); background: transparent; color: var(--text-secondary); cursor: pointer; font-size: 17px; transition: background-color 120ms ease, color 120ms ease; }
.scene-catalog-tab:hover[b-hgtizmvn0n] { background: var(--surface-hover); color: var(--text-primary); }
.scene-catalog-tab.selected[b-hgtizmvn0n] { background: var(--surface-selected); color: var(--text-primary); }
.scene-catalog-tab:focus-visible[b-hgtizmvn0n] { outline: 2px solid var(--accent); outline-offset: -2px; }
.library-timeline-controls[b-hgtizmvn0n] { display: flex; align-items: center; gap: 4px; min-width: 0; padding: 4px 5px; border-bottom: 1px solid var(--stroke); }
.library-timeline-controls[b-hgtizmvn0n]  .timeline-fork-picker { min-width: 0; flex: 1 1 auto; }
.library-timeline-controls[b-hgtizmvn0n]  .timeline-fork-picker__trigger-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.library-timeline-clear[b-hgtizmvn0n] { flex: 0 0 28px; }
.mobile-detail-navigation[b-hgtizmvn0n] { display: none; }
.visually-hidden[b-hgtizmvn0n] { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; }
/* 28x28 qat icon buttons: transparent, radius-control, in-content surface-hover wash. */
.tool-button[b-hgtizmvn0n] { display: grid; place-items: center; width: 28px; height: 28px; border: 0; border-radius: var(--radius-control); background: transparent; cursor: pointer; color: var(--text-secondary); transition: background-color 120ms ease, color 120ms ease; }
.tool-button:hover:not(:disabled)[b-hgtizmvn0n] { background: var(--surface-hover); color: var(--text-primary); }
.tool-button:disabled[b-hgtizmvn0n] { opacity: .35; }
/* Write-surface search well: recessed input fill, hairline brightening on hover, accent on focus. */
.library-search[b-hgtizmvn0n] { width: 100%; min-width: 0; border: 1px solid var(--stroke); border-radius: var(--radius-control); outline: 0; background: var(--surface-input); color: var(--text-primary); padding: 5px 8px; font-size: 12px; transition: border-color 120ms ease; }
.library-search:hover[b-hgtizmvn0n] { border-color: var(--stroke-strong); }
.library-search:focus-visible[b-hgtizmvn0n] { border-color: var(--accent); }
.tree-scroll[b-hgtizmvn0n] { min-width: 0; min-height: 0; overflow: auto; padding: 5px 4px 24px; }

.read-pane[b-hgtizmvn0n] { min-width: 0; overflow-y: auto; background: var(--surface-nav); }
/* Shared empty-state stack: thin muted glyph over section title and empty-state hint. */
.detail-placeholder[b-hgtizmvn0n] { display: grid; justify-items: center; gap: 5px; width: min(440px, calc(100% - 64px)); margin-inline: auto; padding-top: 22vh; text-align: center; }
.detail-placeholder__glyph[b-hgtizmvn0n] { font-size: 28px; color: var(--text-muted); }
.detail-placeholder h1[b-hgtizmvn0n] { margin: 0; }
.detail-placeholder p[b-hgtizmvn0n] { margin: 0; }

/* Compact tier via the shared attribute (wwwroot/js/responsiveTier.js), not a restated @media query —
   :root[data-phone] also outranks the base rules on specificity, so this block no longer depends on source order. */
:root[data-phone] .library-navigator[b-hgtizmvn0n] {
    width: auto !important;
    min-width: 0;
    max-width: none;
    grid-template-rows: auto minmax(0, 1fr);
    border-right: 1px solid var(--stroke);
    background: var(--surface-nav);
}
:root[data-phone] .library-navigator.scene-catalog-active[b-hgtizmvn0n] { grid-template-rows: auto auto minmax(0, 1fr); }
:root[data-phone] .library-navigator.scene-catalog-active.scene-timeline-active[b-hgtizmvn0n] { grid-template-rows: auto auto minmax(0, 1fr); }
:root[data-phone] .library-timeline-controls[b-hgtizmvn0n] { display: none; }
:root[data-phone] .library-navigator-divider[b-hgtizmvn0n] { display: none; }
:root[data-phone] .detail-placeholder[b-hgtizmvn0n] { width: calc(100% - 32px); }

/* 2026-08 narrow-tier redesign (owner refinement: "ease of use for picking things like scenes or tags etc, but
   also need full real estate for reading"). Two screens, two different chrome budgets:
     LIST   (nothing selected, or Browse re-opened) — picking WHICH Tag/Scene/Note is the whole point of this
            screen, so it stays tier-1: a search top bar, the SAME category rail desktop uses (now a
            horizontally scrollable chip row instead of a vertical column), then the full tree.
            .library-section now carries EXACTLY ONE of .mobile-list-only/.mobile-detail-active — the two class
            names are perfect complements of MobileDetailActive (see the Home.razor class binding), closing a
            gap where a master-detail category (Tags/Scenes/Background Notes) with nothing yet selected used to
            match NEITHER class and fall through to a stale two-column rule below, splitting the screen between
            a narrow navigator and a mostly-empty placeholder pane instead of filling it — Metrics/Markers/
            Outcomes (which never open a detail) were the only category kind that reliably got the full-bleed
            treatment before this pass.
     DETAIL (a Scene/Tag/Note is open) — minimal chrome: one sticky bar (chevron back + title), nothing else.
            No rail, no chip row, no Back/Forward history — those stay list-screen tools; see the sticky bar's
            own rule further down for why history specifically was dropped rather than relocated here.
   Colour strategy (owner concern: "one undifferentiated brown mush" stacked on a phone) — investigated, no new
   hex introduced anywhere below:
     1. Authored entity colour is NEVER stripped for a flatter phone look — the navigator's own tinted node
        icons are unchanged (LibraryNavigationView.razor's RowBody already passes Color="@treeNode.Icon.TintHex"
        at every tier), and the new detail sticky bar's title reuses TagReference's own image/glyph+colour/dot
        ladder for a Tag rather than a plain text label.
     2. The three list-screen bands (top bar / chip row / tree) reuse desktop's OWN three-plane Library
        assignment verbatim — this file's own top comment: rail = ControlHoverColor/--surface-hover, tree panel
        = ControlFillColor/--surface-raised, content = SceneColor/--surface-nav — rather than inventing a
        phone-only palette. Checked against actual per-theme hex in app.css: cozy-dark's three tokens are
        #3a322b/#2e251d/#221c15 (39/29 apart), cozy-day's are #d8cabb/#e5ded7/#f3f0ed (61/54 apart) — genuinely
        separated planes in both a dark and a light theme, not three near-identical warm fills.
     3. The one restrained --accent moment is a RING, never a fill, on the selected chip only (owner ruling:
        accent stays reserved for focus/selection/state) — see .rail-item.selected below.
   No fourth per-section hue exists to give Library "its own colour" the way a decorative accent would: app.css's
   six themes expose exactly one --accent (reserved as above) plus three role-locked hues (--danger/--warning/
   --note-accent) that already mean something specific elsewhere in the app. This is the same spacing gap the section rails
   already expose — flagged here again as a follow-up needing an owner palette-level decision,
   not solved locally. */
:root[data-phone] .library-section[b-hgtizmvn0n] { overflow: hidden; }
:root[data-phone] .library-section.mobile-list-only[b-hgtizmvn0n] {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto auto minmax(0, 1fr);
}
:root[data-phone] .library-section.mobile-detail-active[b-hgtizmvn0n] {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: minmax(0, 1fr);
}

/* LIST-screen top bar (row 1): Back chevron, Forward chevron, then search flexing to the remaining width — see
   the markup comment in Home.razor for why the old "…" overflow trigger + BottomSheet (Back/Forward/Collapse
   folders) and the even older <select> category picker are both gone. */
:root[data-phone] .mobile-navigator-tools[b-hgtizmvn0n] {
    grid-column: 1;
    grid-row: 1;
    display: grid;
    grid-template-columns: var(--tap) var(--tap) minmax(0, 1fr);
    align-items: center;
    gap: 4px;
    min-width: 0;
    min-height: 54px;
    border-bottom: 1px solid var(--stroke-strong);
    padding: 5px max(8px, env(safe-area-inset-right)) 5px max(8px, env(safe-area-inset-left));
    padding-top: max(5px, env(safe-area-inset-top));
    background: var(--surface-hover);
}
/* Same chrome as the detail screen's own .mobile-browse-trigger chevron (transparent, surface-pressed on tap,
   44px tap target) so the two screens' history controls read as one idiom. */
:root[data-phone] .mobile-nav-chevron[b-hgtizmvn0n] {
    box-sizing: border-box;
    display: grid;
    place-items: center;
    width: var(--tap);
    min-height: var(--tap);
    padding: 0;
    border: 0;
    border-radius: calc(var(--radius-control) - 2px);
    background: transparent;
    color: var(--text-primary);
    cursor: pointer;
}
:root[data-phone] .mobile-nav-chevron:hover:not(:disabled)[b-hgtizmvn0n] { background: var(--surface-pressed); }
:root[data-phone] .mobile-nav-chevron:disabled[b-hgtizmvn0n] { opacity: .35; }
:root[data-phone] .mobile-nav-chevron:focus-visible[b-hgtizmvn0n] { outline: 2px solid var(--accent); outline-offset: -2px; }
:root[data-phone] .mobile-library-search[b-hgtizmvn0n] { position: relative; min-width: 0; display: block; }
:root[data-phone] .mobile-library-search .library-search[b-hgtizmvn0n] {
    box-sizing: border-box;
    width: 100%;
    min-width: 0;
    min-height: var(--tap);
    padding-right: 38px;
    font-size: 16px;
}
/* Two possible trailing icon buttons (the trait-filter funnel, TAGS category only, then the search clear "x")
   share one absolutely-positioned flex strip rather than each hard-coding its own `right` offset — the funnel
   button is conditionally rendered (IsTagsCategory), so a fixed `right: 38px` for the clear button would leave
   a gap on every OTHER category instead of the two buttons closing up naturally. */
:root[data-phone] .mobile-search-actions[b-hgtizmvn0n] {
    position: absolute;
    top: 0;
    right: 0;
    height: 100%;
    display: flex;
    align-items: stretch;
    gap: 2px;
}
:root[data-phone] .mobile-library-search--with-filter .library-search[b-hgtizmvn0n] { padding-right: 76px; }
/* The funnel trigger is rendered by LibraryTraitFilterPanel (its own scoped CSS owns its look); sizing it as a
   flex item of the shared trailing-actions strip above works across the component boundary since flex layout
   is structural rather than selector-scoped. */
:root[data-phone] .mobile-search-clear[b-hgtizmvn0n] {
    position: static;
    display: grid;
    place-items: center;
    width: var(--tap);
    height: 100%;
    border: 0;
    background: transparent;
    color: var(--text-secondary);
    cursor: pointer;
}
:root[data-phone] .mobile-library-search:focus-within .library-search[b-hgtizmvn0n] {
    border-color: var(--accent);
    outline: 2px solid color-mix(in srgb, var(--accent) 35%, transparent);
    outline-offset: 1px;
}

/* LIST-screen chip row (row 2): desktop's vertical .category-rail becomes a horizontally scrollable row of the
   SAME rail-item buttons. Hidden outright on the detail screen — entity picking belongs to the list screen only
   (owner ruling). */
:root[data-phone] .navigator-tools[b-hgtizmvn0n] { display: none; }
:root[data-phone] .mobile-detail-active .category-rail[b-hgtizmvn0n],
:root[data-phone] .mobile-detail-active .mobile-navigator-tools[b-hgtizmvn0n] { display: none; }
:root[data-phone] .mobile-list-only .category-rail[b-hgtizmvn0n] {
    grid-column: 1;
    grid-row: 2;
    display: flex;
    align-items: stretch;
    overflow-x: auto;
    overflow-y: hidden;
    overscroll-behavior-x: contain;
    gap: 6px;
    padding: 6px max(10px, env(safe-area-inset-right)) 6px max(10px, env(safe-area-inset-left));
    border-right: 0;
    border-bottom: 1px solid var(--stroke);
    background: var(--surface-raised);
}
:root[data-phone] .mobile-list-only .rail-item[b-hgtizmvn0n] {
    flex: 0 0 auto;
    width: auto;
    min-height: var(--tap);
    flex-direction: row;
    gap: 6px;
    padding: 0 var(--sp-3);
    border: 1px solid transparent;
    border-radius: var(--radius-control);
}
/* Owner ruling (fix, amended against a desktop-app reference screenshot, twice now): first pass removed an
   --accent border RING drawn on top of the shared --surface-selected wash — on a near-square icon+short-label
   chip (e.g. "Tags") a 999px-radius ring read as a circle drawn around the icon rather than a highlighted chip.
   Second pass (owner screenshot of the HOVER state): the chip's own 999px pill `border-radius` was still there
   underneath, and on a near-square chip a pill wash reads the same way a ring did — a circle behind the icon.
   Hover and selected must share ONE idiom, a soft ROUNDED-RECT wash, matching the real desktop app's own
   Library category rail (Thruline.AvaloniaUI/Library/Views/LibraryControl.axaml, `ListBox.library-rail
   ListBoxItem:selected`: `TextFillColorPrimaryBrush` foreground over a `ChromeHoverBrush` wash, versus
   `TextFillColorSecondaryBrush` unselected, both on the SAME `var(--radius-control)` shape the desktop rail's
   own `.rail-item` base rule already declares) — never a section-rail filled-pill idiom, and
   never a per-state radius change. The chip row's OWN radius now matches `.rail-item`'s desktop base value
   instead of overriding it to 999px, so `.rail-item:hover`/`.rail-item.selected` (declared above, already
   shared by desktop's own vertical rail and this compact chip row) paint the identical rounded-rect wash in
   both places with no separate rule needed. */
:root[data-phone] .rail-label[b-hgtizmvn0n] { white-space: nowrap; }

/* LIST-screen body (row 3): the tree itself, structurally unchanged — see LibraryNavigationView.razor.css for
   its own row-height/target-size compact rules. */
:root[data-phone] .mobile-list-only .library-navigator[b-hgtizmvn0n] {
    grid-column: 1;
    grid-row: 3;
    width: 100% !important;
    height: 100%;
    min-height: 0;
    max-height: none;
    border-right: 0;
    border-bottom: 0;
}
:root[data-phone] .mobile-list-only .read-pane[b-hgtizmvn0n] { display: none; }

:root[data-phone] .mobile-detail-active .library-navigator[b-hgtizmvn0n] { display: none; }
:root[data-phone] .mobile-detail-active .read-pane[b-hgtizmvn0n] {
    grid-column: 1;
    grid-row: 1;
    width: 100%;
}

/* DETAIL-screen sticky bar: chevron back + title, nothing else — flush and full-width (no floating card/
   border/shadow/margin: chrome should not eat into "full real estate for reading", owner ruling) at the SAME
   --surface-hover tone the list screen's own top bar uses, so both screens' chrome reads as one consistent
   plane rather than two different ideas of "the top of the page". Back/Forward history is DROPPED here rather
   than relocated into a per-screen trigger — see the markup comment in Home.razor for the reasoning; full
   history stays reachable from the list screen's own top bar (permanent Back/Forward chevrons, no sheet). */
:root[data-phone] .mobile-detail-active .mobile-detail-navigation[b-hgtizmvn0n] {
    position: sticky;
    z-index: 3;
    top: 0;
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    box-sizing: border-box;
    width: 100%;
    min-height: 52px;
    padding: 4px max(10px, env(safe-area-inset-right)) 4px max(10px, env(safe-area-inset-left));
    padding-top: max(4px, env(safe-area-inset-top));
    border-bottom: 1px solid var(--stroke-strong);
    background: var(--surface-hover);
}
:root[data-phone] .mobile-browse-trigger[b-hgtizmvn0n] {
    display: grid;
    flex: 0 0 var(--tap);
    place-items: center;
    min-height: var(--tap);
    border: 0;
    border-radius: calc(var(--radius-control) - 2px);
    background: transparent;
    color: var(--text-primary);
    cursor: pointer;
}
:root[data-phone] .mobile-browse-trigger:hover[b-hgtizmvn0n] { background: var(--surface-pressed); }
:root[data-phone] .mobile-browse-trigger:focus-visible[b-hgtizmvn0n] { outline: 2px solid var(--accent); outline-offset: -2px; }
:root[data-phone] .mobile-detail-title[b-hgtizmvn0n] { display: inline-flex; align-items: center; gap: 8px; min-width: 0; overflow: hidden; }
/* Tag detail: TagReference's own identity ladder (image, else authored glyph tinted with the tag's own colour,
   else a plain colour dot) becomes the title itself, just resized up to read as a page heading — the ::deep
   combinator reaches into the child component's own scoped markup, the same pattern the read-pane heading
   overrides further down already use. */
:root[data-phone] .mobile-detail-title[b-hgtizmvn0n]  .tag-reference__name {
    color: var(--text-primary);
    font-size: 16px;
    font-weight: 600;
}
/* Scene/Background Note detail: neither kind carries authored colour, so the title falls back to the active
   category's plain semantic glyph — the same fallback tier an uncoloured navigator row already uses. */
:root[data-phone] .mobile-detail-title__glyph[b-hgtizmvn0n] { flex: 0 0 auto; color: var(--text-secondary); font-size: 16px; }
:root[data-phone] .mobile-detail-title__text[b-hgtizmvn0n] {
    overflow: hidden;
    color: var(--text-primary);
    font-size: 16px;
    font-weight: 600;
    text-overflow: ellipsis;
    white-space: nowrap;
}

:root[data-phone] .tree-scroll[b-hgtizmvn0n] {
    min-width: 0;
    min-height: 0;
    overflow: auto;
    overscroll-behavior: contain;
    background: var(--surface-nav);
}
:root[data-phone] .read-pane[b-hgtizmvn0n] {
    min-width: 0;
    min-height: 0;
    overflow: auto;
    overscroll-behavior: contain;
    background: var(--surface-app);
    color: var(--text-secondary);
}
:root[data-phone] .read-pane[b-hgtizmvn0n]  .t-header-page,
:root[data-phone] .read-pane[b-hgtizmvn0n]  .t-header-section,
:root[data-phone] .read-pane[b-hgtizmvn0n]  .t-header-card { color: var(--text-primary); }
:root[data-phone] .read-pane[b-hgtizmvn0n]  .t-header-group,
:root[data-phone] .read-pane[b-hgtizmvn0n]  .t-caption,
:root[data-phone] .read-pane[b-hgtizmvn0n]  .t-meta { color: var(--text-secondary); }
:root[data-phone] .detail-placeholder[b-hgtizmvn0n] { width: calc(100% - 32px); }

@media (max-width: 820px) and (min-height: 480px) {
    .library-section[b-hgtizmvn0n] {
        grid-template-columns: minmax(0, 1fr);
        grid-template-rows: auto minmax(0, 1fr);
    }
    .library-navigator[b-hgtizmvn0n] {
        width: 100% !important;
        min-height: 148px;
        max-height: max(148px, min(28dvh, 240px));
        border-right: 0;
        border-bottom: 1px solid var(--stroke);
    }
    .tree-scroll[b-hgtizmvn0n] {
        padding: 5px max(4px, env(safe-area-inset-right)) 16px max(4px, env(safe-area-inset-left));
    }
    .read-pane[b-hgtizmvn0n] { padding-bottom: env(safe-area-inset-bottom); }
    .detail-placeholder[b-hgtizmvn0n] { padding-top: clamp(28px, 10vh, 72px); }
}

/* ===========================================================================================================
   Landscape sidebar (owner ruling, additive to the narrow-tier redesign above): "flip controls right — if
   landscape, put them on a left side bar." A short+wide touch viewport keeps the LIST screen's top search bar
   (row 1, unchanged, full width) but turns the category chip row into a vertical 88px left rail beside the
   tree instead of a horizontal row above it — freeing height back to the tree/reader, the same idiom
   the section rail's landscape override and the page's own left-stacked floating
   chips both apply. The DETAIL screen is untouched: it already has no rail/chip row at any orientation (owner
   ruling: chrome there is chevron+title only), so there is nothing for this block to reflow.
   ----------------------------------------------------------------------------------------------------------- */
@media (orientation: landscape) {
    :root[data-phone] .library-section.mobile-list-only[b-hgtizmvn0n] {
        grid-template-columns: 88px minmax(0, 1fr);
        grid-template-rows: auto minmax(0, 1fr);
    }
    :root[data-phone] .mobile-list-only .mobile-navigator-tools[b-hgtizmvn0n] { grid-column: 1 / -1; grid-row: 1; }
    :root[data-phone] .mobile-list-only .category-rail[b-hgtizmvn0n] {
        grid-column: 1;
        grid-row: 2;
        flex-direction: column;
        flex-wrap: nowrap;
        overflow-x: hidden;
        overflow-y: auto;
        border-right: 1px solid var(--stroke);
        border-bottom: 0;
    }
    :root[data-phone] .mobile-list-only .rail-item[b-hgtizmvn0n] {
        flex-direction: column;
        gap: 2px;
        padding: 6px 2px;
        border-radius: var(--radius-control);
        font-size: 11px;
        text-align: center;
    }
    :root[data-phone] .rail-label[b-hgtizmvn0n] { white-space: normal; }
    :root[data-phone] .mobile-list-only .library-navigator[b-hgtizmvn0n] { grid-column: 2; grid-row: 2; }
}
/* viewer/Pages/Read.razor.rz.scp.css */
.read-page[b-edt3fu7hy0] {
    /* Fixed reading defaults (owner ruling: no longer reader-adjustable). Font size and line height read
       app.css's shared --prose-font-size/--prose-line-height tokens rather than repeating 17px/1.5 here, so
       Read's column and the Library Scene inspector's Manuscript tab can never drift onto two different "what
       does prose look like" answers; --read-measure has no such shared token since no other surface needs a
       reading-column width. */
    --read-measure: 720px;
    --read-font-size: var(--prose-font-size);
    --read-line-height: var(--prose-line-height);
    /* --read-tint-surface and --read-dim are session-adjustable (Read.razor's ReadStyle, bound to
       WebReadingState's SurroundingTone/SurroundingColorHex/SurroundingDim via the options popover) — these
       are just the fallback values for the brief window before the inline style attribute first renders. */
    --read-tint-surface: var(--surface-app);
    --read-dim: .88;
    position: relative;
    isolation: isolate;
    display: grid;
    grid-template-rows: 36px minmax(0, 1fr);
    width: 100%;
    height: 100%;
    overflow: hidden;
    outline: none;
    background: transparent;
}

/* .read-ground and .read-tint sandwich Read's own local ambient effect canvas(es) (see the stacking comment
   in Read.razor): .read-ground is the chosen background colour, OPAQUE and painted BEHIND the effect — via
   z-index alone, since .read-page's `isolation:isolate` makes this a self-contained local stacking context, so
   z-index here never fights the app shell's own layers — so the effect draws crisply OVER the colour rather
   than the colour covering the effect (an earlier shape got this backwards: an opaque surround painted INSIDE
   the effect's own stacking bucket, above it, occluding it outright). .read-tint is the separate translucent
   dim veil, tinted with that SAME colour, painted ABOVE the effect instead — it darkens the effect and ground
   together without hiding either at dim's default. Both default to the page's own already-transparent ground
   (--read-tint-surface defaults to --surface-app, --read-dim defaults to FocusVisualDefaults.DimOpacity),
   matching the untouched pre-existing look. */
.read-ground[b-edt3fu7hy0] {
    position: fixed;
    z-index: -1;
    inset: 0;
    background: var(--read-tint-surface);
    pointer-events: none;
}
.read-tint[b-edt3fu7hy0] {
    position: fixed;
    z-index: 3;
    inset: 0;
    background: var(--read-tint-surface);
    opacity: var(--read-dim);
    pointer-events: none;
}

.read-chrome[b-edt3fu7hy0] {
    position: relative;
    z-index: 5;
    display: grid;
    grid-template-columns: minmax(32px, 1fr) minmax(300px, 520px) minmax(72px, 1fr);
    align-items: center;
    gap: 16px;
    padding: 4px 8px;
    border-bottom: 1px solid var(--chrome-border);
    background: var(--chrome-background);
}

.read-chrome-end[b-edt3fu7hy0],
.read-chrome-start[b-edt3fu7hy0],
.read-scene-navigation[b-edt3fu7hy0] {
    display: flex;
    align-items: center;
}

/* The leading cluster: [Read | Review] and the route dropdown. It takes the chrome's own first column, so the
   scene heading stays exactly centred. */
.read-chrome-start[b-edt3fu7hy0] {
    grid-column: 1;
    min-width: 0;
    gap: var(--sp-2);
    overflow-x: auto;
    scrollbar-width: none;
}
.read-chrome-start[b-edt3fu7hy0]::-webkit-scrollbar { display: none; }

.read-timeline-select[b-edt3fu7hy0] {
    flex: 0 1 200px;
    min-width: 120px;
    max-width: 200px;
}

/* Two halves of one control, so they meet: the shared .btn kit draws each half and this only removes the seam. */
.read-mode-segment[b-edt3fu7hy0] { display: flex; flex: 0 0 auto; align-items: center; }
.read-mode-segment .btn:first-child[b-edt3fu7hy0] { border-start-end-radius: 0; border-end-end-radius: 0; }
.read-mode-segment .btn:last-child[b-edt3fu7hy0] { margin-inline-start: -1px; border-start-start-radius: 0; border-end-start-radius: 0; }
.read-mode-segment .btn.is-active[b-edt3fu7hy0] { z-index: 1; }

/* ‹ scene title ⌄ › as ONE cluster, centred in the chrome's middle column. The picker used to FILL that column
   (minmax(220px, 1fr)) while drawing its label left and its caret right, so at desktop width the caret and the
   next-scene chevron sat some 400px away from the title they belong to and the row read as three unrelated
   controls. The title column is content-sized now and the whole cluster centres, which is also what keeps the
   chevrons a constant distance from the title at every window width. */
.read-scene-navigation[b-edt3fu7hy0] {
    grid-column: 2;
    display: grid;
    grid-template-columns: 28px minmax(0, auto) 28px;
    justify-content: center;
    gap: 6px;
}

/* The chrome's controls are the shared .btn kit (icon/ghost/small modifiers); the only thing this page adds is
   the CHROME text role — these buttons sit on the header surface, not on a content surface, so they take
   --chrome-text-* like every other chrome action instead of the body-copy brown. */
.read-chrome .btn--ghost[b-edt3fu7hy0] { color: var(--chrome-text-secondary); }
.read-chrome .btn--ghost:hover:not(:disabled)[b-edt3fu7hy0] { color: var(--chrome-text-primary); }

.read-chrome-end[b-edt3fu7hy0] {
    grid-column: 3;
    position: relative;
    justify-self: end;
    gap: var(--sp-1);
    color: var(--text-muted);
    font-size: 12px;
}

.read-settings[b-edt3fu7hy0] { position: relative; }
/* Chrome from the .btn kit; this class owns the popover ANCHOR name and the trigger's own comfortable width. */
.read-settings-trigger[b-edt3fu7hy0] {
    anchor-name: --read-settings-trigger;
    min-width: 58px;
}
.read-settings:has(.read-settings-panel:popover-open) .read-settings-trigger[b-edt3fu7hy0] {
    border-color: var(--accent);
    color: var(--text-primary);
}
.read-settings-panel[b-edt3fu7hy0] {
    position: fixed;
    position-anchor: --read-settings-trigger;
    z-index: 10;
    inset: auto;
    top: anchor(bottom);
    right: anchor(right);
    gap: 10px;
    width: 310px;
    max-height: calc(100vh - 52px);
    overflow-y: auto;
    border: 1px solid var(--stroke);
    border-radius: var(--radius-overlay);
    background: color-mix(in srgb, var(--surface-raised) 78%, transparent);
    padding: var(--sp-3);
    color: var(--text-secondary);
    box-shadow: var(--shadow-overlay);
    margin: 12px 0 0;
    color-scheme: inherit;
    font-family: var(--font-interface);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
}
.read-settings-panel:popover-open[b-edt3fu7hy0] { display: grid; }
/* A .btn--menu-row from the kit; only the quiet resting colour is local (the panel's own text role). */
.read-action-link[b-edt3fu7hy0],
.read-action-link:visited[b-edt3fu7hy0] { color: var(--text-secondary); }
.read-settings-panel label[b-edt3fu7hy0] { display: grid; gap: 7px; font-size: 13px; }
.read-settings-panel label > span[b-edt3fu7hy0] { display: flex; justify-content: space-between; }
.read-settings-panel output[b-edt3fu7hy0] { color: var(--text-primary); font-variant-numeric: tabular-nums; }
.read-settings-panel input[b-edt3fu7hy0],
.read-settings-panel select[b-edt3fu7hy0] { width: 100%; }
.read-settings-panel select[b-edt3fu7hy0] { min-height: 30px; }

/* Background colour row: theme-derived preset swatches, then the native colour input (owner ruling: a
   real colour choice, not just token presets) and its synced hex field, then a reset-to-theme icon button.
   Width overrides beat the shared ".read-settings-panel input/select { width:100% }" rule on class-count
   specificity alone — no !important needed — since every control here is a small inline chip, not a full-width
   field like the rest of the panel. */
.read-color-row[b-edt3fu7hy0] { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
/* Round kit buttons (.btn .btn--icon .btn--round) whose fill is the tone itself, set inline; only the chip size
   and the selected ring are local. */
.read-color-row .read-color-swatch[b-edt3fu7hy0] {
    width: 22px;
    height: 22px;
    border-color: var(--stroke);
}
.read-color-swatch[aria-pressed="true"][b-edt3fu7hy0] { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }
.read-color-row .read-color-picker[b-edt3fu7hy0] {
    width: 30px;
    height: 26px;
    padding: 2px;
    border: 1px solid var(--stroke);
    border-radius: var(--radius-control);
    background: var(--surface-input);
    cursor: pointer;
}
.read-color-row .read-color-hex[b-edt3fu7hy0] {
    width: 84px;
    min-height: 26px;
    border: 1px solid var(--stroke);
    border-radius: var(--radius-control);
    background: var(--surface-input);
    padding: 0 6px;
    color: var(--text-primary);
    font-family: var(--font-interface);
    font-size: 12px;
    text-transform: uppercase;
}
/* A ghost icon button from the kit, sized to sit level with the colour chips beside it. */
.read-color-row .read-color-reset[b-edt3fu7hy0] { width: 26px; height: 26px; color: var(--text-secondary); }

/* The hierarchical Scene picker: WebEntityPicker's shared trigger+popover shell (2026-07-28 consolidation) holds
   the "Full script" entry above the reused Library Scene tree (LibraryNavigationView, restricted to the Scenes
   category) via its ChildContent slot. This call site reads as a page heading (18px/600 weight, transparent,
   fills its grid cell) rather than WebEntityPicker's small-utility-control default look, so the shared internals
   are overridden here via ::deep instead of changing WebEntityPicker's own defaults for every other consumer. */
.read-scene-picker[b-edt3fu7hy0] {
    position: relative;
    min-width: 0;
}
.read-scene-picker[b-edt3fu7hy0]  .web-entity-picker__trigger {
    display: flex;
    max-width: 100%;
    box-sizing: border-box;
    align-items: center;
    /* Centred, not space-between: the caret belongs to the title, so it hugs it rather than being flung to the
       far edge of whatever width the cell happens to have. */
    justify-content: center;
    gap: var(--sp-1);
    height: 28px;
    min-height: 0;
    border: 0;
    border-radius: var(--radius-control);
    background: transparent;
    padding: 0 8px;
    color: var(--text-primary);
    font: inherit;
    font-size: 18px;
    font-weight: 600;
}
.read-scene-picker[b-edt3fu7hy0]  .web-entity-picker__trigger:hover { background: var(--surface-hover); }
.read-scene-picker[b-edt3fu7hy0]  .web-entity-picker__popover {
    width: min(360px, 90vw);
    max-width: none;
    max-height: min(480px, 70vh);
}
.read-scene-picker__full-script[b-edt3fu7hy0] {
    display: flex;
    flex: 0 0 auto;
    align-items: center;
    gap: var(--sp-2);
    border: 1px solid var(--stroke);
    border-radius: var(--radius-control);
    background: var(--surface-input);
    padding: var(--sp-2) var(--sp-3);
    color: var(--text-primary);
    font: inherit;
    font-size: 14px;
    font-weight: 500;
    cursor: pointer;
}
.read-scene-picker__full-script:hover[b-edt3fu7hy0] { border-color: var(--accent); background: var(--surface-hover); color: var(--text-primary); }
.read-scene-picker__full-script.selected[b-edt3fu7hy0] {
    border-color: var(--accent);
    background: var(--accent-soft);
    color: var(--text-primary);
}
.read-surface[b-edt3fu7hy0] {
    position: relative;
    z-index: 4;
    min-height: 0;
    overflow-y: auto;
    scrollbar-gutter: stable;
    padding: 10px 24px 14px;
    background: transparent;
}
.read-card[b-edt3fu7hy0] {
    position: relative;
    z-index: 1;
    width: min(var(--read-measure), 100%);
    min-height: calc(100vh - 60px);
    margin: 0 auto;
    display: grid;
    align-content: start;
    gap: 0;
    border: 1px solid var(--stroke);
    border-radius: var(--radius-overlay);
    background: var(--surface-nav);
    padding: 24px 26px 72px;
    color: var(--text-primary);
    box-shadow: var(--shadow-panel);
}
/* No top rule. Read's card holds ONE child (the prose) and its heading is visually hidden, so the hairline that
   used to open it separated nothing: it drew a stray line across the top of every reading page. */
.read-content-card[b-edt3fu7hy0] {
    overflow: hidden;
    background: var(--surface-nav);
}
/* 2026-07-30: was 10px/700 uppercase at .13em — a shouty micro-label above a reading surface. */
.read-eyebrow[b-edt3fu7hy0] {
    margin: 0 0 var(--sp-2);
    color: var(--accent);
    font-size: 12px;
    font-weight: 500;
    letter-spacing: 0;
    text-transform: none;
}
.read-empty h1[b-edt3fu7hy0] {
    margin: 0;
    color: var(--text-primary);
    font-family: var(--font-reading);
    font-size: clamp(30px, 4vw, 44px);
    font-weight: 560;
    line-height: 1.12;
}
/* --read-font-size/--read-line-height are the fixed reading defaults (see .read-page above), resolved from
   app.css's shared --prose-font-size/--prose-line-height (see app.css's own comment) rather than a second
   hand-copied "17px"/"1.5". */
.read-manuscript[b-edt3fu7hy0]  .formatted-document,
.read-full-script[b-edt3fu7hy0]  .formatted-document {
    font-family: var(--font-reading);
    font-size: var(--read-font-size);
    line-height: var(--read-line-height);
}
/* Desktop parity (TextRenderMetrics.ParagraphSpacing) across the single-Scene and Full script prose — the same
   app.css --prose-paragraph-spacing token the Library Scene inspector's Manuscript tab applies. */
.read-manuscript[b-edt3fu7hy0]  .prose-line,
.read-full-script[b-edt3fu7hy0]  .prose-line { margin: 0 0 var(--prose-paragraph-spacing); }
.read-manuscript[b-edt3fu7hy0]  .prose-line:last-child,
.read-full-script[b-edt3fu7hy0]  .prose-line:last-child { margin-bottom: 0; }
.read-manuscript[b-edt3fu7hy0],
.read-empty-content[b-edt3fu7hy0] { margin: 0; padding: 24px 4px 42px; }
.read-empty-content[b-edt3fu7hy0] {
    color: var(--text-secondary);
    font-size: 14px;
    font-style: italic;
}
.read-manuscript-empty[b-edt3fu7hy0] { font-family: var(--font-reading); font-size: 16px; }

/* Full script: every Scene rendered as one continuous stream inside the same .read-card/.read-content-card skin a
   single Scene uses, so switching in and out of Full script never changes the reading chrome — only a quiet
   heading (the shared .t-header-section utility) separates one Scene's prose from the next. */
.read-full-script[b-edt3fu7hy0] { padding: 24px 4px 42px; }
.read-full-script-scene[b-edt3fu7hy0] {
    padding-top: 28px;
    border-top: 1px solid var(--stroke);
}
.read-full-script-scene:first-child[b-edt3fu7hy0] {
    padding-top: 0;
    border-top: 0;
}
.read-full-script-heading[b-edt3fu7hy0] {
    margin: 0 0 14px;
    scroll-margin-top: 12px;
}
/* Overrides the shared .read-empty-content rule's reading-column padding, which is sized for a lone Scene's
   full-height empty state rather than a quiet one-line placeholder between Full script neighbours. */
.read-full-script-scene .read-empty-content[b-edt3fu7hy0] { padding: 0 4px 8px; }

.read-empty[b-edt3fu7hy0] {
    position: relative;
    z-index: 4;
    place-self: center;
    max-width: 480px;
    border: 1px solid var(--stroke);
    border-radius: var(--radius-overlay);
    background: var(--surface-app);
    padding: 32px;
    box-shadow: var(--shadow-panel);
    text-align: center;
    color: var(--text-secondary);
}
.read-empty p:not(.read-eyebrow)[b-edt3fu7hy0] { line-height: 1.6; }
/* Chrome from the .btn kit (see app.css's a.btn rule for the anchor colour); only its standoff is local. */
.read-primary-link[b-edt3fu7hy0] { margin-top: 12px; }
.read-title[b-edt3fu7hy0] { grid-column: 2; justify-self: center; }
.visually-hidden[b-edt3fu7hy0] {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* ---------------------------------------------------------------------------
   Responsive tiers (2026-07-30 consolidation).

   This file previously carried FOUR overlapping blocks — max-width:800px,
   max-width:640px (now 820px), "(max-width:1000px) and (max-height:520px)", and
   (pointer: coarse) — where the last three re-stated the same ~15 declarations
   with drifted values (52px vs 58px triggers, 360px vs 420px popovers, two
   different settings-panel anchorings). The tiers now have ONE job each:

     - width tiers change LAYOUT only,
     - the compact tier consumes the shared `data-phone` attribute
       wwwroot/js/responsiveTier.js stamps on <html>, instead of restating a
       query, so the shell and this page can no longer drift apart on where
       "compact" begins. That threshold is 820px viewer-wide (owner ruling
       2026-07-31): the shell's old 640px tier was unified onto the reading
       pages' 820px so ONE value means "mobile" across the Viewer,
     - orientation tiers carry only the overlay anchoring that genuinely differs,
     - the short-landscape tier is a PHONE HELD SIDEWAYS, so it says so:
       (pointer: coarse) qualifies it rather than being inferred from its
       dimensions. A laptop at display scaling lands inside 1000x520 in CSS
       pixels (1366x768 at 150% is 911x512) and was being handed the phone
       composition; a fine pointer now excludes it at any zoom level,
     - (pointer: coarse) otherwise owns TOUCH SIZING only, via var(--tap), at any
       width — consumed as the shared `data-touch` attribute for the same reason
       as compact above.
   --------------------------------------------------------------------------- */

@media (max-width: 800px) {
    .read-chrome[b-edt3fu7hy0] { grid-template-columns: auto minmax(0, 1fr) auto; gap: var(--sp-2); padding-inline: var(--sp-2); }
    .read-scene-navigation[b-edt3fu7hy0] { grid-template-columns: 28px minmax(120px, 1fr) 28px; }
    .read-surface[b-edt3fu7hy0] { padding: var(--sp-2) var(--sp-2) var(--sp-3); }
    .read-card[b-edt3fu7hy0] {
        min-height: calc(100vh - 56px);
        padding: var(--sp-4) var(--sp-4) var(--sp-7);
    }
}

/* Compact shell — the shared `data-phone` attribute (wwwroot/js/responsiveTier.js), not a restated @media
   query; :root[data-phone] also outranks the base rules on specificity, so this block no longer depends on
   source order. */
/* The chrome's own height is content-driven here because the leading cluster takes a SECOND line at this width
   (there is no third column to put it in), and that line exists only when the cluster does. */
:root[data-phone] .read-page[b-edt3fu7hy0] { grid-template-rows: auto minmax(0, 1fr); }
:root[data-phone] .read-chrome[b-edt3fu7hy0] {
    grid-template-columns: minmax(0, 1fr) auto;
    gap: var(--sp-1);
    padding: 2px var(--sp-1);
    min-height: 48px;
}
:root[data-phone] .read-scene-navigation[b-edt3fu7hy0] {
    grid-column: 1;
    /* Content-sized title here too (see the base rule): a stretched cell left the next-scene chevron pinned to
       the far edge of the row while the title and its caret sat well short of it. */
    grid-template-columns: var(--tap) minmax(0, auto) var(--tap);
    justify-content: center;
    gap: 0;
    min-width: 0;
}
:root[data-phone] .read-chrome-end[b-edt3fu7hy0] { grid-column: 2; gap: 0; font-size: 14px; }
/* Mode and route stay REACHABLE at phone width rather than being hidden (deliberate divergence from the Library
   navigator, which hides its own timeline picker here): that picker is a refinement over a list a reader can
   still browse without it, while this cluster carries the only door into the Review section. Hiding it would
   cost a whole section on phones, not a shortcut. It moves under the scene heading and wraps there when the
   mode segment and timeline dropdown do not fit side by side. */
:root[data-phone] .read-chrome-start[b-edt3fu7hy0] {
    grid-row: 2;
    grid-column: 1 / -1;
    flex-wrap: wrap;
    row-gap: var(--sp-1);
    overflow-x: visible;
    padding-bottom: 2px;
}
:root[data-phone] .read-scene-picker[b-edt3fu7hy0]  .web-entity-picker__trigger { font-size: 15px; padding-inline: 6px; }
:root[data-phone] .read-scene-picker[b-edt3fu7hy0]  .web-entity-picker__popover {
    width: min(420px, calc(100vw - var(--sp-5) - env(safe-area-inset-left) - env(safe-area-inset-right)));
    max-width: calc(100vw - var(--sp-5) - env(safe-area-inset-left) - env(safe-area-inset-right));
}
:root[data-phone] .read-settings-panel[b-edt3fu7hy0] {
    box-sizing: border-box;
    width: min(360px, calc(100vw - var(--sp-5) - env(safe-area-inset-left) - env(safe-area-inset-right)));
    border-radius: var(--radius-overlay);
    padding: var(--sp-3);
    overscroll-behavior: contain;
}
:root[data-phone] .read-settings-panel select[b-edt3fu7hy0] { font-size: 16px; }
:root[data-phone] .read-surface[b-edt3fu7hy0] {
    padding: 0 0 var(--sp-3);
    scrollbar-gutter: auto;
}
/* Read is the app's one pure reading surface (owner ruling: "navigation plus the text"). The base .read-card is
   a floating page — border, overlay radius, and a --shadow-panel lift — which reads correctly on desktop, where
   it sits centred with the app ground visible on both sides. At phone width there IS no "both sides": the card
   filled all but ~6px, so its border and shadow just traced the screen edge, adding a frame, a corner radius and
   a drop shadow around prose that had nothing to be lifted off. Dropped here; the opaque --surface-nav prose
   ground stays (it is what keeps the ambient effect from touching text contrast — see APPEARANCE.md) and simply
   goes edge to edge. */
:root[data-phone] .read-card[b-edt3fu7hy0] {
    min-height: 100%;
    border: 0;
    border-radius: 0;
    box-shadow: none;
    padding: var(--sp-3) var(--sp-4) var(--sp-6);
}
/* Artifact-authored media inside prose must never force a horizontal scroll on a phone. */
:root[data-phone] .read-card[b-edt3fu7hy0]  img,
:root[data-phone] .read-card[b-edt3fu7hy0]  picture,
:root[data-phone] .read-card[b-edt3fu7hy0]  video,
:root[data-phone] .read-card[b-edt3fu7hy0]  svg,
:root[data-phone] .read-card[b-edt3fu7hy0]  canvas {
    max-width: 100%;
    height: auto;
    object-fit: contain;
}
:root[data-phone] .read-manuscript[b-edt3fu7hy0],
:root[data-phone] .read-empty-content[b-edt3fu7hy0],
:root[data-phone] .read-full-script[b-edt3fu7hy0] { padding: var(--sp-5) 0 var(--sp-6); }
:root[data-phone] .read-full-script-scene[b-edt3fu7hy0] { padding-top: var(--sp-5); }
:root[data-phone] .read-empty[b-edt3fu7hy0] {
    width: calc(100% - var(--sp-5));
    padding: var(--sp-5) var(--sp-4);
}

/* Portrait phones: the two overlays detach from their trigger and hang from the header, because a
   trigger-anchored popover at this width would open off-screen. */
@media (max-width: 820px) and (orientation: portrait) {
    .read-scene-picker[b-edt3fu7hy0]  .web-entity-picker__popover {
        position: fixed;
        position-area: unset;
        inset: calc(48px + env(safe-area-inset-top) + var(--sp-2)) auto auto 50%;
        transform: translateX(-50%);
        max-height: min(62dvh, 460px);
        margin: 0;
    }
    .read-settings-panel[b-edt3fu7hy0] {
        position-anchor: auto;
        inset: calc(48px + env(safe-area-inset-top) + var(--sp-2)) max(var(--sp-3), env(safe-area-inset-right)) auto auto;
        max-height: min(65dvh, 460px);
        margin: 0;
    }
}

/* A phone held SIDEWAYS: width is suddenly plentiful and HEIGHT is the scarce axis, so the leading cluster goes
   back into the chrome's own first column instead of spending a second 44px line on a viewport only 400px tall.
   Portrait keeps the second line, where there genuinely is no room beside the scene stepper. */
@media (orientation: landscape) {
    :root[data-phone] .read-chrome[b-edt3fu7hy0] { grid-template-columns: auto minmax(0, 1fr) auto; }
    :root[data-phone] .read-chrome-start[b-edt3fu7hy0] {
        grid-row: 1;
        grid-column: 1;
        flex-wrap: nowrap;
        overflow-x: auto;
        padding-bottom: 0;
    }
    :root[data-phone] .read-scene-navigation[b-edt3fu7hy0] { grid-column: 2; }
    :root[data-phone] .read-chrome-end[b-edt3fu7hy0] { grid-column: 3; }
}

/* Short landscape: there is room beside the trigger but not below it, so the panel stays anchored and
   simply gets shorter. */
@media (max-width: 1000px) and (max-height: 520px) and (pointer: coarse) and (orientation: landscape) {
    .read-chrome[b-edt3fu7hy0] { padding-inline: var(--sp-2); }
    .read-scene-navigation[b-edt3fu7hy0] { gap: 2px; }
    .read-scene-picker[b-edt3fu7hy0]  .web-entity-picker__popover { max-height: min(65dvh, 420px); }
    .read-settings-panel[b-edt3fu7hy0] {
        position-anchor: --read-settings-trigger;
        inset: auto;
        top: anchor(bottom);
        right: anchor(right);
        max-height: min(65dvh, 360px);
        margin: 6px 0 0;
    }
}

/* Touch sizing, any width — the one place a comfortable target is declared for this page. */
:root[data-touch] .read-chrome .btn--icon[b-edt3fu7hy0],
:root[data-touch] .read-settings-trigger[b-edt3fu7hy0],
:root[data-touch] .read-scene-picker__full-script[b-edt3fu7hy0],
:root[data-touch] .read-action-link[b-edt3fu7hy0],
:root[data-touch] .read-primary-link[b-edt3fu7hy0] { min-width: var(--tap); min-height: var(--tap); }
:root[data-touch] .read-chrome .btn--icon[b-edt3fu7hy0] { width: var(--tap); height: var(--tap); }
:root[data-touch] .read-settings-trigger[b-edt3fu7hy0] { min-width: 52px; height: var(--tap); padding-inline: var(--sp-2); }
:root[data-touch] .read-scene-picker[b-edt3fu7hy0]  .web-entity-picker__trigger { height: var(--tap); min-height: var(--tap); }
:root[data-touch] .read-settings-panel select[b-edt3fu7hy0] { min-height: var(--tap); }
:root[data-touch] .read-primary-link[b-edt3fu7hy0] { display: inline-flex; align-items: center; padding: 10px 13px; }
:root[data-touch] .read-color-row .read-color-swatch[b-edt3fu7hy0],
:root[data-touch] .read-color-picker[b-edt3fu7hy0],
:root[data-touch] .read-color-row .read-color-reset[b-edt3fu7hy0] { min-width: 32px; min-height: 32px; }
:root[data-touch] .read-color-hex[b-edt3fu7hy0] { min-height: 32px; }
/* viewer/Pages/Review.razor.rz.scp.css */
/* Review's "nothing to review" root shares the overlay placement rules of .review-page below: inside Read's grid
   it must span it and clear the surround veil, and standalone at /review it is simply a centred card. */
.review-empty[b-5dtwj2swxa] {
    position: relative;
    z-index: 6;
    grid-row: 1 / -1;
    align-self: start;
    width: min(480px, calc(100% - 32px));
    margin: var(--sp-7) auto;
    padding: var(--sp-5);
}

/* The unchosen-route surface: the chrome above it is live (its switcher is the way out of this state), so this
   is a body message rather than the gate screen it replaced. */
.review-unchosen[b-5dtwj2swxa] {
    display: grid;
    align-content: start;
    gap: var(--sp-2);
    width: min(560px, 100%);
    margin-inline: auto;
    padding: var(--sp-5);
}
.review-unchosen h1[b-5dtwj2swxa],
.review-unchosen p[b-5dtwj2swxa] { margin-block: 0; }

/* Review mounts INSIDE Read's page grid (Read.razor's `_isReviewMode` branch), so it claims every row of that
   grid rather than landing in the 36px chrome row, and sits above Read's surround veil (.read-tint, z-index 3)
   instead of being dimmed by it. */
.review-page[b-5dtwj2swxa] {
    --review-panel-width: 360px;
    position: relative;
    z-index: 6;
    grid-row: 1 / -1;
    height: 100%;
    min-height: 0;
    /* A column stack rather than a fixed two-row grid: the surface carries an OPTIONAL band between the chrome
       and the body (the resume hint, which is gone the moment a reader answers anything), and a fixed template
       had nowhere to put a third child. Flex sizes whatever is there and collapses the gap when it is not. */
    display: flex;
    flex-direction: column;
    gap: var(--sp-3);
    padding: var(--sp-3);
}

.review-page > .review-workspace[b-5dtwj2swxa],
.review-page > .review-overall[b-5dtwj2swxa] {
    flex: 1 1 auto;
    min-height: 0;
}

/* The unchosen-route card sizes to its own words: it is a sentence, not a surface, so it must not stretch into
   a page-tall empty panel the way the two real bodies above legitimately do. */
.review-page > .review-unchosen[b-5dtwj2swxa] { flex: 0 0 auto; }

.review-page > .review-chrome[b-5dtwj2swxa],
.review-page > .review-resume[b-5dtwj2swxa] { flex: 0 0 auto; }

.review-chrome[b-5dtwj2swxa] {
    min-width: 0;
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    padding: 6px 10px;
    background: var(--surface-header);
}

/* The chrome's leading cluster — which MODE, then which ROUTE — laid out exactly as Read's own leading cluster,
   so the two surfaces read as one chrome with one thing swapped. */
.review-chrome-start[b-5dtwj2swxa] {
    display: flex;
    min-width: 0;
    flex: 0 1 auto;
    align-items: center;
    gap: var(--sp-2);
}
/* Two halves of one control, so they meet; the shared .btn kit draws each half. */
.review-mode-segment[b-5dtwj2swxa] { display: flex; flex: 0 0 auto; align-items: center; }
.review-mode-segment .btn:first-child[b-5dtwj2swxa] { border-start-end-radius: 0; border-end-end-radius: 0; }
.review-mode-segment .btn:last-child[b-5dtwj2swxa] { margin-inline-start: -1px; border-start-start-radius: 0; border-end-start-radius: 0; }
.review-mode-segment .btn.is-active[b-5dtwj2swxa] { z-index: 1; }

/* ‹ scene title › — the reader's position control, and the title itself is the jump picker. */
.review-scene-step[b-5dtwj2swxa] {
    min-width: 0;
    max-width: 42%;
    display: flex;
    flex: 0 1 auto;
    align-items: center;
    gap: 6px;
}
/* The shared picker's small-utility trigger, given the chrome title's own voice at this call site. */
.review-scene-step[b-5dtwj2swxa]  .web-entity-picker__trigger {
    min-width: 0;
    max-width: 260px;
    height: 28px;
    min-height: 0;
    border-color: transparent;
    background: transparent;
    color: var(--text-primary);
    font-size: 14px;
    font-weight: 500;
}
.review-scene-step[b-5dtwj2swxa]  .web-entity-picker__trigger:hover { border-color: var(--stroke); background: var(--surface-hover); }
.review-scene-step[b-5dtwj2swxa]  .web-entity-picker__popover { width: min(360px, 90vw); max-width: none; max-height: min(480px, 70vh); }
.review-field > span[b-5dtwj2swxa] { color: var(--text-secondary); font-size: 12px; line-height: 1.35; }

/* One segmented row for both chrome switches (Review | Overall) and the panel's feedback-layer switcher: the
   shared .btn kit supplies every control's chrome, this only groups them into one strip. */
.review-segmented[b-5dtwj2swxa] { display: flex; flex-wrap: wrap; gap: 4px; }

.review-field[b-5dtwj2swxa] { min-width: 180px; display: grid; gap: 3px; }
.review-field input[b-5dtwj2swxa],
.review-field select[b-5dtwj2swxa],
.review-field textarea[b-5dtwj2swxa] {
    width: 100%;
    min-height: 32px;
    border: 1px solid var(--stroke-control);
    border-radius: var(--radius-control);
    outline: none;
    background: var(--surface-input);
    padding: 6px 10px;
    color: var(--text-primary);
    font-family: var(--font-interface);
    font-size: 13px;
}
.review-field textarea[b-5dtwj2swxa] { line-height: 1.45; resize: vertical; }
.review-field input:hover[b-5dtwj2swxa],
.review-field select:hover[b-5dtwj2swxa],
.review-field textarea:hover[b-5dtwj2swxa] { border-color: var(--accent); }
.review-field input:focus-visible[b-5dtwj2swxa],
.review-field select:focus-visible[b-5dtwj2swxa],
.review-field textarea:focus-visible[b-5dtwj2swxa] { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }
/* Position sits beside the stepper it describes. The trailing edge is claimed by the [Review | Overall] switch
   rather than by this caption, so the chrome keeps the same shape while no route is chosen and the stepper and
   its caption are not on screen at all. */
.review-progress[b-5dtwj2swxa] { color: var(--text-secondary); font-size: 12px; font-variant-numeric: tabular-nums; white-space: nowrap; }
.review-chrome > .review-segmented[b-5dtwj2swxa] { margin-inline-start: auto; }

.review-workspace[b-5dtwj2swxa] {
    min-height: 0;
    display: grid;
    grid-template-columns: minmax(320px, 1fr) 10px minmax(280px, var(--review-panel-width));
}
/* Panel off: the manuscript has the surface to itself (split IS the panel — there is no third arrangement). */
.review-workspace--solo[b-5dtwj2swxa] { grid-template-columns: minmax(0, 1fr); }
.review-manuscript[b-5dtwj2swxa],
.review-response[b-5dtwj2swxa] {
    min-width: 0;
    min-height: 0;
    overflow: auto;
    border-radius: var(--radius-overlay);
}
.review-manuscript[b-5dtwj2swxa] { position: relative; padding: var(--sp-5); }
/* The scene column is the annotation surface too, so it is the coordinate space the affordance below is placed
   in (thrulineReviewAnnotate measures against exactly this element). */
/* --reader-mark-handle is the SAME fixed honey a mark's own wash is painted in (see
   FormattedDocumentView.razor.css, and desktop's ReaderMarkWash): a handle that dragged a honey span in the
   theme accent would read as a different object than the thing it moves. */
.review-scene[b-5dtwj2swxa] { position: relative; width: min(100%, 760px); margin-inline: auto; padding: 10px 0 var(--sp-7); --reader-mark-handle: #CFA857; }
.review-scene > h2[b-5dtwj2swxa] { margin: 0 0 var(--sp-5); }
/* The SAME prose Read renders, so it reads the same: app.css's shared manuscript tokens and the chosen reading
   font, applied here exactly as Pages/Read.razor.css applies them to its own column. Without this the reviewer
   read the manuscript in the interface sans while Read showed it in the reading serif — one text, two faces. */
.review-scene[b-5dtwj2swxa]  .formatted-document {
    font-family: var(--font-reading);
    font-size: var(--prose-font-size);
    line-height: var(--prose-line-height);
}
.review-scene[b-5dtwj2swxa]  .prose-line { margin: 0 0 var(--prose-paragraph-spacing); }
.review-scene[b-5dtwj2swxa]  .prose-line:last-child { margin-bottom: 0; }

/* ── The end-of-scene prompt ───────────────────────────────────────────────── */
/* A line at the scene boundary, not a banner: it sits in the prose column's own measure, wraps like a sentence,
   and carries no card of its own, because a reader who has nothing to say should be able to read straight past
   it. Above the manuscript's own bottom padding, so the last paragraph is not crowded by it. */
.review-nudge[b-5dtwj2swxa] {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-2);
    align-items: center;
    margin-block-start: var(--sp-5);
    padding-block-start: var(--sp-3);
    border-block-start: 1px solid var(--stroke);
    color: var(--text-muted);
}

.review-nudge__copy[b-5dtwj2swxa] {
    flex: 1 1 12rem;
    min-width: 0;
    font-size: 0.9rem;
}

/* On a phone the question takes its own line and the two answers sit under it, each at a real tap size, rather
   than a three-column row squeezing both buttons to their glyphs. The glyph goes with the row it decorated:
   left alone it wraps onto a line of its own above the question, reading as a heading it is not. */
:root[data-phone] .review-nudge__copy[b-5dtwj2swxa] { flex: 1 1 100%; }
:root[data-phone] .review-nudge[b-5dtwj2swxa]  .phosphor-icon { display: none; }

/* ── Marking a passage ─────────────────────────────────────────────────────── */
/* Anchored to the reader's own selection, in the scene column's coordinates. Nothing is on screen until a
   passage is actually selected, and what appears is one small control, not a formatting bar. */
.review-annotate[b-5dtwj2swxa] {
    position: absolute;
    z-index: 4;
    top: var(--annotate-y, 0);
    left: var(--annotate-x, 0);
    transform: translate(-50%, 8px);
    /* Never wider than the column it floats over, and never off its leading edge on a selection near the margin. */
    max-width: min(320px, 100%);
}
.review-annotate--composer[b-5dtwj2swxa] {
    display: grid;
    gap: var(--sp-2);
    /* Fluid, never a fixed 320: on a phone column the card would otherwise be wider than the prose it floats
       over and clip against the pane's own edge. */
    width: min(320px, 100%);
    padding: var(--sp-3);
    box-shadow: var(--shadow-overlay, 0 8px 24px rgb(0 0 0 / .22));
}
.review-annotate__excerpt[b-5dtwj2swxa] {
    margin: 0;
    overflow: hidden;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 3;
    color: var(--text-secondary);
    font-family: var(--font-reading);
    font-size: 13px;
    line-height: 1.45;
}
.review-annotate--composer textarea[b-5dtwj2swxa] {
    width: 100%;
    min-height: 64px;
    border: 1px solid var(--stroke-control);
    border-radius: var(--radius-control);
    outline: none;
    background: var(--surface-input);
    padding: 6px 10px;
    color: var(--text-primary);
    font-family: var(--font-interface);
    font-size: 13px;
    line-height: 1.45;
    resize: vertical;
}
.review-annotate--composer textarea:focus-visible[b-5dtwj2swxa] { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }
/* Remove at the quiet end, Done at the acting end — the desktop composer's own row, and the only two things a
   card can mean. There is no Cancel between them: closing the card keeps the note. */
.review-annotate__actions[b-5dtwj2swxa] { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-1); }
.review-annotate__delete[b-5dtwj2swxa] { color: var(--text-muted); }
.review-annotate__done[b-5dtwj2swxa] { margin-inline-start: auto; }

/* The selection pill carries the Annotate button AND the stamp row, so it is a flow row rather than the bare
   anchor a lone button needed. Wrapping keeps the stamps under the button on a narrow column instead of
   pushing the pill off the prose. */
.review-annotate--pill[b-5dtwj2swxa] {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-1);
    border: 1px solid var(--stroke);
    border-radius: 999px;
    background: var(--surface-raised);
    padding: 3px;
    box-shadow: var(--shadow-overlay, 0 8px 24px rgb(0 0 0 / .22));
}

/* An existing mark's two-entry menu: its words, or where it sits. A stack rather than a row, because the two
   entries are alternatives rather than a toolbar, and both wear their glyph the way every menu row here does. */
.review-annotate--menu[b-5dtwj2swxa] {
    display: grid;
    gap: 2px;
    width: max-content;
    min-width: 150px;
    padding: var(--sp-1);
}
.review-annotate--menu .btn[b-5dtwj2swxa] { justify-content: flex-start; width: 100%; }
/* The picker state: which of the overlapping marks did you mean. One line each, the reader's own words if they
   wrote any, so the rows are told apart by what they SAY rather than by their position in a list. */
.review-annotate__menu-title[b-5dtwj2swxa] { margin: 0 var(--sp-1) 2px; }
.review-annotate--menu .review-annotate__pick[b-5dtwj2swxa] { max-width: 260px; }
.review-annotate__pick-excerpt[b-5dtwj2swxa] { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ── Adjusting a mark's span ───────────────────────────────────────────────── */
/* The handles overlay the prose in the scene column's own coordinates, and the layer itself must never take a
   pointer: an overlay that hit-tests across the whole column swallows every gesture meant for the manuscript
   underneath — the same trap the desktop surface hit with a Canvas that had a Background set. */
.review-mark-handles[b-5dtwj2swxa] { position: absolute; inset: 0; z-index: 3; pointer-events: none; }
.review-mark-handle[b-5dtwj2swxa] {
    position: absolute;
    top: var(--handle-y, 0);
    left: var(--handle-x, 0);
    height: var(--handle-height, 1em);
    /* A finger-sized target around a hairline bar: the bar says exactly where the edge is, the box is what a
       thumb can actually catch on a phone. */
    width: 22px;
    transform: translateX(-50%);
    pointer-events: auto;
    touch-action: none;
    cursor: ew-resize;
}
.review-mark-handle[b-5dtwj2swxa]::before {
    content: "";
    position: absolute;
    inset-block: -1px;
    left: 50%;
    width: 2px;
    transform: translateX(-50%);
    background: var(--reader-mark-handle, #CFA857);
    border-radius: 1px;
}
/* The knob sits OUTSIDE the span on each side — above the opening edge, below the closing one — so neither one
   covers the first or last word of the passage the reader is trying to judge. */
.review-mark-handle[b-5dtwj2swxa]::after {
    content: "";
    position: absolute;
    left: 50%;
    width: 11px;
    height: 11px;
    border-radius: 50%;
    background: var(--reader-mark-handle, #CFA857);
    box-shadow: 0 1px 3px rgb(0 0 0 / .35);
}
.review-mark-handle--start[b-5dtwj2swxa]::after { top: -7px; transform: translateX(-50%); }
.review-mark-handle--end[b-5dtwj2swxa]::after { bottom: -7px; transform: translateX(-50%); }
/* While a handle is moving, the prose must not start selecting itself under the drag. */
.review-scene.is-resizing-mark[b-5dtwj2swxa] { user-select: none; }

/* ── Stamping a reaction ───────────────────────────────────────────────────── */
/* One tap each, no label: three round glyphs the reader can hit without reading, with the words carried by
   title/aria for everyone who needs them. */
.review-stamps[b-5dtwj2swxa] { display: flex; flex-wrap: wrap; align-items: center; gap: 2px; }
.review-stamps--card[b-5dtwj2swxa] { justify-content: flex-start; }
.review-stamps__tap[b-5dtwj2swxa] { color: var(--text-secondary); }
.review-stamps__tap:hover[b-5dtwj2swxa] { color: var(--text-primary); }

/* A stamp already on a note, wherever a note is listed. Glyph first, then the words, because the glyph is what
   the reader will next look for in the prose. */
.review-stamp-mark[b-5dtwj2swxa] {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    color: var(--accent-text, var(--text-secondary));
    font-size: 11px;
}
.review-note__marks[b-5dtwj2swxa] { display: flex; flex-wrap: wrap; align-items: center; gap: 4px; }

/* The panel's list of this scene's notes: a way to reach one without hitting a few words of prose exactly, and
   the one obvious place a note can be removed. */
.review-notes[b-5dtwj2swxa] { display: grid; gap: var(--sp-1); }
.review-notes > h3[b-5dtwj2swxa] { margin: 0; }
.review-note[b-5dtwj2swxa] { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: var(--sp-1); }
.review-note__body[b-5dtwj2swxa] {
    display: grid;
    gap: 2px;
    min-width: 0;
    border: 1px solid var(--stroke);
    border-radius: var(--radius-control);
    background: var(--surface-nav);
    padding: 6px var(--sp-2);
    color: var(--text-primary);
    font: inherit;
    text-align: left;
    cursor: pointer;
}
.review-note__body:hover[b-5dtwj2swxa] { border-color: var(--stroke-strong); background: var(--surface-hover); }
.review-note__excerpt[b-5dtwj2swxa] {
    overflow: hidden;
    color: var(--text-secondary);
    font-family: var(--font-reading);
    font-size: 13px;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The author's own questions, wherever they are asked: on the checkout for a whole-story one, in the panel for
   one scoped to the target being read. One stack of labelled boxes either way. */
.review-questions[b-5dtwj2swxa] { display: grid; gap: var(--sp-3); }
.review-overall > .review-questions[b-5dtwj2swxa] { padding: var(--sp-4); }
.review-overall > .review-questions > h2[b-5dtwj2swxa] { margin: 0; }

/* The boundary is a real handle, not an invisible hit strip: a full-height hairline plus a centred grip that
   states where to grab. The 1:1 mutual resize, the min-width clamps on BOTH sides, and the gentle 50/50 detent
   all live in thrulineReviewSplitter (wwwroot/js/reviewDownload.js) — this file only draws it. */
.review-splitter[b-5dtwj2swxa] {
    position: relative;
    cursor: col-resize;
    touch-action: none;
}
.review-splitter[b-5dtwj2swxa]::after {
    position: absolute;
    inset: 0 4px;
    border-radius: 999px;
    background: var(--stroke);
    content: "";
    transition: inset 120ms ease, background-color 120ms ease;
}
.review-splitter__grip[b-5dtwj2swxa] {
    position: absolute;
    z-index: 1;
    top: 50%;
    left: 50%;
    width: 8px;
    height: 40px;
    transform: translate(-50%, -50%);
    border: 1px solid var(--stroke-strong);
    border-radius: 999px;
    background: var(--surface-raised);
    transition: border-color 120ms ease, background-color 120ms ease;
}
.review-splitter:hover[b-5dtwj2swxa]::after,
.review-splitter:active[b-5dtwj2swxa]::after,
.review-splitter:focus-visible[b-5dtwj2swxa]::after { inset-inline: 3px; background: var(--accent); }
.review-splitter:hover .review-splitter__grip[b-5dtwj2swxa],
.review-splitter:active .review-splitter__grip[b-5dtwj2swxa],
.review-splitter:focus-visible .review-splitter__grip[b-5dtwj2swxa] { border-color: var(--accent); background: var(--accent-soft); }
.review-splitter:focus-visible[b-5dtwj2swxa] { outline: none; }

.review-response[b-5dtwj2swxa] { display: flex; flex-direction: column; gap: var(--sp-3); padding: var(--sp-3); }
/* The panel says what it IS, once, in the same quiet meta voice the desktop panel header uses. */
.review-response__kind[b-5dtwj2swxa] { display: flex; align-items: center; gap: 6px; margin: 0; color: var(--text-secondary); font-size: 12px; }
.review-response__heading h2[b-5dtwj2swxa],
.review-response__heading p[b-5dtwj2swxa] { margin-block: 0 4px; }
/* The upfront resume row: a quiet band above the reading surface, gone the moment a reader answers anything. */
.review-resume[b-5dtwj2swxa] {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-2);
    padding: var(--sp-2) var(--sp-3);
}
.review-resume__copy[b-5dtwj2swxa] { flex: 1 1 220px; margin: 0; color: var(--text-secondary); font-size: 13px; }

.review-metrics[b-5dtwj2swxa] { display: grid; gap: 14px; }
.review-metric[b-5dtwj2swxa] {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto auto auto;
    align-items: baseline;
    gap: 3px 8px;
    color: var(--text-primary);
    font-size: 14px;
}
.review-metric output[b-5dtwj2swxa] { color: var(--text-secondary); font-size: 12px; font-variant-numeric: tabular-nums; }
.review-metric input[b-5dtwj2swxa] { grid-column: 1 / -1; width: 100%; accent-color: var(--accent); }
/* The clear control sits in the quiet tier: no colour of its own until it is reached for, so it never competes
   with the score it removes. */
.review-metric__clear[b-5dtwj2swxa] {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 1px solid transparent;
    border-radius: 5px;
    background: none;
    color: var(--text-muted);
    padding: 2px;
    cursor: pointer;
}
.review-metric__clear:hover[b-5dtwj2swxa],
.review-metric__clear:focus-visible[b-5dtwj2swxa] {
    border-color: var(--stroke);
    background: var(--surface-hover);
    color: var(--text-primary);
}
:root[data-touch] .review-metric__clear[b-5dtwj2swxa] { min-inline-size: var(--tap); min-block-size: var(--tap); }
/* An unanswered metric reads as genuinely blank: the track is dimmed and captioned, and the score shows a
   dash, so a reader can never mistake an untouched slider for a deliberate low score. */
.review-metric--unanswered input[b-5dtwj2swxa] { opacity: .38; }
.review-metric--unanswered:hover input[b-5dtwj2swxa],
.review-metric--unanswered input:focus-visible[b-5dtwj2swxa] { opacity: .7; }
.review-metric__required[b-5dtwj2swxa],
.review-metric__state[b-5dtwj2swxa] { color: var(--text-muted); font-size: 11px; }
.review-metric__state[b-5dtwj2swxa] { grid-column: 1 / -1; }
.review-comment[b-5dtwj2swxa] { margin-top: var(--sp-1); }
.review-comment > span[b-5dtwj2swxa] { font-size: 12px; font-weight: 600; }

/* ── Overall: the scorecard and the checkout ───────────────────────────────── */
.review-overall[b-5dtwj2swxa] {
    min-height: 0;
    overflow: auto;
    display: grid;
    align-content: start;
    /* One bigger step BETWEEN sections than inside them (rows sit at --sp-1): the checkout is five stacked
       cards and a flat 12px everywhere made them read as one undifferentiated column. */
    gap: var(--sp-5);
    padding: var(--sp-1) var(--sp-3) var(--sp-6);
}
.review-overall > *[b-5dtwj2swxa] { width: min(900px, 100%); margin-inline: auto; }
.review-overall h2[b-5dtwj2swxa] { margin: 0 0 var(--sp-1); }
/* One titled card: the surface's whole name ("Before you send") lives here ONCE, so nothing further down ever
   has to repeat it as a row heading of its own. A simple stack, not a flex row: the status line, the name box,
   its own blank-state notes, and the action row each take their natural full width in reading order. */
.review-checkout[b-5dtwj2swxa] {
    display: grid;
    gap: var(--sp-2);
    padding: var(--sp-4);
}
.review-checkout > h1[b-5dtwj2swxa] { margin: 0 0 var(--sp-1); }
.review-checkout__status-line[b-5dtwj2swxa] {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    margin-block: 0;
    border: 1px solid color-mix(in srgb, var(--warning) 45%, var(--stroke));
    border-radius: var(--radius-overlay);
    background: color-mix(in srgb, var(--warning) 14%, var(--surface-raised));
    padding: 10px var(--sp-3);
    color: var(--text-primary);
    font-size: 13px;
}
/* Nothing required is missing — the same line, said calmly. */
.review-checkout__status-line--quiet[b-5dtwj2swxa] {
    border-color: var(--stroke);
    background: var(--surface-raised);
    color: var(--text-secondary);
}
.review-checkout .review-field[b-5dtwj2swxa] { max-width: 320px; }
/* The plain facts under the name box: no row, no icon, gone the moment they stop being true. */
.review-checkout__note[b-5dtwj2swxa] { margin: 0; color: var(--text-secondary); font-size: 12px; }
/* Flex, not a fixed column set: the two buttons keep their own natural widths and wrap when the row runs out of
   room, instead of a grid column squeezing a label until its text clips. */
.review-checkout__actions[b-5dtwj2swxa] {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-3);
    margin-block-start: var(--sp-1);
}
/* The review's terminal action, and the only one since the chrome's "Finish review" button was deleted: it
   carries that weight by size, not by a second accent colour. */
.review-checkout__export[b-5dtwj2swxa] { min-height: 40px; padding-inline: 18px; font-size: 14px; font-weight: 600; }
/* Discarding a session is the one destructive control here, so it sits AFTER the button it belongs beside rather
   than in the same cluster as the export it must never be mistaken for. */
.review-checkout__discard[b-5dtwj2swxa] { flex: 0 0 auto; margin-inline-start: auto; color: var(--text-muted); }
.review-checkout__status[b-5dtwj2swxa] { margin: 0; }

.review-gaps[b-5dtwj2swxa],
.review-scorecard[b-5dtwj2swxa] { display: grid; gap: var(--sp-1); }
.review-gap-row[b-5dtwj2swxa],
.review-score-row[b-5dtwj2swxa] {
    display: grid;
    align-items: center;
    gap: var(--sp-3);
    border: 1px solid var(--stroke);
    border-radius: var(--radius-control);
    background: var(--surface-nav);
    padding: 9px var(--sp-3);
    color: var(--text-primary);
    font: inherit;
    text-align: left;
    cursor: pointer;
    transition: border-color 120ms ease, background-color 120ms ease;
}
.review-gap-row:hover[b-5dtwj2swxa],
.review-score-row:hover[b-5dtwj2swxa] { border-color: var(--stroke-strong); background: var(--surface-hover); }
.review-gap-row[b-5dtwj2swxa] { grid-template-columns: minmax(0, 1fr) auto; }
.review-gap-row--required[b-5dtwj2swxa] { border-color: color-mix(in srgb, var(--warning) 40%, var(--stroke)); }
/* Optional-only rows are present but quieter — nothing here is blocking the reader. */
.review-gap-row--optional[b-5dtwj2swxa] { opacity: .72; }
/* A collapsed untouched target is a title and nothing else: it has EVERYTHING blank, which is what the count
   above it already said, so the row exists only as a way in. */
.review-gap-row--untouched[b-5dtwj2swxa] { border-color: transparent; background: transparent; padding-block: 6px; }
.review-gap-row--untouched:hover[b-5dtwj2swxa] { border-color: var(--stroke); background: var(--surface-hover); }
.review-gap-row__copy[b-5dtwj2swxa],
.review-score-row__copy[b-5dtwj2swxa] { display: grid; gap: 2px; min-width: 0; }
.review-gap-row__copy strong[b-5dtwj2swxa],
.review-score-row__copy strong[b-5dtwj2swxa] { overflow: hidden; font-weight: 500; text-overflow: ellipsis; white-space: nowrap; }
/* A row's missing-answers text: required first, then the quieter optional part, then a question count — each
   its own element so the optional part can sit visually quieter without splitting one text node in two. The
   middle dot between parts is drawn here rather than typed into the copy, so a single part never carries a
   trailing or leading separator. */
.review-gap-row__detail[b-5dtwj2swxa] {
    display: flex;
    flex-wrap: wrap;
    gap: 0;
    color: var(--text-secondary);
    font-size: 12px;
    line-height: 1.4;
}
.review-gap-row__part:not(:first-child)[b-5dtwj2swxa]::before { margin-inline: 5px; color: var(--text-muted); content: "·"; }
/* Optional blanks are legal — nothing here is a warning, so the part reads quieter than the row around it. */
.review-gap-row__part--quiet[b-5dtwj2swxa] { opacity: .72; }

/* ── The structural half of checkout ───────────────────────────────────────── */
.review-gaps--structure[b-5dtwj2swxa] { gap: var(--sp-3); }
.review-gap-group[b-5dtwj2swxa] { display: grid; gap: var(--sp-1); }
.review-gap-group > h3[b-5dtwj2swxa] { margin: 0; }
/* The collapse. Its summary is the count; opening it is for a reader who wants to jump straight at one target. */
.review-gap-collapse > summary[b-5dtwj2swxa] {
    border-radius: var(--radius-control);
    padding: 5px var(--sp-2);
    color: var(--text-muted);
    cursor: pointer;
    list-style-position: inside;
}
.review-gap-collapse > summary:hover[b-5dtwj2swxa] { background: var(--surface-hover); color: var(--text-secondary); }
.review-gap-collapse[open] > summary[b-5dtwj2swxa] { margin-bottom: 2px; }
/* A <details> body is normal flow, not the grid the sibling rows sit in, so these buttons would shrink-wrap to
   their own text and float mid-row. Stated once here rather than on the row class, which is stretched by its
   grid parent everywhere else. */
.review-gap-collapse > .review-gap-row[b-5dtwj2swxa] {
    width: calc(100% - var(--sp-3));
    margin-inline-start: var(--sp-3);
}

/* ── The scorecard row: identity, then the per-metric chip strip ───────────── */
.review-score-row[b-5dtwj2swxa] { grid-template-columns: minmax(0, 12rem) minmax(0, 1fr) 22px; }
.review-score-row__comment[b-5dtwj2swxa] { color: var(--text-muted); justify-self: center; }
.review-score-row--empty .review-score-row__copy strong[b-5dtwj2swxa] { color: var(--text-muted); }

/* Scores as a WRAPPING strip of chips, never a run-on sentence repeating "Not answered" once per metric. */
.review-chips[b-5dtwj2swxa] { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-1); min-width: 0; }
.review-chip[b-5dtwj2swxa] {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    border: 1px solid var(--stroke);
    border-radius: 999px;
    background: var(--surface-raised);
    padding: 2px var(--sp-2);
    color: var(--text-secondary);
    font-size: 11px;
    line-height: 1.6;
    white-space: nowrap;
}
.review-chip__name[b-5dtwj2swxa] { overflow: hidden; max-width: 9rem; text-overflow: ellipsis; }
.review-chip__meter[b-5dtwj2swxa] { width: 26px; height: 3px; overflow: hidden; border-radius: 999px; background: var(--stroke-strong); }
.review-chip__meter > span[b-5dtwj2swxa] { display: block; height: 100%; border-radius: 999px; background: var(--accent); }
.review-chip__value[b-5dtwj2swxa] { color: var(--text-primary); font-size: 12px; font-weight: 600; font-variant-numeric: tabular-nums; }
/* An UNANSWERED chip is the same pill, dimmed, carrying NO placeholder character at all: the dimmed state IS the
   message, the same way the panel's own unanswered slider says it (copy rule 2026-08-14 — no dash placeholders). */
.review-chip--blank[b-5dtwj2swxa] { border-style: dashed; background: transparent; opacity: .45; }

/* Below the split's comfortable width the two panes stack and the WORKSPACE takes over scrolling, so the
   manuscript grows to its content instead of spilling over the feedback panel beneath it. */
@media (max-width: 980px) {
    .review-chrome[b-5dtwj2swxa] { flex-wrap: wrap; }
    .review-workspace[b-5dtwj2swxa] {
        grid-template-columns: minmax(0, 1fr);
        grid-auto-rows: min-content;
        gap: var(--sp-3);
        overflow: auto;
    }
    .review-splitter[b-5dtwj2swxa] { display: none; }
    .review-manuscript[b-5dtwj2swxa],
    .review-response[b-5dtwj2swxa] { height: auto; overflow: visible; }
}

@media (max-width: 640px) {
    .review-page[b-5dtwj2swxa] { gap: var(--sp-2); padding: var(--sp-2); }
    /* Three explicit chrome lines, in reading order: mode and route, then the stepper WITH the position caption
       it describes, then the surface switches. The caption used to fall through to line three and land in front
       of the [Review | Overall] segment, where "1 of 23" read as a stray label on the wrong control. */
    .review-chrome-start[b-5dtwj2swxa] { order: 1; flex: 1 1 100%; }
    /* Content-sized, so the position caption beside it stays BESIDE it rather than being pushed to the far edge
       of a stretched stepper. */
    .review-scene-step[b-5dtwj2swxa] { order: 2; flex: 0 1 auto; max-width: none; }
    .review-progress[b-5dtwj2swxa] { order: 3; }
    .review-segmented[b-5dtwj2swxa] { order: 4; }
    /* The switch and the panel toggle share the last line: the auto margin that pushes the switch to the trailing
       edge on one wide row would otherwise strand it alone across a stacked one. */
    .review-chrome > .review-segmented[b-5dtwj2swxa] { margin-inline-start: 0; }
    .review-chrome > .btn[b-5dtwj2swxa] { order: 5; }
    .review-manuscript[b-5dtwj2swxa] { padding: var(--sp-4); }
    .review-response[b-5dtwj2swxa] { padding: var(--sp-3); }
    .review-overall[b-5dtwj2swxa] { padding: var(--sp-1) var(--sp-1) var(--sp-4); }
    .review-checkout .review-field[b-5dtwj2swxa] { max-width: none; }
    /* Stacked, every control starts at the same edge: the trailing auto margin belongs to the one-row layout. */
    .review-checkout__discard[b-5dtwj2swxa] { margin-inline-start: 0; }
    /* The chip strip takes its own line under the identity rather than fighting it for a 12rem column. */
    .review-score-row[b-5dtwj2swxa] { grid-template-columns: minmax(0, 1fr) 22px; }
    .review-score-row__copy[b-5dtwj2swxa] { grid-column: 1; }
    .review-score-row .review-chips[b-5dtwj2swxa] { grid-column: 1 / -1; }
    .review-score-row__comment[b-5dtwj2swxa] { grid-column: 2; grid-row: 1; }
    /* On a phone the stamp row gets its own line under the Annotate button rather than shrinking five 44px tap
       targets to fit beside it. The width is EXPLICIT rather than a max: an absolutely positioned box is
       shrink-to-fit, so a max-width alone leaves the intrinsic width set by the Annotate line above and the five
       taps wrap 4+1 against it. */
    /* Centred in the column rather than translated onto the selection's own midpoint: a 300px pill anchored to a
       selection near either margin hangs off the edge of a 400px screen, and on a column this narrow "under the
       selection" is already unambiguous from the vertical anchor alone. A structural mode switch, which is the
       one thing a width query is for. */
    .review-annotate--pill[b-5dtwj2swxa],
    .review-annotate--menu[b-5dtwj2swxa],
    .review-annotate--composer[b-5dtwj2swxa] {
        left: 0;
        right: 0;
        width: min(300px, calc(100% - 8px));
        margin-inline: auto;
        transform: translateY(8px);
    }
    .review-annotate--pill[b-5dtwj2swxa] { border-radius: var(--radius-overlay); }
    /* A menu row is a tap target on a phone, not a compact list item. */
    .review-annotate--menu .btn[b-5dtwj2swxa] { min-height: 44px; }
    .review-annotate--pill > .review-stamps[b-5dtwj2swxa] { flex: 1 1 100%; justify-content: space-between; }
}
/* viewer/Pages/Settings.razor.rz.scp.css */
/* Appearance settings page. Extracted from app.css. Surfaces are intentionally
   translucent so the live ambient effect previews through (see APPEARANCE.md). */

.settings-page[b-pgyo1a6xca] { width: 100%; height: 100%; margin: 0; overflow-y: auto; padding: 52px max(24px, calc((100vw - 1080px) / 2)) 96px; background: color-mix(in srgb, var(--surface-app) 88%, transparent); }
.settings-header[b-pgyo1a6xca] { display: grid; grid-template-columns: 34px minmax(0, 1fr); gap: 18px; align-items: start; margin-bottom: 34px; }
.settings-header h1[b-pgyo1a6xca] { margin: 0 0 6px; }
.settings-intro[b-pgyo1a6xca] { max-width: 620px; margin: 0; opacity: .7; }
.settings-back[b-pgyo1a6xca] { display: grid; place-items: center; width: 34px; height: 34px; border: 1px solid transparent; border-radius: var(--radius-control); background: color-mix(in srgb, var(--surface-raised) 72%, transparent); color: var(--text-secondary); cursor: pointer; }
.settings-back:hover[b-pgyo1a6xca] { border-color: var(--stroke-strong); background: var(--surface-hover); color: var(--text-primary); }
.settings-layout[b-pgyo1a6xca] { display: grid; grid-template-columns: 190px minmax(0, 760px); gap: 24px; align-items: start; margin-left: 52px; }
.settings-navigation[b-pgyo1a6xca] { position: sticky; top: 20px; display: grid; gap: 3px; border: 1px solid var(--stroke); border-radius: var(--radius-overlay); background: color-mix(in srgb, var(--surface-nav) 94%, transparent); padding: 8px; }
.settings-navigation a[b-pgyo1a6xca] { display: grid; grid-template-columns: 22px minmax(0, 1fr); align-items: center; gap: 9px; min-height: 38px; border-radius: var(--radius-control); padding: 7px 9px; color: var(--text-secondary); font-size: 13px; font-weight: 550; text-decoration: none; }
.settings-navigation a:hover[b-pgyo1a6xca], .settings-navigation a:focus-visible[b-pgyo1a6xca] { background: var(--surface-hover); color: var(--text-primary); }
.settings-navigation .phosphor-icon[b-pgyo1a6xca] { color: var(--accent); }
.settings-content[b-pgyo1a6xca] { min-width: 0; }
.settings-surface[b-pgyo1a6xca] { scroll-margin-top: 20px; max-width: 760px; margin: 0 0 16px; border: 1px solid var(--stroke); border-radius: var(--radius-overlay); background: color-mix(in srgb, var(--surface-nav) 94%, transparent); padding: 20px; }
.settings-copy[b-pgyo1a6xca] { margin-bottom: 16px; }
.settings-copy h2[b-pgyo1a6xca] { margin-top: 0; }
.settings-copy p[b-pgyo1a6xca] { max-width: 650px; margin: 0; line-height: 1.55; }
.settings-guide-button[b-pgyo1a6xca] { display: flex; align-items: center; gap: 13px; width: min(100%, 430px); border: 1px solid var(--stroke-control); border-radius: var(--radius-overlay); background: var(--surface-raised); padding: 13px 15px; color: var(--text-secondary); text-align: left; cursor: pointer; }
.settings-guide-button:hover[b-pgyo1a6xca] { border-color: var(--stroke-strong); background: var(--surface-hover); color: var(--text-primary); }
.settings-guide-button > .phosphor-icon[b-pgyo1a6xca] { color: var(--accent); font-size: 22px; }
.settings-guide-button > span[b-pgyo1a6xca] { display: grid; gap: 3px; }
.settings-guide-button strong[b-pgyo1a6xca] { color: var(--text-primary); font-size: 13px; }
.theme-grid[b-pgyo1a6xca] { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }
.theme-choice[b-pgyo1a6xca] { display: grid; grid-template-columns: auto 42px minmax(0, 1fr); align-items: center; gap: 9px; border: 1px solid var(--stroke-control); border-radius: var(--radius-control); padding: 9px; color: var(--text-secondary); cursor: pointer; }
.theme-choice:hover[b-pgyo1a6xca] { border-color: var(--stroke-strong); background: var(--surface-hover); color: var(--text-primary); }
.theme-choice:has(input:checked)[b-pgyo1a6xca] { border-color: var(--accent); background: var(--accent-soft); color: var(--text-primary); }
.theme-choice input[b-pgyo1a6xca] { margin: 0; }
.theme-choice > span:last-child[b-pgyo1a6xca] { font-size: 12px; font-weight: 500; }
.theme-swatch[b-pgyo1a6xca] { display: flex; overflow: hidden; width: 42px; height: 25px; border: 1px solid rgb(127 127 127 / 35%); border-radius: var(--radius-control); background: var(--swatch-one); }
.theme-swatch i[b-pgyo1a6xca] { flex: 1; background: var(--swatch-two); }
.theme-swatch i:nth-child(2)[b-pgyo1a6xca] { background: var(--swatch-three); }
.theme-swatch i:nth-child(3)[b-pgyo1a6xca] { background: var(--swatch-accent); }
.theme-choice[data-choice-theme="cozy-dark"][b-pgyo1a6xca] { --swatch-one: #151110; --swatch-two: #2e251d; --swatch-three: #c9bca6; --swatch-accent: #d2b078; }
.theme-choice[data-choice-theme="cozy-day"][b-pgyo1a6xca] { --swatch-one: #ebe6e0; --swatch-two: #d0c0af; --swatch-three: #432d19; --swatch-accent: #806039; }
.theme-choice[data-choice-theme="dark"][b-pgyo1a6xca] { --swatch-one: #151515; --swatch-two: #2c2c2c; --swatch-three: #c6c6c6; --swatch-accent: #7aa3cc; }
.theme-choice[data-choice-theme="day"][b-pgyo1a6xca] { --swatch-one: #fafaf8; --swatch-two: #e4e2da; --swatch-three: #2a2a26; --swatch-accent: #5b7fa3; }
.theme-choice[data-choice-theme="solarized-dark"][b-pgyo1a6xca] { --swatch-one: #002b36; --swatch-two: #0a3f4e; --swatch-three: #93a1a1; --swatch-accent: #4aadec; }
.theme-choice[data-choice-theme="solarized-light"][b-pgyo1a6xca] { --swatch-one: #fdf6e3; --swatch-two: #eee8d5; --swatch-three: #073642; --swatch-accent: #268bd2; }
/* No local background override any more: `surface-group-box` is hollow by default, so the ambient preview
   shows through here the same way it does on every other Settings cluster. */
.font-fields[b-pgyo1a6xca] { display: grid; grid-template-columns: 132px minmax(0, 1fr); gap: 10px 14px; align-items: center; max-width: 480px; }
.type-preview[b-pgyo1a6xca] { margin: 16px 0 0; border-left: 2px solid var(--accent); padding: 8px 14px; color: var(--text-secondary); font-size: 17px; line-height: 1.5; }
.settings-surface > select[b-pgyo1a6xca] { width: min(100%, 348px); }
.range-heading[b-pgyo1a6xca] { display: flex; justify-content: space-between; width: min(100%, 348px); margin-top: 16px; }
.settings-surface input[type="range"][b-pgyo1a6xca] { display: block; width: min(100%, 348px); margin: 8px 0 16px; }
.settings-field-note[b-pgyo1a6xca] { max-width: min(100%, 520px); margin: -8px 0 4px; line-height: 1.45; }
.settings-advanced[b-pgyo1a6xca] { padding: 0; overflow: hidden; }
.settings-advanced__summary[b-pgyo1a6xca] { display: grid; grid-template-columns: 36px minmax(0, 1fr) 24px; align-items: center; gap: 12px; padding: 18px 20px; cursor: pointer; list-style: none; }
.settings-advanced__summary[b-pgyo1a6xca]::-webkit-details-marker { display: none; }
.settings-advanced__summary:hover[b-pgyo1a6xca] { background: var(--surface-hover); }
.settings-advanced__summary > span:nth-child(2)[b-pgyo1a6xca] { display: grid; gap: 3px; }
.settings-advanced__summary strong[b-pgyo1a6xca] { color: var(--text-primary); font-size: 14px; font-weight: 600; }
.settings-advanced__icon[b-pgyo1a6xca] { display: grid; place-items: center; width: 36px; height: 36px; border-radius: var(--radius-control); background: var(--surface-raised); color: var(--text-secondary); font-size: 18px; }
.settings-advanced__caret[b-pgyo1a6xca] { justify-self: end; color: var(--text-secondary); transition: transform 120ms ease; }
.settings-advanced[open] .settings-advanced__summary[b-pgyo1a6xca] { border-bottom: 1px solid var(--stroke); }
.settings-advanced[open] .settings-advanced__caret[b-pgyo1a6xca] { transform: rotate(180deg); }
.settings-advanced__body[b-pgyo1a6xca] { padding: 20px; }
.settings-reset-button[b-pgyo1a6xca] { margin-top: 10px; }

/* Compact tier via the shared attribute (wwwroot/js/responsiveTier.js), not a restated @media query —
   :root[data-phone] also outranks the base rules on specificity, so this block no longer depends on source order. */
:root[data-phone] .settings-page[b-pgyo1a6xca] { padding: 38px 18px 80px; }
:root[data-phone] .settings-layout[b-pgyo1a6xca] { display: block; margin-left: 0; }
:root[data-phone] .settings-navigation[b-pgyo1a6xca] { position: static; grid-auto-flow: column; grid-auto-columns: max-content; overflow-x: auto; margin-bottom: 10px; border-width: 0 0 1px; border-radius: 0; background: transparent; padding: 0 0 10px; }
:root[data-phone] .settings-navigation a[b-pgyo1a6xca] { min-height: var(--tap); }
:root[data-phone] .settings-surface[b-pgyo1a6xca] { margin-left: 0; }
:root[data-phone] .theme-grid[b-pgyo1a6xca] { grid-template-columns: repeat(2, minmax(0, 1fr)); }

:root[data-phone] .settings-page[b-pgyo1a6xca] { padding: 22px 12px 64px; }
:root[data-phone] .settings-header[b-pgyo1a6xca] { grid-template-columns: var(--tap) minmax(0, 1fr); gap: 12px; margin-bottom: 22px; }
:root[data-phone] .settings-back[b-pgyo1a6xca] { width: var(--tap); height: var(--tap); }
:root[data-phone] .settings-surface[b-pgyo1a6xca] { border-width: 0 0 1px; border-radius: 0; background: transparent; padding: 18px 4px; }
:root[data-phone] .settings-advanced[b-pgyo1a6xca] { padding: 0; }
:root[data-phone] .settings-advanced__summary[b-pgyo1a6xca] { min-height: 64px; padding: 10px 4px; }
:root[data-phone] .settings-advanced__body[b-pgyo1a6xca] { padding: 18px 4px; }
:root[data-phone] .settings-guide-button[b-pgyo1a6xca] { min-height: var(--tap); }
:root[data-phone] .settings-guide-button strong[b-pgyo1a6xca],
:root[data-phone] .theme-choice > span:last-child[b-pgyo1a6xca] { font-size: 14px; }
:root[data-phone] .theme-choice[b-pgyo1a6xca] { min-height: var(--tap); }
:root[data-phone] .settings-surface > select[b-pgyo1a6xca],
:root[data-phone] .font-fields[b-pgyo1a6xca],
:root[data-phone] .range-heading[b-pgyo1a6xca],
:root[data-phone] .settings-surface input[type="range"][b-pgyo1a6xca] { width: 100%; }

@media (max-width: 520px) {
    .theme-grid[b-pgyo1a6xca] { grid-template-columns: 1fr; }
    .font-fields[b-pgyo1a6xca] { grid-template-columns: 1fr; gap: 5px; }
    .font-fields select[b-pgyo1a6xca] { margin-bottom: 8px; }
}
/* viewer/Pages/Walk.razor.rz.scp.css */
.walk-page[b-rbo3k07jax] {
    --walk-measure: 720px;
    --walk-font-size: 17px;
    --walk-line-height: 1.6;
    position: relative;
    isolation: isolate;
    display: grid;
    grid-template-rows: auto minmax(0, 1fr);
    width: 100%;
    height: 100%;
    overflow: hidden;
    outline: none;
    background: transparent;
}

.walk-chrome[b-rbo3k07jax] {
    position: relative;
    z-index: 5;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
    padding: 6px 10px;
    border-bottom: 1px solid var(--chrome-border);
    background: var(--chrome-background);
}

.walk-progress[b-rbo3k07jax] {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    min-width: 0;
}
.walk-progress__position[b-rbo3k07jax] { color: var(--text-primary); font-weight: 600; }
.walk-progress__choice[b-rbo3k07jax] {
    overflow: hidden;
    max-width: 220px;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.walk-surface[b-rbo3k07jax] {
    position: relative;
    z-index: 4;
    display: grid;
    min-height: 0;
    overflow-y: auto;
    scrollbar-gutter: stable;
    place-items: start center;
    padding: 16px 16px 24px;
}

.walk-card[b-rbo3k07jax] {
    width: min(var(--walk-measure), 100%);
    margin: 0 auto;
}

.surface-card.walk-card[b-rbo3k07jax] {
    box-shadow: var(--shadow-panel);
}

.walk-landing[b-rbo3k07jax],
.walk-ending[b-rbo3k07jax] {
    display: grid;
    gap: 14px;
    text-align: center;
    justify-items: center;
}
.walk-landing .t-body[b-rbo3k07jax] { max-width: 52ch; }

.walk-prepick[b-rbo3k07jax] {
    display: grid;
    gap: 6px;
    justify-items: center;
}

.walk-begin[b-rbo3k07jax] { margin-top: 6px; }

.walk-ending-choices[b-rbo3k07jax] {
    display: grid;
    gap: 6px;
    justify-items: start;
    width: 100%;
    max-width: 46ch;
    text-align: left;
}
.walk-ending-choices ul[b-rbo3k07jax] {
    display: grid;
    gap: 4px;
    margin: 0;
    padding: 0;
    list-style: none;
}
.walk-ending-choices li[b-rbo3k07jax] {
    border: 1px solid var(--stroke);
    border-radius: var(--radius-control);
    padding: 6px 9px;
    color: var(--text-secondary);
    font-size: 12px;
}
.walk-ending-choices li strong[b-rbo3k07jax] { color: var(--text-primary); }

.walk-ending-actions[b-rbo3k07jax] {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 8px;
    margin-top: 4px;
}

.walk-fork[b-rbo3k07jax] {
    display: grid;
    gap: 16px;
    width: min(880px, 100%);
    margin: 0 auto;
    justify-items: center;
    text-align: center;
}
.walk-fork-options[b-rbo3k07jax] {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: 12px;
    width: 100%;
}
.walk-fork-option[b-rbo3k07jax] {
    display: grid;
    place-items: center;
    min-height: 84px;
    padding: 16px;
    text-align: center;
}
.walk-fork-option__label[b-rbo3k07jax] {
    color: var(--text-primary);
    font-family: var(--font-reading);
    font-size: 15px;
    font-weight: 600;
}
.walk-back[b-rbo3k07jax] { justify-self: center; }

.walk-reading[b-rbo3k07jax] {
    display: grid;
    gap: 0;
    width: min(var(--walk-measure), 100%);
    min-height: calc(100vh - 100px);
    margin: 0 auto;
    align-content: start;
    border: 1px solid var(--stroke);
    border-radius: var(--radius-overlay);
    background: var(--surface-nav);
    padding: 26px 28px 22px;
    color: var(--text-primary);
    box-shadow: var(--shadow-panel);
}
.walk-content-card[b-rbo3k07jax]  .formatted-document {
    font-family: var(--font-reading);
    font-size: var(--walk-font-size);
    line-height: var(--walk-line-height);
}
.walk-content-card[b-rbo3k07jax] { padding-bottom: 24px; }
.walk-passage-scene + .walk-passage-scene[b-rbo3k07jax] {
    margin-top: 36px;
    border-top: 1px solid var(--stroke);
    padding-top: 32px;
}
.walk-passage-scene > h2[b-rbo3k07jax] { margin: 0 0 18px; }
.walk-reading > .walk-fork[b-rbo3k07jax],
.walk-ending--inline[b-rbo3k07jax] {
    width: 100%;
    margin-top: 8px;
    border-top: 1px solid var(--stroke);
    padding-top: 24px;
}
.walk-empty-content[b-rbo3k07jax] {
    color: var(--text-muted);
    font-family: var(--font-reading);
    font-size: 16px;
    font-style: italic;
}
.walk-empty[b-rbo3k07jax] {
    position: relative;
    z-index: 4;
    place-self: center;
    max-width: 480px;
    margin: auto;
    border: 1px solid var(--stroke);
    border-radius: var(--radius-overlay);
    background: var(--surface-app);
    padding: 32px;
    box-shadow: var(--shadow-panel);
    text-align: center;
    color: var(--text-secondary);
}
.walk-empty p:not(.t-eyebrow)[b-rbo3k07jax] { line-height: 1.6; }
.walk-primary-link[b-rbo3k07jax] {
    display: inline-block;
    margin-top: 12px;
    border: 1px solid var(--stroke);
    border-radius: var(--radius-control);
    background: var(--surface-raised);
    padding: 8px 13px;
    color: var(--text-primary);
    text-decoration: none;
}
.walk-primary-link:visited[b-rbo3k07jax] { color: var(--text-primary); }
.walk-primary-link:hover[b-rbo3k07jax] { border-color: var(--stroke-strong); background: var(--surface-hover); }

.visually-hidden[b-rbo3k07jax] {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

@media (max-width: 800px) {
    .walk-surface[b-rbo3k07jax] { padding: 10px 8px 16px; }
    .walk-reading[b-rbo3k07jax] { min-height: calc(100vh - 90px); padding: 16px 14px 14px; }
    .walk-fork-options[b-rbo3k07jax] { grid-template-columns: 1fr; }
    .walk-chrome[b-rbo3k07jax] { padding: 6px 8px; }
}
/* viewer/Pages/Workspace.razor.rz.scp.css */
.workspace-page[b-h56n4ew5az] { display: flex; flex-direction: column; height: 100%; min-height: 0; overflow: hidden; background: transparent; color: var(--text-primary); }
.workspace-reader[b-h56n4ew5az] { display: grid; flex: 1; grid-template-rows: minmax(0, 1fr); grid-template-columns: minmax(0, 1fr); min-width: 0; min-height: 0; overflow: hidden; }
/* Edge-to-edge like its stage — desktop parity: WorkspaceCanvasControl sits directly in its Grid cell
   with no inset border/mat. The explicit row keeps the absolute-positioned surface in the full-height track. */
.workspace-stage[b-h56n4ew5az] { position: relative; grid-row: 1; min-width: 0; min-height: 0; overflow: hidden; }
.workspace-empty[b-h56n4ew5az] { display: grid; justify-items: center; gap: 8px; width: min(420px, calc(100% - 48px)); margin: 25vh auto 0; padding: 24px; text-align: center; }
.workspace-empty__icon[b-h56n4ew5az] { color: var(--text-muted); font-size: 28px; }
