paytolaugh/create.html
gator 28b98f8326 Gather pictures and footage before the script is written
The owner's pictures could only be attached after the script existed, so the
writer planned a piece in ignorance and the pictures were fitted to finished
words afterwards. That is backwards.

Making a video is now two steps. The project is created on hold, collateral is
attached, and only then does the writer run — knowing it has a photograph of
the actual product and eleven seconds of pool footage to write toward.

Footage is a link plus an in and out point, set by scrubbing the video itself.
The renderer already trimmed arbitrary sources with in/out, so this needed no
change to the cut at all, only somewhere to put the information.
2026-09-07 21:15:34 +00:00

226 lines
11 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>New video — PayToLaugh</title>
<meta name="description" content="Pick your pet, paste a link, set the scene, tell the director what you think.">
<link rel="stylesheet" href="/assets/app.css">
</head>
<body>
<div class="wrap">
<header class="page">
<h1>Make a new video</h1>
<p>Pick the pet, paste whatever you want them to react to, and tell the director what you actually think. The opinion is yours; the delivery is theirs. You can direct every shot afterwards.</p>
</header>
<section>
<div class="card">
<div class="msg" id="msg"></div>
<form id="form">
<h2 class="sec">1 &middot; Who is talking</h2>
<div class="field">
<label for="pet">Pet</label>
<span class="hint">Profiles and photos live on <a href="/pets.html">My pets</a>.</span>
<select id="pet"></select>
</div>
<h2 class="sec" style="margin-top:34px">2 &middot; What they are reacting to</h2>
<div class="field">
<label for="url">Link to an article, post or story</label>
<span class="hint">Anything with a claim in it. News piece, a thread, a video.</span>
<input type="url" id="url" placeholder="https://…">
</div>
<div class="field">
<label for="notes">Your opinion, and notes for the director</label>
<span class="hint">Say what you think and how you want it played. Two sentences is plenty.</span>
<textarea id="notes" placeholder="Astro has hated our robot vacuum since day one and barks at it every time it docks. Play it like he is filing a formal grievance with management."></textarea>
</div>
<h2 class="sec" style="margin-top:34px">3 &middot; What kind of video</h2>
<div class="field">
<label for="format">Format</label>
<span class="hint">This decides the set, the framing and how it is written, before a word exists.</span>
<select id="format"><option value="podcast">Podcast episode</option></select>
</div>
<div class="field">
<label for="scene">Override the setting <span style="color:var(--muted);font-weight:400">(optional)</span></label>
<span class="hint">Leave blank to use the format's own set. Real or not: a field of tall grass, floating on a surfboard.</span>
<input type="text" id="scene" placeholder="">
<p class="foot" id="fmtscene" style="margin:8px 0 0;color:var(--muted)"></p>
</div>
<div class="field">
<label>Do they agree or disagree?</label>
<div class="chips" id="stance">
<span class="chip on" data-v="auto">Let the writer decide</span>
<span class="chip" data-v="agree">Agree</span>
<span class="chip" data-v="disagree">Disagree</span>
<span class="chip" data-v="mixed">A bit of both</span>
</div>
</div>
<h2 class="sec" style="margin-top:34px">4 &middot; How long</h2>
<div class="field">
<div class="chips" id="len">
<span class="chip" data-v="2">16 seconds</span>
<span class="chip on" data-v="4">32 seconds</span>
<span class="chip" data-v="6">48 seconds</span>
</div>
<span class="hint" style="margin-top:10px;display:block">The piece is written as one continuous monologue, then cut into 8 second shots that flow into each other.</span>
</div>
<div class="quote">
<div><div class="big" id="price">$8.85</div><div class="sub" id="plen">32 seconds &middot; 4 shots</div></div>
<div class="sub">You direct and approve before anything is filmed.</div>
</div>
<button class="btn" id="go" type="submit" style="margin-top:26px">Next &middot; add pictures and footage</button>
</form>
</div>
<div class="card" id="colcard" style="display:none;margin-top:22px">
<h2 class="sec">5 &middot; What you want shown on screen <span style="color:var(--muted);font-weight:400;font-size:15px">(optional)</span></h2>
<p class="hint" style="margin-bottom:16px">Anything here is given to the writer <em>before</em> it starts, so the script gets written around what you actually have rather than having pictures fitted to it afterwards. Leave it empty and the cutaways are generated.</p>
<div class="drop" id="drop" tabindex="0">Paste a picture, drop one here, or click to choose</div>
<input type="file" id="picker" accept="image/*" multiple style="display:none">
<div class="field" style="margin-top:18px">
<label for="vurl">Or a link to a video file</label>
<span class="hint">It has to be the video file itself &mdash; a link ending .mp4 or similar. A link to a <em>page</em> containing a video is a web page, and there is nothing there for us to cut.</span>
<div style="display:flex;gap:10px;flex-wrap:wrap">
<input type="text" id="vurl" placeholder="https://example.com/pool-robot.mp4" style="flex:1 1 320px">
<button type="button" class="btn ghost" id="vload">Load it</button>
</div>
</div>
<div id="vedit" style="display:none;margin-top:16px;padding:16px;border:1px solid var(--line);border-radius:12px">
<video id="vprev" controls playsinline preload="metadata" style="width:100%;max-width:420px;border-radius:10px;background:#000;display:block"></video>
<div style="margin-top:14px">
<label class="mini" style="font-size:13px;font-weight:600;display:block;margin-bottom:5px">Start <span id="inlab" style="color:var(--muted);font-weight:400">0.0s</span></label>
<input type="range" id="vin" min="0" max="100" step="0.1" value="0" style="width:100%">
<label class="mini" style="font-size:13px;font-weight:600;display:block;margin:12px 0 5px">End <span id="outlab" style="color:var(--muted);font-weight:400">0.0s</span></label>
<input type="range" id="vout" min="0" max="100" step="0.1" value="5" style="width:100%">
<p class="hint" id="vlen" style="margin:10px 0 0"></p>
</div>
<div class="field" style="margin-top:12px">
<label for="vdesc">What is in it</label>
<input type="text" id="vdesc" placeholder="the pool robot climbing the wall and falling off">
</div>
<button type="button" class="btn ghost" id="vplay">Play just that bit</button>
<button type="button" class="btn" id="vadd" style="margin-left:8px">Use this clip</button>
</div>
<div class="pics" id="pics" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:14px;margin-top:18px"></div>
<div style="margin-top:24px;padding-top:20px;border-top:1px solid var(--line)">
<button type="button" class="btn" id="write">Write the script</button>
<span class="hint" style="margin-left:12px">Nothing is charged and nothing is filmed yet.</span>
</div>
</div>
<p class="note" style="margin-top:22px">
<strong>What happens next.</strong> You add anything you want shown on screen, then the
writer runs knowing what it has to work with. You will be able to read it, change the
setting and camera on every shot, ask for rewrites, and approve it.
Nothing is charged and nothing is filmed yet.
</p>
</section>
<footer>PayToLaugh &middot; your pet has opinions.</footer>
</div>
<script src="/assets/app.js"></script>
<script>
navBar("create");
var email = requireSession();
var stance = "auto", segments = 4, havePets = false, FORMATS = [];
(async function loadFormats() {
var r = await api("camera_presets", {});
if (!r.ok || !r.formats) return;
FORMATS = r.formats;
var sel = document.getElementById("format");
sel.innerHTML = FORMATS.map(function (f) {
return '<option value="' + esc(f.id) + '">' + esc(f.label) + '</option>';
}).join("");
function showScene() {
var f = FORMATS.filter(function (x) { return x.id === sel.value; })[0];
document.getElementById("fmtscene").textContent =
f ? "Uses: " + f.scene : "";
}
sel.addEventListener("change", showScene);
showScene();
})();
function chipGroup(id, cb) {
var g = document.getElementById(id);
g.addEventListener("click", function (e) {
var c = e.target.closest(".chip");
if (!c) return;
[].forEach.call(g.querySelectorAll(".chip"), function (x) { x.classList.remove("on"); });
c.classList.add("on");
cb(c.getAttribute("data-v"));
});
}
chipGroup("stance", function (v) { stance = v; });
chipGroup("len", function (v) { segments = parseInt(v, 10); paint(); });
function paint() {
var p = priceFor(segments);
document.getElementById("price").textContent = "$" + p.price.toFixed(2);
document.getElementById("plen").textContent = p.seconds + " seconds · " + segments + " shots";
}
paint();
var petSel = document.getElementById("pet");
(async function loadPets() {
if (!email) return;
var r = await api("list_pets", { email: email });
if (!r.ok) { showMsg("msg", r.error || "Could not load your pets."); return; }
var pets = r.pets || [];
if (!pets.length) {
petSel.innerHTML = '<option value="">No pets yet</option>';
petSel.disabled = true;
document.getElementById("go").disabled = true;
showMsg("msg", "Add a pet first — it only takes a moment on the My pets page.");
return;
}
havePets = true;
petSel.innerHTML = pets.map(function (p) {
return '<option value="' + esc(p.id) + '" data-name="' + esc(p.name) + '">'
+ esc(p.name) + (p.breed ? " · " + esc(p.breed) : "") + '</option>';
}).join("");
})();
document.getElementById("form").addEventListener("submit", async function (e) {
e.preventDefault();
hideMsg("msg");
if (!havePets) { showMsg("msg", "Add a pet first on the My pets page."); return; }
var btn = document.getElementById("go");
var url = document.getElementById("url").value.trim();
var notes = document.getElementById("notes").value.trim();
var scene = document.getElementById("scene").value.trim();
var opt = petSel.options[petSel.selectedIndex];
if (!url) { showMsg("msg", "Paste a link to whatever you want them to react to."); return; }
if (notes.length < 10) { showMsg("msg", "Tell the director what you think, in a sentence or two."); return; }
btn.disabled = true; btn.textContent = "Writing…";
var r = await api("create_project", {
email: email, pet_id: petSel.value,
pet_name: opt ? opt.getAttribute("data-name") : "",
source_url: url, owner_notes: notes, scene: scene,
format: document.getElementById("format").value,
stance: stance, segments: segments
});
btn.disabled = false; btn.textContent = "Write the script";
if (!r.ok) { showMsg("msg", r.error || "That did not go through. Please try again."); return; }
showMsg("msg", "Writing it now…", "ok");
setTimeout(function () { location.href = "/script.html?id=" + encodeURIComponent(r.project.id); }, 500);
});
</script>
</body>
</html>