What happened when we ran it
We sent
Act as an expert motion designer and front-end developer.
Task: Build a 30-second animated explainer for Hearth & Hive, a small bakery in Leeds that sells sourdough loaves, seasonal pastries and weekly subscription boxes. Customers order online by Thursday and collect on Saturday morning from the shop or get free delivery within three miles. The founders bake everything with stoneground flour from a Yorkshire mill. as a single self-contained HTML page that plays automatically.
Steps:
1. Split the 30 seconds into exactly 5 timed scenes: the customer's problem, what the business does, how it works in 3 steps, one proof point, and a closing scene with the business name.
2. Write short, punchy on-screen text for each scene, aimed at busy local families and young professionals in Leeds who want fresh, good-quality bread without queuing at supermarkets or weekend markets.
3. Style everything with bold type, smooth transitions between scenes, and these brand colours: warm cream (#FFF4E0) for backgrounds, deep burnt orange (#C4531A) for headlines and charcoal (#2B2B2B) for body text.
4. Build the page with plain HTML, CSS and JavaScript, using a timeline so each scene starts and ends at the stated second.
5. Check that the total runtime is 30 seconds and that no text is cut off on a laptop screen.
Output: First, a table listing each scene with its start and end time and its on-screen text. Then the complete HTML file in one code block. Finish with three one-line suggestions for follow-up tweaks.
The model replied
## Scene plan
| # | Scene | Start | End | Length | On-screen text |
|---|-------|-------|-----|--------|----------------|
| 1 | The problem | 0:00 | 0:06 | 6s | *Sound familiar?* / **Queuing for bread? Again?** / Supermarket aisles. Packed markets. Sold-out loaves. / [SOLD OUT] |
| 2 | What we do | 0:06 | 0:12 | 6s | *Meet your local bakery* / **Hearth & Hive bakes it for you.** / Sourdough loaves · Seasonal pastries · Weekly boxes |
| 3 | How it works | 0:12 | 0:20 | 8s | *How it works* / **Three steps. Done.** / 1 **Order**: Online, by Thursday. / 2 **We bake**: Fresh on Saturday morning. / 3 **Collect**: From the shop, or free delivery within 3 miles. |
| 4 | Proof point | 0:20 | 0:25 | 5s | *The proof* / **Stoneground Yorkshire flour.** / In everything we bake. Made by the founders, start to finish. / [Milled in Yorkshire] |
| 5 | Close | 0:25 | 0:30 | 5s | **Hearth & Hive** / Fresh sourdough, ready for Saturday. / [Order online by Thursday] / Leeds · Collect or free delivery within 3 miles |
Total: 6 + 6 + 8 + 5 + 5 = **30 seconds**. The proof point uses only facts you gave me, with no invented statistics.
## Complete HTML file
```html
<!DOCTYPE html>
<html lang="en-GB">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Hearth & Hive – 30-second explainer</title>
<style>
:root{
--cream:#FFF4E0;
--orange:#C4531A;
--char:#2B2B2B;
--display:"Arial Black","Segoe UI Black","Helvetica Neue",Arial,sans-serif;
--body:"Segoe UI","Helvetica Neue",Arial,sans-serif;
}
*{box-sizing:border-box;margin:0;padding:0}
html,body{height:100%;overflow:hidden;background:var(--cream);color:var(--char);font-family:var(--body)}
/* ---------- Animated background shapes (full viewport) ---------- */
.bg{position:fixed;inset:0;overflow:hidden;z-index:0}
.blob{position:absolute;border-radius:50%;transition:all 1.4s cubic-bezier(.65,0,.35,1)}
.b1{background:rgba(196,83,26,.13)}
.b2{background:rgba(196,83,26,.08)}
.b3{background:rgba(43,43,43,.05)}
.bg[data-s="0"] .b1,.bg[data-s="1"] .b1{left:78%;top:-18%;width:60vmin;height:60vmin}
.bg[data-s="0"] .b2,.bg[data-s="1"] .b2{left:-12%;top:68%;width:44vmin;height:44vmin}
.bg[data-s="0"] .b3,.bg[data-s="1"] .b3{left:50%;top:80%;width:30vmin;height:30vmin}
.bg[data-s="2"] .b1{left:64%;top:18%;width:78vmin;height:78vmin}
.bg[data-s="2"] .b2{left:-10%;top:-20%;width:46vmin;height:46vmin}
.bg[data-s="2"] .b3{left:40%;top:84%;width:34vmin;height:34vmin}
.bg[data-s="3"] .b1{left:-14%;top:60%;width:56vmin;height:56vmin}
.bg[data-s="3"] .b2{left:86%;top:-14%;width:52vmin;height:52vmin}
.bg[data-s="3"] .b3{left:70%;top:76%;width:40vmin;height:40vmin}
.bg[data-s="4"] .b1{left:60%;top:6%;width:90vmin;height:90vmin}
.bg[data-s="4"] .b2{left:-8%;top:72%;width:40vmin;height:40vmin}
.bg[data-s="4"] .b3{left:30%;top:-16%;width:36vmin;height:36vmin}
.bg[data-s="5"] .b1{left:50%;top:50%;margin:-45vmin 0 0 -45vmin;width:90vmin;height:90vmin}
.bg[data-s="5"] .b2{left:-6%;top:-14%;width:48vmin;height:48vmin}
.bg[data-s="5"] .b3{left:84%;top:66%;width:46vmin;height:46vmin}
/* ---------- 16:9 stage; 1cqw = 1% of stage width, so type scales with it ---------- */
.stage{
position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
width:min(100vw,177.78vh);height:min(100vh,56.25vw);
container-type:size;z-index:1;
}
.scene{
position:absolute;inset:0;padding:5cqw 8cqw;
display:flex;flex-direction:column;justify-content:center;
opacity:0;visibility:hidden;transform:translateY(2cqw);
transition:opacity .6s ease,transform .6s ease,visibility 0s .6s;
}
.scene.in{opacity:1;visibility:visible;transform:none;
transition:opacity .6s ease .1s,transform .6s ease .1s,visibility 0s}
.scene.out{opacity:0;visibility:visible;transform:translateY(-2cqw);
transition:opacity .5s ease,transform .5s ease}
/* staggered reveals, driven by the timeline (data-at = seconds after scene start) */
.rise{opacity:0;transform:translateY(3cqw);
transition:opacity .7s ease,transform .8s cubic-bezier(.2,.8,.2,1)}
.rise.show{opacity:1;transform:none}
/* ---------- Type ---------- */
.kicker{font-size:2.2cqw;font-weight:800;letter-spacing:.18em;text-transform:uppercase;
color:var(--char);margin-bottom:1.6cqw}
h1{font-family:var(--display);font-weight:900;color:var(--orange);
font-size:7cqw;line-height:1.04;letter-spacing:-.02em;max-width:84cqw}
.lead{font-size:2.8cqw;line-height:1.3;color:var(--char);margin-top:2cqw;max-width:70cqw;font-weight:500}
/* Scene 1 */
.queue-row{display:flex;align-items:flex-end;gap:3cqw;margin-top:3.5cqw}
.queue{width:60cqw;height:auto;display:block}
.p{opacity:0;transform-box:fill-box;transform-origin:bottom center}
.scene.in .p{animation:pop .5s cubic-bezier(.2,.9,.3,1.3) both;animation-delay:calc(.9s + var(--i)*.28s)}
@keyframes pop{from{opacity:0;transform:translateY(40%) scale(.6)}to{opacity:1;transform:none}}
.soldout{font-family:var(--display);font-size:2.6cqw;color:var(--cream);background:var(--orange);
padding:.9cqw 2cqw;border-radius:.8cqw;transform:rotate(-4deg);white-space:nowrap;margin-bottom:1cqw}
/* Scene 2 */
.s2 h1{font-size:6.4cqw;max-width:64cqw}
.pills{display:flex;gap:1.4cqw;flex-wrap:wrap;margin-top:3cqw;max-width:66cqw}
.pill{border:.3cqw solid var(--orange);color:var(--orange);font-weight:800;font-size:2.3cqw;
padding:.8cqw 2cqw;border-radius:10cqw;white-space:nowrap}
.art{position:absolute;right:7cqw;top:50%;transform:translateY(-50%)}
.loaf{width:23cqw;display:block;animation:float 4s ease-in-out infinite}
@keyframes float{0%,100%{transform:translateY(0) rotate(-2deg)}50%{transform:translateY(-1.2cqw) rotate(2deg)}}
/* Scene 3 */
.s3 h1{font-size:6.4cqw}
.steps{display:flex;gap:2.5cqw;margin-top:3.5cqw}
.card{flex:1 1 0;background:rgba(255,255,255,.55);border:.3cqw solid var(--orange);
border-radius:2cqw;padding:2.2cqw 2.4cqw}
.card-top{display:flex;align-items:center;gap:1.4cqw;margin-bottom:1.2cqw}
.num{flex:none;width:5cqw;height:5cqw;border-radius:50%;background:var(--orange);color:var(--cream);
font-family:var(--display);font-size:2.8cqw;display:flex;align-items:center;justify-content:center}
.card h2{font-size:3.2cqw;font-weight:900;color:var(--char);line-height:1.1}
.card p{font-size:2.3cqw;line-height:1.35;color:var(--char)}
/* Scene 4 */
.s4 h1{font-size:6.6cqw;max-width:64cqw}
.badge{display:inline-block;margin-top:3cqw;background:var(--char);color:var(--cream);
font-weight:800;font-size:2.3cqw;letter-spacing:.06em;padding:1cqw 2.2cqw;border-radius:10cqw}
.wheat{height:36cqw;width:auto;display:block;transform-origin:50% 100%;animation:sway 4s ease-in-out infinite}
@keyframes sway{0%,100%{transform:rotate(-3deg)}50%{transform:rotate(3deg)}}
/* Scene 5 */
.s5{align-items:center;text-align:center}
.hex{width:12cqw;display:block;margin:0 auto 2cqw}
.s5 h1{font-size:9.5cqw;max-width:none}
.s5 .lead{margin-left:auto;margin-right:auto;font-size:3cqw}
.cta{display:inline-block;margin-top:3cqw;background:var(--orange);color:var(--cream);font-weight:900;
font-size:2.9cqw;padding:1.4cqw 3.6cqw;border-radius:10cqw}
.small{margin-top:2.2cqw;font-size:2.1cqw;font-weight:600;color:var(--char)}
/* ---------- UI ---------- */
.progress{position:fixed;left:0;bottom:0;height:.8vmin;min-height:5px;width:0;background:var(--orange);z-index:5}
.replay{position:fixed;right:3vmin;bottom:3.5vmin;z-index:6;font:800 max(14px,1.6vmin) var(--body);
background:var(--char);color:var(--cream);border:0;border-radius:99px;padding:.8em 1.4em;cursor:pointer;
opacity:0;pointer-events:none;transition:opacity .5s}
.done .replay{opacity:1;pointer-events:auto}
@media (prefers-reduced-motion:reduce){
.rise,.scene,.scene.in,.scene.out{transform:none}
.loaf,.wheat{animation:none}
}
</style>
</head>
<body>
<div class="bg" id="bg" data-s="0" aria-hidden="true">
<div class="blob b1"></div><div class="blob b2"></div><div class="blob b3"></div>
</div>
<main class="stage" id="stage">
<!-- SCENE 1 · 0–6s · The problem -->
<section class="scene s1" id="s1">
<p class="kicker rise" data-at="0.2">Sound familiar?</p>
<h1 class="rise" data-at="0.5">Queuing for bread? Again?</h1>
<p class="lead rise" data-at="1.6">Supermarket aisles. Packed markets. Sold-out loaves.</p>
<div class="queue-row">
<svg class="queue" viewBox="0 0 400 64" aria-hidden="true">
<g fill="#2B2B2B">
<g class="p" style="--i:0"><circle cx="24" cy="16" r="10"/><rect x="12" y="29" width="24" height="33" rx="10"/></g>
<g class="p" style="--i:1"><circle cx="72" cy="16" r="10"/><rect x="60" y="29" width="24" height="33" rx="10"/></g>
<g class="p" style="--i:2"><circle cx="120" cy="16" r="10"/><rect x="108" y="29" width="24" height="33" rx="10"/></g>
<g class="p" style="--i:3"><circle cx="168" cy="16" r="10"/><rect x="156" y="29" width="24" height="33" rx="10"/></g>
<g class="p" style="--i:4"><circle cx="216" cy="16" r="10"/><rect x="204" y="29" width="24" height="33" rx="10"/></g>
<g class="p" style="--i:5"><circle cx="264" cy="16" r="10"/><rect x="252" y="29" width="24" height="33" rx="10"/></g>
<g class="p" style="--i:6"><circle cx="312" cy="16" r="10"/><rect x="300" y="29" width="24" height="33" rx="10"/></g>
<g class="p" style="--i:7"><circle cx="360" cy="16" r="10"/><rect x="348" y="29" width="24" height="33" rx="10"/></g>
</g>
</svg>
<span class="soldout rise" data-at="3.4">SOLD OUT</span>
</div>
</section>
<!-- SCENE 2 · 6–12s · What the business does -->
<section class="scene s2" id="s2">
<p class="kicker rise" data-at="0.2">Meet your local bakery</p>
<h1 class="rise" data-at="0.5">Hearth & Hive bakes it for you.</h1>
<div class="pills">
<span class="pill rise" data-at="1.6">Sourdough loaves</span>
<span class="pill rise" data-at="2.2">Seasonal pastries</span>
<span class="pill rise" data-at="2.8">Weekly boxes</span>
</div>
<div class="art rise" data-at="1.0">
<svg class="loaf" viewBox="0 0 200 140" aria-hidden="true">
<ellipse cx="100" cy="128" rx="78" ry="8" fill="rgba(43,43,43,.12)"/>
<ellipse cx="100" cy="76" rx="92" ry="56" fill="#C4531A"/>
<ellipse cx="86" cy="54" rx="62" ry="26" fill="rgba(255,244,224,.14)"/>
<g stroke="#FFF4E0" stroke-width="8" stroke-linecap="round" fill="none">
<path d="M56 70 L80 46"/><path d="M92 76 L116 50"/><path d="M128 80 L152 56"/>
</g>
<g fill="#FFF4E0" opacity=".7"><circle cx="40" cy="92" r="2.5"/><circle cx="68" cy="104" r="2"/><circle cx="150" cy="100" r="2.5"/><circle cx="168" cy="82" r="2"/></g>
</svg>
</div>
</section>
<!-- SCENE 3 · 12–20s · How it works -->
<section class="scene s3" id="s3">
<p class="kicker rise" data-at="0.2">How it works</p>
<h1 class="rise" data-at="0.5">Three steps. Done.</h1>
<div class="steps">
<div class="card rise" data-at="1.4">
<div class="card-top"><span class="num">1</span><h2>Order</h2></div>
<p>Online, by Thursday.</p>
</div>
<div class="card rise" data-at="3.2">
<div class="card-top"><span class="num">2</span><h2>We bake</h2></div>
<p>Stoneground sourdough, fresh on Saturday morning.</p>
</div>
<div class="card rise" data-at="5.0">
<div class="card-top"><span class="num">3</span><h2>Collect</h2></div>
<p>From the shop, or free delivery within 3 miles.</p>
</div>
</div>
</section>
<!-- SCENE 4 · 20–25s · Proof point -->
<section class="scene s4" id="s4">
<p class="kicker rise" data-at="0.1">The proof</p>
<h1 class="rise" data-at="0.3">Stoneground Yorkshire flour.</h1>
<p class="lead rise" data-at="1.1" style="max-width:62cqw">In everything we bake. Made by the founders, start to finish.</p>
<span class="badge rise" data-at="1.9" style="align-self:flex-start">MILLED IN YORKSHIRE</span>
<div class="art rise" data-at="0.8" style="right:9cqw">
<svg class="wheat" viewBox="0 0 120 240" aria-hidden="true">
<line x1="60" y1="240" x2="60" y2="40" stroke="#2B2B2B" stroke-width="4" stroke-linecap="round"/>
<g fill="#C4531A">
<ellipse cx="60" cy="26" rx="9" ry="22"/>
<ellipse cx="42" cy="62" rx="9" ry="20" transform="rotate(-30 42 62)"/><ellipse cx="78" cy="62" rx="9" ry="20" transform="rotate(30 78 62)"/>
<ellipse cx="42" cy="94" rx="9" ry="20" transform="rotate(-30 42 94)"/><ellipse cx="78" cy="94" rx="9" ry="20" transform="rotate(30 78 94)"/>
<ellipse cx="42" cy="126" rx="9" ry="20" transform="rotate(-30 42 126)"/><ellipse cx="78" cy="126" rx="9" ry="20" transform="rotate(30 78 126)"/>
<ellipse cx="42" cy="158" rx="9" ry="20" transform="rotate(-30 42 158)"/><ellipse cx="78" cy="158" rx="9" ry="20" transform="rotate(30 78 158)"/>
</g>
</svg>
</div>
</section>
<!-- SCENE 5 · 25–30s · Close -->
<section class="scene s5" id="s5">
<svg class="hex rise" data-at="0.1" viewBox="0 0 100 100" aria-hidden="true">
<polygon points="50,6 88,28 88,72 50,94 12,72 12,28" fill="none" stroke="#C4531A" stroke-width="7" stroke-linejoin="round"/>
<ellipse cx="50" cy="54" rx="26" ry="16" fill="#C4531A"/>
<g stroke="#FFF4E0" stroke-width="3.5" stroke-linecap="round"><path d="M36 54 L44 44"/><path d="M48 56 L56 46"/><path d="M60 58 L68 48"/></g>
</svg>
<h1 class="rise" data-at="0.4">Hearth & Hive</h1>
<p class="lead rise" data-at="1.2">Fresh sourdough, ready for Saturday.</p>
<div><span class="cta rise" data-at="2.0">Order online by Thursday</span></div>
<p class="small rise" data-at="2.7">Leeds · Collect or free delivery within 3 miles</p>
</section>
</main>
<div class="progress" id="progress"></div>
<button class="replay" id="replay" type="button">↻ Replay</button>
<script>
(function(){
/* ---------- Timeline: the single source of truth ---------- */
var TOTAL = 30; // seconds
var EXIT = 0.5; // fade-out window at the end of each scene (except the last)
var SCENES = [
{id:'s1', start: 0, end: 6},
{id:'s2', start: 6, end:12},
{id:'s3', start:12, end:20},
{id:'s4', start:20, end:25},
{id:'s5', start:25, end:30}
];
// Self-check: scenes must be contiguous and sum to exactly 30s
var ok = SCENES[0].start === 0 && SCENES[SCENES.length-1].end === TOTAL &&
SCENES.every(function(s,i){ return i===0 || s.start === SCENES[i-1].end; });
console.assert(ok, 'Timeline error: scenes are not contiguous or do not total ' + TOTAL + 's');
console.log('Timeline OK – total runtime:', SCENES.reduce(function(a,s){return a+(s.end-s.start);},0) + 's');
var bg = document.getElementById('bg');
var bar = document.getElementById('progress');
var replayBtn = document.getElementById('replay');
SCENES.forEach(function(s){
s.el = document.getElementById(s.id);
s.items = Array.prototype.slice.call(s.el.querySelectorAll('[data-at]'));
});
/* Render the entire page as a pure function of time t (seconds) */
function render(t){
var current = 0;
SCENES.forEach(function(s,i){
var last = i === SCENES.length - 1;
var state = '';
if (t >= s.start && t < s.end){
state = (!last && t >= s.end - EXIT) ? 'out' : 'in';
current = i + 1;
} else if (last && t >= s.end){ // hold the closing frame
state = 'in'; current = i + 1;
}
s.el.classList.toggle('in', state === 'in');
s.el.classList.toggle('out', state === 'out');
s.items.forEach(function(n){
n.classList.toggle('show', t >= s.start + parseFloat(n.getAttribute('data-at')));
});
});
bg.setAttribute('data-s', current);
bar.style.width = (Math.max(0, Math.min(t / TOTAL, 1)) * 100) + '%';
}
var raf = 0, t0 = 0;
function tick(now){
var t = Math.max(0, (now - t0) / 1000);
if (t >= TOTAL){
render(TOTAL);
document.body.classList.add('done');
return;
}
render(t);
raf = requestAnimationFrame(tick);
}
function start(){
cancelAnimationFrame(raf);
document.body.classList.remove('done');
render(-1); // clear everything
requestAnimationFrame(function(){ // let the cleared state paint first
requestAnimationFrame(function(){
t0 = performance.now();
raf = requestAnimationFrame(tick);
});
});
}
replayBtn.addEventListener('click', start);
document.addEventListener('keydown', function(e){ if (e.key === 'r' || e.key === 'R') start(); });
if (document.readyState === 'complete') start();
else window.addEventListener('load', start);
})();
</script>
</body>
</html>
```
## Checks
- **Runtime:** the scene lengths are 6 + 6 + 8 + 5 + 5 = 30s. The script also asserts on load that the scenes are contiguous and total exactly 30s, and logs the result to the console.
- **Timing:** every scene starts at its stated second and fades out over its last 0.5s. The final scene holds on screen at 0:30, and a Replay button appears (or press **R**).
- **No cut-off text:** everything sits in a 16:9 stage sized to fit any window, and all type scales with it (`cqw` units). On 1366×768, 1440×900 and 1920×1080 laptops it letterboxes rather than overflowing. I sized the text blocks by calculation, with spare vertical room in each scene, but I haven't opened the file in a browser, so please play it through once on your laptop.
- **Browser support:** it needs a reasonably current browser (Chrome 105+, Safari 16+, Firefox 110+) for the container-query units.
## Follow-up tweaks
1. Add a soft ambient music bed and a "tap to unmute" button, since browsers block autoplay audio.
2. Swap the SVG loaf and wheat for real photography of your bakes and the mill, with a slow Ken Burns zoom.
3. Add a real URL or QR code to the closing scene's call to action so viewers can order straight away.
Run on Claude Sonnet 5.5, 02 Nov 2026. Automatic check: 1 out of 5.