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}
|
||||
.vobadge{color:var(--muted);font-size:11px;border:1px solid var(--line);
|
||||
border-radius:999px;padding:0 6px}
|
||||
.revs{margin:0 0 22px}
|
||||
.rev{border:1px solid var(--line);border-radius:10px;padding:12px 14px;margin-bottom:10px}
|
||||
.rev h4{margin:0 0 6px;font-size:14px}
|
||||
.rev h4 .n{color:var(--accent2);font-weight:600;margin-left:8px}
|
||||
.rev ul{margin:4px 0 8px;padding-left:18px;font-size:13px}
|
||||
.rev .er{color:#e0685f}
|
||||
.rev .kp{color:var(--accent2)}
|
||||
.rev .sh{color:var(--muted);font-variant-numeric:tabular-nums}
|
||||
.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 .verdict{font-size:14px;margin:0 0 10px}
|
||||
.gaps ol{margin:0;padding-left:20px;font-size:13px}
|
||||
.gaps li{margin-bottom:7px}
|
||||
.gaps .fix{color:var(--muted);display:block}
|
||||
.gaps .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 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)}
|
||||
|
|
@ -114,8 +120,6 @@
|
|||
function busyRun() {
|
||||
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() {
|
||||
return (PKG.shots || []).some(function (s) {
|
||||
return s.writing || s.drawing || s.filming; });
|
||||
|
|
@ -141,13 +145,14 @@
|
|||
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() {
|
||||
var r = await api("run_status", { run_id: runId });
|
||||
if (!r.ok) return;
|
||||
RUN = r.run; DRAFTS = r.drafts || [];
|
||||
document.getElementById("sub").textContent = sub();
|
||||
paintTabs();
|
||||
var rv = await api("list_reviews", { run_id: runId });
|
||||
REVIEWS = (rv && rv.reviews) || [];
|
||||
await openTab(CUR);
|
||||
tick();
|
||||
}
|
||||
|
|
@ -179,7 +184,8 @@
|
|||
var extra = d.state === "writing" ? '<span class="st">writing…</span>'
|
||||
: d.state === "failed" ? '<span class="st">failed</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' : '')
|
||||
+ (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>');
|
||||
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>');
|
||||
else out.push('<span class="soon">storyboard drawn'
|
||||
+ (meta.draw_usd ? ' · $' + Number(meta.draw_usd).toFixed(2) : '') + '</span>');
|
||||
|
|
@ -244,10 +251,12 @@
|
|||
var others = DRAFTS.filter(function (d) {
|
||||
return d.writer !== RUN.lead_writer && /^ready/.test(String(d.state)); }).length;
|
||||
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)
|
||||
out.push('<button type="button" class="btn" data-review="1">'
|
||||
+ (REVIEWS.length ? "Review it again" : "Have the others review this") + '</button>');
|
||||
out.push('<button type="button" class="btn" data-review="1" title="'
|
||||
+ '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")
|
||||
out.push('<button type="button" class="btn" disabled>Rewriting…</button>');
|
||||
else if (REVIEWS.length || Object.keys(NOTES).length)
|
||||
|
|
@ -264,26 +273,55 @@
|
|||
return out.join("");
|
||||
}
|
||||
|
||||
// What readers understood, and what the writer says it failed to say.
|
||||
function reviewsHtml(meta) {
|
||||
var mine = REVIEWS.filter(function (r) {
|
||||
return r.subject === meta.writer && Number(r.iteration) === Number(meta.iteration); });
|
||||
if (!mine.length) return "";
|
||||
return '<div class="revs">' + mine.map(function (r) {
|
||||
function list(items, cls, fmt) {
|
||||
return (!items || !items.length) ? ""
|
||||
: '<ul class="' + cls + '">' + items.map(fmt).join("") + '</ul>';
|
||||
}
|
||||
return '<div class="rev"><h4>' + esc(r.label) + '<span class="n">' + esc(r.score) + '/100</span></h4>'
|
||||
+ list(r.errors, "er", function (e) {
|
||||
return '<li>' + (e.shot ? '<span class="sh">shot ' + esc(e.shot) + '</span> ' : '')
|
||||
+ esc(e.problem || "") + (e.fix ? ' — <i>' + esc(e.fix) + '</i>' : '') + '</li>'; })
|
||||
+ list(r.suggestions, "", function (s) {
|
||||
return '<li>' + (s.shot ? '<span class="sh">shot ' + esc(s.shot) + '</span> ' : '')
|
||||
+ esc(s.change || "") + '</li>'; })
|
||||
+ list(r.strengths, "kp", function (s) {
|
||||
return '<li>' + esc(typeof s === "string" ? s : JSON.stringify(s)) + '</li>'; })
|
||||
var gaps = mine.filter(function (r) { return r.reviewer === "__gaps__"; })[0];
|
||||
var readers = mine.filter(function (r) { return r.reviewer !== "__gaps__"; });
|
||||
var out = "";
|
||||
|
||||
if (gaps) {
|
||||
var v = gaps.strengths && gaps.strengths.verdict;
|
||||
var list = gaps.suggestions || [];
|
||||
out += '<div class="gaps"><h4>What this trailer does not tell a viewer</h4>'
|
||||
+ (v ? '<p class="verdict">' + esc(v) + '</p>' : '')
|
||||
+ (list.length ? '<ol>' + list.map(function (g) {
|
||||
return '<li>' + esc(g.what || "")
|
||||
+ '<span class="fix"><span class="sh">shot ' + esc(g.shot || "?") + '</span> → '
|
||||
+ esc(g.fix || "") + '</span></li>'; }).join("") + '</ol>' : '')
|
||||
+ '</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) {
|
||||
|
|
@ -318,9 +356,7 @@
|
|||
}).join("") + '</div>';
|
||||
|
||||
var body = insertHtml("start");
|
||||
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>'
|
||||
+ reviewsHtml(meta) + castHtml + body;
|
||||
|
|
@ -420,8 +456,6 @@
|
|||
+ '</div></div>';
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------ actions
|
||||
|
||||
async function act(name, payload, msg) {
|
||||
var r = await api(name, payload);
|
||||
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) {
|
||||
var hit = function (attr) { var el = e.target.closest("[" + attr + "]"); return el; };
|
||||
var hit = function (attr) { return e.target.closest("[" + attr + "]"); };
|
||||
var el, r;
|
||||
|
||||
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; }
|
||||
el.disabled = true;
|
||||
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"))) {
|
||||
var rw = document.getElementById("rough").value.trim();
|
||||
if (rw.length < 20) { showMsg("msg", "Say what should change."); return; }
|
||||
el.disabled = true;
|
||||
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"))) {
|
||||
el.disabled = true;
|
||||
|
|
|
|||
Loading…
Reference in a new issue