﻿/* Crowd Editor — manuscript editor styles.
   Library/parchment palette: warm off-white, ink-toned typography, classic serif body. */

.ce-shell {
    --ce-bg: #f7f1e3;
    --ce-paper: #ffffff;
    --ce-ink: #1a1612;
    --ce-ink-soft: #4a3f33;
    --ce-rule: #d3c4a3;
    --ce-accent: #7a5a2a;

    background: var(--ce-bg);
    color: var(--ce-ink);
    min-height: calc(100vh - 4rem);
    padding: 2rem 1rem;
}

.ce-paper {
    max-width: 760px;
    margin: 0 auto;
    background: var(--ce-paper);
    /* Subtle paper-grain via two layered radial gradients — no images, no extra requests. */
    background-image:
        radial-gradient(ellipse at 25% 15%, rgba(122, 90, 42, 0.045), transparent 60%),
        radial-gradient(ellipse at 75% 80%, rgba(122, 90, 42, 0.035), transparent 70%);
    border: 1px solid var(--ce-rule);
    border-radius: 4px;
    padding: 3rem 4rem;
    box-shadow: 0 2px 12px rgba(60, 40, 10, 0.10);
    font-family: "Iowan Old Style", "Palatino Linotype", "Palatino", "Georgia", serif;
    font-size: 1.05rem;
    line-height: 1.75;
    /* Improve text rendering for serif body. */
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
}

@media (max-width: 720px) {
    .ce-paper {
        padding: 1.5rem 1.25rem;
        font-size: 1rem;
        border-radius: 0;
        border-left: none;
        border-right: none;
    }
    .ce-shell { padding: 1rem 0; }
    .ce-toolbar { padding: 0 1rem; }
}

.ce-loading {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 1.25rem;
    min-height: 200px;
    text-align: center;
}

.ce-oops {
    text-align: center;
    padding: 3rem 2rem;
}
.ce-oops-title {
    border-bottom: none;
    margin: 0 0 0.75rem 0;
    font-size: 1.6rem;
}
.ce-oops-lede {
    font-size: 1.05rem;
    color: var(--ce-ink);
    margin: 0 auto 0.5rem auto;
    max-width: 36rem;
}
.ce-oops-body {
    color: var(--ce-ink-soft);
    margin: 0 auto 1.5rem auto;
    max-width: 36rem;
}
.ce-oops-actions {
    display: flex;
    justify-content: center;
    align-items: center;
    gap: 1.25rem;
}
.ce-loading-text {
    margin: 0;
    color: var(--ce-ink-soft);
    font-size: 1.1rem;
    font-style: italic;
}
.ce-loading-text strong {
    font-style: normal;
    color: var(--ce-ink);
    font-weight: 500;
}
.ce-spinner {
    width: 36px;
    height: 36px;
    border: 3px solid var(--ce-rule);
    border-top-color: var(--ce-accent);
    border-radius: 50%;
    animation: ce-spin 0.9s linear infinite;
}
@keyframes ce-spin {
    to { transform: rotate(360deg); }
}
@media (prefers-reduced-motion: reduce) {
    .ce-spinner { animation-duration: 2.4s; }
}

/* Visible focus rings for keyboard users. */
.ce-shell :focus-visible,
.ce-toolbar :focus-visible {
    outline: 2px solid var(--ce-accent);
    outline-offset: 2px;
    border-radius: 3px;
}

/* Heading hierarchy: title > volume > chapter > section > prose. Each step
   drops by ~0.25rem so the visual weight maps directly to the structural depth. */
.ce-paper h1 {
    font-size: 2rem;
    margin-top: 0;
    border-bottom: 1px solid var(--ce-rule);
    padding-bottom: 0.4rem;
}

.ce-paper h2 {
    font-size: 1.7rem;
    font-weight: 700;
    margin-top: 2.4rem;
    color: var(--ce-ink-soft);
}

.ce-paper h3 {
    font-size: 1.4rem;
    font-weight: 700;
    margin-top: 0;
    color: var(--ce-ink-soft);
}

.ce-paper h4 {
    font-size: 1.15rem;
    font-weight: 700;
    margin-top: 1.4rem;
    color: var(--ce-ink-soft);
}

.ce-paper section[data-block-type="chapter"] {
    border-top: 1px dashed var(--ce-rule);
    padding-top: 0.5rem;
    margin-top: 2.5rem;
}

.ce-paper section[data-block-type="chapter"]:first-child {
    border-top: none;
    margin-top: 0;
    padding-top: 0;
}

/* A chapter opens with sinkage, not a rule — on the read view only.
   The dashed line above is an *editing* convention: it draws the structure so a
   writer can see where the blocks are, which is worth more than the page looking
   like a book while they work. A reader is holding the finished object, and books
   open a chapter with a page and with white space above the title. The stylesheet
   already argues this for the structural badges a few rules down — "readers see
   the structure through typography alone, not behind-the-scenes labels" — and a
   dashed rule is a behind-the-scenes label. */
.ce-paper.ce-paginated section[data-block-type="chapter"] {
    border-top: none;
    padding-top: 0;
    margin-top: 4.5rem;
}

/* Sticky-note block labels in the left margin. Editor only — readers see
   the structure through typography alone, not behind-the-scenes labels. */
/* Structural badges. Volume/chapter/section render via a NodeView that injects
   a real <button class="ce-struct-badge"> in the margin — clickable, opens a
   popout for delete. Title (h1) and Page Break stay as ::before pseudos since
   they don't have a destructure menu. */
.ce-paper .ProseMirror section[data-block-type="volume"],
.ce-paper .ProseMirror section[data-block-type="chapter"],
.ce-paper .ProseMirror section[data-block-type="section"],
.ce-paper hr.page-break,
.ce-paper .ProseMirror > h1 {
    position: relative;
}
.ce-paper .ProseMirror .ce-struct-badge {
    position: absolute;
    left: -5.6rem;
    top: 0.1rem;
    padding: 2px 8px;
    border: 1px solid var(--ce-rule);
    border-radius: 3px;
    background: var(--ce-paper);
    box-shadow: 1px 1px 2px rgba(60, 40, 10, 0.12);
    font-size: 0.65rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--ce-ink-soft);
    font-family: "Inter", "Helvetica Neue", Arial, sans-serif;
    font-weight: 500;
    user-select: none;
    cursor: pointer;
    line-height: 1.25;
}
.ce-paper .ProseMirror .ce-struct-badge::after {
    /* The actual label text — themed via the same CSS variables we used on the
       old ::before pseudos, so renaming Volume→Testament still flows through. */
    content: attr(data-block-type);
}
.ce-paper .ProseMirror .ce-struct-badge[data-block-type="volume"]::after  { content: var(--label-volume, "Volume"); }
.ce-paper .ProseMirror .ce-struct-badge[data-block-type="chapter"]::after { content: var(--label-chapter, "Chapter"); }
.ce-paper .ProseMirror .ce-struct-badge[data-block-type="section"]::after { content: var(--label-section, "Section"); }
.ce-paper .ProseMirror .ce-struct-badge:hover {
    background: var(--ce-deep, #efe5cb);
    border-color: var(--ce-accent);
    color: var(--ce-accent-bold);
}

/* Title + page break are still decorative pseudos. */
.ce-paper hr.page-break::before,
.ce-paper .ProseMirror > h1::before {
    position: absolute;
    left: -5.6rem;
    top: 0.1rem;
    padding: 2px 8px;
    border: 1px solid var(--ce-rule);
    border-radius: 3px;
    background: var(--ce-paper);
    box-shadow: 1px 1px 2px rgba(60, 40, 10, 0.12);
    font-size: 0.65rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--ce-ink-soft);
    font-family: "Inter", "Helvetica Neue", Arial, sans-serif;
    font-weight: 500;
    user-select: none;
    pointer-events: none;
}
.ce-paper hr.page-break::before { content: var(--label-page-break, "Page Break"); top: -0.7rem; }
.ce-paper .ProseMirror > h1::before { content: var(--label-title, "Title"); }
@media (max-width: 900px) {
    .ce-paper .ProseMirror .ce-struct-badge,
    .ce-paper hr.page-break::before,
    .ce-paper .ProseMirror > h1::before { display: none; }
}

/* Export modal. */
.ce-modal-backdrop {
    position: fixed; inset: 0; z-index: 100;
    background: rgba(0, 0, 0, 0.35);
}
.ce-modal {
    position: fixed; z-index: 101;
    top: 50%; left: 50%; transform: translate(-50%, -50%);
    width: min(520px, 92vw);
    background: var(--ce-paper, #fbf6e6);
    border: 1px solid var(--ce-rule, #d3c4a3);
    border-radius: 6px;
    box-shadow: 0 14px 40px rgba(60, 40, 10, 0.30);
    padding: 1.5rem;
    font-family: "Inter", "Helvetica Neue", Arial, sans-serif;
}
.ce-modal h2 { margin: 0 0 0.5rem; font-size: 1.25rem; }
.ce-modal h3 { margin: 1.25rem 0 0.5rem; font-size: 1rem; }

/* Invite editor modal. */
.ce-invite-form {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
    margin-top: 0.75rem;
}
.ce-invite-form label {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    font-size: 0.9rem;
    color: var(--ce-ink-soft);
}
.ce-invite-form .ce-input {
    font-size: 0.95rem;
}
.ce-invite-form-actions {
    display: flex;
    justify-content: flex-end;
    margin-top: 0.25rem;
}
.ce-invite-error {
    color: #a02a2a;
    font-size: 0.9rem;
    margin: 0;
}
.ce-invite-list-title { /* visual rhythm only; uses ce-modal h3 styling */ }
.ce-invite-list {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    max-height: 240px;
    overflow-y: auto;
}
.ce-invite-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.5rem 0.75rem;
    border: 1px solid var(--ce-rule);
    border-radius: 4px;
    background: #fff;
}
.ce-invite-row-main {
    display: flex;
    align-items: baseline;
    gap: 0.5rem;
    flex-wrap: wrap;
    font-size: 0.95rem;
}

/* Right-margin caret button (and its menu) for the current text selection. */
.ce-selection-overlay {
    position: absolute;
    z-index: 50;
    pointer-events: none;
}
.ce-selection-overlay .ce-menu {
    pointer-events: auto;
}
.ce-selection-toggle {
    list-style: none;
    width: 22px;
    height: 22px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--ce-rule);
    border-radius: 50%;
    background: #fff;
    color: var(--ce-ink-soft);
    cursor: pointer;
    box-shadow: 0 2px 6px rgba(60, 40, 10, 0.10);
    transition: color 0.15s, border-color 0.15s, background 0.15s;
}
.ce-selection-toggle::-webkit-details-marker { display: none; }
.ce-selection-toggle:hover {
    color: var(--ce-accent);
    border-color: var(--ce-accent);
}
.ce-selection-menu[open] .ce-selection-toggle {
    color: var(--ce-accent);
    border-color: var(--ce-accent);
    background: rgba(122, 90, 42, 0.08);
}
.ce-selection-menu .ce-menu-list {
    /* Anchor to the right edge of the caret so the menu drops down-left into
       the gutter rather than off-screen to the right. */
    left: auto;
    right: 0;
    min-width: 12rem;
    margin-top: 0.25rem;
}

/* Review-request modal textarea needs to fill the modal width since the user
   may type several sentences. The default <textarea> width is `cols`-driven
   which leaves a narrow box. */
.ce-modal textarea.ce-input {
    width: 100%;
    box-sizing: border-box;
    resize: vertical;
}

/* Cascading voice picker (Cast page). Each filter is a small <label> wrapping
   a <select>; the row wraps so it's usable on narrow screens. */
.ce-voice-picker {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem 0.75rem;
    align-items: flex-end;
    margin: 0.25rem 0;
}
.ce-voice-picker label {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    font-size: 0.85rem;
    color: var(--ce-ink-soft);
}
.ce-voice-picker-label {
    text-transform: uppercase;
    letter-spacing: 0.04em;
    font-size: 0.7rem;
}
.ce-voice-picker select {
    min-width: 8rem;
}
/* Hear-sample button breaks to its own line so it never crowds the cascade
   selects (flex-basis: 100% forces a wrap before this item). */
.ce-voice-picker > .ce-btn {
    flex-basis: 100%;
    margin-top: 0.25rem;
    align-self: flex-start;
}

/* Local AI banner on the Cast page (WebGPU provisioning UI). */
.ce-local-ai-head {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin-bottom: 0.5rem;
}
.ce-local-ai-head h2 { margin: 0; flex: 1; }
.ce-local-ai-form {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem 0.75rem;
    align-items: flex-end;
    margin: 0.5rem 0 0.25rem 0;
}
.ce-local-ai-form label {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    flex: 1;
    min-width: 18rem;
}
.ce-local-ai-form input[type="url"] {
    width: 100%;
    box-sizing: border-box;
}

/* Three-up "small / medium / large" model picker. */
.ce-model-options {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
    gap: 0.75rem;
    margin: 0.75rem 0 0.25rem 0;
}
.ce-model-option {
    text-align: left;
    background: #fff;
    border: 1px solid var(--ce-rule);
    border-radius: 6px;
    padding: 0.75rem 0.9rem;
    cursor: pointer;
    font: inherit;
    color: inherit;
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    transition: border-color 0.12s, transform 0.08s, box-shadow 0.12s;
}
.ce-model-option:hover:not(:disabled) {
    border-color: var(--ce-accent);
    transform: translateY(-1px);
    box-shadow: 0 2px 8px rgba(60, 40, 10, 0.10);
}
.ce-model-option:disabled { opacity: 0.55; cursor: not-allowed; }
.ce-model-option-size {
    font-size: 0.7rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--ce-accent);
}
.ce-model-option-name {
    font-size: 1rem;
    font-weight: 600;
    color: var(--ce-ink);
}
.ce-model-option-desc {
    font-size: 0.85rem;
    color: var(--ce-ink-soft);
    line-height: 1.35;
}

.ce-badge {
    display: inline-block;
    padding: 0.15rem 0.55rem;
    border-radius: 999px;
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    font-weight: 600;
    border: 1px solid transparent;
}
.ce-badge-ok   { color: #2a6d2a; background: rgba(42, 109, 42, 0.10); border-color: rgba(42, 109, 42, 0.30); }
.ce-badge-warn { color: #a05a00; background: rgba(160, 90, 0, 0.10); border-color: rgba(160, 90, 0, 0.30); }

.ce-progress {
    width: 100%;
    height: 8px;
    border-radius: 999px;
    background: rgba(0, 0, 0, 0.06);
    overflow: hidden;
    margin: 0.5rem 0;
}
.ce-progress-bar {
    height: 100%;
    background: var(--ce-accent);
    transition: width 0.15s linear;
}

/* Horizontal rule between top-of-Cast-page sections. */
.ce-section-rule {
    border: 0;
    border-top: 1px solid var(--ce-rule);
    margin: 1.5rem 0;
}

/* Grouped action toolbar (Cast page). Looks like a single connected bar
   instead of a scattered row of independent buttons. */
.ce-action-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.25rem;
    padding: 0.4rem 0.5rem;
    border: 1px solid var(--ce-rule);
    border-radius: 6px;
    background: rgba(0, 0, 0, 0.025);
    margin: 0.75rem 0 1rem 0;
}
.ce-action-toolbar .ce-spacer { flex: 1; }
.ce-action-toolbar-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    padding: 0.35rem 0.75rem;
    border: 1px solid transparent;
    border-radius: 4px;
    background: transparent;
    color: var(--ce-ink);
    font: inherit;
    font-size: 0.9rem;
    line-height: 1;
    text-decoration: none;
    cursor: pointer;
    white-space: nowrap;
    transition: background 0.12s, border-color 0.12s, color 0.12s;
}
.ce-action-toolbar-btn:hover:not(:disabled) {
    background: #fff;
    border-color: var(--ce-rule);
}
.ce-action-toolbar-btn:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}
.ce-action-toolbar-btn.is-primary {
    background: var(--ce-accent);
    color: var(--ce-bg, #fff);
    border-color: var(--ce-accent);
}
.ce-action-toolbar-btn.is-primary:hover:not(:disabled) {
    background: var(--ce-accent-bold, var(--ce-accent));
    border-color: var(--ce-accent-bold, var(--ce-accent));
}
.ce-action-toolbar-sep {
    width: 1px;
    height: 18px;
    background: var(--ce-rule);
    margin: 0 0.25rem;
}
.ce-action-toolbar-status {
    font-size: 0.85rem;
    color: var(--ce-ink-soft);
    padding-right: 0.25rem;
}

/* Live model-transcript aside shown during a Cast extraction run. Two
   collapsible blocks: the prompt sent to the model (collapsed by default;
   long) and the streaming output (open; auto-grows as tokens arrive). */
.ce-extract-transcript {
    margin: 0.75rem 0 1rem 0;
    padding: 0.75rem 1rem;
    border: 1px solid var(--ce-rule);
    border-radius: 6px;
    background: rgba(0, 0, 0, 0.03);
    font-family: "Inter", "Helvetica Neue", Arial, sans-serif;
}
.ce-extract-transcript-head {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin-bottom: 0.5rem;
    font-size: 0.9rem;
}
.ce-extract-transcript-head .ce-spacer { flex: 1; }
.ce-extract-transcript-picker {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
}
.ce-extract-transcript-picker select {
    font-size: 0.85rem;
    max-width: 22rem;
}
.ce-extract-transcript-section {
    border-top: 1px dashed var(--ce-rule);
    padding: 0.4rem 0;
}
.ce-extract-transcript-section:first-of-type { border-top: none; }
.ce-extract-transcript-section > summary {
    cursor: pointer;
    font-size: 0.85rem;
    color: var(--ce-ink-soft);
}
.ce-extract-transcript-body {
    margin: 0.4rem 0 0 0;
    padding: 0.5rem 0.75rem;
    background: #fff;
    border: 1px solid var(--ce-rule);
    border-radius: 4px;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 0.78rem;
    line-height: 1.4;
    white-space: pre-wrap;
    word-break: break-word;
    max-height: 280px;
    overflow-y: auto;
}
.ce-extract-transcript-output {
    max-height: 360px;
    color: var(--ce-accent-bold, var(--ce-accent));
}

/* Chapter-by-chapter progress bar that sits directly under the action toolbar
   during a Cast extraction stage. The bar is the same accent-coloured fill as
   the model-download bar so the page reads as a single progressing surface. */
.ce-chapter-progress {
    position: relative;
    height: 22px;
    border: 1px solid var(--ce-rule);
    border-radius: 999px;
    background: rgba(0, 0, 0, 0.04);
    overflow: hidden;
    margin: 0.4rem 0 1rem 0;
    font-size: 0.8rem;
}
.ce-chapter-progress-bar {
    height: 100%;
    background: var(--ce-accent);
    opacity: 0.85;
    transition: width 0.25s linear;
}
.ce-chapter-progress-label {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--ce-ink);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    padding: 0 0.75rem;
}

/* Cast section card. Each character gets a bordered card with breathing room
   between them so the list reads as discrete entries rather than a wall. */
.ce-card {
    border: 1px solid var(--ce-rule);
    border-radius: 6px;
    padding: 1rem 1.25rem;
    margin: 0 0 1.75rem 0;
    background: #fff;
}
.ce-card:last-child { margin-bottom: 0; }
/* The first heading/paragraph inside a card was inheriting its own top margin
   on top of the card's padding, doubling the gap above (e.g. "Project narrator"
   sat well below the card edge). Zero the leading top margin so the heading
   hugs the card's padding instead. */
.ce-card > :first-child { margin-top: 0; }

.ce-cast-head {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin-bottom: 0.25rem;
}
.ce-cast-head h2 { margin: 0; flex: 1; }

.ce-card-ignored {
    opacity: 0.6;
    background: rgba(0, 0, 0, 0.02);
    /* Slim padding since the body is hidden — the ignored card is just a
       header row, so collapsing the vertical padding makes it read as a
       compact stub rather than an empty card. */
    padding: 0.5rem 1.25rem;
    margin-bottom: 0.5rem;
}
.ce-card-ignored .ce-cast-head { margin-bottom: 0; }

/* Position container for the per-card ⋮ menu trigger + its dropdown. */
.ce-cast-actions {
    position: relative;
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin-left: auto;
}
.ce-card-menu-btn {
    background: transparent;
    border: 1px solid transparent;
    border-radius: 4px;
    width: 1.75rem;
    height: 1.75rem;
    font-size: 1.25rem;
    line-height: 1;
    cursor: pointer;
    color: var(--ce-ink-soft);
}
.ce-card-menu-btn:hover {
    background: rgba(0, 0, 0, 0.05);
    color: var(--ce-ink);
}
.ce-card-menu-btn[aria-expanded="true"] {
    background: rgba(0, 0, 0, 0.08);
    color: var(--ce-ink);
}
.ce-card-menu {
    position: absolute;
    top: 100%;
    right: 0;
    margin-top: 0.25rem;
    min-width: 10rem;
    background: var(--ce-paper, #fff);
    border: 1px solid var(--ce-rule);
    border-radius: 4px;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
    z-index: 10;
    padding: 0.25rem 0;
    display: flex;
    flex-direction: column;
}
.ce-card-menu-item {
    background: transparent;
    border: none;
    text-align: left;
    padding: 0.4rem 0.75rem;
    cursor: pointer;
    font: inherit;
    color: var(--ce-ink);
}
.ce-card-menu-item:hover { background: rgba(0, 0, 0, 0.05); }
.ce-card-menu-item.is-destructive { color: #c0392b; }
.ce-card-menu-item.is-destructive:hover { background: rgba(192, 57, 43, 0.08); }
.ce-card-menu-sep {
    margin: 0.25rem 0;
    border: 0;
    border-top: 1px solid var(--ce-rule);
}

/* Grouped section inside the Extract dialog. legend acts as a subtle label. */
.ce-form-group {
    border: 1px solid var(--ce-rule);
    border-radius: 4px;
    padding: 0.5rem 0.75rem 0.75rem;
    margin: 0 0 0.75rem 0;
}
.ce-form-group legend {
    padding: 0 0.4rem;
    font-size: 0.85rem;
    color: var(--ce-ink-soft);
}
.ce-radio-row,
.ce-checkbox-row {
    display: flex;
    align-items: flex-start;
    gap: 0.5rem;
    padding: 0.25rem 0;
    cursor: pointer;
}
.ce-radio-row input,
.ce-checkbox-row input {
    margin-top: 0.2rem;
    flex: 0 0 auto;
}
.ce-radio-row > span,
.ce-checkbox-row > span { flex: 1; }

/* Compact role dropdown that lives inline with the character's heading. */
.ce-role-picker {
    font-size: 0.85rem;
    font-weight: normal;
    color: var(--ce-ink-soft);
    background: transparent;
    border: 1px solid var(--ce-rule);
    border-radius: 4px;
    padding: 0.1rem 0.3rem;
    margin-left: 0.5rem;
    cursor: pointer;
}

/* Inline alias-merge picker — appears under the cast head when active. */
.ce-alias-picker {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
    margin: 0.5rem 0;
    padding: 0.5rem 0.75rem;
    background: rgba(0, 0, 0, 0.03);
    border-radius: 4px;
}
.ce-alias-picker label { display: flex; align-items: center; gap: 0.5rem; flex: 1; }
.ce-alias-picker select { padding: 0.25rem 0.4rem; }

/* Inline descriptor editor — looks like the surrounding text but reveals an
   Edit affordance on hover so it's discoverable without cluttering the card. */
.ce-descriptor { display: flex; align-items: flex-start; gap: 0.5rem; }
.ce-descriptor em { flex: 1; }
.ce-descriptor-edit-btn {
    font-size: 0.75rem;
    color: var(--ce-ink-soft);
    background: transparent;
    border: 1px solid var(--ce-rule);
    border-radius: 4px;
    padding: 0.15rem 0.5rem;
    cursor: pointer;
    opacity: 0.5;
    transition: opacity 120ms ease;
}
.ce-descriptor:hover .ce-descriptor-edit-btn { opacity: 1; }
.ce-descriptor-edit-btn:hover { opacity: 1; color: var(--ce-ink); }

.ce-descriptor-edit { margin: 0.5rem 0; }
.ce-descriptor-input {
    width: 100%;
    padding: 0.5rem;
    border: 1px solid var(--ce-rule);
    border-radius: 4px;
    font-family: inherit;
    font-size: 0.95rem;
    line-height: 1.4;
    resize: vertical;
}
.ce-descriptor-actions {
    display: flex;
    gap: 0.5rem;
    margin-top: 0.5rem;
    justify-content: flex-end;
    align-items: center;
}

/* "edited" tag next to a descriptor that has a manual chapter override. */
.ce-descriptor-tag {
    display: inline-block;
    font-size: 0.7rem;
    padding: 0.05rem 0.4rem;
    border-radius: 999px;
    background: var(--ce-rule);
    color: var(--ce-ink);
    margin-left: 0.5rem;
    vertical-align: middle;
}

/* Single column: a chapter filter dropdown above the cast list. */
.ce-cast-layout {
    display: flex;
    flex-direction: column;
    gap: 1rem;
}
.ce-cast-list { min-width: 0; }

.ce-chapter-filter {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}
.ce-chapter-filter label {
    font-size: 0.9rem;
    color: var(--ce-ink-soft);
}
.ce-chapter-filter-select {
    flex: 0 1 28rem;
    padding: 0.35rem 0.5rem;
    border: 1px solid var(--ce-rule);
    border-radius: 4px;
    font: inherit;
    background: var(--ce-paper, #fff);
    color: var(--ce-ink);
    cursor: pointer;
}


/* Pill-shaped toggle button. Default state is an outlined pill; pressed (is-on)
   fills with the ink colour so it reads as "engaged" at a glance. */
.ce-toggle {
    margin-left: auto;
    padding: 0.3rem 0.85rem;
    border: 1px solid var(--ce-rule);
    border-radius: 999px;
    background: transparent;
    color: var(--ce-ink-soft);
    cursor: pointer;
    font: inherit;
    font-size: 0.8rem;
    line-height: 1;
    letter-spacing: 0.02em;
    white-space: nowrap;
    transition: background 0.12s, color 0.12s, border-color 0.12s;
}
.ce-toggle:hover {
    background: rgba(122, 90, 42, 0.08);
    color: var(--ce-ink);
    border-color: var(--ce-accent);
}
.ce-toggle.is-on {
    background: var(--ce-ink);
    color: var(--ce-bg, #fff);
    border-color: var(--ce-ink);
}
.ce-toggle.is-on:hover {
    background: var(--ce-accent-bold, var(--ce-accent));
    border-color: var(--ce-accent-bold, var(--ce-accent));
    color: var(--ce-bg, #fff);
}
.ce-toggle:focus-visible {
    outline: 2px solid var(--ce-accent);
    outline-offset: 2px;
}

/* Comment-thread markers in the right gutter. */
.ce-thread-markers {
    position: absolute;
    top: 0;
    left: 0;
    width: 0;
    height: 0;
    pointer-events: none;
}
.ce-thread-marker {
    position: absolute;
    pointer-events: auto;
    width: 24px;
    height: 24px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--ce-rule);
    border-radius: 50%;
    background: #fff;
    color: var(--ce-ink-soft);
    cursor: pointer;
    box-shadow: 0 1px 4px rgba(60, 40, 10, 0.10);
    padding: 0;
    transition: color 0.15s, border-color 0.15s, background 0.15s, transform 0.1s;
}
.ce-thread-marker:hover {
    transform: scale(1.08);
    color: var(--ce-accent);
    border-color: var(--ce-accent);
}
.ce-thread-marker.is-pending  { color: #a05a00; border-color: rgba(160, 90, 0, 0.4); }
.ce-thread-marker.is-replied  { color: #2a6d2a; border-color: rgba(42, 109, 42, 0.4); }
.ce-thread-marker.is-resolved { opacity: 0.55; }

/* Right-side aside panel showing the active thread. Mirrors the TOC drawer
   pattern: always rendered, slid off-screen with translateX(100%), animates
   back in when the open class is toggled. */
.ce-thread-aside {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    width: min(360px, 90vw);
    z-index: 90;
    background: var(--ce-paper, #fbf6e6);
    border-left: 1px solid var(--ce-rule, #d3c4a3);
    box-shadow: -8px 0 24px rgba(60, 40, 10, 0.12);
    display: flex;
    flex-direction: column;
    padding: 1rem;
    overflow-y: auto;
    font-family: "Inter", "Helvetica Neue", Arial, sans-serif;
    transform: translateX(100%);
    transition: transform 0.22s ease;
}
.ce-thread-aside-open { transform: translateX(0); }

.ce-thread-aside-head {
    display: flex;
    align-items: center;
    margin-bottom: 0.75rem;
    font-size: 1rem;
}
.ce-thread-aside-head-actions {
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: 0.4rem;
}
.ce-thread-aside-archive {
    background: transparent;
    border: 1px solid var(--ce-rule);
    color: var(--ce-ink-soft);
    font-size: 0.8rem;
    line-height: 1;
    cursor: pointer;
    padding: 0.3rem 0.55rem;
    border-radius: 3px;
}
.ce-thread-aside-archive:hover {
    background: rgba(122, 90, 42, 0.10);
    color: var(--ce-accent-bold, var(--ce-accent));
    border-color: var(--ce-accent);
}
.ce-thread-aside-archive:disabled { opacity: 0.6; cursor: not-allowed; }

.ce-thread-aside-close {
    background: none;
    border: 0;
    color: var(--ce-ink-soft);
    font-size: 1.4rem;
    line-height: 1;
    cursor: pointer;
    padding: 0.1rem 0.4rem;
    border-radius: 3px;
}
.ce-thread-aside-close:hover {
    background: rgba(122, 90, 42, 0.10);
    color: var(--ce-accent-bold, var(--ce-accent));
}
.ce-thread-messages {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
}
.ce-thread-message {
    border: 1px solid var(--ce-rule);
    border-radius: 4px;
    padding: 0.5rem 0.6rem;
    background: #fff;
}
.ce-thread-message-head {
    font-size: 0.85rem;
    display: flex;
    align-items: baseline;
    gap: 0.4rem;
    margin-bottom: 0.2rem;
}
.ce-thread-message-body {
    margin: 0;
    white-space: pre-wrap;
    font-size: 0.95rem;
    line-height: 1.4;
}

.ce-thread-reply {
    margin-top: 0.75rem;
    padding-top: 0.75rem;
    border-top: 1px dashed var(--ce-rule);
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
}
.ce-thread-reply textarea {
    width: 100%;
    box-sizing: border-box;
    resize: vertical;
    font-size: 0.9rem;
}
.ce-thread-reply-actions {
    display: flex;
    justify-content: flex-end;
}

/* Review-request modal excerpt block. */
.ce-review-excerpt {
    margin: 0 0 0.75rem 0;
    padding: 0.5rem 0.75rem;
    border-left: 3px solid var(--ce-accent);
    background: rgba(122, 90, 42, 0.06);
    color: var(--ce-ink);
    font-size: 0.95rem;
    font-style: italic;
    border-radius: 0 4px 4px 0;
}

/* Edit-history modal. */
.ce-modal-wide {
    width: min(720px, 94vw);
    max-height: 88vh;
    overflow-y: auto;
}
.ce-history-list {
    list-style: none;
    padding: 0;
    margin: 0.75rem 0 0 0;
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
    max-height: 260px;
    overflow-y: auto;
}
.ce-history-row {
    border: 1px solid var(--ce-rule);
    border-radius: 4px;
    background: #fff;
}
.ce-history-row.is-selected {
    border-color: var(--ce-accent);
    box-shadow: 0 0 0 1px var(--ce-accent);
}
.ce-history-row-button {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 0.15rem;
    width: 100%;
    text-align: left;
    background: none;
    border: 0;
    padding: 0.5rem 0.75rem;
    font: inherit;
    color: inherit;
    cursor: pointer;
}
.ce-history-row-button:hover { background: rgba(122, 90, 42, 0.08); }
.ce-history-row-title {
    display: block;
    font-weight: 600;
    line-height: 1.3;
}
.ce-history-meta {
    display: block;
    font-size: 0.85rem;
    color: var(--ce-ink-soft);
    line-height: 1.3;
}
.ce-history-meta code {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.ce-history-detail {
    margin-top: 1rem;
    padding-top: 0.75rem;
    border-top: 1px dashed var(--ce-rule);
}
.ce-history-detail h3 {
    margin: 0 0 0.35rem;
    font-size: 1rem;
}
.ce-history-message {
    white-space: pre-wrap;
    background: rgba(0, 0, 0, 0.04);
    border: 1px solid var(--ce-rule);
    border-radius: 4px;
    padding: 0.5rem 0.75rem;
    font-size: 0.9rem;
    margin: 0.25rem 0 0.5rem;
}
.ce-history-files {
    list-style: none;
    padding: 0;
    margin: 0.25rem 0 0 0;
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    font-size: 0.9rem;
}
.ce-history-files li {
    display: flex;
    align-items: baseline;
    gap: 0.5rem;
}
.ce-history-file-status {
    display: inline-block;
    min-width: 4.5rem;
    text-align: center;
    font-size: 0.75rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    padding: 0.1rem 0.4rem;
    border-radius: 3px;
    color: var(--ce-ink-soft);
    background: rgba(0, 0, 0, 0.05);
}
.ce-history-file-status.added    { color: #2a6d2a; background: rgba(42, 109, 42, 0.10); }
.ce-history-file-status.modified { color: #a05a00; background: rgba(160, 90, 0, 0.10); }
.ce-history-file-status.deleted  { color: #a02a2a; background: rgba(160, 42, 42, 0.10); }

/* Per-file diff viewer (DiffView). */
.ce-history-file {
    margin-top: 0.5rem;
    border: 1px solid var(--ce-rule);
    border-radius: 4px;
    background: #fff;
}
.ce-history-file-summary {
    display: flex;
    align-items: baseline;
    gap: 0.5rem;
    padding: 0.4rem 0.6rem;
    cursor: pointer;
    font-size: 0.9rem;
}
.ce-history-file-label {
    font-weight: 600;
    color: var(--ce-ink);
}
.ce-history-file[open] .ce-history-file-summary {
    border-bottom: 1px solid var(--ce-rule);
}
.ce-diff-empty {
    margin: 0;
    padding: 0.75rem;
    color: var(--ce-ink-soft);
    font-size: 0.9rem;
}
.ce-diff-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 0.78rem;
    line-height: 1.4;
    max-height: 360px;
    overflow: auto;
}
.ce-diff-col + .ce-diff-col {
    border-left: 1px solid var(--ce-rule);
}
.ce-diff-col-head {
    position: sticky;
    top: 0;
    padding: 0.25rem 0.5rem;
    background: rgba(0, 0, 0, 0.04);
    border-bottom: 1px solid var(--ce-rule);
    font-family: "Inter", "Helvetica Neue", Arial, sans-serif;
    font-size: 0.75rem;
    font-weight: 600;
    color: var(--ce-ink-soft);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    z-index: 1;
}
.ce-diff-line {
    display: grid;
    grid-template-columns: 3ch 1.5ch 1fr;
    gap: 0.25rem;
    padding: 0 0.25rem;
    white-space: pre;
}
.ce-diff-line.is-added    { background: rgba(42, 109, 42, 0.12); }
.ce-diff-line.is-removed  { background: rgba(160, 42, 42, 0.12); }
.ce-diff-line.is-modified { background: rgba(160, 90, 0, 0.10); }
.ce-diff-line.is-empty    { background: rgba(0, 0, 0, 0.03); color: transparent; }
.ce-diff-gutter {
    color: var(--ce-ink-soft);
    text-align: right;
    user-select: none;
}
.ce-diff-marker {
    color: var(--ce-ink-soft);
    text-align: center;
    user-select: none;
}
.ce-diff-text {
    overflow-wrap: anywhere;
    white-space: pre-wrap;
}
.ce-diff-piece {
    background: none;
    padding: 0 0.05rem;
    border-radius: 2px;
    color: inherit;
    font-weight: 600;
}
.ce-diff-piece.is-added   { background: rgba(42, 109, 42, 0.30); }
.ce-diff-piece.is-removed { background: rgba(160, 42, 42, 0.30); }
.ce-export-options { display: flex; flex-direction: column; gap: 8px; margin-top: 0.75rem; }
.ce-export-pick {
    display: flex; align-items: baseline; gap: 12px;
    /* Wraps so the round-trip line drops below the description rather than running on after it —
       the extension keeps its own column and the two sentences stack under each other. */
    flex-wrap: wrap;
    padding: 10px 12px;
    border: 1px solid var(--ce-rule);
    border-radius: 4px;
    background: #fff;
    color: var(--ce-ink);
    text-decoration: none;
    font-size: 0.95rem;
}
.ce-export-pick strong {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    color: var(--ce-accent);
    font-size: 0.95rem;
    width: 3rem;
    flex-shrink: 0;
}
.ce-export-pick span { color: var(--ce-ink-soft); }
/* Whether the file can come back, said for every source format rather than only in the archive's
   description. Quieter than the description it sits under: it qualifies the choice rather than
   describing it, and a writer scanning five formats reads the labels first. Aligned past the
   extension column so the two sentences form one block. */
.ce-export-roundtrip {
    flex-basis: 100%;
    margin-left: calc(3rem + 12px);
    font-size: 0.85rem;
    font-style: italic;
}
.ce-export-pick:hover {
    border-color: var(--ce-accent);
    background: var(--ce-deep, #efe5cb);
}

/* Popout menu shown when a badge is clicked. */
.ce-struct-menu {
    position: absolute;
    z-index: 50;
    background: var(--ce-paper, #fbf6e6);
    border: 1px solid var(--ce-rule, #d3c4a3);
    border-radius: 4px;
    box-shadow: 0 4px 14px rgba(60, 40, 10, 0.20);
    padding: 4px;
    font-family: "Inter", "Helvetica Neue", Arial, sans-serif;
    font-size: 0.85rem;
}
.ce-struct-menu button {
    display: block;
    width: 100%;
    text-align: left;
    background: transparent;
    border: none;
    padding: 6px 14px;
    border-radius: 3px;
    cursor: pointer;
    font: inherit;
    color: var(--ce-ink);
}
.ce-struct-menu-item:hover { background: var(--ce-deep, #efe5cb); }
.ce-struct-menu-delete { color: #8a3a2e; }
.ce-struct-menu-delete:hover { background: rgba(138, 58, 46, 0.10); }

.ce-paper p { margin: 0 0 1rem 0; }

/* ──────────────────────────────────────────────────────────────────────────
   Book paragraph rhythm, on the paginated read view only.

   The page is already a faithful trade page — `data-page-size` gives it the
   publisher's trim and margins and `@page` prints at that geometry — and it
   was setting prose in web rhythm: every paragraph flush left with a blank
   line between. Book setting marks a new paragraph with a first-line indent
   and no space between; the blank line is the *alternative* convention, not
   an addition to it, and doing both reads as a web page on a printed leaf.

   Scoped to `.ce-paginated`, which only `Read.razor` carries. The editor
   shares `.ce-paper` and is not a printed page: indenting while somebody is
   typing moves where their cursor appears to be, for a convention that
   belongs to the finished object rather than the draft.
   ────────────────────────────────────────────────────────────────────────── */
.ce-paper.ce-paginated p { margin: 0; }
.ce-paper.ce-paginated p + p { text-indent: 1.5em; }

/* An opening is not indented — there is nothing above it to be distinguished
   from, and under the drop cap an indent is a notch of white nothing holds.
   Higher specificity than the `p + p` rule above, which would otherwise catch
   the opener: the empty paragraph that starts most imported chapters sits
   immediately before it. */
.ce-paper.ce-paginated p.ce-chapter-opener,
.ce-paper.ce-paginated :is(h1, h2, h3, h4, h5, h6) + p { text-indent: 0; }

/* A manuscript uses an empty paragraph as a scene break — one real book has
   seventy-three. With the margin above removed an empty block collapses to
   nothing and takes the break with it, which would be a change to the
   writer's text rather than to its setting. */
.ce-paper.ce-paginated p:empty { height: 1rem; }

/* ──────────────────────────────────────────────────────────────────────────
   Page sizing on the read view.
   The article carries a `data-page-size` from the project's PageSize meta.
   We translate that to width + min-height via CSS variables and use the same
   geometry in the print @page rule. Auto-pagination on screen isn't here yet —
   readers still see one tall continuous page — but the width matches what
   the publisher will print, and explicit hr.page-break nodes show as page
   separators.
   ────────────────────────────────────────────────────────────────────────── */
.ce-paper.ce-paginated {
    --page-w: 6in;
    --page-h: 9in;
    --page-margin-x: 0.75in;
    --page-margin-y: 0.85in;
    max-width: var(--page-w);
    min-height: var(--page-h);
    padding: var(--page-margin-y) var(--page-margin-x);
    /* Inherit the theme's paper colour rather than hardcoding white — themes.css
       overrides --ce-paper per data-theme, and this rule is more specific than
       the .ce-paper base, so a literal value here would shadow every theme. */
    background: var(--ce-paper);
    box-shadow: 0 4px 24px rgba(60, 40, 10, 0.18);
}
.ce-paper.ce-paginated[data-page-size="novel-5x8"],
.ce-paper.ce-paginated[data-page-size="digest-5.5x8.5"] {
    --page-w: 5.5in;
    --page-h: 8.5in;
    --page-margin-x: 0.65in;
    --page-margin-y: 0.8in;
}
.ce-paper.ce-paginated[data-page-size="us-letter"] {
    --page-w: 8.5in;
    --page-h: 11in;
    --page-margin-x: 1in;
    --page-margin-y: 1in;
}
.ce-paper.ce-paginated[data-page-size="a4"] {
    --page-w: 210mm;
    --page-h: 297mm;
    --page-margin-x: 25mm;
    --page-margin-y: 25mm;
}
/* Visible page break on screen: extra breathing room and a paper-edge effect. */
.ce-paper.ce-paginated hr.page-break {
    margin: 3rem -1.5rem;
    border: none;
    border-top: 1px dashed var(--ce-rule);
    position: relative;
    page-break-after: always;
    break-after: page;
}
@media (max-width: 720px) {
    /* On narrow screens the fixed page width would horizontally overflow; let
       the paper shrink to viewport so mobile readers aren't horizontal-scrolling. */
    .ce-paper.ce-paginated {
        max-width: 100%;
        padding: 1.5rem 1.25rem;
        min-height: 0;
    }
}

@media print {
    @page { size: 6in 9in; margin: 0.85in 0.75in; }
    .ce-paper.ce-paginated[data-page-size="novel-5x8"],
    .ce-paper.ce-paginated[data-page-size="digest-5.5x8.5"] {
        page: digest;
    }
    .ce-paper.ce-paginated[data-page-size="us-letter"] { page: letter; }
    .ce-paper.ce-paginated[data-page-size="a4"]        { page: a4; }
    @page digest { size: 5.5in 8.5in; margin: 0.8in 0.65in; }
    @page letter { size: 8.5in 11in; margin: 1in 1in; }
    @page a4     { size: 210mm 297mm; margin: 25mm 25mm; }

    /* The typography that makes a printed page a page, rather than a long
       document cut every nine inches. None of it is visible on screen — the
       read view is one tall continuous leaf and has no breaks to get wrong —
       which is why it went missing for as long as it did.

       Two lines is the floor for widows and orphans everywhere: a single line
       stranded at the head or foot of a page is the oldest defect in
       typesetting, and the one a reader notices without being able to name. */
    .ce-paper.ce-paginated p { orphans: 2; widows: 2; }

    /* Books start a chapter on a new page. Not the first — there is nothing
       above it to break from, and a break there prints an empty sheet. */
    .ce-paper.ce-paginated section[data-block-type="chapter"] { break-before: page; }
    .ce-paper.ce-paginated section[data-block-type="chapter"]:first-child { break-before: auto; }

    /* A title at the foot of a page with its first paragraph overleaf. */
    .ce-paper.ce-paginated :is(h1, h2, h3, h4, h5, h6) { break-after: avoid; }

    /* A figure broken over a fold is not a smaller figure, it is two halves of
       one. Tables likewise: a row split down the middle is unreadable. */
    .ce-paper.ce-paginated figure,
    .ce-paper.ce-paginated img,
    .ce-paper.ce-paginated tr { break-inside: avoid; }

    /* Hide chrome, badges, and sticky-note labels when printing — readers just
       want the manuscript body. */
    .ce-topnav, .ce-toolbar, .ce-footer, .ce-no-copy::before { display: none !important; }
    .ce-paper.ce-paginated {
        box-shadow: none;
        max-width: none;
        margin: 0;
    }
}

.ce-paper blockquote {
    border-left: 3px solid var(--ce-accent);
    margin: 1rem 0;
    padding: 0.5rem 1rem;
    color: var(--ce-ink-soft);
    font-style: italic;
}

/* Pull quote / quote box — themes override this block to give the callout
   its own look. The default is a centered, bordered card with a large
   curly-quote ornament. */
.ce-paper .ce-pull-quote {
    display: block;
    max-width: 80%;
    margin: 1.5rem auto;
    padding: 1rem 1.5rem;
    border-top: 1px solid var(--ce-accent);
    border-bottom: 1px solid var(--ce-accent);
    background: rgba(122, 90, 42, 0.04);
    font-style: italic;
    font-size: 1.05em;
    color: var(--ce-ink);
    text-align: center;
    position: relative;
}
.ce-paper .ce-pull-quote::before {
    content: "“";
    position: absolute;
    top: -0.2rem;
    left: 0.4rem;
    font-size: 3rem;
    font-style: normal;
    color: var(--ce-accent);
    opacity: 0.45;
    line-height: 1;
}
.ce-paper .ce-pull-quote p { margin: 0; }
.ce-paper .ce-pull-quote p + p { margin-top: 0.6rem; }

.ce-paper hr.page-break {
    border: none;
    border-top: 1px dotted var(--ce-rule);
    margin: 2rem 0;
}

.ce-paper .ce-comment {
    border-bottom: 1px dotted var(--ce-accent);
    background: rgba(122, 90, 42, 0.06);
}

.ce-paper .ce-edit-insert { background: rgba(64, 128, 64, 0.12); }
.ce-paper .ce-edit-delete { text-decoration: line-through; color: rgba(160, 50, 50, 0.85); }
.ce-paper .ce-edit-replace { background: rgba(122, 90, 42, 0.12); }

/* ──────────────────────────────────────────────────────────────────────────
   Table of contents drawer. Edge tab + slide-out aside; reused on both
   editor and read views by including <TableOfContents Root="..." />.
   ────────────────────────────────────────────────────────────────────────── */
.ce-toc-tab {
    position: fixed;
    left: 0;
    top: 38%;
    z-index: 30;
    width: 28px;
    height: 56px;
    border: 1px solid var(--ce-rule);
    border-left: none;
    border-radius: 0 4px 4px 0;
    background: var(--ce-paper);
    color: var(--ce-ink-soft);
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    box-shadow: 1px 1px 4px rgba(60, 40, 10, 0.12);
    transition: transform 0.18s ease, background 0.15s ease;
}
.ce-toc-tab:hover { background: var(--ce-deep, #efe5cb); color: var(--ce-accent-bold); }
.ce-toc-tab-open { transform: translateX(320px); }

.ce-toc-drawer {
    position: fixed;
    left: 0;
    top: 0;
    bottom: 0;
    z-index: 29;
    width: 320px;
    background: var(--ce-paper);
    border-right: 1px solid var(--ce-rule);
    box-shadow: 2px 0 16px rgba(60, 40, 10, 0.10);
    transform: translateX(-100%);
    transition: transform 0.22s ease;
    display: flex;
    flex-direction: column;
    font-family: "Inter", "Helvetica Neue", Arial, sans-serif;
}
.ce-toc-drawer-open { transform: translateX(0); }

.ce-toc-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 14px 16px;
    border-bottom: 1px solid var(--ce-rule);
}
.ce-toc-title {
    margin: 0;
    font-size: 0.85rem;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--ce-ink-soft);
    font-weight: 600;
}
.ce-toc-close {
    background: transparent;
    border: none;
    font-size: 1.4rem;
    color: var(--ce-ink-soft);
    cursor: pointer;
    line-height: 1;
    padding: 0 4px;
}
.ce-toc-close:hover { color: var(--ce-accent-bold); }

.ce-toc-search { padding: 10px 16px; border-bottom: 1px solid var(--ce-rule); }
.ce-toc-search-input {
    width: 100%;
    padding: 8px 12px;
    border: 1px solid var(--ce-rule);
    border-radius: 4px;
    background: #ffffff;
    color: var(--ce-ink);
    font: inherit;
    font-size: 0.95rem;
}
.ce-toc-search-input:focus { outline: 2px solid var(--ce-accent); outline-offset: 1px; }

.ce-toc-body { flex: 1; overflow-y: auto; padding: 8px 8px 16px; }
.ce-toc-empty { padding: 1rem; color: var(--ce-ink-soft); font-style: italic; font-size: 0.9rem; margin: 0; }

.ce-toc-list { list-style: none; margin: 0; padding: 0; }
.ce-toc-entry { margin: 0; }
.ce-toc-link {
    width: 100%;
    text-align: left;
    background: transparent;
    border: none;
    color: var(--ce-ink);
    padding: 5px 10px;
    border-radius: 3px;
    cursor: pointer;
    font: inherit;
    font-size: 0.92rem;
    line-height: 1.35;
}
.ce-toc-link:hover { background: var(--ce-deep, #efe5cb); color: var(--ce-accent-bold); }
.ce-toc-entry-d1 .ce-toc-link { padding-left: 10px; font-weight: 600; }
.ce-toc-entry-d2 .ce-toc-link { padding-left: 22px; }
.ce-toc-entry-d3 .ce-toc-link { padding-left: 34px; color: var(--ce-ink-soft); font-size: 0.88rem; }

.ce-toc-results { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.ce-toc-snippet {
    width: 100%;
    text-align: left;
    background: transparent;
    border: none;
    color: var(--ce-ink);
    padding: 8px 10px;
    border-radius: 3px;
    cursor: pointer;
    font: inherit;
    font-size: 0.9rem;
    line-height: 1.45;
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.ce-toc-snippet:hover { background: var(--ce-deep, #efe5cb); }
.ce-toc-snippet-ctx { font-size: 0.75rem; color: var(--ce-ink-soft); text-transform: uppercase; letter-spacing: 0.05em; }
.ce-toc-snippet-body mark { background: rgba(122, 90, 42, 0.25); color: inherit; padding: 0 2px; border-radius: 2px; }

/* ──────────────────────────────────────────────────────────────────────────
   Reordering controls in the contents drawer.

   Three buttons — merge into previous, move earlier, move later — that had no
   rule at all, so each rendered as a browser-default box around one arrow
   glyph: a grey chrome button in a parchment drawer, and about 24px on a phone
   against the 44px floor the thread aside and the player already hold.

   They are also adjacent, which is what makes the size matter. `ControlStylingRatchetTests`
   makes the general case fail rather than waiting for somebody to look.

   Shaped after `.ce-toc-close` — transparent, borderless, inheriting the ink —
   because they sit inside the same drawer and are the same kind of thing: a
   small action on the row you are pointing at.
   ────────────────────────────────────────────────────────────────────────── */
.ce-toc-move { display: inline-flex; gap: 2px; }
.ce-toc-move-btn {
    background: transparent;
    border: none;
    color: var(--ce-ink-soft);
    cursor: pointer;
    font: inherit;
    line-height: 1;
    padding: 4px 6px;
    border-radius: 3px;
}
.ce-toc-move-btn:hover { background: var(--ce-deep, #efe5cb); color: var(--ce-accent-bold); }

/* The orphaned-comment tray's disclosure button. Rendered only when a save has
   left threads with nowhere to anchor, and it had no rule either — so the one
   moment the editor tells a writer something was lost arrived as an unstyled
   chrome button. Reads as a quiet warning: the tray is information, not an
   error the writer caused. */
.ce-orphan-tab {
    background: transparent;
    border: 1px solid var(--ce-rule, #d3c4a3);
    border-radius: 4px;
    color: var(--ce-ink-soft, #4a3f33);
    cursor: pointer;
    font: inherit;
    font-size: 0.85rem;
    padding: 6px 12px;
}
.ce-orphan-tab:hover { background: var(--ce-deep, #efe5cb); color: var(--ce-accent-bold); }

/* Scroll-to-top button, injected globally by menu.js. Hidden until the user
   has scrolled past ~240px, then fades in at the bottom-right. */
.ce-scroll-top {
    position: fixed;
    right: 20px;
    bottom: 20px;
    width: 40px;
    height: 40px;
    z-index: 40;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--ce-rule, #d3c4a3);
    border-radius: 50%;
    background: var(--ce-paper, #fbf6e6);
    color: var(--ce-ink-soft, #4a3f33);
    cursor: pointer;
    box-shadow: 0 2px 8px rgba(60, 40, 10, 0.18);
    opacity: 0;
    transform: translateY(8px);
    pointer-events: none;
    transition: opacity 0.18s ease, transform 0.18s ease, background 0.15s ease, color 0.15s ease;
}
.ce-scroll-top-visible {
    opacity: 1;
    transform: translateY(0);
    pointer-events: auto;
}
.ce-scroll-top:hover {
    background: var(--ce-deep, #efe5cb);
    color: var(--ce-accent-bold, #5a3f15);
    border-color: var(--ce-accent, #7a5a2a);
}
@media print { .ce-scroll-top { display: none !important; } }

/* Brief flash on the destination block after scroll. */
.ce-toc-flash {
    animation: ce-toc-flash 1.2s ease;
}
@keyframes ce-toc-flash {
    0%   { background-color: rgba(122, 90, 42, 0.18); }
    100% { background-color: transparent; }
}

/* When scrollIntoView lands on a manuscript block, leave room for the sticky
   top nav (~55px) plus a little breathing room — otherwise the heading hides
   under the navbar. Applied to every block with a data-id, which is exactly
   the set the TOC and search-hit list scroll to. */
[data-id] { scroll-margin-top: 72px; }

@media (max-width: 720px) {
    .ce-toc-drawer { width: 86vw; }
    .ce-toc-tab-open { transform: translateX(86vw); }
}

/* ──────────────────────────────────────────────────────────────────────────
   Tables + diagrams.
   ────────────────────────────────────────────────────────────────────────── */
.ce-paper table {
    width: 100%;
    border-collapse: collapse;
    margin: 1.25rem 0;
    font-size: 0.95rem;
}
.ce-paper table th,
.ce-paper table td {
    border: 1px solid var(--ce-rule, #d3c4a3);
    padding: 6px 10px;
    vertical-align: top;
    text-align: left;
}
.ce-paper table th {
    background: rgba(122, 90, 42, 0.08);
    font-weight: 600;
}
.ce-paper table p { margin: 0; }

/* Column-resize handles inserted by prosemirror-tables. They're rendered as
   absolutely-positioned children of the hovered cell, so the cell needs to be
   the positioning context — otherwise the handle docks to whatever positioned
   ancestor it finds (e.g. .ProseMirror) and shows up as a tall stripe down the
   right edge of the editor. */
.ce-paper .ProseMirror table { table-layout: fixed; }
.ce-paper .ProseMirror td,
.ce-paper .ProseMirror th { position: relative; }
.ce-paper .column-resize-handle {
    position: absolute;
    right: -2px; top: 0; bottom: 0;
    width: 4px;
    z-index: 20;
    background-color: var(--ce-accent, #7a5a2a);
    pointer-events: none;
    opacity: 0.4;
}
.ce-paper .ProseMirror.resize-cursor { cursor: col-resize; }

/* Floating table action overlay. Positioned by editor.js at the top-right
   corner of whichever table contains the cursor. */
.ce-table-overlay {
    position: absolute;
    z-index: 25;
    font-family: "Inter", "Helvetica Neue", Arial, sans-serif;
}
.ce-table-overlay .ce-table-menu { position: relative; }
.ce-table-overlay .ce-table-toggle {
    list-style: none;
    width: 24px; height: 24px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--ce-rule);
    border-radius: 4px;
    background: var(--ce-paper);
    color: var(--ce-ink-soft);
    cursor: pointer;
    box-shadow: 0 1px 4px rgba(60, 40, 10, 0.18);
}
.ce-table-overlay .ce-table-toggle::-webkit-details-marker { display: none; }
.ce-table-overlay .ce-table-toggle:hover {
    background: var(--ce-deep, #efe5cb);
    color: var(--ce-accent-bold);
    border-color: var(--ce-accent);
}
.ce-table-overlay details[open] .ce-table-toggle { background: var(--ce-deep, #efe5cb); }
.ce-table-overlay .ce-menu-list {
    position: absolute;
    top: calc(100% + 4px);
    right: 0;
    left: auto;
}
.ce-paper .ProseMirror .selectedCell::after {
    z-index: 2;
    position: absolute;
    content: "";
    left: 0; right: 0; top: 0; bottom: 0;
    background: rgba(122, 90, 42, 0.12);
    pointer-events: none;
}

/* Pictures in the manuscript.

   Nothing constrained these. Bootstrap's reboot styles `img` but — since v5 —
   deliberately not its width, which is opt-in via `.img-fluid`, and an asset is
   stored at up to ImageCompressionOptions.LongEdgePixels (2000) against a 760px
   page. So every photograph overflowed the paper by about 2.6x on a desktop and
   scrolled a phone sideways by more than five screens.

   max-width rather than width, so a small picture — an ornament, a signature, a
   scanned marginal note — stays the size it was meant to be. height: auto is
   inert while the renderers state no dimensions, and is here so that stating
   them (the fix for layout shift) cannot silently squash every plate.

   Scoped to .ce-paper: interface images have their own sizing. */
.ce-paper img {
    max-width: 100%;
    height: auto;
}

/* Wide tables scroll inside the page rather than taking it with them.

   A table cannot be contained by constraining it: max-width sets a table's
   *preferred* width, and with table-layout: auto a cell that will not wrap — a
   URL, a long word, a numeric column — makes it wider anyway. So it needs a
   scrolling ancestor.

   Two class names, one rule, deliberately. .ce-table-scroll is what the reader
   and the export walkers emit; .tableWrapper is built by prosemirror-tables'
   own TableView, which columnResizing() installs in the editor. That wrapper
   has been in the DOM all along with nothing styling it — the library ships a
   stylesheet declaring exactly this and the project never included it. Two
   rules for one behaviour is how the two surfaces drift. DaisiGit #93. */
.ce-table-scroll,
.ce-paper .tableWrapper {
    overflow-x: auto;
}

/* Diagrams render via Mermaid into inline SVG. Center within the paper width
   and constrain so the diagram never overflows the page on print. */
.ce-paper .ce-diagram {
    margin: 1.5rem 0;
    text-align: center;
}
.ce-paper .ce-diagram svg {
    max-width: 100%;
    height: auto;
}
.ce-paper .ce-diagram-source {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 0.85rem;
    background: rgba(122, 90, 42, 0.05);
    border: 1px dashed var(--ce-rule, #d3c4a3);
    padding: 8px 12px;
    text-align: left;
    white-space: pre-wrap;
    color: var(--ce-ink-soft);
}

/* Lists — bullet and ordered. */
.ce-paper ul,
.ce-paper ol {
    margin: 0 0 1rem 0;
    padding-left: 2rem;
}
.ce-paper ul { list-style: disc; }
.ce-paper ol { list-style: decimal; }
.ce-paper li { margin: 0.15rem 0; }
.ce-paper li p { margin: 0; }

/* Inline code marker for `code` mark. */
.ce-paper code {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 0.92em;
    background: rgba(122, 90, 42, 0.08);
    border: 1px solid var(--ce-rule, #d3c4a3);
    border-radius: 3px;
    padding: 0 4px;
}

@media print {
    .ce-paper .ce-diagram { break-inside: avoid; page-break-inside: avoid; }
    .ce-paper table       { break-inside: avoid; page-break-inside: avoid; }
}

/* Public read view: discourage casual scraping by disabling selection / copy.
   Not a security control — a determined user can View Source — but it prevents
   accidental copying and the contextual copy menu. */
.ce-no-copy {
    user-select: none;
    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
}

.ce-toolbar {
    max-width: 760px;
    margin: 1.5rem auto 1rem auto;
    display: flex;
    gap: 0.5rem;
    align-items: center;
    flex-wrap: wrap;
    color: var(--ce-ink-soft);
    font-family: "Inter", "Helvetica Neue", Arial, sans-serif;
}

.ce-toolbar .ce-spacer { flex: 1; }
.ce-toolbar button {
    background: transparent;
    border: 1px solid var(--ce-rule);
    color: var(--ce-ink);
    padding: 0.35rem 0.75rem;
    border-radius: 3px;
    cursor: pointer;
    font: inherit;
    font-size: 0.9rem;
}
.ce-toolbar button:hover { background: rgba(122, 90, 42, 0.08); }
.ce-toolbar button:disabled { opacity: 0.5; cursor: not-allowed; }

.ce-menu {
    position: relative;
}
.ce-menu-summary {
    list-style: none;
    cursor: pointer;
    background: transparent;
    border: 1px solid var(--ce-rule);
    color: var(--ce-ink);
    padding: 0.35rem 0.75rem;
    border-radius: 3px;
    font-size: 0.9rem;
    user-select: none;
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
}
.ce-menu-summary::-webkit-details-marker { display: none; }
.ce-menu-summary::after {
    content: "";
    width: 0; height: 0;
    border-left: 4px solid transparent;
    border-right: 4px solid transparent;
    border-top: 4px solid var(--ce-ink-soft);
    margin-left: 0.15rem;
}
.ce-menu-summary:hover { background: rgba(122, 90, 42, 0.08); }
.ce-menu[open] > .ce-menu-summary {
    background: rgba(122, 90, 42, 0.12);
    border-color: var(--ce-accent);
}
.ce-menu-list {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    z-index: 20;
    min-width: 180px;
    list-style: none;
    margin: 0;
    padding: 4px;
    background: var(--ce-paper);
    border: 1px solid var(--ce-rule);
    border-radius: 4px;
    box-shadow: 0 6px 18px rgba(60, 40, 10, 0.18);
    display: flex;
    flex-direction: column;
}
.ce-menu-list li { display: block; }
.ce-menu-list button {
    width: 100%;
    text-align: left;
    background: transparent;
    border: none;
    color: var(--ce-ink);
    padding: 0.4rem 0.75rem;
    border-radius: 3px;
    cursor: pointer;
    font: inherit;
    font-size: 0.9rem;
}
.ce-menu-list button:hover:not(:disabled) { background: rgba(122, 90, 42, 0.10); }
.ce-menu-list button:disabled { opacity: 0.45; cursor: not-allowed; }
.ce-menu-sep { height: 1px; background: var(--ce-rule); margin: 4px 6px; }

.ce-toolbar-title {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    line-height: 1.15;
    gap: 2px;
}

/* The toolbar's h1. Deliberately identical to the <strong> it replaced — this is a semantics fix,
   not a visual one, and the toolbar has no room for heading-sized type. */
.ce-toolbar-heading {
    font-size: 1rem;
    font-weight: 700;
    margin: 0;
    line-height: inherit;
    font-family: inherit;
}

.ce-status {
    font-size: 0.85rem;
    color: var(--ce-ink-soft);
}

/* ──────────────────────────────────────────────────────────────────────────
   Messages a page shows after an action, announced with role="alert" or
   role="status".

   These carried `ce-admin-error` and `ce-admin-ok` and no stylesheet defined
   either, so "Saved." and "that category key is already taken" rendered as
   identical body text on /admin and /review. A screen reader announced them
   and a sighted operator could not tell the save had failed.

   Named `ce-form-*` rather than `ce-admin-*`: /review is not the admin area,
   and the old name told the next reader something untrue about where the class
   belonged.

   A left rule and a wash rather than colour alone — colour is not information
   on its own (WCAG 1.4.1), and the wash is what survives a high-contrast mode
   that drops backgrounds. `.ce-status` beside it stays as it is: that is the
   editor's inline save ticker, a different thing from a form's answer.
   ────────────────────────────────────────────────────────────────────────── */
.ce-form-error,
.ce-form-ok {
    margin: 0.5rem 0;
    padding: 0.5rem 0.75rem;
    border-left: 3px solid currentColor;
    border-radius: 3px;
    font-size: 0.9rem;
}
.ce-form-error {
    color: #a02a2a;
    background: rgba(160, 42, 42, 0.08);
}
.ce-form-ok {
    color: #2a6d2a;
    background: rgba(42, 109, 42, 0.08);
}

/* The editor's asset-budget notice. Advisory below the cap and a warning above
   it, and neither had a rule — so the `over` variant, whose whole purpose is to
   read differently, looked exactly like the one under the cap. */
.ce-budget-notice {
    margin: 0.5rem 0;
    font-size: 0.85rem;
    color: var(--ce-ink-soft);
}
.ce-budget-notice.over {
    padding: 0.5rem 0.75rem;
    border-left: 3px solid currentColor;
    border-radius: 3px;
    color: #a05a00;
    background: rgba(160, 90, 0, 0.08);
}

.ce-status.dirty { color: #a05a00; }
.ce-status.saving { color: #555; }
.ce-status.saved { color: #2a6d2a; }
.ce-status.error { color: #a02a2a; }

.ce-editor-host {
    outline: none;
}

.ce-editor-host .ProseMirror {
    outline: none;
    min-height: 60vh;
}

/* Skip-to-content link — visible only when focused, parchment palette. */
/* ──────────────────────────────────────────────────────────────────────────
   Form controls and form feedback (DaisiGit #105).

   One declaration, here, because these belong to the chrome rather than to a
   page. They used to be declared six times — once in explore.css and once in
   each of five pages' own <style> blocks — and a <style> in a `.razor` is not
   scoped: it renders into the document and applies to it like any other rule.
   Five global declarations of one class that never collided only because a
   reader is on one page at a time.

   They had drifted. Four were identical, explore.css differed, and
   Comments.razor said `font: inherit` — so its inputs took whatever `.ce-paper`
   computed to, which under the 720px breakpoint is exactly `1rem`. Exactly
   16px, clearing iOS Safari's zoom threshold by nothing at all, and no test
   could say so because an inherited size is not a size anything can read.

   16px comes from the narrow-viewport block further down, which this shares
   with every other control. SharedChromeStyleTests keeps the declarations here.
   ────────────────────────────────────────────────────────────────────────── */
.ce-input {
    font-family: inherit;
    font-size: 1rem;
    padding: 8px 12px;
    border: 1px solid var(--ce-rule);
    border-radius: 4px;
    background: var(--ce-paper);
    color: var(--ce-ink);
    width: 100%;
    max-width: 520px;
}

.ce-input:focus {
    outline: 2px solid var(--ce-accent);
    outline-offset: 1px;
    border-color: var(--ce-accent);
}

/* Vertical only: a horizontally resizable field breaks the column the forms
   are laid out in, and no field here is wide enough to want it. */
textarea.ce-input { resize: vertical; }

/* For a field whose content is short and known — a handle, a slug. A modifier
   rather than a second `.ce-input`, so consolidating the six declarations did
   not quietly widen onboarding's fields from 320 to 520 on the way past. */
.ce-input-narrow { max-width: 320px; }

/* Form feedback. Both sit on the light form pages; neither is a token, so
   neither adapts to a dark theme — which is correct today because no themed
   surface renders one, and is the thing to check first if that changes. */
.ce-error { color: #8a3a2e; margin-top: 0.5rem; font-size: 0.95rem; }
.ce-success { color: #2a6d2a; margin-top: 0.5rem; font-size: 0.95rem; }

.ce-skip-link {
    position: absolute;
    left: -10000px;
    top: 0;
    background: var(--ce-paper);
    color: var(--ce-ink);
    padding: 0.5rem 1rem;
    border: 1px solid var(--ce-rule);
    border-radius: 3px;
    z-index: 100;
}
.ce-skip-link:focus {
    left: 0.5rem;
    top: 0.5rem;
}

/* Drop-cap on the paragraph that opens a chapter — elegant in long-form, and for a long time
   entirely theoretical. This selector used to read
   `section[data-block-type="chapter"] > h2 + p::first-letter` and matched nothing in any book: the
   heading level comes from the AST (a chapter under a volume is an h3), chapter prose sits inside a
   nested section so the adjacency never held, and the first <p> of an imported chapter is usually
   empty — which no selector can skip. The reader marks the opener now; see ChapterOpeners. */
.ce-paper p.ce-chapter-opener::first-letter {
    font-size: 2.6em;
    font-weight: 600;
    float: left;
    line-height: 1;
    margin: 0.18em 0.08em 0 0;
    color: var(--ce-accent);
}

/* Honour reader preference for reduced motion. */
@media (prefers-reduced-motion: reduce) {
    * { animation: none !important; transition: none !important; }
}

/* Audio spend, beside the render button (CAST-PLAN 14i). Quiet by default — this is a figure to
   glance at before pressing Render again, not a warning in itself. The escalation is the notice
   below it, which appears only past the guideline in AudioSpendBudget. */
.ce-audio-spend {
    display: inline-flex;
    align-items: baseline;
    gap: 0.35rem;
    padding: 0.35rem 0.5rem;
    font-size: 0.85rem;
    color: var(--ce-ink-soft, #6b5a3e);
    white-space: nowrap;
}
.ce-audio-spend-amount {
    font-weight: 600;
    color: var(--ce-ink, #2e2a24);
    font-variant-numeric: tabular-nums;
}
.ce-audio-spend-none { font-style: italic; }

/* Manuscript size against decision #22's soft 5 MB guideline. Amber, never red: the cap is
   advisory, saving is not blocked, and a colour that reads as failure would contradict the words.
   Sits beside the save status, so it wraps rather than stretching the toolbar. */
.ce-size-notice {
    max-width: 46ch;
    padding: 2px 8px;
    border-radius: 6px;
    border: 1px solid #b58a1b;
    background: rgba(201, 162, 39, 0.12);
    color: #6b5310;
    font-size: 0.8rem;
    line-height: 1.4;
    white-space: normal;
}
.ce-size-notice.is-over {
    border-color: #a8701a;
    background: rgba(201, 122, 39, 0.16);
}

/* Offered when the automatic retries are spent. Deliberately a button rather than a link: it does
   something to the document's state, and it is the one control in the toolbar a writer might be
   reaching for in a hurry. */
.ce-retry-save {
    margin-left: 0.5rem;
    padding: 2px 10px;
    border: 1px solid var(--ce-accent, #7a5a2a);
    border-radius: 4px;
    background: var(--ce-accent, #7a5a2a);
    color: var(--ce-bg, #f7f1e3);
    font-family: inherit;
    font-size: 0.8rem;
    cursor: pointer;
}
.ce-retry-save:hover { background: var(--ce-accent-bold, #5a3f15); border-color: var(--ce-accent-bold, #5a3f15); }

/* Two editors on the same passage. Inset and quieter than the suggestion it sits under: it is
   context for a decision, not a second decision — the accept and reject buttons stay with the
   suggestion the writer is looking at. */
.ce-suggestion-rivals {
    margin: 0.5rem 0 0 1rem;
    padding: 0.5rem 0.75rem;
    border-left: 3px solid var(--ce-rule, #d3c4a3);
    background: rgba(201, 162, 39, 0.07);
    font-size: 0.92rem;
}
.ce-suggestion-rivals > strong { font-size: 0.85rem; letter-spacing: 0.03em; }
.ce-suggestion-rival { margin-top: 0.35rem; }
.ce-suggestion-rival blockquote { margin: 0.15rem 0 0 0; }

/* The PDF entry in the export dialog. Unlike the four link-shaped ones it holds controls, so it is
   a block rather than an anchor — decision #11 makes what goes into the file a choice at export
   time, and the choice has to sit with the format it belongs to. */
.ce-export-pdf {
    display: block;
    cursor: default;
}
.ce-export-option {
    display: block;
    margin: 0.4rem 0 0;
    font-size: 0.9rem;
    font-weight: 400;
}
.ce-export-editors {
    margin: 0.2rem 0 0.5rem 1.2rem;
    padding-left: 0.6rem;
    border-left: 2px solid var(--ce-rule, #d3c4a3);
}
.ce-export-pdf .ce-btn { margin-top: 0.6rem; }


/* The warning that the running total alone could not give. Quiet by default and louder past the
   guideline, but never red: the cap is advisory, nothing is blocked, and a colour that says
   "failure" would contradict the words next to it. Wraps rather than staying on one line — it is a
   sentence, not a figure. */
.ce-audio-spend-notice {
    flex-basis: 100%;
    margin: 0.35rem 0 0;
    padding: 0.4rem 0.6rem;
    border-radius: 6px;
    font-size: 0.85rem;
    line-height: 1.45;
    border: 1px solid #b58a1b;
    background: rgba(201, 162, 39, 0.12);
    color: #6b5310;
    white-space: normal;
}
.ce-audio-spend-notice.is-over {
    border-color: #a8701a;
    background: rgba(201, 122, 39, 0.16);
}

/* The cast came from the placeholder rather than a model. Its own class rather than the amber
   spend notice's: reusing that one made `AudioSpendWarningPageTests` fail, quite correctly — it
   asserts no spend warning appears on a project well inside its budget, and a notice about
   inference is not a notice about money however similar it looks. */
.ce-cast-stub-notice {
    flex-basis: 100%;
    margin: 0.35rem 0 0;
    padding: 0.4rem 0.6rem;
    border-radius: 6px;
    font-size: 0.85rem;
    line-height: 1.45;
    border: 1px solid #b58a1b;
    background: rgba(201, 162, 39, 0.12);
    color: #6b5310;
    white-space: normal;
}

/* The same for the Ask AI dialog (DaisiGit #128). Its own class rather than the cast page's: the
   dialog sets its body text at 0.9rem and a notice a shade smaller than the prose around it reads
   as a footnote, which is the opposite of the point. */
.ce-ai-stub-notice {
    margin: 0.35rem 0 0;
    padding: 0.4rem 0.6rem;
    border-radius: 6px;
    font-size: 0.9rem;
    line-height: 1.45;
    border: 1px solid #b58a1b;
    background: rgba(201, 162, 39, 0.12);
    color: #6b5310;
    white-space: normal;
}

/* What Ignore actually did, on the card of a character it was used on (DaisiGit #111). Quieter
   than the amber notice above: that one warns about money or an unintelligible render, this one
   states the scope of a choice the writer has already made deliberately. Same measurements so the
   two read as one family when both are on the page. */
.ce-cast-ignored-note {
    margin: 0.35rem 0 0;
    padding: 0.4rem 0.6rem;
    border-radius: 6px;
    font-size: 0.85rem;
    line-height: 1.45;
    border: 1px solid var(--ce-rule, #d3c4a3);
    background: rgba(0, 0, 0, 0.03);
    color: var(--ce-ink-soft, #5b5344);
    white-space: normal;
}

/* "How it's being listened to" — CAST-PLAN 14i. Three short rankings rather than a table: the
   counts are only useful next to what they mean, and a wall of numbers gets skipped. */
.ce-playback-insights { margin: 1.5rem 0; }
.ce-playback-insight-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: 1rem;
}
.ce-playback-insight h3 { font-size: 0.95rem; margin: 0 0 0.15rem; }
.ce-playback-insight-why { font-size: 0.8rem; margin: 0 0 0.5rem; }
.ce-playback-insight-none { font-size: 0.85rem; font-style: italic; margin: 0; }
.ce-playback-insight-list {
    margin: 0;
    padding-left: 1.2rem;
    font-size: 0.85rem;
}
.ce-playback-insight-list li {
    display: flex;
    justify-content: space-between;
    gap: 0.5rem;
    padding: 0.1rem 0;
}
.ce-playback-insight-scene {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.ce-playback-insight-count {
    font-variant-numeric: tabular-nums;
    font-weight: 600;
}


/* The prose behind a timeline observation, shown under it in the Cast review list.
   Quiet by design: it is the justification for the line above, not a second line. */
.ce-timeline-evidence {
    margin: 4px 0 0;
    padding: 2px 0 2px 10px;
    border-left: 2px solid rgba(120, 100, 70, 0.35);
    font-size: 0.85em;
    font-style: italic;
    opacity: 0.85;
}


/* Marks a suggestion as coming from an AI account (README's decision on AI participants).
   A label, not a warning: an AI collaborator is a legitimate participant under the same trust
   model as anyone else — the writer simply gets to know which they are talking to. */
.ce-suggestion-ai {
    font-weight: 600;
    letter-spacing: 0.04em;
}

/* Where a dropped picture is going, while it is still on its way (DaisiGit issue #41).
   Sized roughly like a small figure so the page does not jump when the real one replaces it, and
   drawn in the same dashed-outline language as an empty slot rather than as an error. */
.ce-image-pending {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    margin: 1rem 0;
    padding: 1.25rem 1rem;
    border: 2px dashed var(--ce-rule, #d3c4a3);
    border-radius: 4px;
    background: rgba(211, 196, 163, 0.12);
    color: var(--ce-ink-soft, #4a3f33);
    font-size: 0.9rem;
    font-style: italic;
    user-select: none;
}
/* Failure is a solid border and upright text: the dashed box says "working", and a failure that
   kept saying it would be the same lie the silence was. */
.ce-image-pending.is-failed {
    border-style: solid;
    border-color: #a3402c;
    background: rgba(163, 64, 44, 0.08);
    color: #7d3122;
    font-style: normal;
}
.ce-image-pending-dismiss {
    flex: none;
    padding: 0.3rem 0.7rem;
    border: 1px solid currentColor;
    border-radius: 3px;
    background: transparent;
    color: inherit;
    font: inherit;
    font-size: 0.85rem;
    cursor: pointer;
}
.ce-image-pending-dismiss:hover { background: rgba(163, 64, 44, 0.12); }

/* A suggestion that can no longer be applied (DaisiGit issue #46). Stated where the writer decides,
   not as an error after they have. Quiet rather than alarming: nothing is broken, the paragraph
   simply moved on. */
.ce-suggestion-stale {
    margin: 0.5rem 0 0;
    padding: 0.5rem 0.75rem;
    border-left: 3px solid var(--ce-rule, #d3c4a3);
    background: rgba(211, 196, 163, 0.12);
    font-size: 0.85rem;
    color: var(--ce-ink-soft, #4a3f33);
}
.ce-suggestion-stale em {
    display: block;
    margin-top: 0.35rem;
    color: var(--ce-ink, #2e2a24);
}

/* ──────────────────────────────────────────────────────────────────────────
   Text boxes on a phone.

   iOS Safari zooms into a focused form control whose font is under 16 CSS
   pixels, and does not zoom back out. Text inputs and textareas certainly;
   selects are held to the same threshold on the same rule of thumb. Tap the reply box under a comment and
   the page jumps to about 140% and slides sideways; type, dismiss the
   keyboard, and you are left on a manuscript you now have to pinch and pan.
   Invisible on every desktop browser, which is why it survived.

   16px exactly, not 1rem: the threshold is an absolute number of CSS pixels,
   and if the root font size were ever changed 1rem would quietly stop meeting
   it while still reading as the same rule.

   Only at narrow viewports, because nothing zooms on a desktop and the denser
   type these pages are designed around costs nothing there.

   Listed rather than a blanket `input, textarea, select` rule: that has one
   element of specificity and would lose to `.ce-thread-reply textarea`, which
   has two. Naming them is also what makes the stylesheet say what it is
   overriding. MobileInputZoomTests reads the controls out of the markup, so a
   new field with a small font fails without anyone having to remember this
   block exists.
   ────────────────────────────────────────────────────────────────────────── */
@media (max-width: 720px) {
    .ce-thread-reply textarea,
    .ce-extract-transcript-picker select,
    .ce-invite-form .ce-input,
    .ce-descriptor-input,
    .ce-role-picker,
    .ce-toc-search-input {
        font-size: 16px;
    }
}
/* `.ce-style-pick` belongs in this list by rights and cannot live here: it is declared in
   NewProject.razor's own <style> block, which renders after the shell's <link> elements, so at equal
   specificity the page wins and a rule added here would do nothing. Its override sits beside it,
   in that page. */

/* ──────────────────────────────────────────────────────────────────────────
   Touch targets in the comment thread (DaisiGit #95).

   The thread aside is the whole of commenting on a phone, and its controls
   were 22-25px. Close is a bare × sitting 6.4px from Archive, so a mis-tap
   resolves somebody's conversation.

   44px is the floor `PlayerTransportFitTests` already holds the player's
   transport to, citing WCAG 2.5.5, and the argument is the same one: on a
   phone these are the only way to do the thing at all.

   Declared as a minimum rather than reached by padding, because a button's
   rendered height depends on the font's line box, which a stylesheet does not
   state — a floor is a promise a browser keeps, where padding arithmetic is an
   estimate that happens to hold.

   Narrow viewports only: nothing on a desktop needs a 44px target, and growing
   every button there would be a redesign bought with nothing.

   The thread markers are deliberately left at 24x24 — exactly the WCAG 2.5.8
   (AA) minimum, so they pass. They are positioned against the blocks they
   anchor to, so at 44px markers on adjacent paragraphs would overlap each
   other, and that is not a trade to make without someone looking at it.
   ────────────────────────────────────────────────────────────────────────── */
@media (max-width: 720px) {
    .ce-btn,
    .ce-thread-aside-archive,
    /* The tray that tells a writer some comments lost their place. A disclosure
       button with a sentence in it, so height is the only dimension at risk. */
    .ce-orphan-tab {
        min-height: 44px;
    }
    /* The contents drawer's reorder buttons, for the reason the aside's controls
       are here: they are adjacent, and a mis-tap moves a chapter of somebody's
       manuscript rather than doing nothing. `min-width` as well, because their
       label is a single arrow glyph — a height floor alone leaves a tall thin
       target that is no easier to hit with a thumb. */
    .ce-toc-move-btn,
    /* Five adjacent one-glyph targets where a mis-tap sets a different public
       rating than the one intended, and nothing on the page says it changed. */
    .ce-star {
        min-width: 44px;
        min-height: 44px;
    }
    /* Same trade as the thread aside below: when the targets grow, the gap that
       keeps them distinguishable grows with them (WCAG 2.5.8). */
    .ce-toc-move { gap: 0.5rem; }
    .ce-thread-aside-close {
        min-width: 44px;
        min-height: 44px;
    }
    /* Adjacency is what makes a mis-tap here destructive, so the gap grows with
       the targets. WCAG 2.5.8 treats size and spacing as trading off. */
    .ce-thread-aside-head-actions {
        gap: 0.75rem;
    }
}

/* ──────────────────────────────────────────────────────────────────────────
   The admin area (DaisiGit #109).

   Ten classes, sixteen uses of `ce-admin-section` alone, and not one rule for
   any of them — so the operator's tool for curated tags, inference spend,
   dormant audio and stats rebuilds rendered as raw HTML: sections running into
   each other, an unruled table, and its buttons as bare browser chrome.

   Deliberately plainer than the reading surfaces. `/admin` is a console, not a
   manuscript: it wants the parchment palette so it belongs to the product, and
   none of the paper texture or serif measure, because the job here is reading
   figures in rows and deciding whether to act on somebody's project.

   The `ce-rebuild-*` classes are not styled and are listed in
   `TriagedPageStylingTests` instead: they are hooks `AdminRebuildControlTests`
   selects on, and their inputs take their appearance from `ce-input`.
   ────────────────────────────────────────────────────────────────────────── */
.ce-admin-section {
    max-width: 960px;
    margin: 1.5rem auto;
    padding: 1.25rem 1.5rem;
    border: 1px solid var(--ce-rule, #d3c4a3);
    border-radius: 4px;
    background: var(--ce-paper, #fbf6e6);
}

/* Title and its qualifier on one line, so the window selector and the "not
   played in 90 days" caveat sit with the heading they qualify rather than
   above the table they describe. Wraps, because those qualifiers are long. */
.ce-admin-section-head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.75rem;
    margin-bottom: 0.75rem;
}
.ce-admin-section-head h2 { margin: 0; font-size: 1.15rem; }

/* Rules on the rows, not a border round the table. Every table on this page is
   read across — which project is this figure for — and a borderless row makes
   that a tracking exercise. `collapse` so adjacent rules do not double. */
.ce-admin-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.9rem;
}
.ce-admin-table th,
.ce-admin-table td {
    padding: 0.4rem 0.6rem;
    border-bottom: 1px solid var(--ce-rule, #d3c4a3);
    text-align: left;
    vertical-align: top;
}
.ce-admin-table th {
    font-weight: 600;
    color: var(--ce-ink-soft, #4a3f33);
    white-space: nowrap;
}
.ce-admin-table tr:last-child td { border-bottom: none; }

.ce-admin-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-top: 0.75rem;
}

/* Three filters and a button. They combine, so they read as one row of one
   control each rather than as a form to fill in top to bottom. */
.ce-admin-rebuild {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 0.75rem;
    margin-bottom: 0.75rem;
}
.ce-admin-rebuild label { display: flex; flex-direction: column; gap: 0.2rem; }
.ce-admin-rebuild .ce-input { min-width: 12ch; }

/* ──────────────────────────────────────────────────────────────────────────
   The orphaned-comment tray (DaisiGit #109, Edit.razor).

   The one moment the editor tells a writer something was lost: a save moved
   the prose and these threads no longer have a paragraph to sit on. Six
   classes, none of which had a rule — so it rendered as a browser `<ul>` of
   disc bullets and default-indented `<blockquote>`s: chrome that appears
   nowhere else on a parchment surface, announcing a loss in the visual
   register of an unstyled form.

   Sized and centred like `.ce-toolbar`, so it sits in the editor's column
   rather than running the width of the window.

   Warning-coloured but not alarming. Nothing is broken and nothing is gone —
   the comments are still there and still readable, they have only lost their
   anchor — so this takes the same amber the stale-audio banner uses rather
   than the red reserved for a failure the writer has to act on.
   ────────────────────────────────────────────────────────────────────────── */
.ce-orphan-tray {
    max-width: 760px;
    margin: 0 auto 1rem auto;
}

.ce-orphan-list {
    list-style: none;
    margin: 0.5rem 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.ce-orphan-item {
    padding: 0.6rem 0.9rem;
    border: 1px solid var(--ce-rule, #d3c4a3);
    border-left: 3px solid #c9a227;
    border-radius: 3px;
    background: rgba(201, 162, 39, 0.06);
}

/* A blockquote, so the browser gives it 40px of margin on both sides. In a
   760px tray that makes the only surviving trace of the discussion the
   narrowest thing in it — and it leads, so it is the first thing to wrap. */
.ce-orphan-anchor {
    margin: 0 0 0.4rem;
    padding: 0;
    font-style: italic;
    color: var(--ce-ink-soft, #4a3f33);
}

.ce-orphan-msg {
    margin: 0.25rem 0 0;
    font-size: 0.9rem;
    line-height: 1.5;
}
.ce-orphan-msg strong { margin-right: 0.4rem; }

/* The invite lookup's preview card: who the writer is about to invite, with
   their reputation beside the name. Unstyled it was a run of inline text
   between the field and the Send button, which is the one place a writer
   checks they have the right person. */
.ce-candidate {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.5rem;
    margin: 0.5rem 0;
    padding: 0.5rem 0.75rem;
    border: 1px solid var(--ce-rule, #d3c4a3);
    border-radius: 3px;
    background: rgba(255, 255, 255, 0.4);
}
.ce-candidate .ce-invite-error { flex-basis: 100%; }

/* ──────────────────────────────────────────────────────────────────────────
   Rating an editor, on /review (DaisiGit #109).

   Five ★ buttons where the ones up to the chosen score carry `ce-star-on`.
   Neither class had a rule, so all five rendered as identical grey chrome
   buttons: a writer clicking three stars saw nothing change. `aria-pressed`
   told a screen reader and a sighted user got no feedback at all — and
   decision #27 makes the rating public and unhidable, so this is the one
   control in the product where an unnoticed mis-click is permanent.

   The sweep could not see these because the class is entirely conditional and
   the control sweep's attribute reader stopped at the first quote inside the
   interpolation. It reads them now, via `RazorClasses`.

   Colour is not the only difference — the unchosen stars are outlined rather
   than filled, because "which of five identical shapes is darker" is exactly
   the judgement colour alone asks of someone who cannot make it (WCAG 1.4.1).
   ────────────────────────────────────────────────────────────────────────── */
.ce-stars { display: flex; gap: 0.25rem; margin: 0.5rem 0; }

.ce-star {
    background: transparent;
    border: none;
    padding: 0.2rem 0.3rem;
    font-size: 1.5rem;
    line-height: 1;
    cursor: pointer;
    /* The outline of a star, so an unchosen one differs in shape and not only
       in shade. `color: transparent` fills it with nothing; the stroke draws it. */
    color: transparent;
    -webkit-text-stroke: 1px var(--ce-ink-soft, #4a3f33);
}
.ce-star-on {
    color: var(--ce-accent-bold, #7a5a2a);
    -webkit-text-stroke: 1px var(--ce-accent-bold, #7a5a2a);
}
.ce-star:hover { color: var(--ce-accent, #a08048); -webkit-text-stroke-color: var(--ce-accent, #a08048); }

.ce-rating-prompt,
.ce-finish-collab {
    margin: 1rem 0;
    padding: 0.75rem 1rem;
    border: 1px solid var(--ce-rule, #d3c4a3);
    border-radius: 4px;
    background: rgba(255, 255, 255, 0.4);
}
.ce-rating-prompt h3 { margin: 0 0 0.25rem; font-size: 1.05rem; }

/* Both sections hold a bare <button> — no class, so nothing reached them: the
   control sweep only examines controls that carry one. Found by looking at the
   rendered page rather than by a test, which is the second time that has been
   the way (see the ce-callout-warn note in TECH-STACK).

   The child combinator matters and is not tidiness. Written as a descendant,
   this rule (0,1,1) outranks `.ce-star` (0,1,0) and put the five identical
   grey boxes straight back — with every test still green, because both star
   classes did have rules. Caught by looking at the rendered page. The submit
   button is a direct child; the stars sit inside `.ce-stars`. */
.ce-rating-prompt > button,
.ce-finish-collab > button {
    padding: 0.4rem 0.9rem;
    border: 1px solid var(--ce-rule, #d3c4a3);
    border-radius: 3px;
    background: transparent;
    color: var(--ce-ink);
    font: inherit;
    font-size: 0.9rem;
    cursor: pointer;
}
.ce-rating-prompt > button:hover:not(:disabled),
.ce-finish-collab > button:hover:not(:disabled) { background: rgba(122, 90, 42, 0.08); }
.ce-rating-prompt > button:disabled,
.ce-finish-collab > button:disabled { opacity: 0.55; cursor: default; }

/* Disabled states for the two remaining control families that can be disabled
   and had no way to show it (DaisiGit #109 family).

   `ce-card-menu-item` is the per-character menu on the cast page — Delete and
   Clear descriptor, disabled while an extraction runs, which is exactly when a
   writer is most likely to try them. */
.ce-card-menu-item:disabled { opacity: 0.5; cursor: not-allowed; }

/* Two bare buttons whose containers style them, named here because a classless
   control cannot be reached by its own class and is therefore invisible to the
   control sweep — the blind spot that let two unstyled buttons ship on /review
   until somebody looked at the rendered page. */
.ce-comment-form button:disabled,
.ce-admin-actions button:disabled { opacity: 0.55; cursor: not-allowed; }

/* ──────────────────────────────────────────────────────────────────────────
   The reader's editorial controls (DaisiGit #109, Read.razor).

   Phase 3's editorial toggle and per-editor filter, and neither class had a
   rule. Rendering the page showed what that cost, which reading the CSS did
   not: the controls inherit `.ce-paper` — the manuscript's serif face at body
   size — and sit hard against the first paragraph. On a *reading* surface that
   is the whole problem: nothing tells a reader where the apparatus ends and
   the book begins.

   A face of their own, then, matching `.ce-toolbar`'s: this is chrome about
   the text, not text. And a rule under them, because the gap alone is what a
   paragraph break looks like.
   ────────────────────────────────────────────────────────────────────────── */
.ce-editorial-controls {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem 1rem;
    margin-bottom: 1.25rem;
    padding-bottom: 0.6rem;
    border-bottom: 1px solid var(--ce-rule, #d3c4a3);
    font-family: "Inter", "Helvetica Neue", Arial, sans-serif;
    font-size: 0.85rem;
    color: var(--ce-ink-soft, #4a3f33);
}

/* Each label holds a checkbox and a name, and as an inline box the row wrapped
   *between* them — with three editors, "Wren Halloway" landed on its own line
   away from the box that toggles it. inline-flex keeps the pair together and
   makes the whole label the target. */
.ce-editorial-controls label {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    cursor: pointer;
}

/* The per-editor filter is subordinate to the toggle above it — it only exists
   while that one is on — so it reads as a group hanging off it rather than as
   three more peers. */
.ce-editorial-filter {
    display: inline-flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.35rem 0.75rem;
    padding-left: 0.75rem;
    border-left: 1px solid var(--ce-rule, #d3c4a3);
}

/* ──────────────────────────────────────────────────────────────────────────
   The cast page's extract dialog and spend line (DaisiGit #109, Cast.razor).

   Rendering the dialog showed the field row's cost, which reading it did not:
   "From chapter [box] To chapter" filled the first line and the second box
   wrapped onto the next — directly under the *From* control. So the obvious
   place to type the end of a chapter range was the box for its start, and the
   extraction that follows is minutes of inference against whatever was typed.

   The same wrap the reader's per-editor filter had, and it costs more here.
   ────────────────────────────────────────────────────────────────────────── */
.ce-form-row {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem 1.25rem;
}
.ce-form-row label {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
}
.ce-form-row input { width: 7ch; }

/* Cancel and Extract sat hard against the fieldset above them, reading as one
   more row of the form rather than as the decision about it. Right-aligned
   with the primary last, which is where a dialog's confirm belongs. */
.ce-modal-actions {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: 0.5rem;
    margin-top: 1rem;
}

/* "$4.12" is styled and "on 318 renders" was not, so the qualifier arrived at
   the same size and weight as the figure — which is the one thing on that line
   a writer is reading for. */
.ce-audio-spend-detail {
    font-size: 0.85rem;
    color: var(--ce-ink-soft, #4a3f33);
}

/* The "… 12 lines unchanged …" divider between diff hunks (DaisiGit #109).
   Unstyled it was a line of ordinary text in the middle of a diff — the one
   place a reader is scanning for what *did* change, so a sentence that is not
   part of the manuscript has to say so by looking different. */
.ce-diff-gap {
    text-align: center;
    font-size: 0.8rem;
    color: var(--ce-ink-soft, #4a3f33);
    font-style: italic;
    padding: 0.35rem 0;
    border-top: 1px dashed var(--ce-rule, #d3c4a3);
    border-bottom: 1px dashed var(--ce-rule, #d3c4a3);
    margin: 0.5rem 0;
}
