trailer.html: show what viewers understood, not what reviewers thought
The review panel rendered the old critique shape - errors, suggestions, strengths. The comprehension test produces something else: three summaries written by readers who saw only the script, and the writer's own list of what the trailer failed to convey. The gap list leads, because that is what the revision consumes and what the author needs to decide about. Each reader's summary is then shown verbatim, with what they were still asking and whether they would watch the film - reading three strangers describe your trailer badly is the most persuasive evidence there is that it does not tell the story. "Got wrong" is called out separately from "missed". A trailer that leaves something out is incomplete; one that leads a viewer to the wrong conclusion is worse, and they are different problems to fix.
This commit is contained in:
parent
d21360ea2b
commit
50157f0d5b
1 changed files with 73 additions and 39 deletions
112
trailer.html
112
trailer.html
|
|
@ -60,14 +60,20 @@
|
||||||
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}
|
||||||
.revs{margin:0 0 22px}
|
.gaps{border:1px solid var(--accent);border-radius:10px;padding:14px 16px;margin:0 0 14px}
|
||||||
.rev{border:1px solid var(--line);border-radius:10px;padding:12px 14px;margin-bottom:10px}
|
.gaps h4{margin:0 0 4px;font-size:15px;color:var(--accent)}
|
||||||
.rev h4{margin:0 0 6px;font-size:14px}
|
.gaps .verdict{font-size:14px;margin:0 0 10px}
|
||||||
.rev h4 .n{color:var(--accent2);font-weight:600;margin-left:8px}
|
.gaps ol{margin:0;padding-left:20px;font-size:13px}
|
||||||
.rev ul{margin:4px 0 8px;padding-left:18px;font-size:13px}
|
.gaps li{margin-bottom:7px}
|
||||||
.rev .er{color:#e0685f}
|
.gaps .fix{color:var(--muted);display:block}
|
||||||
.rev .kp{color:var(--accent2)}
|
.gaps .sh{color:var(--muted);font-variant-numeric:tabular-nums}
|
||||||
.rev .sh{color:var(--muted);font-variant-numeric:tabular-nums}
|
.rd{border:1px solid var(--line);border-radius:10px;padding:12px 14px;margin-bottom:10px}
|
||||||
|
.rd h4{margin:0 0 6px;font-size:14px}
|
||||||
|
.rd h4 .n{color:var(--accent2);font-weight:600;margin-left:8px}
|
||||||
|
.rd blockquote{margin:0 0 8px;font-size:14px;border-left:2px solid var(--line);padding-left:10px}
|
||||||
|
.rd .kv{color:var(--muted);font-size:12.5px;margin:2px 0}
|
||||||
|
.rd ul{margin:6px 0 0;padding-left:18px;font-size:13px}
|
||||||
|
.rd .wrong{color:#e0685f}
|
||||||
.tools{display:flex;gap:7px;flex-wrap:wrap;align-items:center;margin-top:10px}
|
.tools{display:flex;gap:7px;flex-wrap:wrap;align-items:center;margin-top:10px}
|
||||||
.tools button{font-size:12px;padding:5px 10px}
|
.tools button{font-size:12px;padding:5px 10px}
|
||||||
.chk{font-size:12px;border-radius:999px;padding:1px 8px;border:1px solid var(--line);color:var(--muted)}
|
.chk{font-size:12px;border-radius:999px;padding:1px 8px;border:1px solid var(--line);color:var(--muted)}
|
||||||
|
|
@ -114,8 +120,6 @@
|
||||||
function busyRun() {
|
function busyRun() {
|
||||||
return RUN && /^(writing|reviewing|revising)$/.test(String(RUN.stage || ""));
|
return RUN && /^(writing|reviewing|revising)$/.test(String(RUN.stage || ""));
|
||||||
}
|
}
|
||||||
// A shot is busy while it is being written, drawn or filmed. Any of those
|
|
||||||
// means the package on screen is about to change.
|
|
||||||
function busyShots() {
|
function busyShots() {
|
||||||
return (PKG.shots || []).some(function (s) {
|
return (PKG.shots || []).some(function (s) {
|
||||||
return s.writing || s.drawing || s.filming; });
|
return s.writing || s.drawing || s.filming; });
|
||||||
|
|
@ -141,13 +145,14 @@
|
||||||
if (!busy && timer) { clearInterval(timer); timer = null; }
|
if (!busy && timer) { clearInterval(timer); timer = null; }
|
||||||
}
|
}
|
||||||
|
|
||||||
// A refresh keeps the tab you are on and the box you are typing in.
|
|
||||||
async function refresh() {
|
async function refresh() {
|
||||||
var r = await api("run_status", { run_id: runId });
|
var r = await api("run_status", { run_id: runId });
|
||||||
if (!r.ok) return;
|
if (!r.ok) return;
|
||||||
RUN = r.run; DRAFTS = r.drafts || [];
|
RUN = r.run; DRAFTS = r.drafts || [];
|
||||||
document.getElementById("sub").textContent = sub();
|
document.getElementById("sub").textContent = sub();
|
||||||
paintTabs();
|
paintTabs();
|
||||||
|
var rv = await api("list_reviews", { run_id: runId });
|
||||||
|
REVIEWS = (rv && rv.reviews) || [];
|
||||||
await openTab(CUR);
|
await openTab(CUR);
|
||||||
tick();
|
tick();
|
||||||
}
|
}
|
||||||
|
|
@ -179,7 +184,8 @@
|
||||||
var extra = d.state === "writing" ? '<span class="st">writing…</span>'
|
var extra = d.state === "writing" ? '<span class="st">writing…</span>'
|
||||||
: d.state === "failed" ? '<span class="st">failed</span>'
|
: d.state === "failed" ? '<span class="st">failed</span>'
|
||||||
: (s && !/^(drawn|failed)/.test(s)) ? '<span class="st">' + esc(s) + '</span>'
|
: (s && !/^(drawn|failed)/.test(s)) ? '<span class="st">' + esc(s) + '</span>'
|
||||||
: d.score ? '<span class="sc">' + d.score + '</span>'
|
: d.score ? '<span class="sc" title="how well readers understood it">'
|
||||||
|
+ d.score + '</span>'
|
||||||
: '';
|
: '';
|
||||||
return '<button type="button" class="tab' + (d.id === CUR ? ' on' : '')
|
return '<button type="button" class="tab' + (d.id === CUR ? ' on' : '')
|
||||||
+ (d.writer === RUN.lead_writer ? ' lead' : '') + '" data-tab="' + esc(d.id) + '">'
|
+ (d.writer === RUN.lead_writer ? ' lead' : '') + '" data-tab="' + esc(d.id) + '">'
|
||||||
|
|
@ -235,7 +241,8 @@
|
||||||
|
|
||||||
if (drawing) out.push('<button type="button" class="btn" disabled>Drawing… ' + esc(s) + '</button>');
|
if (drawing) out.push('<button type="button" class="btn" disabled>Drawing… ' + esc(s) + '</button>');
|
||||||
else if (undrawn) out.push('<button type="button" class="btn" data-draw="' + esc(meta.id)
|
else if (undrawn) out.push('<button type="button" class="btn" data-draw="' + esc(meta.id)
|
||||||
+ '">Draw ' + (undrawn === shots.length ? 'this storyboard' : undrawn + ' undrawn shots')
|
+ '">Draw ' + (undrawn === shots.length ? 'this storyboard'
|
||||||
|
: undrawn + ' undrawn shot' + (undrawn === 1 ? '' : 's'))
|
||||||
+ ' · $' + (imgs * 0.068).toFixed(2) + '</button>');
|
+ ' · $' + (imgs * 0.068).toFixed(2) + '</button>');
|
||||||
else out.push('<span class="soon">storyboard drawn'
|
else out.push('<span class="soon">storyboard drawn'
|
||||||
+ (meta.draw_usd ? ' · $' + Number(meta.draw_usd).toFixed(2) : '') + '</span>');
|
+ (meta.draw_usd ? ' · $' + Number(meta.draw_usd).toFixed(2) : '') + '</span>');
|
||||||
|
|
@ -244,10 +251,12 @@
|
||||||
var others = DRAFTS.filter(function (d) {
|
var others = DRAFTS.filter(function (d) {
|
||||||
return d.writer !== RUN.lead_writer && /^ready/.test(String(d.state)); }).length;
|
return d.writer !== RUN.lead_writer && /^ready/.test(String(d.state)); }).length;
|
||||||
if (RUN.stage === "reviewing")
|
if (RUN.stage === "reviewing")
|
||||||
out.push('<button type="button" class="btn" disabled>The others are reading it…</button>');
|
out.push('<button type="button" class="btn" disabled>Readers are watching it…</button>');
|
||||||
else if (others)
|
else if (others)
|
||||||
out.push('<button type="button" class="btn" data-review="1">'
|
out.push('<button type="button" class="btn" data-review="1" title="'
|
||||||
+ (REVIEWS.length ? "Review it again" : "Have the others review this") + '</button>');
|
+ 'Each other model reads ONLY this script - no book - and says what it understood.">'
|
||||||
|
+ (REVIEWS.length ? "Test it again" : "Does it make sense? Ask " + others + " readers")
|
||||||
|
+ '</button>');
|
||||||
if (RUN.stage === "revising")
|
if (RUN.stage === "revising")
|
||||||
out.push('<button type="button" class="btn" disabled>Rewriting…</button>');
|
out.push('<button type="button" class="btn" disabled>Rewriting…</button>');
|
||||||
else if (REVIEWS.length || Object.keys(NOTES).length)
|
else if (REVIEWS.length || Object.keys(NOTES).length)
|
||||||
|
|
@ -264,26 +273,55 @@
|
||||||
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); });
|
||||||
if (!mine.length) return "";
|
if (!mine.length) return "";
|
||||||
return '<div class="revs">' + mine.map(function (r) {
|
var gaps = mine.filter(function (r) { return r.reviewer === "__gaps__"; })[0];
|
||||||
function list(items, cls, fmt) {
|
var readers = mine.filter(function (r) { return r.reviewer !== "__gaps__"; });
|
||||||
return (!items || !items.length) ? ""
|
var out = "";
|
||||||
: '<ul class="' + cls + '">' + items.map(fmt).join("") + '</ul>';
|
|
||||||
}
|
if (gaps) {
|
||||||
return '<div class="rev"><h4>' + esc(r.label) + '<span class="n">' + esc(r.score) + '/100</span></h4>'
|
var v = gaps.strengths && gaps.strengths.verdict;
|
||||||
+ list(r.errors, "er", function (e) {
|
var list = gaps.suggestions || [];
|
||||||
return '<li>' + (e.shot ? '<span class="sh">shot ' + esc(e.shot) + '</span> ' : '')
|
out += '<div class="gaps"><h4>What this trailer does not tell a viewer</h4>'
|
||||||
+ esc(e.problem || "") + (e.fix ? ' — <i>' + esc(e.fix) + '</i>' : '') + '</li>'; })
|
+ (v ? '<p class="verdict">' + esc(v) + '</p>' : '')
|
||||||
+ list(r.suggestions, "", function (s) {
|
+ (list.length ? '<ol>' + list.map(function (g) {
|
||||||
return '<li>' + (s.shot ? '<span class="sh">shot ' + esc(s.shot) + '</span> ' : '')
|
return '<li>' + esc(g.what || "")
|
||||||
+ esc(s.change || "") + '</li>'; })
|
+ '<span class="fix"><span class="sh">shot ' + esc(g.shot || "?") + '</span> → '
|
||||||
+ list(r.strengths, "kp", function (s) {
|
+ esc(g.fix || "") + '</span></li>'; }).join("") + '</ol>' : '')
|
||||||
return '<li>' + esc(typeof s === "string" ? s : JSON.stringify(s)) + '</li>'; })
|
|
||||||
+ '</div>';
|
+ '</div>';
|
||||||
}).join("") + '</div>';
|
}
|
||||||
|
|
||||||
|
out += readers.map(function (r) {
|
||||||
|
var s = r.strengths || {}, e = r.errors || {};
|
||||||
|
if (typeof s !== "object") s = {};
|
||||||
|
if (typeof e !== "object") e = {};
|
||||||
|
function ul(items, cls) {
|
||||||
|
return (!items || !items.length) ? ""
|
||||||
|
: '<ul class="' + cls + '">' + items.map(function (x) {
|
||||||
|
return '<li>' + esc(typeof x === "string" ? x : JSON.stringify(x)) + '</li>';
|
||||||
|
}).join("") + '</ul>';
|
||||||
|
}
|
||||||
|
return '<div class="rd"><h4>' + esc(r.label) + ' watched it without reading the book'
|
||||||
|
+ (r.score ? '<span class="n" title="how much of the real story they got">'
|
||||||
|
+ r.score + '/100</span>' : '') + '</h4>'
|
||||||
|
+ (s.summary ? '<blockquote>' + esc(s.summary) + '</blockquote>' : '')
|
||||||
|
+ '<div class="kv">main character: ' + esc(s.who || "not clear to them") + '</div>'
|
||||||
|
+ '<div class="kv">what they want: ' + esc(s.wants || "not clear to them") + '</div>'
|
||||||
|
+ '<div class="kv">what is in the way: ' + esc(s.obstacle || "not clear to them") + '</div>'
|
||||||
|
+ (s.would_watch ? '<div class="kv">would they watch the film: '
|
||||||
|
+ esc(s.would_watch) + '</div>' : '')
|
||||||
|
+ (s.unanswered && s.unanswered.length
|
||||||
|
? '<div class="kv">still asking:</div>' + ul(s.unanswered, "") : '')
|
||||||
|
+ (e.missed && e.missed.length ? '<div class="kv">they missed:</div>' + ul(e.missed, "") : '')
|
||||||
|
+ (e.got_wrong && e.got_wrong.length
|
||||||
|
? '<div class="kv wrong">the trailer misled them:</div>' + ul(e.got_wrong, "wrong") : '')
|
||||||
|
+ '</div>';
|
||||||
|
}).join("");
|
||||||
|
|
||||||
|
return '<div class="revs">' + out + '</div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
function paneHtml(meta, draft) {
|
function paneHtml(meta, draft) {
|
||||||
|
|
@ -318,9 +356,7 @@
|
||||||
}).join("") + '</div>';
|
}).join("") + '</div>';
|
||||||
|
|
||||||
var body = insertHtml("start");
|
var body = insertHtml("start");
|
||||||
shots.forEach(function (s) {
|
shots.forEach(function (s) { body += shotHtml(s, voice) + insertHtml(s.id); });
|
||||||
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;
|
+ reviewsHtml(meta) + castHtml + body;
|
||||||
|
|
@ -420,8 +456,6 @@
|
||||||
+ '</div></div>';
|
+ '</div></div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------ actions
|
|
||||||
|
|
||||||
async function act(name, payload, msg) {
|
async function act(name, payload, msg) {
|
||||||
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; }
|
||||||
|
|
@ -431,7 +465,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById("pane").addEventListener("click", async function (e) {
|
document.getElementById("pane").addEventListener("click", async function (e) {
|
||||||
var hit = function (attr) { var el = e.target.closest("[" + attr + "]"); return el; };
|
var hit = function (attr) { return e.target.closest("[" + attr + "]"); };
|
||||||
var el, r;
|
var el, r;
|
||||||
|
|
||||||
if ((el = hit("data-cancel"))) { EDITING = null; openTab(CUR); return; }
|
if ((el = hit("data-cancel"))) { EDITING = null; openTab(CUR); return; }
|
||||||
|
|
@ -443,14 +477,14 @@
|
||||||
if (rough.length < 20) { showMsg("msg", "Say what happens in the scene."); return; }
|
if (rough.length < 20) { showMsg("msg", "Say what happens in the scene."); return; }
|
||||||
el.disabled = true;
|
el.disabled = true;
|
||||||
r = await act("add_shot", { draft_id: CUR, after: el.getAttribute("data-addgo"), rough: rough });
|
r = await act("add_shot", { draft_id: CUR, after: el.getAttribute("data-addgo"), rough: rough });
|
||||||
EDITING = null; if (r) { await refresh(); } return;
|
EDITING = null; if (r) await refresh(); return;
|
||||||
}
|
}
|
||||||
if ((el = hit("data-rewrite"))) {
|
if ((el = hit("data-rewrite"))) {
|
||||||
var rw = document.getElementById("rough").value.trim();
|
var rw = document.getElementById("rough").value.trim();
|
||||||
if (rw.length < 20) { showMsg("msg", "Say what should change."); return; }
|
if (rw.length < 20) { showMsg("msg", "Say what should change."); return; }
|
||||||
el.disabled = true;
|
el.disabled = true;
|
||||||
r = await act("write_shot", { draft_id: CUR, shot_id: el.getAttribute("data-rewrite"), rough: rw });
|
r = await act("write_shot", { draft_id: CUR, shot_id: el.getAttribute("data-rewrite"), rough: rw });
|
||||||
EDITING = null; if (r) { await refresh(); } return;
|
EDITING = null; if (r) await refresh(); return;
|
||||||
}
|
}
|
||||||
if ((el = hit("data-drawone"))) {
|
if ((el = hit("data-drawone"))) {
|
||||||
el.disabled = true;
|
el.disabled = true;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue