From d21360ea2be99ef8c6923b5fb01c9e317d20c075 Mon Sep 17 00:00:00 2001 From: gator Date: Thu, 17 Sep 2026 06:31:29 +0000 Subject: [PATCH] trailer.html: edit, draw, film and reassemble one shot at a time The trailer was all-or-nothing. If one shot was wrong - a line cut short, a character duplicated, a scene missing from the front - the only lever was to film all twenty-four again. Every shot now carries its own controls: describe a change and have the writer turn it into a proper shot, draw its two frames, film just that one, watch the clip that came back, and read the machine check that compared what it says with what the script says. "Add a scene here" sits between shots, because the note that prompted this was that the trailer needed an opening the writer never thought to include. Assemble is separate and cheap - it joins whatever has been filmed - so re-cutting after changing one shot costs nothing but the one shot. Prices are on the buttons because they differ by two orders of magnitude: rewriting a shot is cents, drawing it is fourteen, filming it is about a dollar. --- trailer.html | 424 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 250 insertions(+), 174 deletions(-) diff --git a/trailer.html b/trailer.html index bcff926..c49cfea 100644 --- a/trailer.html +++ b/trailer.html @@ -14,7 +14,6 @@ .tab.lead{color:var(--accent)} .tab .sc{color:var(--accent2);margin-left:7px;font-weight:600} .tab .st{color:var(--muted);margin-left:7px;font-size:12px} - .tab .crown{margin-left:6px} .head{display:flex;gap:18px;flex-wrap:wrap;align-items:baseline;margin-bottom:6px} .head h2{margin:0;font-size:22px} .tag{color:var(--muted);font-style:italic} @@ -28,7 +27,7 @@ .sheets img{width:33%;border-radius:6px;border:1px solid var(--line);display:block; aspect-ratio:16/9;object-fit:cover} .shot{border-top:1px solid var(--line);padding:16px 0;display:flex;gap:16px} - .shot .num{width:34px;flex:none;color:var(--muted);font-variant-numeric:tabular-nums} + .shot .num{width:40px;flex:none;color:var(--muted);font-variant-numeric:tabular-nums} .shot .body{flex:1;min-width:0} .shot h3{margin:0 0 3px;font-size:15px} .shot .where{color:var(--muted);font-size:12px;margin-bottom:8px} @@ -36,7 +35,7 @@ .shot .cam,.shot .cont{color:var(--muted);font-size:12.5px;margin:0 0 5px} .frames{display:flex;gap:10px;margin:10px 0} .frames figure{margin:0;flex:1;min-width:0} - .frames img{width:100%;border-radius:8px;border:1px solid var(--line);display:block} + .frames img,.frames video{width:100%;border-radius:8px;border:1px solid var(--line);display:block} .frames figcaption{color:var(--muted);font-size:11px;margin-top:3px} .line{display:flex;gap:9px;align-items:flex-start;margin:7px 0} .line .say{flex:1;min-width:0;font-size:14px} @@ -65,11 +64,21 @@ .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 .v{color:var(--muted);font-size:13px;margin:0 0 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} + .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)} + .chk.ok{color:var(--accent2);border-color:var(--accent2)} + .chk.bad{color:#e0685f;border-color:#e0685f} + .ins{border-top:1px dashed var(--line);text-align:center;padding:6px 0} + .ins button{font-size:12px;padding:4px 12px;color:var(--muted)} + .rough{width:100%;box-sizing:border-box;margin-top:8px;font:inherit;font-size:13px; + background:transparent;color:var(--ink);border:1px solid var(--accent); + border-radius:8px;padding:9px 11px;resize:vertical} + .busy{color:var(--accent);font-size:12px} @@ -92,14 +101,12 @@ navBar("trailer"); var RUN = null, DRAFTS = [], CUR = null, PKG = {}, NOTES = {}, REVIEWS = []; - var timer = null; + var timer = null, EDITING = null; var runId = new URLSearchParams(location.search).get("run") || ""; var audio = new Audio(); if (!runId) { showMsg("msg", "No run was named in the link."); return; } - // A draft is busy while it is being written or drawn; a run is busy while it - // is reviewing or revising. Both are minutes long and report on the row. function busyDraft(d) { var s = String(d.draw_state || ""); return d.state === "writing" || (s && !/^(drawn|failed)/.test(s)); @@ -107,28 +114,42 @@ 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; }); + } - async function load(repaint) { + async function load() { var r = await api("run_status", { run_id: runId }); if (!r.ok) { showMsg("msg", r.error || "Could not load that run."); return; } - var wasBusy = (DRAFTS.length && DRAFTS.some(busyDraft)) || busyRun(); RUN = r.run; DRAFTS = r.drafts || []; document.getElementById("title").textContent = RUN.book_title || "Trailers"; document.getElementById("sub").textContent = sub(); if (!CUR && DRAFTS.length) CUR = leadDraftId() || DRAFTS[0].id; paintTabs(); - var rv = await api("list_reviews", { run_id: runId }); REVIEWS = (rv && rv.reviews) || []; + await openTab(CUR); + tick(); + } - var busy = DRAFTS.some(busyDraft) || busyRun(); - if (busy && !timer) timer = setInterval(load, 5000); + function tick() { + var busy = DRAFTS.some(busyDraft) || busyRun() || busyShots(); + if (busy && !timer) timer = setInterval(refresh, 5000); if (!busy && timer) { clearInterval(timer); timer = null; } + } - var me = DRAFTS.filter(function (d) { return d.id === CUR; })[0]; - if (me && me.state === "writing") { paintWriting(); return; } - if (repaint !== false && (wasBusy || !document.querySelector(".shot"))) openTab(CUR); - else paintBar(); + // 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(); + await openTab(CUR); + tick(); } function leadDraftId() { @@ -146,6 +167,7 @@ bits.push(DRAFTS.length + " script" + (DRAFTS.length === 1 ? "" : "s")); var spent = DRAFTS.reduce(function (a, d) { return a + Number(d.write_usd || 0) + Number(d.draw_usd || 0); }, 0); + (PKG.shots || []).forEach(function (s) { spent += Number(s.film_usd || 0); }); if (spent) bits.push("$" + spent.toFixed(2) + " so far"); if (RUN.created_at) bits.push(when(RUN.created_at)); return bits.join(" · "); @@ -159,30 +181,21 @@ : (s && !/^(drawn|failed)/.test(s)) ? '' + esc(s) + '' : d.score ? '' + d.score + '' : ''; - var isLead = d.writer === RUN.lead_writer; return ''; + + (d.writer === RUN.lead_writer ? ' ★' : '') + extra + ''; }).join(""); } - function paintWriting() { - document.getElementById("pane").innerHTML = - '

Working. Reading the whole book takes a few minutes.

'; - } - document.getElementById("tabs").addEventListener("click", function (e) { var id = e.target.closest ? e.target.closest(".tab") : null; if (!id) return; - CUR = id.getAttribute("data-tab"); + CUR = id.getAttribute("data-tab"); EDITING = null; paintTabs(); openTab(CUR); }); - // ------------------------------------------------------------ one trailer - async function openTab(id) { var meta = DRAFTS.filter(function (d) { return d.id === id; })[0]; if (!meta) return; @@ -194,8 +207,10 @@ + esc(meta.id) + '">Try this writer again'; return; } - if (meta.state === "writing") { paintWriting(); return; } - pane.innerHTML = '

Loading…

'; + if (meta.state === "writing") { + pane.innerHTML = '

Working. Reading the whole book takes a few minutes.

'; + return; + } var r = await api("get_draft", { draft_id: id }); if (!r.ok) { pane.innerHTML = '

' + esc(r.error || "") + '

'; return; } PKG = r.draft.package || {}; @@ -204,95 +219,77 @@ } function barHtml(meta) { - var shots = (PKG.shots || []).length, cast = (PKG.characters || []).length; - var imgs = cast * 3 + shots * 2; - var price = imgs * 0.068; + var shots = (PKG.shots || []), cast = (PKG.characters || []); + var undrawn = shots.filter(function (s) { return !s.opening_url; }).length; + var unfilmed = shots.filter(function (s) { return !s.clip_url; }).length; + var filmed = shots.length - unfilmed; + var imgs = cast.length * 3 + shots.length * 2; var s = String(meta.draw_state || ""); var drawing = s && !/^(drawn|failed)/.test(s); - var drawn = /^drawn/.test(s); var isLead = meta.writer === RUN.lead_writer; var out = []; - // 1. Whose concept wins. The author's call, not a score's. - if (!isLead) { - out.push(''); - } else { - out.push('★ head writer'); - } + if (!isLead) out.push(''); + else out.push('★ head writer'); - // 2. Drawing. - if (drawing) { - out.push(''); - } else { - out.push(''); - if (!drawn) out.push('' + imgs + ' pictures'); - else out.push('' + esc(s) - + (meta.draw_usd ? ' · $' + Number(meta.draw_usd).toFixed(2) : '') + ''); - } + if (drawing) out.push(''); + else if (undrawn) out.push(''); + else out.push('storyboard drawn' + + (meta.draw_usd ? ' · $' + Number(meta.draw_usd).toFixed(2) : '') + ''); - // 3. Review, revise, film - only meaningful on the head writer's tab. if (isLead) { var others = DRAFTS.filter(function (d) { return d.writer !== RUN.lead_writer && /^ready/.test(String(d.state)); }).length; - if (RUN.stage === "reviewing") { + if (RUN.stage === "reviewing") out.push(''); - } else if (others) { + else if (others) out.push(''); - } - if (RUN.stage === "revising") { + + (REVIEWS.length ? "Review it again" : "Have the others review this") + ''); + if (RUN.stage === "revising") out.push(''); - } else if (REVIEWS.length || Object.keys(NOTES).length) { - out.push(''); - } - if (drawn) { + else if (REVIEWS.length || Object.keys(NOTES).length) + out.push(''); + + if (unfilmed && !undrawn) out.push(''); - } else { - out.push('Draw the storyboard before filming — ' - + 'every shot is filmed from its opening frame.'); - } + + '">Film ' + (filmed ? 'the ' + unfilmed + ' remaining shots' : 'all ' + shots.length + ' shots') + + ' · $' + (unfilmed * 0.75).toFixed(2) + ''); + if (filmed) + out.push(''); } return out.join(""); } - function paintBar() { - var el = document.querySelector(".bar"); - var meta = DRAFTS.filter(function (d) { return d.id === CUR; })[0]; - if (el && meta) el.innerHTML = barHtml(meta); - } - 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 '
' + mine.map(function (r) { - var parts = ['

' + esc(r.label) - + '' + esc(r.score) + '/100

']; function list(items, cls, fmt) { - if (!items || !items.length) return ""; - return '
    ' + items.map(fmt).join("") + '
'; + return (!items || !items.length) ? "" + : '
    ' + items.map(fmt).join("") + '
'; } - parts.push(list(r.errors, "er", function (e) { - return '
  • ' + (e.shot ? 'shot ' + esc(e.shot) + ' ' : '') - + esc(e.problem || "") + (e.fix ? ' — ' + esc(e.fix) + '' : '') + '
  • '; })); - parts.push(list(r.suggestions, "", function (s) { - return '
  • ' + (s.shot ? 'shot ' + esc(s.shot) + ' ' : '') - + esc(s.change || "") + (s.why ? ' (' + esc(s.why) + ')' : '') + '
  • '; })); - parts.push(list(r.strengths, "kp", function (s) { - return '
  • ' + esc(typeof s === "string" ? s : JSON.stringify(s)) + '
  • '; })); - return parts.join("") + '
    '; + return '

    ' + esc(r.label) + '' + esc(r.score) + '/100

    ' + + list(r.errors, "er", function (e) { + return '
  • ' + (e.shot ? 'shot ' + esc(e.shot) + ' ' : '') + + esc(e.problem || "") + (e.fix ? ' — ' + esc(e.fix) + '' : '') + '
  • '; }) + + list(r.suggestions, "", function (s) { + return '
  • ' + (s.shot ? 'shot ' + esc(s.shot) + ' ' : '') + + esc(s.change || "") + '
  • '; }) + + list(r.strengths, "kp", function (s) { + return '
  • ' + esc(typeof s === "string" ? s : JSON.stringify(s)) + '
  • '; }) + + '
    '; }).join("") + '
    '; } function paneHtml(meta, draft) { - var p = PKG, shots = p.shots || [], cast = p.characters || []; - var voice = {}; + var p = PKG, shots = (p.shots || []).slice().sort(function (a, b) { + return String(a.id) < String(b.id) ? -1 : 1; }); + var cast = p.characters || [], voice = {}; cast.forEach(function (c) { voice[c.id] = c; }); var secs = shots.reduce(function (a, s) { return a + (Number(s.edit_seconds) || 0); }, 0); @@ -303,43 +300,58 @@ + (Number(meta.iteration) > 1 ? ' · revision ' + meta.iteration : '') + ' · ' + shots.length + ' shots · ' + Math.floor(secs / 60) + ':' + String(Math.round(secs % 60)).padStart(2, "0") - + ' · ' + cast.length + ' characters' - + (meta.write_usd ? ' · $' + Number(meta.write_usd).toFixed(2) + ' to write' : '') - + (meta.note ? ' · ' + esc(meta.note) : '') - + '

    '; + + ' · ' + cast.length + ' characters

    '; - var probs = ""; - if (draft.problems) { - probs = '
    The checker still has objections
    ' + : ""; var castHtml = '
    ' + cast.map(function (c) { - var sheets = ""; - if (c.head_url || c.front_url || c.side_url) { - sheets = '
    ' - + [c.head_url, c.front_url, c.side_url].map(function (u) { - return u ? '' : ''; }).join("") - + '
    '; - } + var sheets = (c.head_url || c.front_url || c.side_url) + ? '
    ' + [c.head_url, c.front_url, c.side_url].map(function (u) { + return u ? '' : ''; }).join("") + '
    ' : ''; return '
    ' + sheets + '' + esc(c.name || c.id) + '' - + '' + esc(c.role || "") + (c.age ? " · " + esc(c.age) : "") - + (c.voice_id ? " · voiced" : " · silent") + '' - + '
    ' + esc((c.appearance || "").slice(0, 150)) + '
    '; + + '' + esc(c.role || "") + (c.voice_id ? " · voiced" : " · silent") + + '
    ' + esc((c.appearance || "").slice(0, 150)) + '
    '; }).join("") + ''; + var body = insertHtml("start"); + shots.forEach(function (s) { + body += shotHtml(s, voice) + insertHtml(s.id); + }); + return head + probs + '
    ' + barHtml(meta) + '
    ' - + reviewsHtml(meta) + castHtml - + shots.map(function (s) { return shotHtml(s, voice); }).join(""); + + reviewsHtml(meta) + castHtml + body; + } + + function insertHtml(after) { + if (EDITING === "new:" + after) { + return '
    ' + + '' + + '
    ' + + '
    '; + } + return '
    '; } function shotHtml(s, voice) { + if (s.writing) { + return '
    ' + esc(s.id) + '
    ' + + '

    ' + esc(s.title || "New scene") + '

    ' + + '

    The writer is turning your note into a shot…

    ' + + '

    ' + esc(String(s.rough || "").slice(0, 300)) + '

    '; + } + var lines = (s.audio || []).map(function (a) { var who = voice[a.character_id] || {}; return '
    ' - + '' + + '' + '
    ' + esc(who.name || a.character_id) + ' ' + (a.lip_sync ? 'on camera' : 'voice over') + '
    ' + esc(a.text || "") @@ -347,102 +359,168 @@ + '
    '; }).join(""); - var frames = ""; - if (s.opening_url || s.closing_url) { - frames = '
    ' + var media = ""; + if (s.clip_url) { + media = '
    the filmed shot' + + (s.clip_seconds ? ' · ' + Number(s.clip_seconds).toFixed(1) + 's' : '') + + '
    ' + + (s.opening_url ? '
    filmed from this frame
    ' : '') + + '
    '; + } else if (s.opening_url || s.closing_url) { + media = '
    ' + (s.opening_url ? '
    opens on
    ' : '') + (s.closing_url ? '
    ends on
    ' : '') + '
    '; } + var chk = s.audio_check || {}; + var chkHtml = ""; + if (chk.verdict && chk.verdict !== "no dialogue") { + var good = chk.verdict === "ok" || chk.verdict === "drifted"; + chkHtml = 'audio ' + esc(chk.verdict) + + (chk.coverage != null ? ' ' + Math.round(chk.coverage * 100) + '%' : '') + + (chk.missing ? ' — lost “' + esc(String(chk.missing).slice(0, 40)) + '”' : '') + + ''; + } + + var tools; + if (s.drawing) tools = 'drawing…'; + else if (s.filming) tools = 'filming…'; + else if (EDITING === s.id) { + tools = '
    ' + + '
    ' + + '
    '; + } else { + tools = '' + + '' + + (s.opening_url ? '' : '') + + chkHtml + + ''; + } + return '
    ' + esc(s.id) + '
    ' + '

    ' + esc(s.title || "") + '

    ' + '
    ' + esc(s.setting || "") + ' · ' + Number(s.edit_seconds || 0).toFixed(1) + 's
    ' - + frames + + media + '

    ' + esc(s.action || "") + '

    ' + (s.camera ? '

    camera: ' + esc(s.camera) + '

    ' : '') + (s.continuity ? '

    continuity: ' + esc(s.continuity) + '

    ' : '') + lines + + '
    ' + tools + '
    ' + '' + '
    '; } // ------------------------------------------------------------ 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; } + hideMsg("msg"); + if (msg) showMsg("msg", msg(r), "ok"); + return r; + } + document.getElementById("pane").addEventListener("click", async function (e) { - var t = e.target.closest ? e.target : null; - if (!t) return; + var hit = function (attr) { var el = e.target.closest("[" + attr + "]"); return el; }; + var el, r; - var lead = e.target.closest("[data-lead]"); - if (lead) { - lead.disabled = true; - var a = await api("set_lead", { run_id: runId, draft_id: lead.getAttribute("data-lead") }); - if (!a.ok) { lead.disabled = false; showMsg("msg", a.error || ""); return; } - hideMsg("msg"); await load(); openTab(CUR); return; + if ((el = hit("data-cancel"))) { EDITING = null; openTab(CUR); return; } + if ((el = hit("data-add"))) { EDITING = "new:" + el.getAttribute("data-add"); openTab(CUR); return; } + if ((el = hit("data-edit"))) { EDITING = el.getAttribute("data-edit"); openTab(CUR); return; } + + if ((el = hit("data-addgo"))) { + var rough = document.getElementById("newrough").value.trim(); + 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; } - - var draw = e.target.closest("[data-draw]"); - if (draw) { - draw.disabled = true; - var d = await api("draw_storyboard", { draft_id: draw.getAttribute("data-draw") }); - if (!d.ok) { draw.disabled = false; showMsg("msg", d.error || ""); return; } - hideMsg("msg"); load(); 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; } - - var rev = e.target.closest("[data-review]"); - if (rev) { - rev.disabled = true; - var v = await api("start_review", { run_id: runId }); - if (!v.ok) { rev.disabled = false; showMsg("msg", v.error || ""); return; } - hideMsg("msg"); load(); return; + if ((el = hit("data-drawone"))) { + el.disabled = true; + r = await act("draw_one_shot", { draft_id: CUR, shot_id: el.getAttribute("data-drawone") }); + if (r) await refresh(); return; } - - var rvs = e.target.closest("[data-revise]"); - if (rvs) { - rvs.disabled = true; - var w = await api("start_revision", { run_id: runId }); - if (!w.ok) { rvs.disabled = false; showMsg("msg", w.error || ""); return; } - hideMsg("msg"); load(); return; + if ((el = hit("data-filmone"))) { + el.disabled = true; + r = await act("film_one_shot_now", { draft_id: CUR, shot_id: el.getAttribute("data-filmone") }); + if (r) await refresh(); return; } - - var film = e.target.closest("[data-film]"); - if (film) { - film.disabled = true; - var f = await api("film_draft", { draft_id: film.getAttribute("data-film") }); - if (!f.ok) { film.disabled = false; showMsg("msg", f.error || ""); return; } - showMsg("msg", "Filming " + f.shots + " shots, about $" + f.usd - + ". It appears in My videos as it goes.", "ok"); - setTimeout(function () { location.href = "/videos.html"; }, 2500); + if ((el = hit("data-delete"))) { + if (!confirm("Remove shot " + el.getAttribute("data-delete") + " from this trailer?")) return; + el.disabled = true; + r = await act("delete_shot", { draft_id: CUR, shot_id: el.getAttribute("data-delete") }); + if (r) await refresh(); return; + } + if ((el = hit("data-assemble"))) { + el.disabled = true; + r = await act("assemble_trailer", { draft_id: CUR }); + if (r) { showMsg("msg", "Joining " + r.shots + " shots. It appears in My videos.", "ok"); + setTimeout(function () { location.href = "/videos.html"; }, 2200); } return; } - - var retry = e.target.closest("[data-retry]"); - if (retry) { - retry.disabled = true; - var y = await api("retry_writer", { draft_id: retry.getAttribute("data-retry") }); - if (!y.ok) { retry.disabled = false; showMsg("msg", y.error || ""); return; } - hideMsg("msg"); load(); return; + if ((el = hit("data-lead"))) { + el.disabled = true; + r = await act("set_lead", { run_id: runId, draft_id: el.getAttribute("data-lead") }); + if (r) await refresh(); return; + } + if ((el = hit("data-draw"))) { + el.disabled = true; + r = await act("draw_storyboard", { draft_id: el.getAttribute("data-draw") }); + if (r) await refresh(); return; + } + if ((el = hit("data-review"))) { + el.disabled = true; + r = await act("start_review", { run_id: runId }); if (r) await refresh(); return; + } + if ((el = hit("data-revise"))) { + el.disabled = true; + r = await act("start_revision", { run_id: runId }); if (r) await refresh(); return; + } + if ((el = hit("data-film"))) { + el.disabled = true; + r = await act("film_draft", { draft_id: el.getAttribute("data-film") }); + if (r) { showMsg("msg", "Filming " + r.shots + " shots, about $" + r.usd + + ". It appears in My videos as it goes.", "ok"); + setTimeout(function () { location.href = "/videos.html"; }, 2500); } + return; + } + if ((el = hit("data-retry"))) { + el.disabled = true; + r = await act("retry_writer", { draft_id: el.getAttribute("data-retry") }); + if (r) await refresh(); return; } var btn = e.target.closest(".play"); if (!btn) return; var sid = btn.getAttribute("data-shot"); btn.disabled = true; btn.innerHTML = "…"; - var r = await api("shot_audio", { draft_id: CUR, shot_id: sid }); + var a = await api("shot_audio", { draft_id: CUR, shot_id: sid }); btn.disabled = false; btn.innerHTML = "▶"; - if (!r.ok) { showMsg("msg", r.error || "That would not play."); return; } + if (!a.ok) { showMsg("msg", a.error || "That would not play."); return; } hideMsg("msg"); - if (!r.url) return; - // Spoken length against the length the writer planned for. A line that - // overruns its shot is the single most common fault in these scripts. - document.querySelectorAll('[data-dur="' + sid + '"]').forEach(function (el) { - el.textContent = r.seconds.toFixed(1) + "s / " + Number(r.planned).toFixed(1) + "s"; - el.className = "dur" + (r.seconds > Number(r.planned) ? " over" : ""); + if (!a.url) return; + document.querySelectorAll('[data-dur="' + sid + '"]').forEach(function (x) { + x.textContent = a.seconds.toFixed(1) + "s / " + Number(a.planned).toFixed(1) + "s"; + x.className = "dur" + (a.seconds > Number(a.planned) ? " over" : ""); }); - audio.pause(); audio.src = r.url; audio.play(); + audio.pause(); audio.src = a.url; audio.play(); }); var noteTimers = {}; @@ -452,8 +530,6 @@ if (!sid) return; ta.classList.remove("saved"); clearTimeout(noteTimers[sid]); - // Saved as you stop typing. A note the author has to remember to save is - // a note that gets lost between tabs. noteTimers[sid] = setTimeout(async function () { var r = await api("add_shot_note", { draft_id: CUR, shot_id: sid, note: ta.value }); if (r.ok) { NOTES[sid] = ta.value; ta.classList.add("saved"); }