/* ===========================================================================
   progress.css — Horizon redesign for /progress
   Phase 4a of 2026-04-19 Progress Page Horizon Redesign
   Scope: every selector is scoped to `.progress-v2` except keyframes and
   the `body.settings-hydrated` / `body.transitions-enabled` rules whose
   classes toggle on <body>.
   =========================================================================== */

/* Aliases onto the canonical design tokens (_head_styles.html /
   docs/DESIGN_SYSTEM.md). progress.css predates the token system and named
   the same values independently; keeping these as aliases means a palette
   change lands here for free. Values without a canonical token yet
   (raised bg, faint text ramp, mono font) stay literal. */
:root {
    --bg:           var(--color-bg-base, #09090B);
    --bg-raised:    #111113;
    --bg-card:      var(--color-bg-subtle, #18181B);
    --line:         var(--color-bg-muted, #27272A);
    --line-strong:  #3f3f46;

    --text:         var(--color-text-heading, #F5F5F4);
    --text-body:    var(--color-text-body, #D6D3D1);
    --text-muted:   var(--color-text-muted, #A8A29E);
    --text-faint:   #71717a;
    --text-dim:     #52525b;

    --teal:         var(--color-primary, #0D9488);
    --teal-2:       var(--color-primary-light, #14B8A6);
    --teal-3:       #5eead4;

    --serif:        var(--font-heading, "DM Serif Display", Georgia, serif);
    --ui:           var(--font-body, "Plus Jakarta Sans", system-ui, sans-serif);
    --mono:         "JetBrains Mono", ui-monospace, monospace;
}

/* ---------------------------------------------------------------------------
   Keyframes (global — referenced by scoped classes below)
   --------------------------------------------------------------------------- */
@keyframes pulse     { 50% { opacity: 0.4; transform: scale(0.85); } }
@keyframes shimmer   { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }
@keyframes slideIn   { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }

/* ---------------------------------------------------------------------------
   Toggle anti-flash + transition gating (from legacy progress.html lines 75-113)
   The body class toggles are handled at document level, so these selectors
   must stay global. Rules preserved verbatim.
   --------------------------------------------------------------------------- */
.toggle-bg {
    position: relative;
    width: 44px;
    height: 24px;
    background-color: #4b5563;
    border-radius: 9999px;
    cursor: pointer;
    visibility: hidden; /* Hidden until hydrated to prevent visual flash */
}
.settings-hydrated .toggle-bg {
    visibility: visible;
}
.toggle-bg::after {
    content: '';
    position: absolute;
    top: 2px;
    left: 2px;
    width: 20px;
    height: 20px;
    background-color: white;
    border-radius: 9999px;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
}
/* Only enable transitions after page has loaded (prevents animation on page load) */
.transitions-enabled .toggle-bg {
    transition: background-color 0.2s ease-in-out;
}
.transitions-enabled .toggle-bg::after {
    transition: transform 0.2s ease-in-out;
}
#audio-toggle-progress:checked + .toggle-bg,
#automatic-mode-toggle:checked + .toggle-bg {
    background-color: #0D9488;
}
#audio-toggle-progress:checked + .toggle-bg::after,
#automatic-mode-toggle:checked + .toggle-bg::after {
    transform: translateX(20px);
}

/* Chevron rotate helper used by progress-editors.js */
.rotate-180 { transform: rotate(180deg); }

/* ---------------------------------------------------------------------------
   Generation tips crossfade (from legacy progress.html lines 119-153, verbatim)
   --------------------------------------------------------------------------- */
.generation-tip-wrapper {
    position: relative;
    min-height: 20px;
}
#generation-tip-text {
    transition: opacity 0.3s cubic-bezier(0.25, 1, 0.5, 1),
                transform 0.3s cubic-bezier(0.25, 1, 0.5, 1);
}
#generation-tip-text.tip-fade-out {
    opacity: 0;
    transform: translateY(-4px);
}
#generation-tip-text.tip-fade-in {
    opacity: 0;
    transform: translateY(4px);
}
/* Nav row: prev chevron · dots · next chevron. Centered under the tip text,
   chevrons are ghost buttons that brighten on hover/focus. Each dot is
   clickable + keyboard-navigable so users can jump straight to any tip. */
.tip-nav {
    display: inline-flex;
    align-items: center;
    gap: 12px;
    margin-top: 10px;
}
.tip-nav-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    padding: 0;
    background: transparent;
    border: 0;
    color: rgba(255, 255, 255, 0.35);
    border-radius: 999px;
    cursor: pointer;
    transition: color 0.2s cubic-bezier(0.25, 1, 0.5, 1),
                background-color 0.2s cubic-bezier(0.25, 1, 0.5, 1);
}
.tip-nav-btn:hover {
    color: rgba(94, 234, 212, 0.95);
    background-color: rgba(13, 148, 136, 0.1);
}
.tip-nav-btn:focus-visible {
    outline: 2px solid rgba(94, 234, 212, 0.6);
    outline-offset: 2px;
    color: rgba(94, 234, 212, 0.95);
}
.tip-dots {
    display: inline-flex;
    gap: 6px;
}
#generation-tip-dots .tip-dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background-color: rgba(255, 255, 255, 0.15);
    transition: background-color 0.3s cubic-bezier(0.25, 1, 0.5, 1),
                transform 0.2s cubic-bezier(0.25, 1, 0.5, 1);
    cursor: pointer;
}
#generation-tip-dots .tip-dot:hover {
    background-color: rgba(94, 234, 212, 0.35);
    transform: scale(1.15);
}
#generation-tip-dots .tip-dot:focus-visible {
    outline: 2px solid rgba(94, 234, 212, 0.6);
    outline-offset: 2px;
}
#generation-tip-dots .tip-dot.active {
    background-color: rgba(13, 148, 136, 0.7);
}
@media (prefers-reduced-motion: reduce) {
    #generation-tip-text { transition: none; }
    #generation-tip-dots .tip-dot { transition: none; }
}

/* ===========================================================================
   .progress-v2 wrapper — all new styling below is scoped to this class.
   Existing navbar and app chrome live outside and remain untouched.
   =========================================================================== */
.progress-v2 {
    font-family: var(--ui);
    color: var(--text);
    font-size: 14px;
    line-height: 1.5;
    -webkit-font-smoothing: antialiased;
}

.progress-v2 *,
.progress-v2 *::before,
.progress-v2 *::after { box-sizing: border-box; }

/* ---------------------------------------------------------------------------
   Page head
   --------------------------------------------------------------------------- */
.progress-v2 .page-head {
    font-family: var(--serif);
    font-size: 44px;
    font-weight: 400;
    text-align: center;
    letter-spacing: -0.5px;
    margin: 0 0 6px;
    line-height: 1.05;
    color: var(--text);
}
.progress-v2 .page-sub {
    text-align: center;
    color: var(--text-muted);
    font-size: 14px;
    margin: 0 0 36px;
}
.progress-v2 .page-sub .book-title {
    color: var(--text-body);
    font-style: italic;
    font-family: var(--serif);
}

/* ---------------------------------------------------------------------------
   Settings bar (replaces #generation-settings-container styling)
   --------------------------------------------------------------------------- */
.progress-v2 .settings {
    background: var(--bg-card);
    border: 1px solid var(--line);
    border-radius: 12px;
    padding: 12px 18px;
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 16px;
    font-size: 13px;
}
.progress-v2 .settings h3 {
    margin: 0;
    font-size: 14px;
    font-weight: 600;
    letter-spacing: -0.1px;
    color: var(--text);
}
.progress-v2 .settings .ctrls {
    display: flex;
    gap: 18px;
    align-items: center;
}
.progress-v2 .settings .ctrl {
    display: flex;
    gap: 8px;
    align-items: center;
    color: var(--text-muted);
}
.progress-v2 .settings .ctrl .i {
    width: 14px; height: 14px; border-radius: 50%;
    border: 1px solid var(--text-dim); color: var(--text-dim);
    font-size: 10px;
    display: inline-flex; align-items: center; justify-content: center;
}

/* Alternative pill toggle shown in the design reference — supports the new
   `.toggle` markup without disturbing existing .toggle-bg toggles. */
.progress-v2 .toggle {
    width: 36px; height: 20px;
    background: #4b5563;
    border-radius: 9999px;
    position: relative;
    display: inline-block;
    transition: background .2s;
}
.progress-v2 .toggle::after {
    content: "";
    position: absolute;
    top: 2px; left: 2px;
    width: 16px; height: 16px;
    background: #fff;
    border-radius: 50%;
    transition: transform .2s;
}
.progress-v2 .toggle.on { background: var(--teal); }
.progress-v2 .toggle.on::after { transform: translateX(16px); }

/* ---------------------------------------------------------------------------
   HERO section
   --------------------------------------------------------------------------- */
.progress-v2 .hero {
    background: var(--bg-raised);
    border: 1px solid var(--line);
    border-radius: 16px;
    padding: 40px 40px 28px;
    text-align: center;
    position: relative;
    overflow: hidden;
}
.progress-v2 .hero::before {
    content: "";
    position: absolute;
    inset: 0;
    background:
        radial-gradient(600px 200px at 50% -20%, rgba(94, 234, 212, 0.08), transparent 60%),
        radial-gradient(400px 200px at 50% 120%, rgba(13, 148, 136, 0.07), transparent 60%);
    pointer-events: none;
}
.progress-v2 .hero > * { position: relative; z-index: 1; }

.progress-v2 .hero-eyebrow {
    font-size: 11px;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    color: var(--teal-3);
    margin-bottom: 14px;
    font-weight: 600;
    display: inline-flex;
    align-items: center;
    gap: 10px;
}
.progress-v2 .hero-eyebrow .dot {
    width: 6px; height: 6px;
    border-radius: 50%;
    background: var(--teal-3);
    box-shadow: 0 0 10px var(--teal-3);
    animation: pulse 1.6s ease-in-out infinite;
}

.progress-v2 .hero-big {
    font-family: var(--serif);
    font-size: 88px;
    color: var(--text);
    line-height: 1;
    font-variant-numeric: tabular-nums;
    letter-spacing: -3px;
}
.progress-v2 .hero-big .int {
    display: inline-block;
    min-width: 100px;
    text-align: right;
}
.progress-v2 .hero-big .dec {
    font-size: 42px;
    color: var(--text-faint);
    letter-spacing: -1px;
}
.progress-v2 .hero-big .pct {
    font-size: 36px;
    color: var(--text-dim);
    margin-left: 4px;
}

.progress-v2 .hero-status {
    font-family: var(--serif);
    font-style: italic;
    color: var(--text-body);
    font-size: 18px;
    margin: 12px auto 28px;
    max-width: 520px;
    min-height: 26px;
    transition: opacity .25s;
}

.progress-v2 .horizon {
    position: relative;
    height: 2px;
    background: var(--line-strong);
    border-radius: 1px;
    margin: 0 4px 14px;
}
.progress-v2 .horizon-fill {
    position: absolute;
    left: 0; top: 0;
    height: 100%;
    background: linear-gradient(90deg, var(--teal), var(--teal-3));
    border-radius: 1px;
    transition: width 0.6s cubic-bezier(0.25, 1, 0.5, 1);
    box-shadow: 0 0 12px rgba(13, 148, 136, 0.5);
}
.progress-v2 .horizon-fill::after {
    content: "";
    position: absolute;
    right: -5px; top: -3px;
    width: 8px; height: 8px;
    border-radius: 50%;
    background: var(--teal-3);
    box-shadow: 0 0 16px var(--teal-3);
    animation: pulse 1.8s ease-in-out infinite;
}
.progress-v2 .horizon-labels {
    position: relative;
    height: 14px;
    font-size: 10px;
    letter-spacing: 0.15em;
    text-transform: uppercase;
    color: var(--text-dim);
    margin: 0 4px;
    font-weight: 500;
}
/* Anchor each label at its phase's start percentage so the bar-fill tip
   lands at the label matching the current eyebrow. Edge labels (spec, done)
   are pinned flush to the rail ends; interior labels are centered on their
   anchor. Thresholds mirror progress-horizon.js phase boundaries:
     0 → Spec · 8 → Metadata · 20 → Plot · 35 → Writing · 92 → Final · 100 → Done */
.progress-v2 .horizon-labels span {
    position: absolute;
    top: 0;
    transform: translateX(-50%);
    white-space: nowrap;
    transition: color .4s cubic-bezier(0.25, 1, 0.5, 1);
}
/* Edge anchors avoid overlap where phase thresholds cluster (spec/meta on the
   left, polish/done on the right). Interior labels stay centered on their
   phase mark so the bar tip sits symmetrically at the "now" label. */
/* Generation tips are an aside — they sit below the hero (when detail is
   closed) or below the manuscript card (when detail is open). Give them a
   generous top margin so they never crowd the element above, no matter
   which state the drawer is in. The drawer animation tweens margin-top on
   open/close; adding breathing room here keeps the gap readable in both. */
.progress-v2 #generation-tips {
    margin-top: 32px;
}
@media (max-width: 640px) {
    .progress-v2 #generation-tips {
        margin-top: 24px;
    }
}

.progress-v2 .horizon-labels span[data-phase="spec"]  { left: 0%;   transform: translateX(0); }
.progress-v2 .horizon-labels span[data-phase="meta"]  { left: 8%;   transform: translateX(0); }
.progress-v2 .horizon-labels span[data-phase="plot"]  { left: 20%; }
.progress-v2 .horizon-labels span[data-phase="write"] { left: 35%; }
.progress-v2 .horizon-labels span[data-phase="final"] { left: 92%;  transform: translateX(-100%); }
.progress-v2 .horizon-labels span[data-phase="done"]  { left: 100%; transform: translateX(-100%); }
.progress-v2 .horizon-labels .hit { color: var(--teal-3); }
.progress-v2 .horizon-labels .now { color: var(--text); font-weight: 700; }

.progress-v2 .hero-stats {
    margin-top: 32px;
    display: grid;
    grid-template-columns: 1fr 1fr 1fr;
    gap: 1px;                         /* divider trick: background line shows through */
    background: var(--line);
    border: 1px solid var(--line);
    border-radius: 10px;
    overflow: hidden;
}
.progress-v2 .hero-stat {
    background: var(--bg-raised);
    padding: 14px 16px 12px;
    text-align: center;
}
.progress-v2 .hero-stat .k {
    font-size: 10px;
    letter-spacing: 0.15em;
    text-transform: uppercase;
    color: var(--text-faint);
    margin-bottom: 6px;
    font-weight: 600;
}
.progress-v2 .hero-stat .v {
    font-family: var(--serif);
    font-size: 24px;
    color: var(--text);
    line-height: 1;
    font-variant-numeric: tabular-nums;
}
.progress-v2 .hero-stat .v small {
    font-family: var(--ui);
    font-size: 12px;
    color: var(--text-muted);
    font-weight: 400;
    margin-left: 2px;
}

.progress-v2 .detail-toggle-wrap { text-align: center; }
.progress-v2 .detail-toggle {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    margin: 18px auto 0;
    background: transparent;
    border: 1px solid var(--line);
    color: var(--text-muted);
    font-family: var(--ui);
    font-size: 12px;
    font-weight: 500;
    letter-spacing: 0.05em;
    padding: 8px 16px;
    border-radius: 999px;
    cursor: pointer;
    transition: all .15s;
}
.progress-v2 .detail-toggle:hover {
    border-color: var(--line-strong);
    color: var(--text-body);
}
.progress-v2 .detail-toggle .chev { transition: transform .2s; }
.progress-v2 .detail-toggle.open .chev { transform: rotate(180deg); }

/* ---------------------------------------------------------------------------
   Detail section (collapsible) + Manuscript card
   --------------------------------------------------------------------------- */
.progress-v2 .detail-section {
    overflow: hidden;
    transition:
        max-height .4s cubic-bezier(0.25, 1, 0.5, 1),
        opacity .3s,
        margin-top .3s;
    max-height: 0;
    opacity: 0;
    margin-top: 0;
}
.progress-v2 .detail-section.open {
    max-height: 4000px;
    opacity: 1;
    margin-top: 24px;
}

.progress-v2 .manuscript-card {
    background: var(--bg-raised);
    border: 1px solid var(--line);
    border-radius: 14px;
    overflow: hidden;
}

.progress-v2 .mn-head {
    padding: 16px 22px 12px;
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    border-bottom: 1px solid var(--line);
}
.progress-v2 .mn-head .t {
    font-size: 12px;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--text-faint);
    font-weight: 600;
}
.progress-v2 .mn-head .m {
    font-family: var(--mono);
    font-size: 11px;
    color: var(--text-muted);
}

.progress-v2 .chapters-list { padding: 4px 18px 6px; }

.progress-v2 .a-chapter {
    padding: 12px 4px;
    border-bottom: 1px dashed var(--line);
}
.progress-v2 .a-chapter:last-child { border-bottom: none; }

.progress-v2 .a-chead {
    display: grid;
    grid-template-columns: 52px 1fr auto;
    gap: 12px;
    align-items: center;
}
.progress-v2 .a-chead .n {
    font-family: var(--mono);
    font-size: 11px;
    color: var(--text-faint);
    letter-spacing: 0.05em;
    font-weight: 600;
    text-transform: uppercase;
}
.progress-v2 .a-chead .t {
    color: var(--text-body);
    font-family: var(--serif);
    font-style: italic;
    font-size: 15px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.progress-v2 .a-chead .s {
    font-family: var(--mono);
    font-size: 11px;
    color: var(--text-faint);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.progress-v2 .a-bar {
    height: 2px;
    background: var(--line);
    border-radius: 1px;
    overflow: hidden;
    margin-top: 8px;
}
.progress-v2 .a-bar > div {
    height: 100%;
    background: var(--teal);
    border-radius: 1px;
    transition: width 0.5s cubic-bezier(0.25, 1, 0.5, 1);
}

/* Chapter status modifiers */
.progress-v2 .a-chapter.done .a-chead .n { color: var(--teal-3); }
.progress-v2 .a-chapter.done .a-chead .t { color: var(--text-muted); }
.progress-v2 .a-chapter.done .a-chead .s::before { content: "\2713  "; color: var(--teal-3); }
.progress-v2 .a-chapter.done .a-chead .s { color: var(--text-muted); }
.progress-v2 .a-chapter.done .a-bar > div { background: var(--teal-3); width: 100% !important; }

.progress-v2 .a-chapter.active .a-chead .n { color: var(--text); }
.progress-v2 .a-chapter.active .a-chead .t { color: var(--text); }
.progress-v2 .a-chapter.active .a-bar > div {
    background: linear-gradient(90deg, var(--teal), var(--teal-3));
    background-size: 200% 100%;
    animation: shimmer 1.6s linear infinite;
    box-shadow: 0 0 8px rgba(94, 234, 212, 0.3);
}

.progress-v2 .a-chapter.pending .a-chead .n,
.progress-v2 .a-chapter.pending .a-chead .t,
.progress-v2 .a-chapter.pending .a-chead .s { color: var(--text-dim); }

/* Live scene strip inside active chapter */
.progress-v2 .a-scene {
    margin-top: 10px;
    margin-left: 64px;
    padding: 10px 14px;
    background: rgba(13, 148, 136, 0.07);
    border-left: 2px solid var(--teal-3);
    border-radius: 0 6px 6px 0;
    animation: slideIn .3s ease-out;
}
.progress-v2 .a-scene .sh {
    font-family: var(--mono);
    font-size: 10px;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--teal-3);
    font-weight: 600;
    margin-bottom: 4px;
    display: inline-flex;
    align-items: center;
    gap: 6px;
}
.progress-v2 .a-scene .sh::before {
    content: "";
    width: 5px; height: 5px;
    border-radius: 50%;
    background: var(--teal-3);
    animation: pulse 1.4s ease-in-out infinite;
}
.progress-v2 .a-scene .st {
    font-family: var(--serif);
    font-style: italic;
    color: var(--text);
    font-size: 14px;
    margin-bottom: 4px;
    line-height: 1.4;
}
/* Live scene event — one-line summary of WHAT this scene is doing, sourced
   from Stage 6 SceneBreakdown.event. Sits between the scene title (.st)
   and the live word count (.sw). Slightly brighter than .ch-summary
   because it describes the moment being written right now. */
.progress-v2 .a-scene .se {
    font-family: var(--ui);
    font-size: 12px;
    line-height: 1.45;
    color: var(--text-body);
    margin: 2px 0 6px;
    max-width: 56ch;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    text-overflow: ellipsis;
}
.progress-v2 .a-scene .sw {
    font-family: var(--mono);
    font-size: 11px;
    color: var(--text-muted);
}
.progress-v2 .a-scene .sw .wc { color: var(--teal-3); }

/* Chapter summary subtitle — sits between .a-chead and .a-bar, always visible
   when the chapter has a Stage-5 description. Indented to the same 64px gutter
   as .a-scene and .a-total for a clean vertical alignment. Two-line clamp keeps
   long descriptions bounded without server-side truncation. */
.progress-v2 .a-chapter .ch-summary {
    margin: 6px 0 0 64px;
    font-family: var(--ui);
    font-size: 12.5px;
    line-height: 1.45;
    color: var(--text-muted);
    letter-spacing: 0.005em;
    max-width: 56ch;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    text-overflow: ellipsis;
}
/* Status-dependent dimming so the summary tracks its chapter's lifecycle. */
.progress-v2 .a-chapter.pending .ch-summary { color: var(--text-dim); }
.progress-v2 .a-chapter.done .ch-summary    { color: var(--text-faint); }

/* Completed chapter summary */
.progress-v2 .a-total {
    margin-top: 6px;
    margin-left: 64px;
    font-family: var(--mono);
    font-size: 10px;
    color: var(--text-faint);
    letter-spacing: 0.02em;
}

/* Manuscript foot stats */
.progress-v2 .mn-foot {
    padding: 16px 22px;
    border-top: 1px solid var(--line);
    background: var(--bg);
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 16px;
}
/* Fixed 4-col (Scenes/Chapters/Pace/Words). */
.progress-v2 .mn-foot .s { text-align: left; }
.progress-v2 .mn-foot .k {
    font-size: 10px;
    letter-spacing: 0.15em;
    text-transform: uppercase;
    color: var(--text-faint);
    margin-bottom: 4px;
    font-weight: 600;
}
.progress-v2 .mn-foot .v {
    font-family: var(--serif);
    font-size: 22px;
    color: var(--text);
    line-height: 1;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}
.progress-v2 .mn-foot .v small {
    font-family: var(--ui);
    font-size: 11px;
    color: var(--text-muted);
    margin-left: 2px;
    font-weight: 400;
}

/* ---------------------------------------------------------------------------
   Editor overrides — preserve existing DOM/IDs, restyle via CSS only.
   `.progress-container` is the wrapper class used by every editor block.
   --------------------------------------------------------------------------- */
.progress-v2 .progress-container {
    background: var(--bg-raised);
    border: 1px solid var(--line);
    border-radius: 14px;
    backdrop-filter: none;  /* clear the old 10px blur */
}

.progress-v2 .book-spec-field {
    background-color: var(--bg-card);
    border: 1px solid var(--line);
    color: var(--text);
    border-radius: 6px;
    padding: 10px 12px;
    resize: none;
    overflow: hidden;
    scrollbar-width: none;
    -ms-overflow-style: none;
}
.progress-v2 .book-spec-field::-webkit-scrollbar { display: none; }
.progress-v2 .book-spec-field:focus {
    border-color: rgba(13, 148, 136, 0.5);
    box-shadow: 0 0 0 3px rgba(13, 148, 136, 0.3);
    outline: none;
}

/* Section titles inside every editor (h2) */
.progress-v2 .spec-editor h2 {
    font-family: var(--serif);
    font-size: 22px;
    text-align: center;
    color: var(--text);
    letter-spacing: -0.2px;
    font-weight: 400;
}

/* Editor visibility helper (preserved from legacy) */
.progress-v2 .spec-editor { display: none; }
.progress-v2 .spec-editor.active { display: block; }

/* Submit / approve buttons inside editors adopt teal tokens */
.progress-v2 .spec-editor button[type="submit"],
.progress-v2 #approve-metadata-cover-btn {
    background-color: var(--teal);
    color: #fff;
    border: none;
    border-radius: 10px;
    font-family: var(--ui);
    font-weight: 600;
    letter-spacing: 0.02em;
    transition: background-color 0.15s ease-in-out;
    cursor: pointer;
}
.progress-v2 .spec-editor button[type="submit"]:hover,
.progress-v2 #approve-metadata-cover-btn:hover {
    background-color: var(--teal-2);
}
.progress-v2 .spec-editor button[type="submit"]:disabled,
.progress-v2 #approve-metadata-cover-btn:disabled {
    opacity: 0.55;
    cursor: not-allowed;
}

/* Legacy striped progress-bar animation kept for any residual uses */
.progress-v2 .progress-bar { transition: width 0.5s ease; }
.progress-v2 .progress-detail { animation: progress-detail-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; }
@keyframes progress-detail-pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: .7; }
}
@keyframes progress-bar-animation {
    0%   { background-position: 0px 0; }
    100% { background-position: 30px 0; }
}
.progress-v2 .progress-animation {
    background-image: linear-gradient(
        135deg,
        rgba(255, 255, 255, 0.15) 25%,
        transparent 25%,
        transparent 50%,
        rgba(255, 255, 255, 0.15) 50%,
        rgba(255, 255, 255, 0.15) 75%,
        transparent 75%,
        transparent
    );
    background-size: 30px 30px;
    animation: progress-bar-animation 1s linear infinite;
    width: 100%;
    height: 100%;
}

/* ---------------------------------------------------------------------------
   Summary cards (#book-spec-summary / #metadata-cover-summary / #plot-summary)
   already use .progress-container as their wrapper; the rule above upgrades
   them. Fine-tune inner chrome here.
   --------------------------------------------------------------------------- */
.progress-v2 #book-spec-summary-header,
.progress-v2 #metadata-cover-summary-header,
.progress-v2 #plot-summary-header {
    cursor: pointer;
}
.progress-v2 #book-spec-summary-header h2,
.progress-v2 #metadata-cover-summary-header h2,
.progress-v2 #plot-summary-header h2 {
    font-family: var(--serif);
    font-weight: 400;
    color: var(--text);
    letter-spacing: -0.1px;
}
.progress-v2 #book-spec-summary-content,
.progress-v2 #metadata-cover-summary-content,
.progress-v2 #plot-summary-content {
    color: var(--text-body);
}

/* ---------------------------------------------------------------------------
   Tab buttons (carried over from legacy inline styles)
   --------------------------------------------------------------------------- */
.progress-v2 .tab-button {
    position: relative;
    transition: all 0.2s;
}
.progress-v2 .tab-button:hover { color: var(--teal-3); }
.progress-v2 .tab-content { transition: opacity 0.3s; }

/* ---------------------------------------------------------------------------
   Completion actions — style existing Tailwind buttons with teal tokens.
   Tailwind classes bg-teal-600 / hover:bg-teal-700 in _head_styles.html do
   most of the work; add token-driven radius + font-family for cohesion.
   --------------------------------------------------------------------------- */
.progress-v2 #completion-actions { text-align: center; }
.progress-v2 #completion-actions .bg-green-500 {
    background-color: var(--teal);
    color: #fff;
    border-radius: 999px;
    font-family: var(--ui);
    font-weight: 600;
    letter-spacing: 0.02em;
}
.progress-v2 #read-novel-btn,
.progress-v2 #novel-details-btn {
    border-radius: 10px;
    font-family: var(--ui);
    font-weight: 600;
    letter-spacing: 0.02em;
    transition: background-color 0.15s ease-in-out;
}

/* ---------------------------------------------------------------------------
   Tweaks panel — dev-only, hidden in prod but preserved for parity
   --------------------------------------------------------------------------- */
.progress-v2 .tweaks {
    position: fixed;
    bottom: 16px; right: 16px;
    background: var(--bg-raised);
    border: 1px solid var(--line-strong);
    border-radius: 10px;
    padding: 14px 16px;
    z-index: 100;
    font-family: var(--ui);
    font-size: 12px;
    color: var(--text-body);
    max-width: 240px;
    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
    display: none;
}
.progress-v2 .tweaks h5 {
    margin: 0 0 10px;
    font-family: var(--serif);
    font-weight: 400;
    font-size: 16px;
}
.progress-v2 .tweak-row {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 10px;
    margin: 8px 0;
}
.progress-v2 .tweak-row label {
    font-size: 11px;
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: 0.08em;
}
.progress-v2 .tweak-row input[type="range"] { flex: 1; accent-color: var(--teal); }
.progress-v2 .tweak-row select {
    background: var(--bg-card);
    border: 1px solid var(--line);
    color: var(--text-body);
    padding: 4px 6px;
    border-radius: 4px;
    font-family: var(--mono);
    font-size: 10px;
}
.progress-v2 .tweak-row input[type="checkbox"] { accent-color: var(--teal); }

/* ---------------------------------------------------------------------------
   Reduced motion
   --------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
    .progress-v2 .hero-eyebrow .dot,
    .progress-v2 .horizon-fill::after,
    .progress-v2 .a-chapter.active .a-bar > div,
    .progress-v2 .a-scene .sh::before {
        animation: none !important;
    }
    .progress-v2 .horizon-fill,
    .progress-v2 .a-bar > div {
        transition: none !important;
    }
}

/* ---------------------------------------------------------------------------
   Responsive (≤600px)
   --------------------------------------------------------------------------- */
@media (max-width: 600px) {
    /* Reduce horizontal padding on the page main when the redesigned
       wrapper is present — scoped to avoid leaking into other routes. */
    .progress-v2 { padding-left: 4px; padding-right: 4px; }
    .progress-v2 .page-head { font-size: 32px; }
    .progress-v2 .hero { padding: 30px 20px 20px; }
    .progress-v2 .hero-big { font-size: 64px; letter-spacing: -2px; }
    .progress-v2 .hero-big .dec { font-size: 28px; }
    .progress-v2 .hero-big .pct { font-size: 24px; }
    .progress-v2 .hero-stats { grid-template-columns: 1fr; }
    .progress-v2 .mn-foot { grid-template-columns: 1fr 1fr; }
    .progress-v2 .a-scene,
    .progress-v2 .a-total { margin-left: 0; }
}

/* =============== POLISH: A11y · Responsive · Performance =============== */
/*
  Palette contrast audit (WCAG AA target: 4.5:1 body / 3:1 large/UI)
  ---------------------------------------------------------------------
    --text       #F5F5F4 on --bg        #09090B      18.8:1  PASS (body+UI)
    --text-body  #D6D3D1 on --bg        #09090B      14.6:1  PASS (body+UI)
    --text-muted #A8A29E on --bg-raised #111113       7.9:1  PASS (body+UI)
    --text-faint #71717A on --bg-raised #111113       3.9:1  MARGINAL (UI only)
    --text-dim   #52525B on --bg-raised #111113       2.5:1  FAIL for text;
                                                             reserved for
                                                             decorative
                                                             dividers and
                                                             dormant state
                                                             paired with a
                                                             dashed border or
                                                             other non-color
                                                             signal.
    --teal       #0D9488 on --bg        #09090B       5.2:1  PASS (button fill)
    --teal-3     #5EEAD4 on --bg        #09090B      13.5:1  PASS (accent)
*/

/* ---------------------------------------------------------------------------
   1. Skip-to-content — visually hidden until keyboard focus reveals it.
   --------------------------------------------------------------------------- */
.skip-to-content {
    position: absolute;
    left: -9999px;
    top: auto;
    width: 1px;
    height: 1px;
    overflow: hidden;
    z-index: 9999;
}
.skip-to-content:focus,
.skip-to-content:focus-visible {
    left: 12px;
    top: 12px;
    width: auto;
    height: auto;
    padding: 8px 12px;
    background: var(--bg-card);
    color: var(--text);
    border-radius: 6px;
    outline: 2px solid var(--teal);
    outline-offset: 2px;
    font-family: var(--ui);
    font-size: 14px;
    font-weight: 600;
    text-decoration: none;
}

/* ---------------------------------------------------------------------------
   2. Screen-reader-only utility (for icon-paired text labels).
   --------------------------------------------------------------------------- */
.sr-only {
    position: absolute !important;
    width: 1px !important;
    height: 1px !important;
    padding: 0 !important;
    margin: -1px !important;
    overflow: hidden !important;
    clip: rect(0, 0, 0, 0) !important;
    white-space: nowrap !important;
    border: 0 !important;
}

/* ---------------------------------------------------------------------------
   3. Focus-visible rings — consistent teal outline across every interactive
      control in `.progress-v2`. Overrides any ring:none from Tailwind.
   --------------------------------------------------------------------------- */
.progress-v2 button:focus-visible,
.progress-v2 a:focus-visible,
.progress-v2 input:focus-visible,
.progress-v2 textarea:focus-visible,
.progress-v2 [role="button"]:focus-visible,
.progress-v2 .detail-toggle:focus-visible,
.progress-v2 label.cursor-pointer:focus-within {
    outline: 2px solid var(--teal-3);
    outline-offset: 2px;
    border-radius: 8px;
    box-shadow: 0 0 0 3px rgba(94, 234, 212, 0.25);
}
/* Tooltips reveal on keyboard focus too (not just hover) — matches CLAUDE
   rule 4: tooltips must be keyboard-reachable. */
.progress-v2 .relative.group:focus-within > span[role="tooltip"],
.relative.group:focus-within #audio-toggle-tooltip,
.relative.group:focus-within #yolo-toggle-tooltip {
    opacity: 1 !important;
}

/* ---------------------------------------------------------------------------
   4. Touch targets — 44×44 CSS-pixel minimum per WCAG 2.5.5 / iOS HIG.
   --------------------------------------------------------------------------- */
.progress-v2 .detail-toggle {
    padding: 10px 18px;
    font-size: 13px;
    min-height: 44px;
}
/* Wrap the hidden checkbox + visible pill in a 44px-tall tap target. */
.progress-v2 label.cursor-pointer,
#audio-toggle-container label.relative,
#automatic-mode-container label.relative {
    min-height: 44px;
    padding: 8px 0;
    display: inline-flex;
    align-items: center;
}
/* Summary-card chevron buttons — force a true square tap target. */
.progress-v2 #book-spec-summary-toggle,
.progress-v2 #metadata-cover-summary-toggle,
.progress-v2 #plot-summary-toggle,
.progress-v2 #voice-casting-toggle {
    width: 44px;
    height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
/* The entire summary header is a role=button — give it tap-friendly height. */
.progress-v2 #book-spec-summary-header,
.progress-v2 #metadata-cover-summary-header,
.progress-v2 #plot-summary-header,
.progress-v2 #voice-casting-header {
    min-height: 44px;
}
/* Completion action CTAs stay large-enough for reliable tap. */
.progress-v2 #read-novel-btn,
.progress-v2 #novel-details-btn {
    min-height: 44px;
}

/* ---------------------------------------------------------------------------
   5. Layout stability (CLS) — reserve space for async-arriving content so the
      hero + manuscript don't jank when the first SSE payload lands.
   --------------------------------------------------------------------------- */
.progress-v2 .hero-stat .v { min-height: 1em; }
.progress-v2 .hero-status { min-height: 1.5em; }
.progress-v2 #chapters-list { min-height: 300px; }
.progress-v2 .mn-foot .v { min-height: 1em; }
.progress-v2 .hero-big { min-height: 88px; }
@media (max-width: 600px) {
    .progress-v2 .hero-big { min-height: 64px; }
    .progress-v2 #chapters-list { min-height: 220px; }
}

/* ---------------------------------------------------------------------------
   6. Responsive breakpoints — ultra-compact / tablet / desktop / large.
      Each tier tunes type scale and layout density, not content.
   --------------------------------------------------------------------------- */
@media (max-width: 375px) {
    /* Rationale: iPhone SE / small Android. The default hero 64px is still
       too dense; scale hero numbers and collapse the foot to a single column. */
    .progress-v2 .page-head { font-size: 28px; }
    .progress-v2 .hero { padding: 24px 14px 18px; }
    .progress-v2 .hero-big { font-size: 56px; letter-spacing: -1.5px; min-height: 56px; }
    .progress-v2 .hero-big .dec { font-size: 24px; }
    .progress-v2 .hero-big .pct { font-size: 20px; }
    .progress-v2 .hero-status { font-size: 16px; }
    .progress-v2 .mn-foot { grid-template-columns: 1fr; gap: 10px; }
    .progress-v2 .hero-stats { grid-template-columns: 1fr; }
    .progress-v2 .a-chead { grid-template-columns: 44px 1fr auto; gap: 8px; }
}
@media (min-width: 768px) {
    /* Rationale: tablet. Keep the 3-up hero stats and 4-up foot grid; restore
       the default hero padding from the ≤600px shrink. */
    .progress-v2 .hero { padding: 40px 40px 28px; }
    .progress-v2 .hero-stats { grid-template-columns: 1fr 1fr 1fr; }
    .progress-v2 .mn-foot { grid-template-columns: repeat(4, 1fr); }
    .progress-v2 .a-scene,
    .progress-v2 .a-total { margin-left: 64px; }
}
@media (min-width: 1024px) {
    /* Rationale: desktop. Slightly widen the wrapper from the Tailwind
       max-w-3xl (768px) to 860px so hero digits get some air. Content still
       reads comfortably. */
    .progress-v2 { max-width: 860px; }
}
@media (min-width: 1440px) {
    /* Rationale: large desktop. Do NOT keep widening — long lines hurt
       reading comfort. Cap at 860px. */
    .progress-v2 { max-width: 860px; }
}

/* ---------------------------------------------------------------------------
   7. Comprehensive reduced-motion — extends the existing @media block to
      kill every decorative animation + transform/opacity/width/max-height
      transition, and snap the collapsible detail section.
   --------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
    .progress-v2 .hero-eyebrow .dot,
    .progress-v2 .horizon-fill::after,
    .progress-v2 .a-chapter.active .a-bar > div,
    .progress-v2 .a-scene .sh::before,
    .progress-v2 .a-scene,
    .progress-v2 .chapter-flash-in,
    .progress-v2 .scene-flash-in,
    .progress-v2 .row-ripple,
    .progress-v2 .tick,
    #generation-tip-text,
    #generation-tip-dots .tip-dot {
        animation: none !important;
    }
    .progress-v2 .horizon-fill,
    .progress-v2 .a-bar > div,
    .progress-v2 .hero-status,
    .progress-v2 .detail-section,
    .progress-v2 .detail-toggle,
    .progress-v2 .detail-toggle .chev,
    .progress-v2 .toggle,
    .progress-v2 .toggle::after,
    .transitions-enabled .toggle-bg,
    .transitions-enabled .toggle-bg::after,
    .progress-v2 .tab-content,
    #generation-tip-text {
        transition: none !important;
    }
    /* Snap the collapsible detail section to open/closed. */
    .progress-v2 .detail-section { transition: none !important; }
    .progress-v2 .detail-section:not(.open) { max-height: 0; opacity: 0; }
    .progress-v2 .detail-section.open { max-height: none; opacity: 1; }
    /* Horizon fill: snap width. */
    .progress-v2 .horizon-fill { transition: none !important; }
}

/* ---------------------------------------------------------------------------
   8. Decorative pseudo-elements — no-op for ATs. The actual DOM nodes that
      use aria-hidden="true" are set in the template; these ::before/::after
      pseudos cannot carry ARIA so we rely on their parent being decorative.
      No CSS needed here; left as a documentation anchor.
   --------------------------------------------------------------------------- */

/* ---------------------------------------------------------------------------
   9. High-contrast media query (forced-colors, e.g. Windows High Contrast)
      so our semantic progressbar remains visible when teal gradients are
      stripped by the OS.
   --------------------------------------------------------------------------- */
@media (forced-colors: active) {
    .progress-v2 .horizon-fill,
    .progress-v2 .a-bar > div { background: Highlight; }
    .progress-v2 .hero,
    .progress-v2 .manuscript-card,
    .progress-v2 .progress-container { border: 1px solid CanvasText; }
    .progress-v2 .detail-toggle,
    .progress-v2 #read-novel-btn,
    .progress-v2 #novel-details-btn { border: 1px solid ButtonText; }
}

/* =============== POLISH: Typography · Content · Palette ===============
   Surgical refinements on top of the Horizon redesign. All rules are
   scoped to `.progress-v2` so nothing leaks to the app chrome. Ordered
   to mirror the polish checklist:

   1. Letter-spacing normalization (uppercase labels at 0.18em)
   2. Numeric + serif font-feature-settings (tnum/lnum + dlig/liga)
   3. Text-wrap: pretty / balance
   4. Calmer accent glows (lowered teal alpha)
   5. Liquid-glass inner-border refraction on raised cards
   6. Hero digit hierarchy (dec + pct de-emphasized)
   7. Desaturated .hit horizon labels (color-mix w/ fallback)
   8. Body-text letter-spacing
   9. Foot-stat density discipline
  10. Middot separator for hero meta
   =========================================================================== */

/* -- Global body-text rhythm ------------------------------------------------ */
.progress-v2 {
    letter-spacing: -0.005em;
}

/* -- Page head: tighter, balanced wrap ------------------------------------- */
.progress-v2 .page-head {
    letter-spacing: -0.035em;
    text-wrap: balance;
}
.progress-v2 .page-sub {
    text-wrap: pretty;
}

/* Middot separator between book title and meta (book · est. X words). Rendered
   only when the meta span has content, via a sibling-aware ::before so empty
   payloads stay clean. */
.progress-v2 .page-sub #hero-book-meta:not(:empty)::before {
    content: "\00b7";
    margin: 0 0.4em;
    opacity: 0.5;
    color: var(--text-faint);
}

/* -- Uppercase label tracking: standardize at 0.18em ----------------------- */
.progress-v2 .hero-eyebrow,
.progress-v2 .horizon-labels,
.progress-v2 .mn-head .t,
.progress-v2 .a-scene .sh,
.progress-v2 .mn-foot .k,
.progress-v2 .hero-stat .k {
    letter-spacing: 0.18em;
}

/* -- Eyebrow dot vertical alignment (x-height nudge) ----------------------- */
.progress-v2 .hero-eyebrow .dot {
    transform: translateY(-0.5px);
}

/* -- Hero big number: solid color, tighter tracking, tabular + lining ----- */
.progress-v2 .hero-big {
    letter-spacing: -0.04em;
    color: var(--text);
    background: none;
    -webkit-background-clip: border-box;
    -webkit-text-fill-color: currentColor;
    font-feature-settings: "tnum" 1, "lnum" 1;
}
.progress-v2 .hero-big .int,
.progress-v2 .hero-big .dec,
.progress-v2 .hero-big .pct {
    font-feature-settings: "tnum" 1, "lnum" 1;
}
/* Decimal pushed further back: muted + opacity for tiered reading order */
.progress-v2 .hero-big .dec {
    color: var(--text-muted);
    opacity: 0.55;
}
/* Percent sign: smaller + tighter so digits dominate */
.progress-v2 .hero-big .pct {
    font-size: 28px;
    letter-spacing: -0.02em;
    color: var(--text-dim);
}

/* -- Numeric displays in stats: column-aligned tabular digits -------------- */
.progress-v2 .hero-stat .v,
.progress-v2 .mn-foot .v,
.progress-v2 .a-chead .n,
.progress-v2 .a-chead .s {
    font-feature-settings: "tnum" 1, "lnum" 1;
}

/* -- Italic serif: enable discretionary ligatures for natural prose ------- */
.progress-v2 .hero-status,
.progress-v2 .a-chead .t,
.progress-v2 .a-scene .st {
    font-feature-settings: "dlig" 1, "liga" 1;
    text-wrap: pretty;
}
.progress-v2 .mn-head .t {
    text-wrap: balance;
}

/* -- Raised cards: liquid-glass inner highlight + soft diffusion ----------- */
.progress-v2 .hero,
.progress-v2 .manuscript-card,
.progress-v2 .settings {
    box-shadow:
        0 1px 0 rgba(255, 255, 255, 0.03) inset,
        0 30px 60px -40px rgba(9, 9, 11, 0.6);
}

/* -- Calmer teal glows (lowered alpha; no neon scream) -------------------- */
.progress-v2 .horizon-fill {
    box-shadow: 0 0 12px rgba(13, 148, 136, 0.35);
}
.progress-v2 .horizon-fill::after {
    box-shadow: 0 0 12px rgba(94, 234, 212, 0.55);
}
.progress-v2 .hero-eyebrow .dot {
    box-shadow: 0 0 8px rgba(94, 234, 212, 0.6);
}
.progress-v2 .a-chapter.active .a-bar > div {
    box-shadow: 0 0 6px rgba(94, 234, 212, 0.22);
}

/* -- Horizon label .hit: desaturate to mid-teal/muted --------------------- */
/* Fallback for browsers without color-mix */
.progress-v2 .horizon-labels .hit {
    color: #8fd4c6;
}
@supports (color: color-mix(in oklab, red 50%, blue)) {
    .progress-v2 .horizon-labels .hit {
        color: color-mix(in oklab, var(--teal-3) 60%, var(--text-muted));
    }
}

/* -- Foot-stat density: strip any inner borders, rely on top rule only --- */
.progress-v2 .mn-foot .s {
    border: 0;
    padding: 0;
    background: transparent;
}
.progress-v2 .mn-foot .s + .s {
    border-left: 0;
}

/* -- Completion/error eyebrow color states stay restrained ---------------- */
.progress-v2 .hero-eyebrow.is-complete,
.progress-v2 .hero-eyebrow.is-error {
    letter-spacing: 0.18em;
}

/* =============== POLISH: Motion · Interaction ===============
   Appended 2026-04-19 as part of the taste-skill / ui-ux-pro-max pass.
   Every transition below uses a spring bezier; every interactive element
   has a cursor, hover, :active press, and :focus-visible ring. New
   perpetual micro-interactions are declared here; reduced-motion kills all
   new animations at the bottom of the block.
   =========================================================================== */

/* --- Keyframes (new) --- */
@keyframes hero-tick-shimmer {
    0%   { opacity: 1;    text-shadow: 0 0 0 rgba(94, 234, 212, 0); }
    45%  { opacity: 0.85; text-shadow: 0 0 18px rgba(94, 234, 212, 0.35); }
    100% { opacity: 1;    text-shadow: 0 0 0 rgba(94, 234, 212, 0); }
}
@keyframes row-complete {
    0%   { background-color: transparent;              box-shadow: 0 0 0 0  rgba(94, 234, 212, 0.5); }
    40%  { background-color: rgba(13, 148, 136, 0.08); box-shadow: 0 0 20px 0 rgba(94, 234, 212, 0.35); }
    100% { background-color: transparent;              box-shadow: 0 0 0 0  rgba(94, 234, 212, 0); }
}
@keyframes active-title-breathe {
    0%, 100% { opacity: 1; }
    50%      { opacity: 0.92; }
}
@keyframes skeleton-shimmer {
    0%   { background-position: 200% 0; }
    100% { background-position: -200% 0; }
}
@keyframes row-ripple {
    0%   { box-shadow: 0 0 0 0 rgba(94, 234, 212, 0.45); }
    100% { box-shadow: 0 0 0 6px rgba(94, 234, 212, 0); }
}

/* --- Spring-physics upgrades for existing transitions + GPU guardrails --- */
.progress-v2 .horizon-fill {
    transition: width 0.6s cubic-bezier(0.34, 1.4, 0.64, 1);
    will-change: width;
    transform: translateZ(0);
}
.progress-v2 .a-bar > div {
    transition: width 0.55s cubic-bezier(0.34, 1.4, 0.64, 1);
    will-change: width;
    transform: translateZ(0);
}
.progress-v2 .detail-toggle {
    transition:
        border-color 0.25s cubic-bezier(0.25, 1, 0.5, 1),
        color 0.25s cubic-bezier(0.25, 1, 0.5, 1),
        background-color 0.25s cubic-bezier(0.25, 1, 0.5, 1),
        transform 0.3s cubic-bezier(0.34, 1.4, 0.64, 1),
        box-shadow 0.25s cubic-bezier(0.25, 1, 0.5, 1);
    will-change: transform;
}
.progress-v2 .detail-toggle .chev {
    transition: transform 0.3s cubic-bezier(0.34, 1.4, 0.64, 1);
}
.progress-v2 .toggle {
    transition: background-color 0.25s cubic-bezier(0.25, 1, 0.5, 1);
}
.progress-v2 .toggle::after {
    transition: transform 0.3s cubic-bezier(0.34, 1.4, 0.64, 1);
}
.progress-v2 .settings .ctrl,
.progress-v2 .settings .ctrl .i {
    transition:
        color 0.25s cubic-bezier(0.25, 1, 0.5, 1),
        border-color 0.25s cubic-bezier(0.25, 1, 0.5, 1);
}
.progress-v2 .spec-editor button[type="submit"],
.progress-v2 #approve-metadata-cover-btn {
    transition:
        background-color 0.25s cubic-bezier(0.25, 1, 0.5, 1),
        box-shadow 0.25s cubic-bezier(0.25, 1, 0.5, 1),
        transform 0.3s cubic-bezier(0.34, 1.4, 0.64, 1);
}
.progress-v2 #read-novel-btn,
.progress-v2 #novel-details-btn {
    transition:
        background-color 0.25s cubic-bezier(0.25, 1, 0.5, 1),
        transform 0.3s cubic-bezier(0.34, 1.4, 0.64, 1),
        box-shadow 0.25s cubic-bezier(0.25, 1, 0.5, 1);
}
.progress-v2 .tab-button {
    transition:
        color 0.25s cubic-bezier(0.25, 1, 0.5, 1),
        border-color 0.25s cubic-bezier(0.25, 1, 0.5, 1);
}
.progress-v2 .tab-content {
    transition: opacity 0.3s cubic-bezier(0.25, 1, 0.5, 1);
}
.progress-v2 .hero-status {
    transition: opacity 0.3s cubic-bezier(0.25, 1, 0.5, 1);
}
.progress-v2 .book-spec-field {
    transition:
        border-color 0.25s cubic-bezier(0.25, 1, 0.5, 1),
        box-shadow 0.25s cubic-bezier(0.25, 1, 0.5, 1);
}

/* Collapse: keep max-height (least invasive) but swap to spring curve and
   raise the clamp so long chapter lists do not get truncated. */
.progress-v2 .detail-section {
    transition:
        max-height 0.45s cubic-bezier(0.25, 1, 0.5, 1),
        opacity 0.35s cubic-bezier(0.25, 1, 0.5, 1),
        margin-top 0.35s cubic-bezier(0.25, 1, 0.5, 1);
}
.progress-v2 .detail-section.open {
    max-height: 6000px;
}

/* --- Hover states (explicit, clear) --- */
.progress-v2 .detail-toggle:hover {
    border-color: var(--line-strong);
    color: var(--text);
    background: color-mix(in srgb, var(--bg-raised) 80%, var(--bg-card));
    transform: translateY(-1px);
}
.progress-v2 .spec-editor button[type="submit"]:hover,
.progress-v2 #approve-metadata-cover-btn:hover {
    background-color: var(--teal-2);
    box-shadow: 0 0 0 4px rgba(13, 148, 136, 0.15);
}
.progress-v2 #read-novel-btn:hover,
.progress-v2 #novel-details-btn:hover {
    box-shadow: 0 0 0 4px rgba(13, 148, 136, 0.12);
}
.progress-v2 #book-spec-summary-header:hover,
.progress-v2 #plot-summary-header:hover,
.progress-v2 #metadata-cover-summary-header:hover {
    background: color-mix(in srgb, var(--bg-raised) 85%, var(--bg-card));
    border-radius: 8px;
}

/* --- Cursor discipline --- */
.progress-v2 .detail-toggle,
.progress-v2 .toggle,
.progress-v2 #book-spec-summary-header,
.progress-v2 #plot-summary-header,
.progress-v2 #metadata-cover-summary-header,
.progress-v2 #book-spec-summary-toggle,
.progress-v2 #plot-summary-toggle,
.progress-v2 #metadata-cover-summary-toggle,
.progress-v2 #voice-casting-toggle {
    cursor: pointer;
}
.progress-v2 .spec-editor button[type="submit"]:disabled,
.progress-v2 #approve-metadata-cover-btn:disabled {
    cursor: not-allowed;
}
/* Locked audio toggle: when parent has .opacity-50, set not-allowed on its
   interactive children so the cursor matches the visual disabled treatment. */
#audio-toggle-container.opacity-50,
#audio-toggle-container.opacity-50 label,
#audio-toggle-container.opacity-50 .toggle-bg {
    cursor: not-allowed;
}

/* --- Tactile press: 1px nudge, quick ease-out; scoped to interactive
       elements (no page-wide :active that would eat scrollbars etc.) --- */
.progress-v2 .detail-toggle:active,
.progress-v2 .toggle:active,
.progress-v2 .spec-editor button[type="submit"]:active,
.progress-v2 #approve-metadata-cover-btn:active,
.progress-v2 #completion-actions a:active,
.progress-v2 #read-novel-btn:active,
.progress-v2 #novel-details-btn:active,
.progress-v2 #book-spec-summary-toggle:active,
.progress-v2 #plot-summary-toggle:active,
.progress-v2 #metadata-cover-summary-toggle:active,
.progress-v2 #book-spec-summary-header:active,
.progress-v2 #plot-summary-header:active,
.progress-v2 #metadata-cover-summary-header:active,
.progress-v2 #voice-casting-toggle:active,
.progress-v2 #voice-casting-header:active {
    transform: translateY(1px);
    transition: transform 80ms ease-out;
}

/* --- Focus-visible rings (never plain :focus so click alone doesn't fire) --- */
.progress-v2 .detail-toggle:focus-visible {
    outline: 2px solid rgba(13, 148, 136, 0.7);
    outline-offset: 2px;
    border-radius: 999px;
}
.progress-v2 .toggle:focus-visible {
    outline: 2px solid rgba(13, 148, 136, 0.7);
    outline-offset: 2px;
    border-radius: 9999px;
}
.progress-v2 .spec-editor button[type="submit"]:focus-visible,
.progress-v2 #approve-metadata-cover-btn:focus-visible,
.progress-v2 #read-novel-btn:focus-visible,
.progress-v2 #novel-details-btn:focus-visible {
    outline: 2px solid rgba(13, 148, 136, 0.7);
    outline-offset: 2px;
    border-radius: 10px;
}
.progress-v2 #book-spec-summary-toggle:focus-visible,
.progress-v2 #plot-summary-toggle:focus-visible,
.progress-v2 #metadata-cover-summary-toggle:focus-visible,
.progress-v2 #voice-casting-toggle:focus-visible {
    outline: 2px solid rgba(13, 148, 136, 0.7);
    outline-offset: 2px;
    border-radius: 6px;
}
.progress-v2 #book-spec-summary-header:focus-visible,
.progress-v2 #plot-summary-header:focus-visible,
.progress-v2 #metadata-cover-summary-header:focus-visible,
.progress-v2 #voice-casting-header:focus-visible {
    outline: 2px solid rgba(13, 148, 136, 0.7);
    outline-offset: 2px;
    border-radius: 10px;
}
.progress-v2 .book-spec-field:focus-visible {
    outline: 2px solid rgba(13, 148, 136, 0.7);
    outline-offset: 1px;
    border-color: rgba(13, 148, 136, 0.6);
    box-shadow: 0 0 0 3px rgba(13, 148, 136, 0.25);
}

/* --- Perpetual micro-interactions --- */
/* Hero tick: 300ms shimmer + faint glow when the integer digit rolls. */
.progress-v2 .hero-big .int.tick {
    animation: hero-tick-shimmer 300ms cubic-bezier(0.25, 1, 0.5, 1);
}
/* Active chapter title breathes gently so the eye tracks it. */
.progress-v2 .a-chapter.active .a-chead .t {
    animation: active-title-breathe 2.2s ease-in-out infinite;
}

/* --- Chapter-complete micro-flash + status-change ripple --- */
.progress-v2 .a-chapter.just-completed {
    animation: row-complete 600ms cubic-bezier(0.34, 1.4, 0.64, 1);
    border-radius: 4px;
}
.progress-v2 .a-chapter.row-ripple {
    animation: row-ripple 420ms cubic-bezier(0.25, 1, 0.5, 1);
    border-radius: 4px;
}

/* --- Loading skeleton for chapters list (before first SSE payload) --- */
.progress-v2 .a-chapter.skeleton {
    padding: 12px 4px;
    border-bottom: 1px dashed var(--line);
    pointer-events: none;
}
.progress-v2 .a-chapter.skeleton:last-child { border-bottom: none; }
.progress-v2 .a-chapter.skeleton .a-chead {
    display: grid;
    grid-template-columns: 52px 1fr 80px;
    gap: 12px;
    align-items: center;
}
.progress-v2 .a-chapter.skeleton .sk {
    height: 12px;
    border-radius: 4px;
    background: linear-gradient(
        90deg,
        var(--line) 0%,
        var(--line-strong) 50%,
        var(--line) 100%
    );
    background-size: 200% 100%;
    animation: skeleton-shimmer 1.8s linear infinite;
}
.progress-v2 .a-chapter.skeleton .sk-n { width: 32px; }
.progress-v2 .a-chapter.skeleton .sk-t { width: 70%; height: 14px; }
.progress-v2 .a-chapter.skeleton .sk-s { width: 60px; justify-self: end; }
.progress-v2 .a-chapter.skeleton .a-bar {
    height: 2px;
    background: var(--line);
    border-radius: 1px;
    margin-top: 10px;
    overflow: hidden;
}
.progress-v2 .a-chapter.skeleton .a-bar > div {
    height: 100%;
    width: 30%;
    background: linear-gradient(
        90deg,
        var(--line) 0%,
        var(--line-strong) 50%,
        var(--line) 100%
    );
    background-size: 200% 100%;
    animation: skeleton-shimmer 1.8s linear infinite;
}

/* --- Stragglers: legacy .progress-bar + global toggle-bg get spring easings
       so the whole page feels consistent, not half-linear. --- */
.progress-v2 .progress-bar {
    transition: width 0.55s cubic-bezier(0.34, 1.4, 0.64, 1);
    will-change: width;
    transform: translateZ(0);
}
.transitions-enabled .toggle-bg {
    transition: background-color 0.25s cubic-bezier(0.25, 1, 0.5, 1);
}
.transitions-enabled .toggle-bg::after {
    transition: transform 0.3s cubic-bezier(0.34, 1.4, 0.64, 1);
}

/* --- Reduced-motion killswitch for every new animation/transform --- */
@media (prefers-reduced-motion: reduce) {
    .progress-v2 .hero-big .int.tick,
    .progress-v2 .a-chapter.active .a-chead .t,
    .progress-v2 .a-chapter.just-completed,
    .progress-v2 .a-chapter.row-ripple,
    .progress-v2 .a-chapter.skeleton .sk,
    .progress-v2 .a-chapter.skeleton .a-bar > div {
        animation: none !important;
    }
    .progress-v2 .detail-toggle,
    .progress-v2 .detail-toggle:hover,
    .progress-v2 .detail-toggle:active,
    .progress-v2 .toggle:active,
    .progress-v2 .spec-editor button[type="submit"]:active,
    .progress-v2 #approve-metadata-cover-btn:active,
    .progress-v2 #completion-actions a:active,
    .progress-v2 #read-novel-btn:active,
    .progress-v2 #novel-details-btn:active,
    .progress-v2 #book-spec-summary-toggle:active,
    .progress-v2 #plot-summary-toggle:active,
    .progress-v2 #metadata-cover-summary-toggle:active,
    .progress-v2 #book-spec-summary-header:active,
    .progress-v2 #plot-summary-header:active,
    .progress-v2 #metadata-cover-summary-header:active {
        transform: none !important;
    }
}

/* Visible error surface (ErrorUI.showInsufficientCreditsError /
 * showGenericError). Lives below the hero card; raised-card tone with a
 * controlled rose-alpha accent -- desaturated, no neon. */
.progress-v2 #error-container {
    margin: 24px 0;
    padding: 18px 22px;
    background: color-mix(in oklab, var(--bg-raised) 88%, #dc2626 12%);
    border: 1px solid color-mix(in oklab, var(--line-strong) 60%, #dc2626 40%);
    border-radius: 14px;
    color: var(--text);
    font-family: var(--ui);
    font-size: 14px;
    line-height: 1.55;
    box-shadow: 0 1px 0 rgba(255, 255, 255, 0.04) inset,
                0 20px 40px -24px rgba(220, 38, 38, 0.28);
}
.progress-v2 #error-container.hidden { display: none; }
.progress-v2 #error-container a,
.progress-v2 #error-container .upgrade-link,
.progress-v2 #error-container button {
    color: var(--teal-3);
    text-decoration: underline;
    text-underline-offset: 3px;
    font-weight: 600;
}
.progress-v2 #error-container a:hover,
.progress-v2 #error-container button:hover {
    color: var(--text);
}

/* ===================================================================
   POLISH (2026-04-20): pre-writing hide + review-pause calm
   ===================================================================
   Two small state layers the hero can wear, driven by progress.js:

     .progress-v2.is-pre-writing
       Stage is anything before write_scenes — manuscript data isn't
       meaningful yet. Hide the "Show writing detail" toggle and the
       collapsible drawer entirely so they don't leak into the review
       flow or advertise empty skeletons.

     .hero-eyebrow.is-paused  /  .horizon.is-paused
       Backend is idle waiting on a human review (book spec, plot,
       metadata/cover). Replace the urgent teal pulse with a slow
       "breath" — scale barely deviates from 1.0, opacity dips to the
       mid-range so the dot reads as "I'm here, patient" instead of
       "I'm still working". The tween is already snapped in JS so the
       big number cannot drift past the backend value while paused.
   =================================================================== */

.progress-v2.is-pre-writing .detail-toggle-wrap { display: none; }
.progress-v2.is-pre-writing .detail-section {
    display: none !important;
    max-height: 0 !important;
    opacity: 0 !important;
    margin-top: 0 !important;
}

@keyframes paused-breath {
    0%, 100% { opacity: 0.55; transform: scale(0.92); }
    50%      { opacity: 0.95; transform: scale(1.0); }
}

.progress-v2 .hero-eyebrow.is-paused .dot {
    animation: paused-breath 3.6s ease-in-out infinite;
    box-shadow: 0 0 6px rgba(94, 234, 212, 0.32);
}
.progress-v2 .horizon.is-paused .horizon-fill::after {
    animation: paused-breath 3.6s ease-in-out infinite;
    box-shadow: 0 0 8px rgba(94, 234, 212, 0.3);
}
.progress-v2 .horizon.is-paused .horizon-fill {
    box-shadow: 0 0 8px rgba(13, 148, 136, 0.22);
}

@media (prefers-reduced-motion: reduce) {
    .progress-v2 .hero-eyebrow.is-paused .dot,
    .progress-v2 .horizon.is-paused .horizon-fill::after {
        animation: none !important;
    }
}

/* ---------------------------------------------------------------------------
   Voice casting
   Sits between the settings bar and the hero, outside every review-gate editor:
   auto-approve clicks the metadata gate past about a second after it renders, so
   a picker inside that gate would vanish mid-selection. This panel persists
   until scene writing starts.
   --------------------------------------------------------------------------- */
/* The card itself -- surface, border, padding, spacing -- comes from the shared
   `progress-container p-6 mb-8` classes, the same ones the Book Spec and
   Metadata & Cover summaries use, so the three read as one column. Only what is
   particular to casting is styled below. */

.progress-v2 .voice-casting.hidden { display: none; }

/* Collapsed points right, expanded points down -- the accordion convention
   everyone already knows from Finder and every file tree. Shared by all four
   fold panels: keying it to element ids is what let the casting panel drift
   away from its neighbours in the first place.

   Written out rather than reached for as a Tailwind utility: the compiled
   tailwind.css carries rotate-180 but no rotate-90, so `-rotate-90` would be a
   class that silently does nothing. The base chevron path draws a downward V,
   and -90deg swings its vertex from six o'clock to three. */
.progress-v2 .fold-chevron {
    transform: rotate(0deg);
}

.progress-v2 .fold-chevron.is-collapsed {
    transform: rotate(-90deg);
}

.progress-v2 .vc-sub {
    margin: 0;
    font-size: 0.85rem;
    color: var(--text-muted);
}

.progress-v2 .vc-list {
    display: flex;
    flex-direction: column;
    gap: 10px;
}

/* One card per character, collapsed to a single line of business until the
   reader asks to change it. Expanded by default meant five characters rendered
   twenty-five option cards at once, which reads as a wall rather than a choice. */
.progress-v2 .vc-character {
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 12px 14px;
    border: 1px solid var(--hairline, rgba(255, 255, 255, 0.08));
    border-radius: 10px;
    background: rgba(255, 255, 255, 0.02);
}

.progress-v2 .vc-summary {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 16px;
    flex-wrap: wrap;
}

.progress-v2 .vc-who { min-width: 0; flex: 1 1 260px; }

.progress-v2 .vc-name {
    margin: 0;
    font-size: 0.95rem;
    font-weight: 600;
    color: var(--text-body);
}

.progress-v2 .vc-meta {
    margin: 2px 0 0;
    font-size: 0.8rem;
    letter-spacing: 0.02em;
    color: var(--text-muted, var(--text-faint));
}

.progress-v2 .vc-desc {
    margin: 5px 0 0;
    font-size: 0.82rem;
    line-height: 1.45;
    /* Not --text-faint: on the character card that was 3.5:1 in the dialog
       and 3.7:1 on the progress page, under AA for 13px text. Muted is 6.7:1
       and 7.2:1. The same swap is made on every other quiet line in this
       panel; voice-casting-panel-css.spec.mjs keeps --text-faint out. */
    color: var(--text-muted);
    /* Flush on both edges. The last line stays ragged, as `text-align-last`
       defaults to -- stretching four words across the full column to square off
       a paragraph is the one place justification looks like a mistake. */
    text-align: justify;
    /* Three lines is enough to recognise the character and covers the shorter
       descriptions whole; a full paragraph per row would put the choice itself
       below the fold again. Anything actually cut off grows a "Show more" -- see
       `refreshDescriptionToggles` -- so nothing is hidden without a way in. */
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.progress-v2 .vc-desc.is-expanded {
    display: block;
    -webkit-line-clamp: none;
    overflow: visible;
}

/* Quiet: it belongs to the description, not to the row of real choices below
   it. Sized and padded to stay a 24px target (WCAG 2.5.8) without drawing a
   box around itself. */
.progress-v2 .vc-desc-toggle {
    display: inline-block;
    margin: 2px 0 0;
    padding: 3px 0;
    min-height: 24px;
    border: 0;
    background: none;
    font: inherit;
    font-size: 0.8rem;
    color: var(--teal-2, rgb(45 212 191));
    cursor: pointer;
}

.progress-v2 .vc-desc-toggle:hover { text-decoration: underline; }

.progress-v2 .vc-desc-toggle:focus-visible {
    outline: 2px solid var(--teal-2, rgb(45 212 191));
    outline-offset: 2px;
    border-radius: 4px;
}

.progress-v2 .vc-current {
    display: flex;
    align-items: center;
    gap: 8px;
    flex: 0 0 auto;
}

.progress-v2 .vc-current-name {
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--text-body);
    white-space: nowrap;
}

.progress-v2 .vc-note { margin: 0; font-size: 0.8rem; color: var(--text-muted); }

.progress-v2 .vc-options {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
    gap: 8px;
    /* Every card the same height as the tallest in the list. A held voice
       carries an extra line naming its holder, which otherwise made its row
       taller than the rest and the grid read as misaligned rather than as one
       card saying more. */
    grid-auto-rows: 1fr;
}

/* The card is the control. The radio inside fills it (`.vc-choose`, below)
   and the audition button sits in its corner, positioned against the card.
   No padding of its own, so the radio's hit area reaches the border. */
.progress-v2 .vc-option {
    position: relative;
    background: var(--bg-card);
    border: 1px solid var(--line);
    border-radius: 10px;
    display: flex;
    min-width: 0;
}

.progress-v2 .vc-option.is-selected {
    border-color: var(--teal);
    box-shadow: inset 0 0 0 1px var(--teal);
}

/* Present but unusable, with the reason shown. A row that disappears reads as a
   bug and leaves the user unable to tell why an option went away.

   These used to be near-unreachable: only an explicitly reserved voice produced
   one, and those were filtered out of the list entirely. Now any voice another
   character holds renders this way -- 0 to 3 rows per character on real casts --
   so the dimming has to stay legible rather than merely suggest "not this one".
   The dimming is applied per element rather than to the card. `opacity` below 1
   makes the element a compositing group, and nothing inside can be more opaque
   than the group -- so an earlier attempt here, `opacity: 0.55` on the card with
   `opacity: 1` on the reason line, could not do what its comment claimed: the
   reason stayed dimmed with everything else, at 1.94:1. The reason line is the
   only thing telling the reader whom to change, so it is the one part that must
   not be dimmed at all. The radio is the whole face of the card now, and the
   reason line is inside it, so it is never in this list.

   The name is greyed by colour rather than by opacity: --text-muted is 7:1
   on the card and does not depend on what happens to be behind it. The
   reason line is --text-muted already (`.vc-tag-muted`, below). */
.progress-v2 .vc-option.is-taken .vc-voice-name { color: var(--text-muted); }

.progress-v2 .vc-option.is-taken .vc-play { opacity: 0.55; }

.progress-v2 .vc-option.is-taken {
    border-style: dashed;
    border-color: var(--line);
}

.progress-v2 .vc-voice-name {
    font-size: 0.95rem;
    font-weight: 600;
    color: var(--text);
    overflow-wrap: anywhere;
}

/* 0.8rem is the floor for every `.vc-*` rule: 12.8px at the 16px root. This
   was 0.7rem, 11.2px, the smallest text in the panel. Pinned by "font-size
   floor" in voice-casting-panel-css.spec.mjs. */
.progress-v2 .vc-tag {
    font-size: 0.8rem;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--teal-3);
}

/* The one actionable sentence on a held row, "cast as X". --text-muted, 7:1
   on the card; --text-faint was 3.7:1. */
.progress-v2 .vc-tag-muted { color: var(--text-muted); text-transform: none; letter-spacing: 0; }

.progress-v2 .vc-play,
.progress-v2 .vc-toggle {
    font: inherit;
    font-size: 0.8rem;
    border-radius: 7px;
    padding: 5px 10px;
    cursor: pointer;
    border: 1px solid var(--line-strong);
    background: transparent;
    color: var(--text-body);
}

/* Icon-only, so it needs its own box: the shared padding above is sized around
   a word and left a 13px glyph adrift in it. Round, like a transport control,
   rather than sharing the rounded-rectangle of the row's text buttons. 30px
   clears the 24px minimum target size (WCAG 2.5.8) and matches their height. */
.progress-v2 .vc-play {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    padding: 0;
    border-radius: 999px;
    flex: none;
}

/* A triangle inside a circle reads as sitting left of centre even when it is
   centred, so it is nudged. The pause bars are symmetrical and need no such
   correction -- applying it to both would make the mark visibly jump on press. */
.progress-v2 .vc-play[data-state="idle"] svg,
.progress-v2 .vc-play[data-state="paused"] svg { margin-left: 1px; }

/* This button swaps between "Change", "Done" and "Locked", and it sits at the
   end of a flex row whose other half -- the name, traits and description -- takes
   whatever width is left. Measured: Change 68px, Done 54.19px, Locked 64.42px,
   so opening the options moved the description column 13.81px and rewrapped
   every line of it. Pressing Change is a request to see more voices, not to
   reflow the paragraph above them.

   The width is reserved by the longest label rather than pinned to a number:
   these are words, and their pixel widths differ by platform and font, so any
   figure chosen here would be right on this machine and wrong on a phone.
   `visibility: hidden` keeps the reserved copy out of the accessibility tree.
   Bound to the labels in `renderCharacter` by a test -- see
   "the width the reviews reserve" in voice-casting-status-line.spec.mjs. */
.progress-v2 .vc-toggle {
    white-space: nowrap;
    display: grid;
    justify-items: center;
}

.progress-v2 .vc-toggle::after {
    content: 'Change';
    height: 0;
    overflow: hidden;
    visibility: hidden;
    pointer-events: none;
}
/* Both controls read as offers from the moment the row is drawn.
   Grey-on-grey made them look like labels: on a card whose only coloured thing
   was the "Use this" button, nothing said the play mark and "Change" could be
   pressed at all until the reader tried one.

   Three steps, quiet to loud, so the tint that means "actionable" and the tint
   that means "you are in this one" stay tellable apart:
     resting   dim teal edge, brighter glyph  -- press me
     engaged   full teal edge over a wash     -- open, or stopped part-way
     sounding  solid fill                     -- this is the one you can hear */
.progress-v2 .vc-play,
.progress-v2 .vc-toggle {
    border-color: var(--teal);
    color: var(--teal-2);
}

.progress-v2 .vc-toggle[aria-expanded="true"],
.progress-v2 .vc-play[data-state="paused"] {
    border-color: var(--teal-2);
    color: var(--teal-3);
    background: rgba(20, 184, 166, 0.12);
}

/* A voice with no sample keeps the grey it always had. Tinting a control that
   cannot be pressed would be the one place this hint lies. */
.progress-v2 .vc-play:disabled {
    border-color: var(--line-strong);
    color: var(--text-body);
    background: transparent;
}

/* One voice, as a radio the size of its card. Frank, PR #297 review: six
   identical green "Use this" pills per character were the loudest thing in
   the panel, said nothing, and competed with the audition button. The card's
   own face is the choice now: the checked voice is told by a tick in the
   gutter and the card's accent edge (`.is-selected`, above), and an unchecked
   one shows no button at all -- the list-with-checkmarks pattern in
   docs/ui-ux-design-principles.md.

   A grid, so the tick has a column of its own and the name and tags line up
   beside it whether or not it is showing. Only the name line keeps clear of
   the 30px audition control and its 8px inset; the tags sit below it and
   can use the width, which "CLOSEST AVAILABLE" at 12.8px needs in a
   four-across dialog. */
.progress-v2 .vc-choose {
    flex: 1 1 auto;
    display: grid;
    grid-template-columns: 1.1em minmax(0, 1fr);
    column-gap: 2px;
    row-gap: 4px;
    align-content: start;
    min-width: 0;
    min-height: 46px;
    padding: 10px 12px;
    border: 0;
    border-radius: inherit;
    background: transparent;
    color: var(--text);
    font: inherit;
    font-size: 0.95rem;
    text-align: left;
    cursor: pointer;
}

.progress-v2 .vc-choose > * { grid-column: 2; }

.progress-v2 .vc-choose > .vc-voice-name { padding-right: 34px; }

.progress-v2 .vc-choose::before {
    content: "\2713";
    grid-column: 1;
    grid-row: 1;
    font-weight: 700;
    color: var(--teal-2);
    /* Reserved on every row so the names align; shown on the checked one. */
    visibility: hidden;
}

.progress-v2 .vc-choose[aria-checked="true"]::before { visibility: visible; }

.progress-v2 .vc-choose[aria-checked="true"] { cursor: default; }

.progress-v2 .vc-choose:hover:not(:disabled):not([aria-checked="true"]) {
    background: rgba(255, 255, 255, 0.04);
}

/* Not dimmed: the face is the whole card, and fading it would fade the
   "cast as X" reason with it (the trap `.is-taken` describes above). A held
   voice reads as held from its dashed edge and muted name; a fenced one
   from the "Saving…" beside the voice that is being saved. */
.progress-v2 .vc-choose:disabled { cursor: default; }

/* While the choice is on its way to the server. The name and tags are the
   radio's children, so the state is drawn beside them rather than written
   over them; `aria-busy` says the same to assistive tech. */
.progress-v2 .vc-choose[aria-busy="true"]::after {
    content: "Saving\2026";
    grid-column: 2;
    font-size: 0.8rem;
    color: var(--text-muted);
}

/* The audition control, in the card's corner and outside the radio. Scoped
   with `>` so the same control on the summary row keeps its place in the
   flex flow. */
.progress-v2 .vc-option > .vc-play {
    position: absolute;
    top: 8px;
    right: 8px;
}

.progress-v2 .vc-play:hover:not(:disabled),
.progress-v2 .vc-toggle:hover:not(:disabled) { filter: brightness(1.12); }

.progress-v2 .vc-play:disabled,
.progress-v2 .vc-toggle:disabled { opacity: 0.45; cursor: default; }

.progress-v2 .vc-play:focus-visible,
.progress-v2 .vc-choose:focus-visible,
.progress-v2 .vc-toggle:focus-visible {
    outline: 2px solid var(--teal-2);
    outline-offset: 2px;
}

/* The ring follows the card's corners, not the 8px the generic button ring
   in section 3 would give a control that fills a 10px card. */
.progress-v2 .vc-choose:focus-visible { border-radius: inherit; }

/* A settled cast, in both of the places this panel is mounted. The progress
   page puts `voice-casting` on a section inside the `.progress-v2` wrapper;
   the dialog on the novel page carries `progress-v2`, `voice-casting` and
   `vc-modal` on one element, so a descendant selector alone reaches only the
   first. It did, and a locked dialog was pixel-identical to an editable one --
   teal accent edge, white names, teal audition buttons -- with all 24 of its
   radios disabled. It invited presses that could do nothing.

   Per element rather than `opacity` on the card, for the reason `.is-taken`
   records above: opacity below 1 makes the card a compositing group, and the
   "cast as X" line inside the radio cannot then be more opaque than the card
   around it. That line is the only thing naming whom a voice is held by, so
   it keeps --text-muted at full strength here.

   Keyed off the lock the payload declares (`vc-locked`, set in `render`)
   rather than off `:disabled`, which a radio also wears for the second its
   own POST is in flight and for a voice another character holds. The tick
   keeps its teal: with everything around it quiet, it is the thing that says
   which voice is being recorded. */
.progress-v2 .voice-casting.vc-locked .vc-option,
.progress-v2.voice-casting.vc-locked .vc-option { border-style: dashed; }

.progress-v2 .voice-casting.vc-locked .vc-option.is-selected,
.progress-v2.voice-casting.vc-locked .vc-option.is-selected {
    border-color: var(--line-strong);
    box-shadow: none;
}

.progress-v2 .voice-casting.vc-locked .vc-voice-name,
.progress-v2.voice-casting.vc-locked .vc-voice-name { color: var(--text-muted); }

.progress-v2 .voice-casting.vc-locked .vc-tag,
.progress-v2.voice-casting.vc-locked .vc-tag { color: var(--text-muted); }

/* Auditioning still works on a locked cast, so this is quietened rather than
   drawn as disabled -- the same 0.55 a held card gives the control it keeps. */
.progress-v2 .voice-casting.vc-locked .vc-play,
.progress-v2.voice-casting.vc-locked .vc-play { opacity: 0.55; }

/* The confirm step. Centred under a rule, so it reads as the end of the
   section rather than one more control among the character rows -- and so the
   one action that closes this gate sits where the eye lands after scanning a
   column of voices, not off in the corner the character rows already own. */
/* The casting prompt at the top of the page. Quiet by default -- it is an
   invitation, not an alarm -- and only raised once writing is actually waiting
   on it. */
.progress-v2 .vc-prompt {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    margin: 0 0 16px;
    padding: 10px 14px;
    border: 1px solid var(--line);
    border-left: 3px solid var(--teal-2, #2dd4bf);
    border-radius: 8px;
    background: var(--bg-raised);
    font-size: 0.85rem;
    color: var(--text-muted);
    /* The bar pulses rather than sitting lit: the panel is several screens down
       and a steady stripe reads as decoration next to everything else on this
       page that is also steady. */
    animation: vc-prompt-pulse 2s ease-in-out infinite;
}

.progress-v2 .vc-prompt.hidden { display: none; }

.progress-v2 .vc-prompt.is-waiting {
    border-left-color: #f59e0b;
    color: var(--text);
    /* Faster and wider when writing is actually stopped, so the two states are
       distinguishable at a glance and not only by colour. */
    animation: vc-prompt-pulse-waiting 1.1s ease-in-out infinite;
}

@keyframes vc-prompt-pulse {
    0%, 100% { border-left-color: rgba(45, 212, 191, 0.25); }
    50%      { border-left-color: rgba(45, 212, 191, 1); }
}

@keyframes vc-prompt-pulse-waiting {
    0%, 100% { border-left-color: rgba(245, 158, 11, 0.2); }
    50%      { border-left-color: rgba(245, 158, 11, 1); }
}

/* A blinking element is a genuine problem for vestibular and photosensitive
   readers, and this one would blink for minutes. Anyone who has asked their
   system for less motion gets the steady bar instead -- the colour still tells
   the two states apart. */
@media (prefers-reduced-motion: reduce) {
    .progress-v2 .vc-prompt,
    .progress-v2 .vc-prompt.is-waiting {
        animation: none;
    }
    .progress-v2 .vc-prompt { border-left-color: var(--teal-2, #2dd4bf); }
    .progress-v2 .vc-prompt.is-waiting { border-left-color: #f59e0b; }
}

.progress-v2 .vc-prompt-jump {
    background: none;
    border: none;
    padding: 4px 2px;
    color: var(--teal-2, #2dd4bf);
    font-size: 0.85rem;
    font-weight: 600;
    text-decoration: underline;
    cursor: pointer;
}

.progress-v2 .vc-prompt-jump:focus-visible {
    outline: 2px solid rgba(13, 148, 136, 0.7);
    outline-offset: 2px;
    border-radius: 4px;
}

.progress-v2 .vc-footer:empty { display: none; }

.progress-v2 .vc-footer {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 10px;
    margin-top: 18px;
    padding-top: 16px;
    border-top: 1px solid var(--line);
}

.progress-v2 .vc-confirm-note {
    margin: 0;
    font-size: 0.8rem;
    color: var(--text-muted);
    text-align: center;
}

.progress-v2 .vc-confirm {
    padding: 9px 20px;
    border-radius: 999px;
    border: 1px solid transparent;
    background: var(--teal-1, #0d9488);
    /* White on this teal is 3.74:1, under AA at 14px. The dark ink the old
       "Use this" pill used on the same fill is 4.51:1. */
    color: #04211f;
    font-size: 0.9rem;
    font-weight: 600;
    cursor: pointer;
    min-height: 44px;
}

.progress-v2 .vc-confirm:hover:not(:disabled) { filter: brightness(1.12); }

/* Disabled while a choice or the confirmation is saving, and it says so:
   "Saving choices..." has to stay readable. Fading the whole button took the
   dark ink to 1.5:1 on the faded fill, so the fill is thinned instead and
   the text set in muted grey, 4.8:1 on the panel. */
.progress-v2 .vc-confirm:disabled {
    background: rgba(13, 148, 136, 0.3);
    color: var(--text-muted);
    cursor: default;
}

.progress-v2 .vc-confirm:focus-visible {
    outline: 2px solid rgba(13, 148, 136, 0.7);
    outline-offset: 2px;
}

/* "Review 3 of 4 - Plot Structure", above each review panel's title.
   Readers used to meet the casting gate believing they had finished reviewing,
   because nothing had ever said how many reviews there were. */
.review-step {
    text-align: center;
    font-size: 0.75rem;
    line-height: 1rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: rgb(45 212 191);          /* teal-400, matching the panel icons */
    margin-bottom: 0.5rem;
}

/* The audition control while its sample is sounding.
   Filled rather than outlined, so "which one is playing" is answerable at a
   glance in a list of a dozen voices -- the word alone is too small to scan. */
.progress-v2 .vc-play.is-playing {
    background: var(--accent, rgb(45 212 191));
    border-color: var(--accent, rgb(45 212 191));
    color: #06231f;
    font-weight: 600;
}

/* ---------------------------------------------------------------------------
   The casting dialog on a finished novel's page.

   A dialog rather than a panel in the sidebar. Inline, it inherited that
   column's width and rendered as a tall narrow strip -- a dozen voices in a
   single file down the left of the page, which reads as a list that has gone
   wrong rather than as a choice being offered.

   Only the container is defined here; every row inside it is the same `vc-*`
   markup the generation-time picker uses, so the two cannot drift apart.
   --------------------------------------------------------------------------- */

.vc-modal-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.66);
    z-index: 60;
}

.progress-v2.vc-modal {
    position: fixed;
    z-index: 61;
    /* About 1000px on a desktop. At 46rem one character's six voices filled
       the whole panel; at 62.5rem a row of four sits under its description.
       A custom property, because the anchors below are computed from it. */
    --vc-modal-w: min(62.5rem, 100vw - 2rem);
    --vc-modal-left: calc((100vw - var(--vc-modal-w)) / 2);
    /* Where a 52rem-tall panel would sit if centred, and never nearer the
       top edge than 1.5rem. Anchored by top and left rather than centred
       with a transform: a box that grows around its centre moves its corner
       at half the pointer's speed, and the resize grip slides out from
       under the cursor. */
    --vc-modal-top: max(1.5rem, 50vh - 26rem);
    top: var(--vc-modal-top);
    left: var(--vc-modal-left);
    width: var(--vc-modal-w);
    /* A cap of its own, or `.progress-v2 { max-width: 860px }` wins from
       1024px up. As the viewport remainder it also keeps a drag on-screen. */
    max-width: calc(100vw - var(--vc-modal-left) - 1rem);
    /* The body scrolls, not the page behind it. Bounded by the viewport
       alone: a 42rem cap gave a 1440x900 screen the same 672px as a laptop,
       when the whole point of the taller screen is more rows. */
    max-height: calc(100vh - var(--vc-modal-top) - 1.5rem);
    /* Dragged larger from the bottom-right corner, in CSS alone. `resize`
       needs an overflow other than visible; `.vc-modal-body` owns the
       scrolling, so hiding it here clips nothing. The minimums keep the
       header and one row of voices in view. */
    resize: both;
    overflow: hidden;
    min-width: 22rem;
    min-height: 16rem;
    display: flex;
    flex-direction: column;
    background: var(--panel, #111827);
    border: 1px solid var(--line-strong);
    border-radius: 14px;
    padding: 20px 22px 18px;
    box-shadow: 0 24px 60px rgba(0, 0, 0, 0.55);
}

.progress-v2.vc-modal.hidden { display: none; }

/* The grip Blink and WebKit draw is a faint grey that disappears on this
   panel. Two diagonal strokes in the panel's own line colour say "drag me".
   Firefox draws its own and cannot be told otherwise. */
.progress-v2.vc-modal::-webkit-resizer {
    background: linear-gradient(
        135deg,
        transparent 0 50%, var(--line-strong) 50% 60%,
        transparent 60% 75%, var(--line-strong) 75% 85%,
        transparent 85%
    );
}

.vc-modal-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 12px;
}

.vc-modal-head h3 {
    margin: 0;
    font-size: 1.15rem;
    font-weight: 600;
    color: var(--text);
}

.vc-modal-close {
    border: 0;
    background: none;
    color: var(--text-muted, #A8A29E);
    font-size: 1.6rem;
    line-height: 1;
    /* 44px, because dismissing is the one action a reader reaches for in a
       hurry and a 20px glyph is not a target. */
    min-width: 44px;
    min-height: 44px;
    cursor: pointer;
}

.vc-modal-close:hover { color: var(--text); }

.vc-modal-close:focus-visible {
    outline: 2px solid var(--teal-2);
    outline-offset: 2px;
    border-radius: 8px;
}

.vc-modal-body {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    margin-top: 10px;
    /* Room for the scrollbar so a row's right edge is not under it. */
    padding-right: 8px;
    /* The platform default is a bright white bar, which on this panel is the
       lightest thing on screen and pulls the eye away from the choice being
       made. Firefox and the WebKit/Blink pseudo-elements need saying twice --
       there is no one property both honour. */
    scrollbar-width: thin;
    scrollbar-color: var(--line-strong) transparent;
}

.vc-modal-body::-webkit-scrollbar { width: 10px; }

.vc-modal-body::-webkit-scrollbar-track { background: transparent; }

.vc-modal-body::-webkit-scrollbar-thumb {
    background: var(--line-strong);
    border-radius: 999px;
    /* An inset border, so the thumb reads as a slim bar with air around it
       rather than a block filling the gutter. */
    border: 3px solid transparent;
    background-clip: content-box;
}

.vc-modal-body::-webkit-scrollbar-thumb:hover { background-color: var(--text-dim); }

@media (max-width: 640px) {
    .progress-v2.vc-modal {
        /* The anchors above derive from these, so the phone width is set
           here rather than on `width`; `left` then comes out at 0.5rem. */
        --vc-modal-w: calc(100vw - 1rem);
        --vc-modal-top: 0.75rem;
        max-width: calc(100vw - 1rem);
        max-height: calc(100vh - 1.5rem);
        /* 22rem is wider than a small phone, and a drag grip is useless on
           touch: it would only sit over the last row. */
        min-width: 0;
        resize: none;
        padding: 16px;
    }
}
