trailer.html: show the story the writer worked out, above the script

The writer now reads the book and commits to what the story IS before writing
a single shot - premise, protagonist, the emotional engine stated as a moment,
what a viewer must understand, which moments have to be SHOWN rather than
narrated, and how it ends so it knows what it is withholding.

That document is worth more to an author than the script is. It is where a
misreading of the book is visible and cheap to fix; by the time it is
twenty-six shots and a storyboard it is neither. So it sits above the shots,
collapsed, and the two lists that decide whether the trailer works - what a
viewer must understand, and what must be dramatised - are open by default.

"What a viewer must understand" is also literally the checklist the
comprehension test measures against, so the two panels now read as a pair: here
is what it must convey, and here is what three readers actually took away.
This commit is contained in:
gator 2026-09-17 07:25:34 +00:00
parent 50157f0d5b
commit c900dce0aa

View file

@ -60,6 +60,16 @@
border-radius:999px;padding:0 6px} border-radius:999px;padding:0 6px}
.vobadge{color:var(--muted);font-size:11px;border:1px solid var(--line); .vobadge{color:var(--muted);font-size:11px;border:1px solid var(--line);
border-radius:999px;padding:0 6px} border-radius:999px;padding:0 6px}
.spine{border:1px solid var(--line);border-radius:10px;padding:14px 16px;margin:0 0 14px}
.spine h4{margin:0 0 8px;font-size:15px}
.spine .eng{font-size:14px;border-left:2px solid var(--accent);padding-left:11px;margin:0 0 12px}
.spine .kv{font-size:13px;margin:3px 0;color:var(--muted)}
.spine .kv b{color:var(--ink);font-weight:600}
.spine h5{margin:12px 0 4px;font-size:12px;letter-spacing:.06em;text-transform:uppercase;
color:var(--muted)}
.spine ul{margin:0;padding-left:18px;font-size:13px}
.spine details{margin-top:10px}
.spine summary{cursor:pointer;color:var(--muted);font-size:13px}
.gaps{border:1px solid var(--accent);border-radius:10px;padding:14px 16px;margin:0 0 14px} .gaps{border:1px solid var(--accent);border-radius:10px;padding:14px 16px;margin:0 0 14px}
.gaps h4{margin:0 0 4px;font-size:15px;color:var(--accent)} .gaps h4{margin:0 0 4px;font-size:15px;color:var(--accent)}
.gaps .verdict{font-size:14px;margin:0 0 10px} .gaps .verdict{font-size:14px;margin:0 0 10px}
@ -214,7 +224,8 @@
return; return;
} }
if (meta.state === "writing") { if (meta.state === "writing") {
pane.innerHTML = '<p class="empty">Working. Reading the whole book takes a few minutes.</p>'; pane.innerHTML = '<p class="empty">Working. It reads the whole book, works out what the '
+ 'story is, and then writes the trailer from that. Several minutes.</p>';
return; return;
} }
var r = await api("get_draft", { draft_id: id }); var r = await api("get_draft", { draft_id: id });
@ -224,6 +235,43 @@
pane.innerHTML = paneHtml(meta, r.draft); pane.innerHTML = paneHtml(meta, r.draft);
} }
// The story the writer committed to before writing a shot. This is where a
// misreading of the book is visible and cheap to fix; by the time it is
// twenty-six shots and a storyboard, it is neither.
function spineHtml() {
var sp = PKG.spine;
if (!sp || typeof sp !== "object") return "";
var p = sp.protagonist || {};
function ul(items) {
return (!items || !items.length) ? ""
: '<ul>' + items.map(function (x) {
return '<li>' + esc(typeof x === "string" ? x : JSON.stringify(x)) + '</li>';
}).join("") + '</ul>';
}
var beats = (sp.beats || []).map(function (b) {
return '<li>' + esc(b.what || "")
+ (b.is_ending ? ' <b>(the ending)</b>' : '')
+ (b.why_it_matters ? ' <span class="kv">' + esc(b.why_it_matters) + '</span>' : '')
+ '</li>'; }).join("");
return '<div class="spine"><h4>The story, as the writer worked it out from the book</h4>'
+ (sp.the_engine ? '<p class="eng">' + esc(sp.the_engine) + '</p>' : '')
+ (p.who ? '<div class="kv"><b>who</b> ' + esc(p.who) + '</div>' : '')
+ (p.wants ? '<div class="kv"><b>wants</b> ' + esc(p.wants) + '</div>' : '')
+ (p.obstacle ? '<div class="kv"><b>in the way</b> ' + esc(p.obstacle) + '</div>' : '')
+ (p.cost ? '<div class="kv"><b>cost</b> ' + esc(p.cost) + '</div>' : '')
+ (sp.what_a_viewer_must_understand && sp.what_a_viewer_must_understand.length
? '<h5>A viewer must come away knowing</h5>' + ul(sp.what_a_viewer_must_understand) : '')
+ (sp.moments_to_dramatise && sp.moments_to_dramatise.length
? '<h5>Must be shown, not narrated</h5>' + ul(sp.moments_to_dramatise) : '')
+ (sp.premise ? '<details><summary>the premise in full</summary><p>'
+ esc(sp.premise) + '</p></details>' : '')
+ (beats ? '<details><summary>the beats of the book</summary><ol>' + beats + '</ol></details>' : '')
+ (sp.withhold ? '<details><summary>what the trailer is withholding</summary><p>'
+ esc(sp.withhold) + '</p></details>' : '')
+ '</div>';
}
function barHtml(meta) { function barHtml(meta) {
var shots = (PKG.shots || []), cast = (PKG.characters || []); var shots = (PKG.shots || []), cast = (PKG.characters || []);
var undrawn = shots.filter(function (s) { return !s.opening_url; }).length; var undrawn = shots.filter(function (s) { return !s.opening_url; }).length;
@ -273,7 +321,6 @@
return out.join(""); return out.join("");
} }
// What readers understood, and what the writer says it failed to say.
function reviewsHtml(meta) { function reviewsHtml(meta) {
var mine = REVIEWS.filter(function (r) { var mine = REVIEWS.filter(function (r) {
return r.subject === meta.writer && Number(r.iteration) === Number(meta.iteration); }); return r.subject === meta.writer && Number(r.iteration) === Number(meta.iteration); });
@ -359,7 +406,7 @@
shots.forEach(function (s) { body += shotHtml(s, voice) + insertHtml(s.id); }); shots.forEach(function (s) { body += shotHtml(s, voice) + insertHtml(s.id); });
return head + probs + '<div class="bar">' + barHtml(meta) + '</div>' return head + probs + '<div class="bar">' + barHtml(meta) + '</div>'
+ reviewsHtml(meta) + castHtml + body; + spineHtml() + reviewsHtml(meta) + castHtml + body;
} }
function insertHtml(after) { function insertHtml(after) {
@ -456,11 +503,10 @@
+ '</div></div>'; + '</div></div>';
} }
async function act(name, payload, msg) { async function act(name, payload) {
var r = await api(name, payload); var r = await api(name, payload);
if (!r.ok) { showMsg("msg", r.error || "That did not work."); return null; } if (!r.ok) { showMsg("msg", r.error || "That did not work."); return null; }
hideMsg("msg"); hideMsg("msg");
if (msg) showMsg("msg", msg(r), "ok");
return r; return r;
} }