publish: sowing-the-spare

This commit is contained in:
anna-martin 2026-08-20 22:37:13 +00:00
commit 148097318f
3 changed files with 159 additions and 0 deletions

11
README.md Normal file
View File

@ -0,0 +1,11 @@
# sowing-the-spare
Short film + page on contingency as disciplined attention — the gardener's spare seed meeting the factory risk register.
**Live demo:** https://anna-martin.4ort.net/films/sowing-the-spare/index.html
## Related in the galaxy
- https://anna-martin.4ort.net/
_Built by anna-martin in the 4ort galaxy._

View File

@ -0,0 +1,23 @@
{
"captions": true,
"voice": "af_nova",
"music_url": "https://4ort.live/v1/mtv/video/1cac6ebc5829?download=1",
"scenes": [
{
"id": "s1",
"narration": "Every gardener keeps a spare. A second envelope of pepper seed, set back on a dry shelf for the year the first one fails. Nothing dramatic. Just a promise made on a quiet Tuesday, honoured in a hard one."
},
{
"id": "s2",
"narration": "We plant to the frost date. Not the prettiest one, the latest one we can remember, and then we add a week of margin we earned the hard way. On the factory floor that calendar has a name. A risk register. Safety stock, alternate suppliers, procedures walked once and stamped. The frost date, typed up and signed."
},
{
"id": "s3",
"narration": "When a supplier stumbles, the panic is not a plan. The alternate lane is already printed, already walked, already tested in October, not in the middle of the freeze. Contingency is not worry turned loose. It is worry, disciplined, and told exactly where to stand."
},
{
"id": "s4",
"narration": "A garden that survives the bad year is not lucky. A plant that survives it is not either. It sowed the spare when no one was watching, against a season it hoped never to see. And when the frost came, it was ready enough that the frost hardly mattered."
}
]
}

View File

@ -0,0 +1,125 @@
<!doctype html>
<html><head><meta charset="UTF-8"/>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<style>
body{margin:0;background:#f7f1e5;color:#3a2c1f;font-family:Georgia,'Times New Roman',serif;-webkit-font-smoothing:antialiased}
#root{position:relative;width:1920px;height:1080px;overflow:hidden;background:#f7f1e5}
.clip{position:absolute;inset:0;display:grid;place-items:center;opacity:0}
.kicker{font-family:ui-monospace,'Courier New',monospace;letter-spacing:.35em;font-size:30px;color:#a05c3c;margin-bottom:28px}
h1{font-weight:normal;font-size:96px;line-height:1.15;margin:0}
.sub{font-family:ui-monospace,'Courier New',monospace;font-size:32px;color:#6b5a47;line-height:1.6;max-width:1200px}
.bg{position:absolute;inset:0}
/* s1 seed tin */
.tin{position:absolute;width:420px;height:300px;left:760px;top:420px;background:#5b4632;border-radius:18px;border-top:30px solid #7a5f42;box-shadow:0 28px 50px rgba(90,65,40,.35)}
.tin:after{content:'';position:absolute;left:60px;right:60px;top:48px;height:4px;background:#c9a878}
.tinglow{position:absolute;left:660px;top:340px;width:600px;height:460px;background:radial-gradient(closest-side,#e8c97f88,transparent)}
/* s2 ledger */
.ledger{position:absolute;width:1280px;height:780px;left:320px;top:230px;opacity:0;transform:rotate(-1.2deg)}
.row{height:92px;border-bottom:2px solid #d8c9ae;font-family:ui-monospace,'Courier New',monospace;font-size:44px;color:#6b5a47;display:flex;align-items:center;padding:0 60px}
.row b{color:#3a2c1f;font-weight:normal}
.row .mark{padding-left:40px;color:#a05c3c}
/* s3 two lanes */
.lane{position:absolute;height:150px;top:440px;border-radius:90px}
.laneA{left:120px;width:700px;background:#cfc0a2}
.laneB{left:1080px;width:720px;background:#a05c3c;opacity:0}
.tag{position:absolute;font-family:ui-monospace,'Courier New',monospace;font-size:34px;color:#6b5a47;letter-spacing:.15em}
/* s4 dawn */
.bar{position:absolute;bottom:0;width:150px;background:#3a2c1f;border-top:6px solid #a05c3c}
.sun{position:absolute;width:520px;height:520px;border-radius:50%;background:radial-gradient(closest-side,#e8c97f,transparent)}
.bigtitle{font-size:130px;line-height:1.1;max-width:1500px;text-align:center}
</style></head>
<body>
<div id="root" data-composition-id="main" data-start="0" data-width="1920" data-height="1080" data-duration="90">
<section id="s1" class="clip" data-start="0" data-duration="22" data-track-index="1">
<div class="bg" style="background:#f7f1e5"></div>
<div class="tinglow"></div>
<div class="tin"></div>
<div style="position:absolute;top:120px;left:0;right:0;text-align:center">
<div class="kicker">THE SPARE · SEED TIN</div>
<h1 style="text-align:center">The year the<br/>first one fails</h1>
</div>
</section>
<section id="s2" class="clip" data-start="22" data-duration="24" data-track-index="1">
<div class="bg" style="background:#f2ead9"></div>
<div style="position:absolute;top:70px;left:0;right:0;text-align:center">
<div class="kicker">THE FROST DATE · RISK REGISTER</div>
<h1 style="text-align:center;font-size:76px">Plant late, on purpose</h1>
</div>
<div id="ledger" class="ledger">
<div class="row"><b>Last frost on record</b><span class="mark">— plus one week margin</span></div>
<div class="row"><b>Safety stock</b><span class="mark">ok</span></div>
<div class="row"><b>Alternate supplier</b><span class="mark">printed · walked · stamped</span></div>
<div class="row"><b>Procedure 44-A</b><span class="mark">tested in October</span></div>
</div>
</section>
<section id="s3" class="clip" data-start="46" data-duration="20" data-track-index="1">
<div class="bg" style="background:#efe6d2"></div>
<div style="position:absolute;top:120px;left:0;right:0;text-align:center">
<div class="kicker">THE ALTERNATE ROW</div>
<h1 style="text-align:center;font-size:84px">The lane that was<br/>already walked</h1>
</div>
<div id="laneA" class="lane"></div>
<div id="laneB" class="lane"></div>
<div id="tagA" class="tag" style="top:620px;left:180px">primary · holds</div>
<div id="tagB" class="tag" style="top:620px;left:1150px">alternate · ready</div>
</section>
<section id="s4" class="clip" data-start="66" data-duration="24" data-track-index="1">
<div class="bg" style="background:linear-gradient(#f7f1e5,#efe2c8)"></div>
<div class="sun" style="top:340px;left:700px"></div>
<div style="position:absolute;bottom:250px;left:0;right:0;text-align:center;z-index:3">
<div class="kicker">DAWN · AFTER THE FROST</div>
<h1 class="bigtitle">Sowing the spare</h1>
<div class="sub" style="margin:30px auto 0">ready enough that the frost hardly mattered</div>
</div>
<div id="bars"></div>
</section>
<audio id="voice-s1" src="audio/s1.wav" data-start="0"></audio>
<audio id="voice-s2" src="audio/s2.wav" data-start="22"></audio>
<audio id="voice-s3" src="audio/s3.wav" data-start="46"></audio>
<audio id="voice-s4" src="audio/s4.wav" data-start="66"></audio>
<audio id="bgm" src="audio/music.mp3" data-start="0" data-volume="0.14"></audio>
<script>
const bars = document.getElementById('bars');
for (let i = 0; i < 9; i++) {
const b = document.createElement('div');
b.className = 'bar';
b.style.left = (140 + i * 200) + 'px';
b.style.height = (160 + (i % 4) * 90) + 'px';
b.style.opacity = 0;
b.id = 'bar' + i;
bars.appendChild(b);
}
window.__timelines = window.__timelines || {};
const tl = gsap.timeline({ paused: true });
tl.set(['#s1','#s2','#s3','#s4'], { opacity: 0 });
tl.set('#s1', { opacity: 1, zIndex: 1 }, 0);
tl.set('#s2', { opacity: 1, zIndex: 2 }, 22);
tl.set('#s3', { opacity: 1, zIndex: 3 }, 46);
tl.set('#s4', { opacity: 1, zIndex: 4 }, 66);
tl.from('.tin', { y: 120, opacity: 0, duration: 1.1, ease: 'power2.out' }, 1.5);
tl.from('#s1 h1', { opacity: 0, y: 26, duration: 1, ease: 'power2.out' }, 2.6);
tl.to('.tinglow', { opacity: 0.35, duration: 6, ease: 'sine.inOut' }, 3);
tl.to('#ledger', { opacity: 1, rotate: 0, duration: 1, ease: 'power2.out' }, 24);
tl.from('.row', { opacity: 0, x: -40, duration: 0.8, stagger: 0.5, ease: 'power2.out' }, 25);
tl.to('#laneA', { opacity: 0.35, duration: 0.8 }, 47.5);
tl.to('#laneB', { opacity: 1, duration: 1.6, ease: 'power2.out' }, 49);
tl.from('#tagB', { opacity: 0, x: -20, duration: 0.8 }, 52);
tl.from('.sun', { scale: 0.7, opacity: 0, duration: 2.4, ease: 'sine.inOut' }, 68);
tl.from('#s4 h1', { opacity: 0, y: 30, duration: 1.1, ease: 'power2.out' }, 69);
tl.from('#s4 .sub', { opacity: 0, y: 16, duration: 1, ease: 'power2.out' }, 71);
tl.from('.bar', { opacity: 0, y: 60, duration: 1.4, stagger: 0.15, ease: 'power3.out' }, 71);
window.__timelines['main'] = tl;
</script>
</body></html>