diff --git a/CHANGELOG.md b/CHANGELOG.md index 46111bc..c172e3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,13 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · versioned wi --- +## [1.13.0] — 2026-07-06 + +### Changed +- **Script Rehearser's Stage character list is now a sidebar**, matching Read Aloud's Casting sidebar look exactly (same `.ab-cv-side`/`.ab-char-item` styling: colored dot avatar, name, line count) instead of the old horizontal avatar strip above the transport bar. It sits alongside the script page, collapses to just the avatar dots via the same chevron control, and clicking a character scrolls the script to their first line. + +--- + ## [1.12.99] — 2026-07-05 ### Fixed diff --git a/VERSION b/VERSION index d8e4738..feaae22 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.12.99 +1.13.0 diff --git a/static/index.html b/static/index.html index 3e21e7a..ab1f69a 100644 --- a/static/index.html +++ b/static/index.html @@ -10,7 +10,7 @@ - + @@ -27,7 +27,7 @@ - + @@ -365,7 +365,7 @@ window.toggleNavTree = function(treeId, chevronId) { - + diff --git a/static/js/rehearser.js b/static/js/rehearser.js index 391496e..5a8a1ae 100644 --- a/static/js/rehearser.js +++ b/static/js/rehearser.js @@ -2252,12 +2252,21 @@ function rehStageFontStep(delta) { } function rehApplyCastCollapsed() { + // Same collapse pattern as Read Aloud's Casting sidebar (.ab-cv-side.is-collapsed + // shrinks to just the avatar dots); the stage area's grid column narrows to match. const row = $('reh-cast-row'); + const stage = $('reh-stage-area'); if (!row) return; const collapsed = localStorage.getItem(REH_CAST_COLLAPSED_KEY) === '1'; - row.classList.toggle('reh-cast-collapsed', collapsed); + row.classList.toggle('is-collapsed', collapsed); + if (stage) stage.classList.toggle('side-collapsed', collapsed); const btn = $('reh-cast-toggle'); - if (btn) btn.setAttribute('aria-expanded', String(!collapsed)); + if (btn) { + btn.setAttribute('aria-expanded', String(!collapsed)); + btn.title = collapsed ? 'Expand character list' : 'Collapse to avatars'; + const icon = btn.querySelector('.mdi'); + if (icon) icon.className = 'mdi ' + (collapsed ? 'mdi-chevron-right' : 'mdi-chevron-left'); + } } function rehToggleCast() { const collapsed = localStorage.getItem(REH_CAST_COLLAPSED_KEY) === '1'; @@ -2275,18 +2284,27 @@ function buildScriptPage() { const titleEl = $('reh-page-title'); if (titleEl) titleEl.textContent = $('reh-script-title')?.value.trim() || 'Script'; - // Cast strip + // Cast sidebar — same look (.ab-char-item rows) as Read Aloud's Casting sidebar. const castStrip = $('reh-cast-strip'); if (castStrip) { const names = Object.keys(rehState.cast); + const lineCountFor = sp => rehState.lines.filter(l => l.speaker === sp && l.type === 'dialog').length; castStrip.innerHTML = names.map(sp => { const c = rehState.cast[sp], isMe = c.voice === 'me'; - return `
- ${voiceAvatarHtml(isMe?'':c.voice, c.color, 28)} - ${escHtml(sp)} - ${isMe?'':''} + const displayName = (sp === REH_NARRATOR_KEY) ? 'Narrator' : sp; + return `
+ ${escHtml((displayName||'?')[0].toUpperCase())} + ${escHtml(displayName)} + ${isMe?'':''} + ${lineCountFor(sp)}
`; }).join(''); + castStrip.querySelectorAll('.ab-char-item').forEach(item => { + item.addEventListener('click', () => { + const line = document.querySelector(`.reh-block[data-speaker="${CSS.escape(item.dataset.speaker)}"]`); + if (line) line.scrollIntoView({ behavior: 'smooth', block: 'center' }); + }); + }); const lbl = $('reh-cast-toggle-label'); if (lbl) lbl.textContent = `Characters (${names.length})`; } diff --git a/static/sections/s-rehearser.html b/static/sections/s-rehearser.html index cc1d0f2..e563f86 100644 --- a/static/sections/s-rehearser.html +++ b/static/sections/s-rehearser.html @@ -293,15 +293,6 @@
-
- -
-
-
@@ -390,7 +381,7 @@
-
+
@@ -398,6 +389,15 @@
+ + +
+
+ Characters + +
+
+
diff --git a/static/style.css b/static/style.css index dc72d17..0984d22 100644 --- a/static/style.css +++ b/static/style.css @@ -4058,9 +4058,6 @@ code { background: var(--panel); border-radius: 4px; padding: 1px 5px; font-fami .reh-input-row textarea { min-height: 220px; font-family: monospace; font-size: 13px; width: 100%; resize: vertical; background: var(--surface); border: 1px solid var(--border); border-radius: 6px; padding: 10px; color: var(--text); } .reh-input-side { display: flex; flex-direction: column; gap: 8px; } -/* Phase 2 cast */ -.reh-cast-row { display: flex; align-items: center; gap: 10px; padding: 8px 12px; background: var(--panel); border-radius: 8px; flex-wrap: wrap; border: 1px solid var(--border); } - /* ── Cast list: toolbar + Card/List view toggle ───────────────────────────── */ .reh-cast-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 12px; flex-wrap: wrap; } .reh-cast-count { font-size: 13px; color: var(--subtext); font-weight: 600; } @@ -4183,8 +4180,6 @@ code { background: var(--panel); border-radius: 4px; padding: 1px 5px; font-fami .reh-cc-soul-text:focus { outline: none; border-color: var(--accent); } -.reh-cast-strip { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; } -.reh-strip-char { display: flex; flex-direction: column; align-items: center; gap: 2px; cursor: default; } .reh-tb-btn { width: 36px; height: 36px; border-radius: 50%; border: 1px solid var(--border); background: var(--panel); color: var(--text); cursor: pointer; font-size: 16px; display: flex; align-items: center; justify-content: center; transition: background .15s; } .reh-tb-btn:hover { background: var(--border); } .reh-tb-play { background: var(--accent); color: #fff; border-color: var(--accent); font-size: 18px; } @@ -4194,6 +4189,17 @@ code { background: var(--panel); border-radius: 4px; padding: 1px 5px; font-fami .reh-tb-progress { height: 100%; background: var(--accent); width: 0%; transition: width .3s ease; } .reh-tb-label { font-size: 12px; color: var(--subtext); white-space: nowrap; font-variant-numeric: tabular-nums; } +/* Stage: script page + character sidebar (same .ab-cv-side look Read Aloud's + Casting view uses). The sidebar sticks in place while the script scrolls. */ +.reh-stage-area { display: flex; gap: 12px; align-items: flex-start; transition: none; } +.reh-stage-area .reh-page-wrap { flex: 1; min-width: 0; } +.reh-stage-area .ab-cv-side { + width: 220px; flex-shrink: 0; position: sticky; top: 12px; + max-height: calc(100vh - 160px); +} +.reh-stage-area.side-collapsed .ab-cv-side { width: 44px; } +.reh-stage-area .ab-cv-side .ab-cv-chars { flex: 1; overflow-y: auto; } + /* A4 page */ .reh-page-wrap { padding: 24px 16px; background: var(--panel); min-height: 60vh; } .reh-a4-page { @@ -4435,18 +4441,6 @@ code { background: var(--panel); border-radius: 4px; padding: 1px 5px; font-fami transition: background .15s; } -/* Cast strip takes its own full row inside the console */ -.reh-console .reh-cast-strip { flex-basis: 100%; order: -1; padding-bottom: 5px; border-bottom: 1px solid var(--border); margin-bottom: 0; } - -/* Collapsible character list (Stage) */ -.reh-console .reh-cast-row { flex-basis: 100%; order: -1; display: flex; align-items: flex-start; gap: 10px; padding-bottom: 5px; border-bottom: 1px solid var(--border); } -.reh-console .reh-cast-row .reh-cast-strip { flex-basis: auto; order: 0; flex: 1; padding-bottom: 0; border-bottom: none; min-width: 0; } -.reh-cast-toggle { flex-shrink: 0; display: inline-flex; align-items: center; gap: 5px; padding: 4px 10px; border: 1px solid var(--border); border-radius: 16px; background: var(--panel); color: var(--text); font-size: 11px; font-weight: 700; cursor: pointer; white-space: nowrap; height: 28px; } -.reh-cast-toggle:hover { background: var(--border); } -.reh-cast-toggle .reh-cast-chev { transition: transform .2s; font-size: 14px; } -.reh-cast-row.reh-cast-collapsed .reh-cast-strip { display: none; } -.reh-cast-row.reh-cast-collapsed .reh-cast-chev { transform: rotate(-90deg); } - /* Play font-size stepper (+A / -A) */ .reh-fontsize-ctl { display: inline-flex; gap: 2px; } .reh-fontsize-btn { font-weight: 700; padding: 2px 7px; }