Fix cursor flicker from a resizing modal; retire the bundled examples
Browse filesThe stage was a vertically centred dialog whose content changes every
second, so each height delta moved ALL of its content by half — which
reads as the pointer flashing between hand and arrow as buttons slide
under it. The dialog is now anchored near the top so growth only
extends downward, the log and text rows have reserved heights, and the
progress bar is always present (sweeping during warm-up) instead of
toggling hidden and shifting everything by 22px. Verified: no
cursor-flapping points across a 680x760 grid over a full lifecycle.
Examples: the bundled scripts predate the current app, so they no
longer load; the chip row hides itself and the empty-state copy points
at Write with AI and import instead. Loader and files are left intact
behind a flag for a future curated set.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- app.py +5 -1
- static/app.js +12 -5
- static/index.html +2 -2
- static/styles.css +9 -1
|
@@ -455,7 +455,11 @@ def _build_examples_index() -> list[dict]:
|
|
| 455 |
return index
|
| 456 |
|
| 457 |
|
| 458 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 459 |
|
| 460 |
|
| 461 |
# --- Modal Connection ---
|
|
|
|
| 455 |
return index
|
| 456 |
|
| 457 |
|
| 458 |
+
# Examples retired 2026-08-22 (Josh) — the bundled scripts predate the current
|
| 459 |
+
# app and no longer fit it. The loader and text_examples/ files are left intact
|
| 460 |
+
# so restoring a curated set is a one-line change.
|
| 461 |
+
SHOW_BUNDLED_EXAMPLES = False
|
| 462 |
+
EXAMPLES_INDEX = _build_examples_index() if SHOW_BUNDLED_EXAMPLES else []
|
| 463 |
|
| 464 |
|
| 465 |
# --- Modal Connection ---
|
|
@@ -1061,7 +1061,7 @@ function renderTurns() {
|
|
| 1061 |
empty.id = "emptyTurns";
|
| 1062 |
empty.innerHTML =
|
| 1063 |
'<div class="empty-title">No scene yet</div>' +
|
| 1064 |
-
"
|
| 1065 |
el.turnsList.append(empty);
|
| 1066 |
updateMeta();
|
| 1067 |
return;
|
|
@@ -1170,6 +1170,7 @@ function loadScriptResult(result, titleFallback, summary) {
|
|
| 1170 |
/* ---------------- Examples ---------------- */
|
| 1171 |
function renderExamplePills() {
|
| 1172 |
el.examplePills.innerHTML = "";
|
|
|
|
| 1173 |
state.examples.forEach((example) => {
|
| 1174 |
const btn = document.createElement("button");
|
| 1175 |
btn.type = "button";
|
|
@@ -1703,7 +1704,12 @@ function paintProgress() {
|
|
| 1703 |
let pctText = "Starting…";
|
| 1704 |
if (!progress.genStartedAt) {
|
| 1705 |
// Still warming up — say so plainly instead of ticking a generation clock.
|
|
|
|
| 1706 |
el.progressMeta.hidden = false;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1707 |
const warm = `GPU warming up · ${formatMinutes(elapsed) || "0s"}`;
|
| 1708 |
el.progressMeta.textContent = warm;
|
| 1709 |
el.genStagePct.textContent = "Warming up…";
|
|
@@ -1768,7 +1774,8 @@ function showStagePane(which) {
|
|
| 1768 |
function openGenerateStage(title) {
|
| 1769 |
el.genStageTitle.textContent = (title || "Untitled conversation").toUpperCase();
|
| 1770 |
el.genStagePct.textContent = "Starting…";
|
| 1771 |
-
el.genStageTrack.hidden =
|
|
|
|
| 1772 |
el.genStageFill.style.width = "0%";
|
| 1773 |
el.genStageLog.textContent = "";
|
| 1774 |
showStagePane("generating");
|
|
@@ -1789,9 +1796,9 @@ function startProgress() {
|
|
| 1789 |
progress.genStartedAt = 0;
|
| 1790 |
progress.warmupSecs = 0;
|
| 1791 |
el.progressFill.style.width = "0%";
|
| 1792 |
-
el.progressTrack.classList.
|
| 1793 |
-
el.genStageTrack.classList.
|
| 1794 |
-
el.progressTrack.hidden =
|
| 1795 |
clearInterval(progress.ticker);
|
| 1796 |
progress.ticker = setInterval(paintProgress, 1000);
|
| 1797 |
paintProgress();
|
|
|
|
| 1061 |
empty.id = "emptyTurns";
|
| 1062 |
empty.innerHTML =
|
| 1063 |
'<div class="empty-title">No scene yet</div>' +
|
| 1064 |
+
"Describe a scenario above and click <strong>Write with AI</strong>, import a script, or start typing your own line below.";
|
| 1065 |
el.turnsList.append(empty);
|
| 1066 |
updateMeta();
|
| 1067 |
return;
|
|
|
|
| 1170 |
/* ---------------- Examples ---------------- */
|
| 1171 |
function renderExamplePills() {
|
| 1172 |
el.examplePills.innerHTML = "";
|
| 1173 |
+
el.examplePills.hidden = !state.examples.length; // no empty gap when unset
|
| 1174 |
state.examples.forEach((example) => {
|
| 1175 |
const btn = document.createElement("button");
|
| 1176 |
btn.type = "button";
|
|
|
|
| 1704 |
let pctText = "Starting…";
|
| 1705 |
if (!progress.genStartedAt) {
|
| 1706 |
// Still warming up — say so plainly instead of ticking a generation clock.
|
| 1707 |
+
// The bar stays present and sweeping: hiding it would resize the dialog.
|
| 1708 |
el.progressMeta.hidden = false;
|
| 1709 |
+
el.progressTrack.hidden = false;
|
| 1710 |
+
el.genStageTrack.hidden = false;
|
| 1711 |
+
el.progressTrack.classList.add("indeterminate");
|
| 1712 |
+
el.genStageTrack.classList.add("indeterminate");
|
| 1713 |
const warm = `GPU warming up · ${formatMinutes(elapsed) || "0s"}`;
|
| 1714 |
el.progressMeta.textContent = warm;
|
| 1715 |
el.genStagePct.textContent = "Warming up…";
|
|
|
|
| 1774 |
function openGenerateStage(title) {
|
| 1775 |
el.genStageTitle.textContent = (title || "Untitled conversation").toUpperCase();
|
| 1776 |
el.genStagePct.textContent = "Starting…";
|
| 1777 |
+
el.genStageTrack.hidden = false;
|
| 1778 |
+
el.genStageTrack.classList.add("indeterminate");
|
| 1779 |
el.genStageFill.style.width = "0%";
|
| 1780 |
el.genStageLog.textContent = "";
|
| 1781 |
showStagePane("generating");
|
|
|
|
| 1796 |
progress.genStartedAt = 0;
|
| 1797 |
progress.warmupSecs = 0;
|
| 1798 |
el.progressFill.style.width = "0%";
|
| 1799 |
+
el.progressTrack.classList.add("indeterminate");
|
| 1800 |
+
el.genStageTrack.classList.add("indeterminate");
|
| 1801 |
+
el.progressTrack.hidden = false;
|
| 1802 |
clearInterval(progress.ticker);
|
| 1803 |
progress.ticker = setInterval(paintProgress, 1000);
|
| 1804 |
paintProgress();
|
|
@@ -129,8 +129,8 @@
|
|
| 129 |
<div id="turnsList">
|
| 130 |
<div class="empty-transcript" id="emptyTurns">
|
| 131 |
<div class="empty-title">No scene yet</div>
|
| 132 |
-
|
| 133 |
-
start typing your own line below.
|
| 134 |
</div>
|
| 135 |
</div>
|
| 136 |
<button class="add-turn" id="addTurnBtn" type="button">+ Add a line</button>
|
|
|
|
| 129 |
<div id="turnsList">
|
| 130 |
<div class="empty-transcript" id="emptyTurns">
|
| 131 |
<div class="empty-title">No scene yet</div>
|
| 132 |
+
Describe a scenario above and click <strong>Write with AI</strong>, import a script,
|
| 133 |
+
or start typing your own line below.
|
| 134 |
</div>
|
| 135 |
</div>
|
| 136 |
<button class="add-turn" id="addTurnBtn" type="button">+ Add a line</button>
|
|
@@ -845,6 +845,11 @@ dialog::backdrop { background: rgba(42, 32, 22, 0.45); }
|
|
| 845 |
border-radius: 24px;
|
| 846 |
max-height: 90vh;
|
| 847 |
overflow-y: auto;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 848 |
}
|
| 849 |
.player-stage::backdrop { background: rgba(42, 32, 22, 0.55); }
|
| 850 |
.stage-body { padding: 36px 36px 30px; }
|
|
@@ -889,20 +894,23 @@ body.is-generating .canvas { opacity: 0.6; transition: opacity 0.2s; }
|
|
| 889 |
font-variant-numeric: tabular-nums;
|
| 890 |
}
|
| 891 |
.gen-track { height: 10px; margin-bottom: 12px; }
|
|
|
|
| 892 |
.gen-stage-meta {
|
| 893 |
text-align: center;
|
| 894 |
font-size: 0.9rem;
|
| 895 |
font-weight: 700;
|
| 896 |
color: var(--ink);
|
| 897 |
font-variant-numeric: tabular-nums;
|
|
|
|
| 898 |
}
|
| 899 |
.gen-stage-desc {
|
| 900 |
text-align: center;
|
| 901 |
font-size: 0.84rem;
|
| 902 |
color: var(--ink-faint);
|
| 903 |
margin-top: 6px;
|
| 904 |
-
min-height:
|
| 905 |
}
|
|
|
|
| 906 |
.gen-stage-log {
|
| 907 |
margin-top: 18px;
|
| 908 |
background: var(--paper-soft);
|
|
|
|
| 845 |
border-radius: 24px;
|
| 846 |
max-height: 90vh;
|
| 847 |
overflow-y: auto;
|
| 848 |
+
/* Anchored near the top rather than centred: the generating view changes
|
| 849 |
+
height every second, and a centred dialog would shift ALL its content by
|
| 850 |
+
half of every delta — which reads as the pointer flickering. */
|
| 851 |
+
margin-top: 5vh;
|
| 852 |
+
margin-bottom: auto;
|
| 853 |
}
|
| 854 |
.player-stage::backdrop { background: rgba(42, 32, 22, 0.55); }
|
| 855 |
.stage-body { padding: 36px 36px 30px; }
|
|
|
|
| 894 |
font-variant-numeric: tabular-nums;
|
| 895 |
}
|
| 896 |
.gen-track { height: 10px; margin-bottom: 12px; }
|
| 897 |
+
/* Fixed heights so per-second text changes can't resize the dialog. */
|
| 898 |
.gen-stage-meta {
|
| 899 |
text-align: center;
|
| 900 |
font-size: 0.9rem;
|
| 901 |
font-weight: 700;
|
| 902 |
color: var(--ink);
|
| 903 |
font-variant-numeric: tabular-nums;
|
| 904 |
+
min-height: 1.4em;
|
| 905 |
}
|
| 906 |
.gen-stage-desc {
|
| 907 |
text-align: center;
|
| 908 |
font-size: 0.84rem;
|
| 909 |
color: var(--ink-faint);
|
| 910 |
margin-top: 6px;
|
| 911 |
+
min-height: 2.6em;
|
| 912 |
}
|
| 913 |
+
.gen-stage-log { height: 260px; }
|
| 914 |
.gen-stage-log {
|
| 915 |
margin-top: 18px;
|
| 916 |
background: var(--paper-soft);
|