diff --git a/trailer.html b/trailer.html index a66df1d..1533051 100644 --- a/trailer.html +++ b/trailer.html @@ -28,7 +28,37 @@ .ch .ap{color:var(--muted);font-size:12px;margin-top:4px} .sheets{display:flex;gap:5px;margin-bottom:8px} .sheets img{width:33%;border-radius:6px;border:1px solid var(--line);display:block; - aspect-ratio:16/9;object-fit:cover} + aspect-ratio:3/4;object-fit:cover;object-position:top} + img[data-zoom]{cursor:zoom-in} + .lookset{border-top:1px dashed var(--line);margin-top:8px;padding-top:8px} + .lookset .lw{color:var(--muted);font-size:12px;margin-bottom:5px} + .lookset .lw b{display:inline;color:inherit} + .lookset.idle{opacity:.55} + .whoin{display:flex;flex-wrap:wrap;gap:10px;margin:10px 0} + .whoin .p{display:flex;gap:8px;border:1px solid var(--line);border-radius:10px; + padding:8px;width:310px;font-size:12px;align-items:flex-start} + .whoin .p img{width:64px;aspect-ratio:3/4;object-fit:cover;object-position:top; + border-radius:6px;border:1px solid var(--line);flex:none} + .whoin .p .none{width:64px;aspect-ratio:3/4;border-radius:6px;flex:none; + border:1px dashed var(--line);display:flex;align-items:center; + justify-content:center;color:var(--muted);font-size:10px;text-align:center} + .whoin .p .t{flex:1;min-width:0} + .whoin .p .t b{display:block;font-size:13px} + .whoin .p .t .w{color:var(--muted);margin:2px 0 6px} + .whoin .p .t .st{color:#b4531f} + .whoin .p .t .new{color:#1f7a3a;font-weight:600} + .whoin .p input{width:100%;box-sizing:border-box;font:inherit;padding:5px 7px; + border:1px solid var(--line);border-radius:6px;background:transparent;color:inherit} + .whoin .p .go{margin-top:5px;font-size:12px;padding:3px 9px} + .whole{border:1px solid var(--line);border-radius:10px;padding:12px;margin:0 0 18px} + .whole h4{margin:0 0 4px;font-size:14px} + .whole p{margin:0 0 8px;color:var(--muted);font-size:13px} + .whole textarea{width:100%;box-sizing:border-box} + #zoom{position:fixed;inset:0;background:rgba(0,0,0,.9);z-index:99;display:none; + align-items:center;justify-content:center;cursor:zoom-out;padding:18px} + #zoom.on{display:flex} + #zoom img{max-width:100%;max-height:100%;border-radius:6px} + #zoom .cap{position:fixed;left:0;right:0;bottom:10px;text-align:center;color:#ddd;font-size:13px} .shot{border-top:1px solid var(--line);padding:16px 0;display:flex;gap:16px} .shot .num{width:40px;flex:none;color:var(--muted);font-variant-numeric:tabular-nums} .shot .body{flex:1;min-width:0} @@ -191,6 +221,8 @@ } function busyShots() { + if (String(PKG.look_state || "") + && (Date.now() - (Date.parse(PKG.look_started || "") || 0)) < 15 * 60 * 1000) return true; return (PKG.shots || []).some(function (s) { return s.writing || s.drawing || stillFilming(s); }); } @@ -413,7 +445,10 @@ out.push(''); else { var over = (PKG.timing && Number(PKG.timing.over_by)) || 0; - if (REVIEWS.length || Object.keys(NOTES).length || over > 5) + // Always on offer. It used to appear only once a note existed, and + // a note typed a moment ago does not repaint the bar - so an author + // wrote their notes and found nothing to press. + if (true) out.push(''; return out; @@ -759,12 +801,14 @@ : ""; var castHtml = '
' + cast.map(function (c) { - 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) + '' + var nm = c.name || c.id; + var sheets = c.head_url + ? '
' : ''; + return '
' + sheets + '' + esc(nm) + '' + '' + esc(c.role || "") + (c.voice_id ? " · voiced" : " · silent") - + '
' + esc((c.appearance || "").slice(0, 150)) + '
'; + + '
' + esc((c.appearance || "").slice(0, 150)) + '
' + + looksHtml(c) + '
'; }).join("") + '
'; var body = insertHtml("start"); @@ -772,7 +816,81 @@ return head + probs + '
' + barHtml(meta) + '
' + '
' + reelHtml() + '
' - + spineHtml() + cutsHtml(meta) + reviewsHtml(meta) + castHtml + body; + + spineHtml() + cutsHtml(meta) + reviewsHtml(meta) + wholeHtml() + castHtml + body; + } + + // Every look a character has, in the order the story reaches them: the + // clothes in words, then front, side and back. A look nobody wears any more + // is kept (the author may change their mind back) but shown faded. + function looksHtml(c) { + var looks = c.looks || {}; + var keys = Object.keys(looks).sort(function (a, b) { + return String(looks[a].first_shot || "").localeCompare(String(looks[b].first_shot || "")); }); + if (!keys.length) return ""; + var nm = c.name || c.id; + return keys.map(function (k) { + var L = looks[k], pics = [["front_url", "front"], ["side_url", "side"], ["back_url", "back"]]; + var imgs = pics.map(function (p) { + return L[p[0]] ? '' : ''; }).join(""); + return '
' + + '
from shot ' + esc(L.first_shot || "?") + ': ' + esc(L.wear || "") + + (L.state ? ' · ' + esc(L.state) + '' : '') + + '
' + (imgs ? '
' + imgs + '
' + : '
not drawn yet
') + + '
'; + }).join(""); + } + + // WHO IS IN THIS SCENE, as they look in it. This is where an author changes + // a hat, a dress, a cut lip - in words, about one person, in one scene - and + // it holds from here on until they change it again. + function whoInHtml(s) { + var ids = s.visible_characters || []; + if (!ids.length) return ""; + var byId = {}; (PKG.characters || []).forEach(function (c) { byId[String(c.id)] = c; }); + var busy = !!String(PKG.look_state || ""); + var prevLooks = {}; + (PKG.shots || []).slice().sort(function (a, b) { return String(a.id).localeCompare(String(b.id)); }) + .some(function (x) { + if (String(x.id) === String(s.id)) return true; + Object.keys(x.looks || {}).forEach(function (cid) { prevLooks[cid] = x.looks[cid]; }); + return false; }); + return '
' + ids.map(function (cid) { + cid = String(cid); + var c = byId[cid] || {}, key = (s.looks || {})[cid], L = (c.looks || {})[key] || {}; + var nm = c.name || cid; + var pic = L.front_url || c.front_url || c.head_url || ""; + var isNew = prevLooks[cid] && prevLooks[cid] !== key; + var asked = (s.look_asks || {})[cid]; + return '
' + + (pic ? '' + : '
not drawn yet
') + + '
' + esc(nm) + '' + + '
' + (isNew ? 'changes here: ' : '') + + esc(L.wear || "as described") + (L.state ? ' · ' + esc(L.state) + '' : '') + + (asked ? '
you asked: ' + esc(asked) + '' : '') + '
' + + '' + + '' + + '
'; + }).join("") + '
'; + } + + // The author's note on the script as a whole. It is an ordinary note filed + // under ALL, so it saves itself like the others and the head writer is shown + // it before anything else. + function wholeHtml() { + return '

Your notes on the script as a whole

' + + '

What is missing, what should go, what order the story should be told in, ' + + 'what you want people to feel. The head writer reads this first when you press ' + + 'Revise the whole script. Notes on a single shot go in the box under that shot.

' + + '
'; } function insertHtml(after) { @@ -831,12 +949,15 @@ + 'preload="metadata">
the filmed shot' + (s.clip_seconds ? ' · ' + Number(s.clip_seconds).toFixed(1) + 's' : '') + '
' - + (s.opening_url ? '
filmed from this frame
' : '') + + (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
' : '') + + (s.opening_url ? '
opens on
' : '') + + (s.closing_url ? '
ends on
' : '') + '
'; } @@ -893,6 +1014,7 @@ + (s.day ? ' · ' + esc(s.day) + '' : '') + '' + media + + whoInHtml(s) + '

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

' + (s.camera ? '

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

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

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

' : '') @@ -1071,6 +1193,28 @@ el.disabled = true; r = await act("start_review", { run_id: runId }); if (r) await refresh(); return; } + var zi = e.target.closest && e.target.closest("img[data-zoom]"); + if (zi) { + var z = document.getElementById("zoom"); + z.querySelector("img").src = zi.getAttribute("src"); + z.querySelector(".cap").textContent = zi.getAttribute("data-zoom") || ""; + z.classList.add("on"); + return; + } + if ((el = hit("data-lookgo"))) { + var pr = el.getAttribute("data-lookgo").split("|"); + var box = document.getElementById("look-" + pr[0] + "-" + pr[1]); + var ask = box ? box.value.trim() : ""; + if (ask.length < 3) { if (box) box.focus(); return; } + el.disabled = true; el.textContent = "working…"; + r = await act("set_look", { draft_id: CUR, shot_id: pr[0], character_id: pr[1], prompt: ask }); + if (r) { + showMsg("msg", "Redrawing that look" + (r.frames ? " and " + r.frames + " scene" + + (r.frames === 1 ? "" : "s") + " it appears in" : "") + ", about $" + r.usd + ".", "ok"); + await refresh(); + } else { el.disabled = false; el.textContent = "Change"; } + return; + } if ((el = hit("data-revise"))) { el.disabled = true; r = await act("start_revision", { run_id: runId }); if (r) await refresh(); return; @@ -1140,6 +1284,15 @@ }, 700); }); + (function () { + var z = document.createElement("div"); + z.id = "zoom"; z.innerHTML = '
'; + z.addEventListener("click", function () { z.classList.remove("on"); }); + document.addEventListener("keydown", function (e) { + if (e.key === "Escape") z.classList.remove("on"); }); + document.body.appendChild(z); + })(); + load(); })();