What happened when we ran it
We sent
You are a front-end developer who specialises in motion graphics written as code.
Task: Build a short animated explainer as one self-contained HTML file, using only plain HTML, inline SVG and CSS animations with a small amount of vanilla JavaScript and no external images, that shows how a customer's online order travels from the checkout button through payment, the warehouse and the courier to their front door, taking about 30 seconds in total to small shop owners who have never run an online store and want to understand what happens after a sale.
Steps:
1. Plan the animation as a timeline of 4 to 6 scenes. For each scene give a start time, a duration and one sentence on what appears and moves.
2. Write the code around a single master timeline so scenes can be reordered, and keep colours, fonts and timings as constants at the top of the file.
3. Style it to match a calm, friendly look with a cream background, rounded shapes, a teal and orange palette and a clean sans-serif font, apply easing to every movement, and keep on-screen captions to eight words or fewer.
4. Make it loop cleanly, scale to any window size, and include a replay button.
5. Re-read the code for mistakes, such as undefined variables or missing closing tags, and fix them before replying.
Output: First the scene timeline as a short table, then the complete HTML in one code block, then two lines explaining how to change the speed and the colours.
The model replied
## Scene timeline
| # | Start | Duration | Scene | What appears and moves |
|---|-------|----------|-------|------------------------|
| 1 | 0.0 s | 4.5 s | Checkout | A shop page slides up, a cursor glides to **Pay now**, the button presses and an order ticket arcs out. |
| 2 | 4.5 s | 6.5 s | Payment | A card travels to a secure shield, the padlock closes and a tick draws, then a coin rolls into the shop. |
| 3 | 11.0 s | 7.0 s | Packing | The order slip appears, the mug lifts off the shelf into a box, the flaps close, tape and label appear, and the box slides out. |
| 4 | 18.0 s | 6.5 s | Courier | A van drives in, the parcel slides in, and the van drives off past scrolling hills and trees. |
| 5 | 24.5 s | 5.5 s | Delivered | A courier walks up, tosses the parcel by the door and rings the bell, then walks off as the door opens and a tick badge pops. |
The scenes total 30 s, plus a 0.8 s pause before the loop restarts.
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>From checkout to front door</title>
<script>
/* ==================================================================
1. CONSTANTS: colours, font, easing and timings. Edit here only.
================================================================== */
const THEME = {
cream: '#FFF4E0', // page background
paper: '#FFFBF3', // the "stage" card
white: '#FFFFFF',
ink: '#1F4B50', // text and dark details
teal: '#2A9D8F',
tealDark: '#1D7A72',
mint: '#D3EEE8',
grass: '#A8D8CD',
orange: '#E06F1F',
orangeDark: '#C45A12',
peach: '#F6B77A', // cardboard
peachDark: '#E39A55',
sand: '#F3E3C3',
sandDark: '#E3CFA6',
skin: '#F3C7A2',
road: '#46636A',
wall: '#FBEBD3' // warehouse wall
};
const FONT = '"Nunito", ui-rounded, "Segoe UI", system-ui, -apple-system, "Helvetica Neue", Arial, sans-serif';
const EASING = {
inOut: 'cubic-bezier(.65,0,.35,1)', // default for every movement
out: 'cubic-bezier(.16,1,.3,1)', // arrivals
pop: 'cubic-bezier(.34,1.56,.64,1)' // gentle overshoot
};
const TIMING = {
speed: 1, // 1 = normal, 0.8 = slower, 1.25 = faster
gap: 0.8, // pause (timeline seconds) before the loop restarts
// Order here = order on screen. Each id matches an element id="scene-<id>".
scenes: [
{ id: 'checkout', label: 'Checkout', dur: 4.5 },
{ id: 'payment', label: 'Payment', dur: 6.5 },
{ id: 'warehouse', label: 'Packing', dur: 7.0 },
{ id: 'courier', label: 'Courier', dur: 6.5 },
{ id: 'doorstep', label: 'Delivered', dur: 5.5 }
]
};
/* Push the constants into CSS custom properties */
(function applyTheme() {
const root = document.documentElement;
Object.keys(THEME).forEach(function (k) { root.style.setProperty('--' + k, THEME[k]); });
root.style.setProperty('--font', FONT);
root.style.setProperty('--ease', EASING.inOut);
root.style.setProperty('--ease-out', EASING.out);
root.style.setProperty('--ease-pop', EASING.pop);
root.style.setProperty('--u', (1 / TIMING.speed) + 's'); // one timeline second in real time
})();
</script>
<style>
*{box-sizing:border-box}
html,body{height:100%;margin:0;background:var(--cream);font-family:var(--font);overflow:hidden}
#wrap{position:fixed;inset:0;display:flex;align-items:center;justify-content:center}
#movie{width:100%;height:100%;display:block}
text{font-family:var(--font)}
/* Replay button */
#replay{position:fixed;top:max(12px,2vmin);right:max(12px,2vmin);z-index:5;display:inline-flex;align-items:center;gap:.5em;
padding:.65em 1.2em;border:0;border-radius:999px;background:var(--ink);color:var(--cream);
font:700 clamp(14px,2.2vmin,18px) var(--font);cursor:pointer;box-shadow:0 4px 14px rgba(31,75,80,.25);
transition:transform .3s var(--ease-pop),background .3s var(--ease)}
#replay:hover{transform:scale(1.06);background:var(--tealDark)}
#replay:focus-visible{outline:3px solid var(--orange);outline-offset:3px}
/* Colour helpers (values come from THEME) */
.f-ink{fill:var(--ink)}.f-teal{fill:var(--teal)}.f-tealDark{fill:var(--tealDark)}.f-mint{fill:var(--mint)}
.f-orange{fill:var(--orange)}.f-orangeDark{fill:var(--orangeDark)}.f-peach{fill:var(--peach)}.f-peachDark{fill:var(--peachDark)}
.f-sand{fill:var(--sand)}.f-sandDark{fill:var(--sandDark)}.f-white{fill:var(--white)}.f-paper{fill:var(--paper)}
.f-cream{fill:var(--cream)}.f-skin{fill:var(--skin)}.f-grass{fill:var(--grass)}.f-road{fill:var(--road)}.f-wall{fill:var(--wall)}
.s-ink{stroke:var(--ink)}.s-teal{stroke:var(--teal)}.s-tealDark{stroke:var(--tealDark)}.s-orange{stroke:var(--orange)}
.s-white{stroke:var(--white)}.s-sand{stroke:var(--sand)}.s-sandDark{stroke:var(--sandDark)}
/* Text styles */
.cap{font-size:42px;font-weight:700;fill:var(--ink);text-anchor:middle}
.lbl{font-size:26px;font-weight:700;fill:var(--ink);text-anchor:middle}
.step{font-size:20px;font-weight:700;fill:var(--ink);text-anchor:middle;opacity:.65}
.prod{font-size:28px;font-weight:700;fill:var(--ink)}
.price{font-size:46px;font-weight:800;fill:var(--orangeDark)}
.btn{font-size:28px;font-weight:800;fill:var(--white)}
.url{font-size:16px;font-weight:600;fill:var(--white)}
.hdr{font-size:16px;font-weight:800;fill:var(--white);text-anchor:middle;letter-spacing:.08em}
.coin{font-size:30px;font-weight:800;fill:var(--orangeDark)}
.plus{font-size:40px;font-weight:800;fill:var(--tealDark);text-anchor:middle}
/* Progress dots (built by JS) */
.track{stroke:var(--sandDark);stroke-width:4;stroke-linecap:round}
.dot{fill:var(--sandDark);transform-box:fill-box;transform-origin:center;
transition:fill .4s var(--ease),transform .5s var(--ease-pop)}
.dot.done{fill:var(--teal)}
.dot.active{fill:var(--orange);transform:scale(1.5)}
/* ==================================================================
2. ANIMATION ENGINE
A scene's animations only run while the scene has the class "play".
Every animated element gets class "an" plus ONE motion class, and
sets its own start (--at) and length (--for) in timeline seconds,
counted from the start of its scene.
================================================================== */
.scene{visibility:hidden}
.scene.play{visibility:visible;animation:sceneFade calc(var(--d) * var(--u)) var(--ease) both}
@keyframes sceneFade{0%{opacity:0}6%{opacity:1}94%{opacity:1}100%{opacity:0}}
/* custom properties inherit, so reset them on every animated element */
.an{transform-box:fill-box;transform-origin:center;
--at:initial;--for:initial;--n:initial;--fx:initial;--fy:initial;--fs:initial;--tx:initial;--ty:initial;
--rot:initial;--ps:initial;--rs:initial;--len:initial;--apex:initial;--dx:initial;--dy1:initial;--dy2:initial}
.dash{stroke-dasharray:var(--len,60)}
.scene.play .an{
animation-duration:calc(var(--for,1) * var(--u));
animation-delay:calc(var(--at,0) * var(--u));
animation-timing-function:var(--ease);
animation-fill-mode:both;
animation-iteration-count:var(--n,1)}
.scene.play .out{animation-timing-function:var(--ease-out)}
.scene.play .pop{animation-timing-function:var(--ease-pop)}
.scene.play .appear{animation-name:appear}
.scene.play .go{animation-name:go}
.scene.play .press{animation-name:press}
.scene.play .ripple{animation-name:ripple}
.scene.play .spin{animation-name:spin}
.scene.play .draw{animation-name:draw}
.scene.play .close{animation-name:close}
.scene.play .grow{animation-name:grow}
.scene.play .toss{animation-name:toss}
.scene.play .bob{animation-name:bob}
.scene.play .flip{animation-name:flip}
.scene.play .squash{animation-name:squash}
.scene.play .show{animation-name:show}
.scene.play .hide{animation-name:hide}
.scene.play .shrink{animation-name:shrink}
.scene.play .openDoor{animation-name:openDoor}
.scene.play .mugTravel{animation-name:mugTravel}
@keyframes appear{from{opacity:0;transform:translate(var(--fx,0px),var(--fy,0px)) scale(var(--fs,1))}to{opacity:1;transform:none}}
@keyframes go{from{transform:translate(var(--fx,0px),var(--fy,0px))}to{transform:translate(var(--tx,0px),var(--ty,0px))}}
@keyframes press{0%,100%{transform:scale(1)}45%{transform:scale(var(--ps,.92))}}
@keyframes ripple{0%{opacity:0;transform:scale(.3)}15%{opacity:.75}100%{opacity:0;transform:scale(var(--rs,2.6))}}
@keyframes spin{from{transform:rotate(0deg)}to{transform:rotate(var(--rot,360deg))}}
@keyframes draw{from{stroke-dashoffset:var(--len,60)}to{stroke-dashoffset:0}}
@keyframes close{from{transform:rotate(var(--rot,-100deg))}to{transform:rotate(0deg)}}
@keyframes grow{from{transform:scaleY(0)}to{transform:scaleY(1)}}
@keyframes toss{
0%{transform:translate(0px,0px);animation-timing-function:ease-out}
50%{transform:translate(calc(var(--tx) / 2),var(--apex));animation-timing-function:ease-in}
100%{transform:translate(var(--tx),var(--ty))}}
@keyframes bob{0%,100%{transform:translateY(0)}50%{transform:translateY(-7px)}}
@keyframes flip{from{transform:scaleX(1)}to{transform:scaleX(-1)}}
@keyframes squash{0%,100%{transform:scale(1,1)}40%{transform:scale(1.14,.82)}}
@keyframes show{from{opacity:0}to{opacity:1}}
@keyframes hide{from{opacity:1}to{opacity:0}}
@keyframes shrink{from{opacity:1;transform:scale(1)}to{opacity:0;transform:scale(.3)}}
@keyframes openDoor{from{transform:scaleX(1)}to{transform:scaleX(.16)}}
@keyframes mugTravel{
0%{transform:translate(0px,0px)}
55%{transform:translate(var(--dx),var(--dy1))}
100%{transform:translate(var(--dx),var(--dy2))}}
</style>
</head>
<body>
<main id="wrap">
<svg id="movie" viewBox="0 0 1280 720" preserveAspectRatio="xMidYMid meet" role="img"
aria-label="Animated explainer: an online order goes from checkout to payment, packing, courier and the customer's front door.">
<title>How an online order reaches the customer</title>
<defs>
<clipPath id="stageClip"><rect x="60" y="60" width="1160" height="500" rx="40"/></clipPath>
<clipPath id="doorClip"><rect x="885" y="336" width="70" height="134" rx="10"/></clipPath>
<filter id="soft" x="-5%" y="-10%" width="110%" height="125%">
<feDropShadow dx="0" dy="10" stdDeviation="14" style="flood-color:var(--ink);flood-opacity:.14"/>
</filter>
<!-- the product -->
<g id="mug">
<path d="M22,-10 h8 a10,10 0 0 1 0,20 h-8" class="s-teal" fill="none" stroke-width="7" stroke-linecap="round"/>
<rect x="-22" y="-26" width="44" height="52" rx="11" class="f-teal"/>
<rect x="-22" y="-6" width="44" height="9" class="f-orange"/>
</g>
<!-- a parcel, 84 x 64 -->
<g id="parcel">
<rect x="-42" y="-32" width="84" height="64" rx="9" class="f-peach"/>
<rect x="-8" y="-32" width="16" height="64" class="f-orange"/>
<rect x="14" y="4" width="20" height="16" rx="3" class="f-white"/>
</g>
</defs>
<!-- stage -->
<rect x="60" y="60" width="1160" height="500" rx="40" class="f-paper" filter="url(#soft)"/>
<g clip-path="url(#stageClip)">
<!-- ============ SCENE: CHECKOUT ============ -->
<g id="scene-checkout" class="scene">
<g transform="translate(640,310)">
<g class="an appear out" style="--at:0;--for:.9;--fy:40px">
<rect x="-300" y="-200" width="600" height="400" rx="28" class="f-white s-sandDark" stroke-width="3"/>
<path d="M-300,-152 V-172 A28,28 0 0 1 -272,-200 H272 A28,28 0 0 1 300,-172 V-152 Z" class="f-teal"/>
<circle cx="-268" cy="-176" r="6" class="f-cream"/>
<circle cx="-248" cy="-176" r="6" class="f-cream"/>
<circle cx="-228" cy="-176" r="6" class="f-cream"/>
<rect x="-170" y="-188" width="340" height="24" rx="12" class="f-white" opacity=".28"/>
<text x="0" y="-171" text-anchor="middle" class="url">yourshop.com/checkout</text>
<rect x="-264" y="-124" width="236" height="250" rx="22" class="f-mint"/>
<circle cx="-146" cy="-10" r="82" class="f-white" opacity=".65"/>
<ellipse cx="-146" cy="68" rx="58" ry="9" class="f-ink" opacity=".12"/>
<use href="#mug" transform="translate(-146,-6) scale(2.4)"/>
<text x="20" y="-90" class="prod">Handmade mug</text>
<text x="20" y="-34" class="price">$24</text>
<rect x="20" y="0" width="230" height="12" rx="6" class="f-sand"/>
<rect x="20" y="26" width="170" height="12" rx="6" class="f-sand"/>
<g class="an press" style="--at:2.2;--for:.4">
<rect x="20" y="80" width="240" height="64" rx="32" class="f-orange"/>
<text x="140" y="122" text-anchor="middle" class="btn">Pay now</text>
</g>
</g>
</g>
<!-- click ripple on the button -->
<g transform="translate(780,422)">
<circle r="40" fill="none" class="s-orange an ripple" stroke-width="6" style="--at:2.2;--for:.9;--rs:3"/>
</g>
<!-- order ticket pops out and arcs away -->
<g transform="translate(780,422)">
<g class="an toss" style="--at:2.55;--for:1.3;--tx:260px;--ty:-172px;--apex:-210px">
<g class="an appear pop" style="--at:2.55;--for:.5;--fs:.3">
<rect x="-62" y="-44" width="124" height="88" rx="16" class="f-white s-teal" stroke-width="5"/>
<rect x="-38" y="-24" width="76" height="9" rx="4.5" class="f-sand"/>
<rect x="-38" y="-6" width="52" height="9" rx="4.5" class="f-sand"/>
<path d="M-12,24 L-4,32 L14,14" class="s-teal dash an draw" fill="none" stroke-width="6"
stroke-linecap="round" stroke-linejoin="round" style="--at:3.1;--for:.5;--len:50"/>
</g>
</g>
</g>
<!-- cursor -->
<g transform="translate(790,432)">
<g class="an go" style="--at:.6;--for:1.5;--fx:240px;--fy:-170px">
<g class="an press" style="--at:2.2;--for:.35;--ps:.8;transform-origin:0% 0%">
<path d="M0,0 L0,34 L9,26 L15,40 L21,37 L15,24 L27,24 Z" class="f-ink s-white" stroke-width="2.5" stroke-linejoin="round"/>
</g>
</g>
</g>
<text class="cap an appear" x="640" y="612" style="--at:.3;--for:.7;--fy:16px">A customer taps Pay now.</text>
</g>
<!-- ============ SCENE: PAYMENT ============ -->
<g id="scene-payment" class="scene">
<!-- dotted track -->
<g class="an appear" style="--at:.2;--for:.8">
<line x1="320" y1="320" x2="950" y2="320" class="s-sandDark" stroke-width="8" stroke-linecap="round" stroke-dasharray="1 20"/>
</g>
<!-- secure shield -->
<g transform="translate(640,310)">
<g class="an appear pop" style="--at:.2;--for:.7;--fs:.6">
<g class="an press" style="--at:2.5;--for:.5;--ps:1.1">
<path d="M0,-100 L88,-66 V8 C88,62 48,98 0,120 C-48,98 -88,62 -88,8 V-66 Z" class="f-teal s-tealDark" stroke-width="6" stroke-linejoin="round"/>
<rect x="-30" y="-8" width="60" height="48" rx="10" class="f-white"/>
<g class="an go" style="--at:2.6;--for:.5;--fy:-16px">
<path d="M-18,-8 V-26 a18,18 0 0 1 36,0 V-8" class="s-white" fill="none" stroke-width="9" stroke-linecap="round"/>
</g>
<path d="M-14,16 L-4,27 L15,5" class="s-teal dash an draw" fill="none" stroke-width="7"
stroke-linecap="round" stroke-linejoin="round" style="--at:3.3;--for:.5;--len:60"/>
</g>
</g>
</g>
<!-- the shop -->
<g transform="translate(1040,325)">
<g class="an appear pop" style="--at:.35;--for:.7;--fs:.6">
<g class="an press" style="--at:5.35;--for:.5;--ps:1.07">
<rect x="-75" y="-60" width="150" height="130" rx="14" class="f-white s-teal" stroke-width="5"/>
<rect x="-90" y="-104" width="180" height="46" rx="16" class="f-orange"/>
<circle cx="-68" cy="-58" r="23" class="f-orange"/>
<circle cx="-23" cy="-58" r="23" class="f-orangeDark"/>
<circle cx="23" cy="-58" r="23" class="f-orange"/>
<circle cx="68" cy="-58" r="23" class="f-orangeDark"/>
<rect x="-18" y="0" width="36" height="70" rx="8" class="f-teal"/>
<rect x="-62" y="-30" width="34" height="34" rx="7" class="f-mint"/>
<rect x="28" y="-30" width="34" height="34" rx="7" class="f-mint"/>
</g>
</g>
</g>
<!-- customer's card flies in and is absorbed -->
<g transform="translate(220,320)">
<g class="an appear" style="--at:0;--for:.7;--fx:-40px">
<g class="an go" style="--at:.9;--for:1.5;--tx:420px">
<g class="an shrink" style="--at:2.4;--for:.5">
<rect x="-70" y="-46" width="140" height="92" rx="14" class="f-orange"/>
<rect x="-70" y="-24" width="140" height="18" class="f-ink"/>
<rect x="-50" y="8" width="30" height="20" rx="5" class="f-sand"/>
<rect x="-8" y="14" width="56" height="8" rx="4" class="f-white" opacity=".7"/>
</g>
</g>
</g>
</g>
<!-- coin travels to the shop -->
<g transform="translate(640,320)">
<g class="an go" style="--at:4;--for:1.4;--tx:378px">
<g class="an appear pop" style="--at:4;--for:.4;--fs:.3">
<g class="an shrink" style="--at:5.2;--for:.3">
<circle r="26" class="f-peach s-orange" stroke-width="5"/>
<text y="10" text-anchor="middle" class="coin">$</text>
</g>
</g>
</g>
</g>
<text x="1040" y="196" class="plus an appear" style="--at:5.4;--for:.6;--fy:18px">+$24</text>
<text x="220" y="478" class="lbl an appear" style="--at:.4;--for:.6">Card</text>
<text x="640" y="478" class="lbl an appear" style="--at:.4;--for:.6">Secure check</text>
<text x="1040" y="478" class="lbl an appear" style="--at:.4;--for:.6">Your shop</text>
<text class="cap an appear" x="640" y="612" style="--at:.3;--for:.7;--fy:16px">Payment is checked securely.</text>
</g>
<!-- ============ SCENE: WAREHOUSE / PACKING ============ -->
<g id="scene-warehouse" class="scene">
<rect x="60" y="60" width="1160" height="460" class="f-wall"/>
<rect x="60" y="520" width="1160" height="40" class="f-mint"/>
<!-- shelf -->
<g class="an appear" style="--at:0;--for:.8;--fx:-40px">
<rect x="106" y="108" width="14" height="414" rx="7" class="f-tealDark"/>
<rect x="520" y="108" width="14" height="414" rx="7" class="f-tealDark"/>
<rect x="106" y="108" width="428" height="14" rx="7" class="f-tealDark"/>
<rect x="106" y="232" width="428" height="14" rx="7" class="f-tealDark"/>
<rect x="106" y="352" width="428" height="14" rx="7" class="f-tealDark"/>
<rect x="106" y="472" width="428" height="14" rx="7" class="f-tealDark"/>
<!-- top row -->
<rect x="150" y="172" width="70" height="60" rx="8" class="f-peach"/>
<rect x="240" y="192" width="56" height="40" rx="8" class="f-mint"/>
<rect x="330" y="162" width="60" height="70" rx="8" class="f-teal" opacity=".85"/>
<rect x="410" y="182" width="80" height="50" rx="8" class="f-peachDark"/>
<!-- middle row (the mug lives at x=330) -->
<rect x="150" y="292" width="64" height="60" rx="8" class="f-peachDark"/>
<rect x="240" y="312" width="50" height="40" rx="8" class="f-mint"/>
<rect x="410" y="302" width="70" height="50" rx="8" class="f-peach"/>
<!-- bottom row -->
<rect x="150" y="412" width="80" height="60" rx="8" class="f-peach"/>
<rect x="260" y="432" width="60" height="40" rx="8" class="f-mint"/>
<rect x="350" y="402" width="70" height="70" rx="8" class="f-teal" opacity=".85"/>
<rect x="440" y="422" width="50" height="50" rx="8" class="f-peachDark"/>
</g>
<!-- highlight ring on the picked item -->
<g transform="translate(330,324)">
<circle r="44" fill="none" class="s-orange an ripple" stroke-width="6" style="--at:.9;--for:.7;--n:2;--rs:1.6"/>
</g>
<!-- order slip -->
<g transform="translate(1040,200) rotate(4)">
<g class="an appear pop" style="--at:.2;--for:.7;--fy:-30px;--fs:.8">
<rect x="-72" y="-84" width="144" height="176" rx="14" class="f-white s-teal" stroke-width="4"/>
<rect x="-72" y="-84" width="144" height="30" rx="14" class="f-teal"/>
<rect x="-72" y="-70" width="144" height="16" class="f-teal"/>
<text x="0" y="-63" class="hdr">ORDER</text>
<use href="#mug" transform="translate(-40,-18) scale(.55)"/>
<rect x="-14" y="-24" width="48" height="10" rx="5" class="f-sand"/>
<path d="M40,-14 L47,-7 L60,-22" class="s-teal dash an draw" fill="none" stroke-width="5"
stroke-linecap="round" stroke-linejoin="round" style="--at:3.75;--for:.4;--len:40"/>
<rect x="-52" y="22" width="104" height="9" rx="4.5" class="f-sand"/>
<rect x="-52" y="40" width="80" height="9" rx="4.5" class="f-sand"/>
<rect x="-52" y="58" width="92" height="9" rx="4.5" class="f-sand"/>
</g>
</g>
<!-- packing table -->
<rect x="560" y="420" width="520" height="18" rx="9" class="f-tealDark"/>
<rect x="590" y="438" width="16" height="82" class="f-tealDark"/>
<rect x="1034" y="438" width="16" height="82" class="f-tealDark"/>
<!-- box + mug (slides away as one group at the end) -->
<g transform="translate(780,420)">
<g class="an go" style="--at:5.6;--for:1;--tx:220px">
<!-- mug: shelf -> above box -> down into the box (hidden behind the box front) -->
<g transform="translate(-450,-96)">
<g class="an mugTravel" style="--at:1.7;--for:2.2;--dx:450px;--dy1:-174px;--dy2:26px">
<use href="#mug"/>
</g>
</g>
<g class="an appear pop" style="--at:.3;--for:.7;--fs:.6;--fy:20px">
<rect x="-80" y="-110" width="160" height="110" rx="10" class="f-peach"/>
<!-- flaps -->
<g class="an close" style="--at:3.9;--for:.7;--rot:-105deg;transform-origin:0% 100%">
<rect x="-80" y="-122" width="80" height="12" rx="4" class="f-peachDark"/>
</g>
<g class="an close" style="--at:3.9;--for:.7;--rot:105deg;transform-origin:100% 100%">
<rect x="0" y="-122" width="80" height="12" rx="4" class="f-peachDark"/>
</g>
<!-- tape -->
<g class="an grow" style="--at:4.6;--for:.45;transform-origin:50% 0%">
<rect x="-14" y="-122" width="28" height="76" rx="3" class="f-orange"/>
</g>
<!-- label -->
<g transform="translate(46,-34)">
<g class="an appear pop" style="--at:5.05;--for:.45;--fs:.3">
<rect x="-25" y="-18" width="50" height="36" rx="5" class="f-white"/>
<rect x="-17" y="-10" width="3" height="14" class="f-ink"/>
<rect x="-11" y="-10" width="2" height="14" class="f-ink"/>
<rect x="-6" y="-10" width="4" height="14" class="f-ink"/>
<rect x="1" y="-10" width="2" height="14" class="f-ink"/>
<rect x="6" y="-10" width="3" height="14" class="f-ink"/>
<rect x="12" y="-10" width="2" height="14" class="f-ink"/>
<rect x="-17" y="8" width="34" height="4" rx="2" class="f-sandDark"/>
</g>
</g>
</g>
</g>
</g>
<text class="cap an appear" x="640" y="612" style="--at:.3;--for:.7;--fy:16px">You pick and pack the order.</text>
</g>
<!-- ============ SCENE: COURIER ============ -->
<g id="scene-courier" class="scene">
<!-- far hills -->
<g class="an go" style="--at:3.3;--for:2.9;--tx:-160px">
<ellipse cx="250" cy="470" rx="380" ry="110" class="f-mint"/>
<ellipse cx="900" cy="485" rx="420" ry="120" class="f-mint" opacity=".75"/>
<ellipse cx="1500" cy="470" rx="400" ry="115" class="f-mint"/>
</g>
<!-- clouds -->
<g class="an go" style="--at:3.3;--for:2.9;--tx:-260px">
<g transform="translate(300,150)" opacity=".8">
<ellipse rx="64" ry="22" class="f-mint"/><circle cx="-22" cy="-16" r="26" class="f-mint"/><circle cx="20" cy="-22" r="30" class="f-mint"/>
</g>
<g transform="translate(820,210)" opacity=".8">
<ellipse rx="54" ry="18" class="f-mint"/><circle cx="-18" cy="-14" r="22" class="f-mint"/><circle cx="16" cy="-18" r="26" class="f-mint"/>
</g>
<g transform="translate(1150,135)" opacity=".8">
<ellipse rx="60" ry="20" class="f-mint"/><circle cx="-20" cy="-15" r="24" class="f-mint"/><circle cx="18" cy="-20" r="28" class="f-mint"/>
</g>
</g>
<rect x="60" y="455" width="1160" height="105" class="f-grass"/>
<!-- trees (scroll left while the van drives) -->
<g class="an go" style="--at:3.3;--for:2.9;--tx:-700px">
<g transform="translate(140,472) scale(1.1)"><rect x="-7" y="-58" width="14" height="58" rx="5" class="f-peachDark"/><circle cy="-86" r="36" class="f-teal"/><circle cx="-20" cy="-68" r="24" class="f-tealDark" opacity=".55"/></g>
<g transform="translate(470,472)"><rect x="-7" y="-58" width="14" height="58" rx="5" class="f-peachDark"/><circle cy="-86" r="36" class="f-teal"/><circle cx="-20" cy="-68" r="24" class="f-tealDark" opacity=".55"/></g>
<g transform="translate(820,472) scale(1.2)"><rect x="-7" y="-58" width="14" height="58" rx="5" class="f-peachDark"/><circle cy="-86" r="36" class="f-teal"/><circle cx="-20" cy="-68" r="24" class="f-tealDark" opacity=".55"/></g>
<g transform="translate(1100,472)"><rect x="-7" y="-58" width="14" height="58" rx="5" class="f-peachDark"/><circle cy="-86" r="36" class="f-teal"/><circle cx="-20" cy="-68" r="24" class="f-tealDark" opacity=".55"/></g>
<g transform="translate(1450,472) scale(1.1)"><rect x="-7" y="-58" width="14" height="58" rx="5" class="f-peachDark"/><circle cy="-86" r="36" class="f-teal"/><circle cx="-20" cy="-68" r="24" class="f-tealDark" opacity=".55"/></g>
<g transform="translate(1800,472)"><rect x="-7" y="-58" width="14" height="58" rx="5" class="f-peachDark"/><circle cy="-86" r="36" class="f-teal"/><circle cx="-20" cy="-68" r="24" class="f-tealDark" opacity=".55"/></g>
</g>
<!-- road -->
<rect x="60" y="490" width="1160" height="56" class="f-road"/>
<g class="an go" style="--at:3.3;--for:2.9;--tx:-640px">
<line x1="0" y1="518" x2="1920" y2="518" class="s-sand" stroke-width="5" stroke-dasharray="36 28"/>
</g>
<!-- van -->
<g transform="translate(640,525)">
<g class="an go out" style="--at:0;--for:1.7;--fx:-760px">
<g class="an go" style="--at:3.3;--for:2.9;--tx:900px">
<!-- speed lines behind the van -->
<g class="an appear" style="--at:3.4;--for:.6;--fx:24px">
<path d="M-128,-90 H-170 M-128,-66 H-190 M-128,-42 H-160" class="s-tealDark" fill="none" stroke-width="6" stroke-linecap="round" opacity=".35"/>
</g>
<!-- parcel: drops in beside the van, then slides in behind the cargo box -->
<g transform="translate(-190,-62)">
<g class="an appear pop" style="--at:1.7;--for:.5;--fy:-70px">
<g class="an go" style="--at:2.4;--for:.8;--tx:175px">
<use href="#parcel"/>
</g>
</g>
</g>
<!-- van body -->
<rect x="-110" y="-120" width="150" height="106" rx="14" class="f-orange"/>
<path d="M36,-96 H74 Q92,-96 102,-72 L116,-42 Q120,-34 120,-24 V-14 H36 Z" class="f-orange"/>
<path d="M48,-84 H72 Q83,-84 89,-68 L96,-52 H48 Z" class="f-mint"/>
<circle cx="62" cy="-66" r="11" class="f-skin"/>
<path d="M51,-68 a11,11 0 0 1 22,0 z" class="f-teal"/>
<rect x="-110" y="-48" width="230" height="9" class="f-tealDark"/>
<circle cx="-35" cy="-84" r="22" class="f-white"/>
<use href="#parcel" transform="translate(-35,-84) scale(.46)"/>
<rect x="100" y="-26" width="24" height="12" rx="5" class="f-ink"/>
<circle cx="114" cy="-38" r="6" class="f-sand"/>
<!-- wheels -->
<g transform="translate(-62,-6)">
<g class="an spin out" style="--at:0;--for:1.7;--rot:1800deg">
<g class="an spin" style="--at:3.3;--for:2.9;--rot:2150deg">
<circle r="24" class="f-ink"/><circle r="10" class="f-cream"/>
<line x1="0" y1="0" x2="0" y2="-17" class="s-cream" stroke="var(--cream)" stroke-width="5" stroke-linecap="round"/>
</g>
</g>
</g>
<g transform="translate(74,-6)">
<g class="an spin out" style="--at:0;--for:1.7;--rot:1800deg">
<g class="an spin" style="--at:3.3;--for:2.9;--rot:2150deg">
<circle r="24" class="f-ink"/><circle r="10" class="f-cream"/>
<line x1="0" y1="0" x2="0" y2="-17" stroke="var(--cream)" stroke-width="5" stroke-linecap="round"/>
</g>
</g>
</g>
</g>
</g>
</g>
<text class="cap an appear" x="640" y="612" style="--at:.3;--for:.7;--fy:16px">A courier collects the parcel.</text>
</g>
<!-- ============ SCENE: DOORSTEP ============ -->
<g id="scene-doorstep" class="scene">
<ellipse cx="300" cy="480" rx="430" ry="150" class="f-mint"/>
<ellipse cx="1000" cy="490" rx="380" ry="120" class="f-mint" opacity=".7"/>
<rect x="60" y="470" width="1160" height="90" class="f-grass"/>
<rect x="60" y="474" width="1160" height="28" class="f-sand"/>
<!-- trees -->
<g class="an appear" style="--at:0;--for:.8">
<g transform="translate(170,474)"><rect x="-7" y="-58" width="14" height="58" rx="5" class="f-peachDark"/><circle cy="-86" r="36" class="f-teal"/><circle cx="-20" cy="-68" r="24" class="f-tealDark" opacity=".55"/></g>
<g transform="translate(340,474) scale(.8)"><rect x="-7" y="-58" width="14" height="58" rx="5" class="f-peachDark"/><circle cy="-86" r="36" class="f-teal"/><circle cx="-20" cy="-68" r="24" class="f-tealDark" opacity=".55"/></g>
<g transform="translate(1160,474)"><rect x="-7" y="-58" width="14" height="58" rx="5" class="f-peachDark"/><circle cy="-86" r="36" class="f-teal"/><circle cx="-20" cy="-68" r="24" class="f-tealDark" opacity=".55"/></g>
</g>
<!-- house -->
<g class="an appear out" style="--at:0;--for:.9;--fy:30px">
<rect x="990" y="160" width="28" height="70" class="f-orangeDark"/>
<rect x="800" y="250" width="240" height="220" rx="8" class="f-white s-sandDark" stroke-width="3"/>
<polygon points="770,262 920,140 1070,262" class="f-orange s-orange" stroke-width="14" stroke-linejoin="round"/>
<rect x="822" y="290" width="44" height="44" rx="8" class="f-mint"/>
<rect x="974" y="290" width="44" height="44" rx="8" class="f-mint"/>
<rect x="885" y="336" width="70" height="134" rx="10" class="f-ink"/>
<!-- customer waiting behind the door -->
<g clip-path="url(#doorClip)">
<g class="an appear" style="--at:3.7;--for:.5;--fy:18px">
<path d="M893,470 V440 Q893,416 920,416 Q947,416 947,440 V470 Z" class="f-orange"/>
<circle cx="920" cy="392" r="17" class="f-skin"/>
<circle cx="914" cy="389" r="2" class="f-ink"/>
<circle cx="926" cy="389" r="2" class="f-ink"/>
<path d="M912,397 Q920,405 928,397" class="s-ink" fill="none" stroke-width="2.5" stroke-linecap="round"/>
</g>
</g>
<!-- door -->
<g class="an openDoor" style="--at:3.4;--for:.6;transform-origin:0% 50%">
<rect x="885" y="336" width="70" height="134" rx="10" class="f-teal"/>
<rect x="895" y="346" width="50" height="48" rx="6" fill="none" class="s-white" stroke-width="3" opacity=".4"/>
<circle cx="944" cy="408" r="5" class="f-sand"/>
</g>
<rect x="870" y="470" width="100" height="10" rx="5" class="f-sandDark"/>
<!-- doorbell -->
<circle cx="973" cy="405" r="6" class="f-orange"/>
<g transform="translate(973,405)">
<circle r="14" fill="none" class="s-orange an ripple" stroke-width="3" style="--at:2.9;--for:.55;--n:2;--rs:2.4"/>
</g>
</g>
<!-- parcel dropped by the courier -->
<g transform="translate(744,430)">
<g class="an toss" style="--at:1.9;--for:.7;--tx:92px;--ty:30px;--apex:-70px">
<g class="an show" style="--at:1.9;--for:.01">
<g class="an squash" style="--at:2.6;--for:.45;transform-origin:50% 100%">
<use href="#parcel" transform="scale(.8)"/>
</g>
</g>
</g>
</g>
<!-- courier: walks in, drops parcel, turns, walks away -->
<g transform="translate(700,500)">
<g class="an go" style="--at:3.3;--for:1.4;--tx:-780px">
<g class="an bob" style="--at:3.3;--for:.7;--n:2">
<g class="an go out" style="--at:.3;--for:1.4;--fx:-780px">
<g class="an bob" style="--at:.3;--for:.7;--n:2">
<g class="an flip" style="--at:3;--for:.35">
<rect x="-80" y="-150" width="160" height="150" fill="none"/>
<rect x="-19" y="-38" width="15" height="38" rx="6" class="f-ink"/>
<rect x="4" y="-38" width="15" height="38" rx="6" class="f-ink"/>
<rect x="-28" y="-102" width="56" height="72" rx="20" class="f-teal"/>
<circle cx="0" cy="-124" r="22" class="f-skin"/>
<path d="M-23,-128 A23,23 0 0 1 23,-128 Z" class="f-orange"/>
<rect x="4" y="-132" width="28" height="8" rx="4" class="f-orange"/>
<circle cx="9" cy="-120" r="2.6" class="f-ink"/>
<path d="M3,-112 Q10,-107 17,-113" class="s-ink" fill="none" stroke-width="2.5" stroke-linecap="round"/>
<g class="an hide" style="--at:1.9;--for:.01">
<use href="#parcel" transform="translate(44,-70) scale(.8)"/>
</g>
<path d="M4,-92 L28,-66" class="s-teal" fill="none" stroke-width="14" stroke-linecap="round"/>
<circle cx="30" cy="-64" r="7" class="f-skin"/>
</g>
</g>
</g>
</g>
</g>
</g>
<!-- delivered badge -->
<g transform="translate(1120,200)">
<g class="an appear pop" style="--at:4.4;--for:.5;--fs:.2">
<circle r="46" class="f-teal s-white" stroke-width="6"/>
<path d="M-18,2 L-6,15 L19,-14" class="s-white dash an draw" fill="none" stroke-width="9"
stroke-linecap="round" stroke-linejoin="round" style="--at:4.7;--for:.5;--len:70"/>
</g>
</g>
<text class="cap an appear" x="640" y="612" style="--at:.3;--for:.7;--fy:16px">Delivered to their front door!</text>
</g>
</g><!-- /stage clip -->
<!-- progress dots (built from TIMING.scenes) -->
<g id="dots"></g>
</svg>
</main>
<button id="replay" type="button" aria-label="Replay animation">
<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true">
<path d="M20 12a8 8 0 1 1-2.34-5.66" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"/>
<path d="M20 4v5h-5" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
Replay
</button>
<script>
/* ==================================================================
3. MASTER TIMELINE
One clock decides which scene is "playing". Scene start times are
worked out from the order and durations in TIMING.scenes, so
reordering that list reorders the film.
================================================================== */
(function () {
var NS = 'http://www.w3.org/2000/svg';
var svg = document.getElementById('movie');
var dotsLayer = document.getElementById('dots');
// Build the scene list with cumulative start times
var cursor = 0;
var scenes = TIMING.scenes.map(function (s) {
var el = document.getElementById('scene-' + s.id);
if (!el) { throw new Error('Missing scene group: scene-' + s.id); }
var item = { id: s.id, label: s.label, dur: s.dur, el: el, start: cursor, dot: null };
cursor += s.dur;
el.style.setProperty('--d', s.dur);
return item;
});
var total = cursor + TIMING.gap;
// Progress dots
function make(tag, attrs) {
var e = document.createElementNS(NS, tag);
Object.keys(attrs).forEach(function (k) { e.setAttribute(k, attrs[k]); });
return e;
}
var spacing = 170, mid = (scenes.length - 1) / 2;
dotsLayer.appendChild(make('line', { x1: 640 - mid * spacing, x2: 640 + mid * spacing, y1: 652, y2: 652, 'class': 'track' }));
scenes.forEach(function (s, i) {
var x = 640 + (i - mid) * spacing;
s.dot = make('circle', { cx: x, cy: 652, r: 8, 'class': 'dot' });
dotsLayer.appendChild(s.dot);
var label = make('text', { x: x, y: 690, 'class': 'step' });
label.textContent = s.label;
dotsLayer.appendChild(label);
});
// Show scene i (i === scenes.length means the pause before looping)
var current = -2;
function show(i) {
if (i === current) { return; }
current = i;
scenes.forEach(function (s, j) {
s.el.classList.remove('play');
s.dot.classList.toggle('active', j === i);
s.dot.classList.toggle('done', j < i);
});
void svg.getBoundingClientRect(); // force reflow so CSS animations restart
if (i < scenes.length) { scenes[i].el.classList.add('play'); }
}
var t0 = performance.now();
var rafId = 0;
function frame(now) {
var t = ((now - t0) / 1000) * TIMING.speed; // timeline seconds
if (t < 0) { t = 0; }
if (t >= total) { t0 = now; t = 0; } // loop
var i = -1;
for (var k = 0; k < scenes.length; k++) {
if (t >= scenes[k].start && t < scenes[k].start + scenes[k].dur) { i = k; break; }
}
if (i === -1) { i = scenes.length; }
show(i);
rafId = requestAnimationFrame(frame);
}
function replay() {
cancelAnimationFrame(rafId);
t0 = performance.now();
current = -2;
show(0);
rafId = requestAnimationFrame(frame);
}
document.getElementById('replay').addEventListener('click', replay);
document.addEventListener('visibilitychange', function () {
if (document.visibilityState === 'visible') { replay(); } // keep CSS and clock in step
});
replay();
})();
</script>
</body>
</html>
```
**Speed:** change `TIMING.speed` at the top of the file (for example `0.8` for slower, `1.25` for faster). The scene clock and every CSS animation scale together.
**Colours:** edit the hex values in `THEME` (and `FONT`) in the same constants block. Every shape reads from them, and reordering the entries in `TIMING.scenes` reorders the film.
Run on Claude Sonnet 5.5, 30 Oct 2026. Automatic check: 1 out of 5.