Cutting room floor per shot; cast, props, story and master pictures fold away

This commit is contained in:
gator 2026-09-24 19:18:53 +00:00
parent 47ed869d1d
commit baaca626bc

View file

@ -109,6 +109,16 @@
.shot .num{width:40px;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 .body{flex:1;min-width:0}
.shot h3{margin:0 0 3px;font-size:15px} .shot h3{margin:0 0 3px;font-size:15px}
.shot.floor{opacity:.5}
.shot.floor:hover{opacity:.85}
.floorbox{float:right;font-size:12px;color:var(--muted);cursor:pointer;user-select:none}
.floornote{font-size:12px;color:#b4531f;margin:2px 0 4px}
details.fold{margin:10px 0;border:1px solid var(--line,#2a2f3a);border-radius:10px;padding:0 12px}
details.fold>summary{cursor:pointer;padding:10px 0;font-weight:600;list-style:none}
details.fold>summary::-webkit-details-marker{display:none}
details.fold>summary::before{content:"\25B6";display:inline-block;margin-right:8px;font-size:11px;transition:transform .15s}
details.fold[open]>summary::before{transform:rotate(90deg)}
details.fold>summary .tiny{font-weight:400;margin-left:6px}
.shot .where{color:var(--muted);font-size:12px;margin-bottom:8px} .shot .where{color:var(--muted);font-size:12px;margin-bottom:8px}
.shot .act{font-size:14px;margin:0 0 8px} .shot .act{font-size:14px;margin:0 0 8px}
.shot .cam,.shot .cont{color:var(--muted);font-size:12.5px;margin:0 0 5px} .shot .cam,.shot .cont{color:var(--muted);font-size:12.5px;margin:0 0 5px}
@ -1069,9 +1079,31 @@
DRAFTNOW = draft; DRAFTNOW = draft;
return head + partsHtml(draft) + contestHtml() + guideHtml(meta) + probs + '<div class="bar">' + barHtml(meta) + '</div>' return head + partsHtml(draft) + contestHtml() + guideHtml(meta) + probs + '<div class="bar">' + barHtml(meta) + '</div>'
+ '<div id="reelhost">' + reelHtml() + '</div>' + '<div id="reelhost">' + reelHtml() + '</div>'
+ spineHtml() + cutsHtml(meta) + reviewsHtml(meta) + wholeHtml() + filmCheckHtml() + speakerNoticeHtml(draft) + viewsHtml(draft) + castHtml + propsHtml() + body; + fold("spine", "The story, as the writer worked it out", "", spineHtml())
+ cutsHtml(meta) + reviewsHtml(meta) + wholeHtml() + filmCheckHtml() + speakerNoticeHtml(draft)
+ fold("views", "Master pictures for each set-up", "", viewsHtml(draft))
+ fold("cast", "The cast", cast.length + " characters - faces, looks, voices", castHtml)
+ fold("props", "Things that must look the same in every shot", ((PKG.props || []).length || "no") + " tracked", propsHtml())
+ body;
} }
// FOLDS. The cast, the props, the story notes and the master pictures are
// reference: open them when needed, otherwise the shots are one scroll
// away. Closed by default; each one remembers being opened.
var FOLD = {};
try { FOLD = JSON.parse(localStorage.getItem("ptl_folds") || "{}"); } catch (e) { FOLD = {}; }
function fold(key, title, note, html) {
if (!html) return "";
return '<details class="fold" data-fold="' + key + '"' + (FOLD[key] ? ' open' : '') + '><summary>' + esc(title)
+ (note ? '<span class="tiny">' + esc(String(note)) + '</span>' : '') + '</summary>' + html + '</details>';
}
document.addEventListener("toggle", function (e) {
var d = e.target;
if (!d || !d.getAttribute || !d.getAttribute("data-fold")) return;
FOLD[d.getAttribute("data-fold")] = d.open;
try { localStorage.setItem("ptl_folds", JSON.stringify(FOLD)); } catch (er) {}
}, true);
// THINGS THAT MUST STAY THE SAME. A face is held steady by a reference // THINGS THAT MUST STAY THE SAME. A face is held steady by a reference
// picture; so is an urn, and so is a car. Without one, the object the whole // picture; so is an urn, and so is a car. Without one, the object the whole
// story is about was three different objects in three shots. // story is about was three different objects in three shots.
@ -1472,18 +1504,20 @@
function scenesHtml(shots, voice, draft) { function scenesHtml(shots, voice, draft) {
var out = insertHtml("start"); var out = insertHtml("start");
sceneGroups(shots).forEach(function (g, gi) { sceneGroups(shots).forEach(function (g, gi) {
var filmed = g.shots.filter(function (x) { return x.clip_url; }).length, done = g.shots.every(shotDone); // The cutting room floor counts for nothing: not filmed, not seconds, not the link.
var live = g.shots.filter(function (x) { return !x.floor; }), floored = g.shots.length - live.length;
var filmed = live.filter(function (x) { return x.clip_url; }).length, done = live.every(shotDone);
var busy = g.shots.some(function (x) { return x.writing || x.drawing || stillFilming(x); }); var busy = g.shots.some(function (x) { return x.writing || x.drawing || stillFilming(x); });
var id = draft.id + "|" + gi + "|" + g.key, open = !!OPEN[id]; var id = draft.id + "|" + gi + "|" + g.key, open = !!OPEN[id];
var secs = g.shots.reduce(function (a, x) { return a + Number(x.edit_seconds || 0); }, 0); var secs = live.reduce(function (a, x) { return a + Number(x.edit_seconds || 0); }, 0);
out += '<button type="button" class="scenebar ' + (done ? 'ok' : 'todo') + '" data-scene="' + esc(id) + '">' out += '<button type="button" class="scenebar ' + (done ? 'ok' : 'todo') + '" data-scene="' + esc(id) + '">'
+ '<span class="arrow">' + (open ? '&#9660;' : '&#9654;') + '</span>' + '<span class="arrow">' + (open ? '&#9660;' : '&#9654;') + '</span>'
+ '<span class="n">Scene ' + (gi + 1) + '</span>' + '<span class="n">Scene ' + (gi + 1) + '</span>'
+ '<span class="t">' + esc(g.name) + '</span>' + '<span class="t">' + esc(g.name) + '</span>'
+ '<span class="st">shots ' + esc(g.shots[0].id) + '&ndash;' + esc(g.shots[g.shots.length - 1].id) + ' &middot; ' + g.shots.length + ' shot' + (g.shots.length === 1 ? '' : 's') + ' &middot; ' + secs.toFixed(0) + ' s &middot; ' + '<span class="st">shots ' + esc(g.shots[0].id) + '&ndash;' + esc(g.shots[g.shots.length - 1].id) + ' &middot; ' + live.length + ' shot' + (live.length === 1 ? '' : 's') + (floored ? ' (+' + floored + ' on the floor)' : '') + ' &middot; ' + secs.toFixed(0) + ' s &middot; '
+ (busy ? 'working&hellip;' : done ? 'all filmed' : filmed + ' of ' + g.shots.length + ' filmed') + '</span></button>'; + (busy ? 'working&hellip;' : done ? 'all filmed' : filmed + ' of ' + live.length + ' filmed') + '</span></button>';
if (open) { if (open) {
var ids = g.shots.filter(function (x) { return x.clip_url; }).map(function (x) { return x.id; }); var ids = live.filter(function (x) { return x.clip_url; }).map(function (x) { return x.id; });
out += '<div class="sceneshare">' + (ids.length ? '<button type="button" class="btn small ghost" data-sharescene="' + esc(ids.join(",")) + '" data-scenelabel="' + esc("Scene " + (gi + 1) + " - " + g.name) + '">Share this scene (' + ids.length + ' clip' + (ids.length === 1 ? '' : 's') + ', played in order)</button>' : 'Nothing filmed in this scene yet.') out += '<div class="sceneshare">' + (ids.length ? '<button type="button" class="btn small ghost" data-sharescene="' + esc(ids.join(",")) + '" data-scenelabel="' + esc("Scene " + (gi + 1) + " - " + g.name) + '">Share this scene (' + ids.length + ' clip' + (ids.length === 1 ? '' : 's') + ', played in order)</button>' : 'Nothing filmed in this scene yet.')
+ (SHARES["scene:" + ids.join(",")] ? ' <a class="sharelink" href="' + esc(SHARES["scene:" + ids.join(",")]) + '" target="_blank">' + esc(SHARES["scene:" + ids.join(",")]) + '</a>' : '') + '</div>'; + (SHARES["scene:" + ids.join(",")] ? ' <a class="sharelink" href="' + esc(SHARES["scene:" + ids.join(",")]) + '" target="_blank">' + esc(SHARES["scene:" + ids.join(",")]) + '</a>' : '') + '</div>';
g.shots.forEach(function (x) { out += shotHtml(x, voice) + insertHtml(x.id); }); g.shots.forEach(function (x) { out += shotHtml(x, voice) + insertHtml(x.id); });
@ -1497,7 +1531,8 @@
// ball" starts with the ball, the dog and Dorothy exactly where the last // ball" starts with the ball, the dog and Dorothy exactly where the last
// clip left them. // clip left them.
function prevShot(s) { function prevShot(s) {
var all = (PKG.shots || []).slice().sort(function (a, b) { return String(a.id).localeCompare(String(b.id)); }); var all = (PKG.shots || []).filter(function (x) { return x === s || x.id === s.id || !x.floor; })
.sort(function (a, b) { return String(a.id).localeCompare(String(b.id)); });
var i = all.findIndex(function (x) { return x.id === s.id; }); var i = all.findIndex(function (x) { return x.id === s.id; });
return i > 0 ? all[i - 1] : null; return i > 0 ? all[i - 1] : null;
} }
@ -1508,7 +1543,7 @@
var all = (PKG.shots || []).slice().sort(function (a, b) { return String(a.id).localeCompare(String(b.id)); }); var all = (PKG.shots || []).slice().sort(function (a, b) { return String(a.id).localeCompare(String(b.id)); });
var i = all.findIndex(function (x) { return x.id === s.id; }); var i = all.findIndex(function (x) { return x.id === s.id; });
var opts = all.slice(Math.max(0, i - 12), i).filter(function (x) { var opts = all.slice(Math.max(0, i - 12), i).filter(function (x) {
return x.id !== p.id && x.clip_url && (x.scene || "") === (s.scene || ""); return x.id !== p.id && x.clip_url && !x.floor && (x.scene || "") === (s.scene || "");
}).reverse(); }).reverse();
if (!opts.length) return ""; if (!opts.length) return "";
return ' <span style="white-space:nowrap">or shot <select id="lastof-' + esc(s.id) + '" style="font:inherit;font-size:12px">' return ' <span style="white-space:nowrap">or shot <select id="lastof-' + esc(s.id) + '" style="font:inherit;font-size:12px">'
@ -1668,8 +1703,11 @@
+ '<button type="button" class="btn" data-delete="' + esc(s.id) + '">Remove</button>'; + '<button type="button" class="btn" data-delete="' + esc(s.id) + '">Remove</button>';
} }
return '<div class="shot"><div class="num">' + esc(s.id) + '</div><div class="body">' return '<div class="shot' + (s.floor ? ' floor' : '') + '"><div class="num">' + esc(s.id) + '</div><div class="body">'
+ '<h3>' + esc(s.title || "") + '</h3>' + '<h3>' + esc(s.title || "") + '</h3>'
+ '<label class="floorbox" title="Kept here with everything it has, but left out of the film, the scene link and the running time. Untick to put it back.">'
+ '<input type="checkbox" data-floor="' + esc(s.id) + '"' + (s.floor ? ' checked' : '') + '> Cutting room floor</label>'
+ (s.floor ? '<div class="floornote">On the cutting room floor - left out of the film. Untick to put it back.</div>' : '')
+ '<div class="where">' + esc(s.setting || "") + ' &middot; ' + '<div class="where">' + esc(s.setting || "") + ' &middot; '
+ '<span class="secs"><input type="number" step="0.5" min="3" max="15" value="' + Number(s.edit_seconds || 0).toFixed(1) + '" data-secs="' + esc(s.id) + '" style="width:58px;font:inherit;font-size:12px"> s ' + '<span class="secs"><input type="number" step="0.5" min="3" max="15" value="' + Number(s.edit_seconds || 0).toFixed(1) + '" data-secs="' + esc(s.id) + '" style="width:58px;font:inherit;font-size:12px"> s '
+ '<button type="button" class="btn small ghost" data-estsecs="' + esc(s.id) + '" title="From the lines (speaking time plus a lead-in and a lead-out), or from the action for a silent shot">Estimate</button>' + '<button type="button" class="btn small ghost" data-estsecs="' + esc(s.id) + '" title="From the lines (speaking time plus a lead-in and a lead-out), or from the action for a silent shot">Estimate</button>'
@ -1738,6 +1776,14 @@
if (r) { showMsg("msg", "Shot " + ssid + " is now " + Number(r.seconds).toFixed(1) + " s." + (PKG.shots.some(function (x) { return x.id === ssid && x.clip_url; }) ? " The clip was filmed at the old length; make the shot again to refilm." : ""), "ok"); await refresh(); } if (r) { showMsg("msg", "Shot " + ssid + " is now " + Number(r.seconds).toFixed(1) + " s." + (PKG.shots.some(function (x) { return x.id === ssid && x.clip_url; }) ? " The clip was filmed at the old length; make the shot again to refilm." : ""), "ok"); await refresh(); }
return; return;
} }
if ((el = hit("data-floor"))) {
var flid = el.getAttribute("data-floor"), flon = !!el.checked;
el.disabled = true;
r = await act("set_floor", { draft_id: CUR, shot_id: flid, floor: flon });
if (r) { showMsg("msg", flon ? "Shot " + flid + " is on the cutting room floor - left out of the film. Untick to put it back." : "Shot " + flid + " is back in the film.", "ok"); await refresh(); }
else { el.checked = !flon; el.disabled = false; }
return;
}
if ((el = hit("data-useframe"))) { if ((el = hit("data-useframe"))) {
var uf = el.getAttribute("data-useframe").split("|"); var uf = el.getAttribute("data-useframe").split("|");
el.disabled = true; el.disabled = true;