Files
hanzo.ai/film/promo/square/index.html

210 lines
11 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1080, height=1200" />
<title>hanzo — an afternoon</title>
<script src="assets/gsap.min.js"></script>
<style>
@font-face { font-family: "Zen"; src: url("./assets/zen.woff2") format("woff2"); font-weight: 100 900; font-display: block; }
@font-face { font-family: "Zen Mono"; src: url("./assets/zen-mono.woff2") format("woff2"); font-weight: 100 900; font-display: block; }
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { width: 1080px; height: 1200px; overflow: hidden; background: #06070a; }
#root { position: relative; width: 1080px; height: 1200px; overflow: hidden;
--accent: #8b5cf6; --ink: #e7ebf3; --dim: #7c8698; --line: #1d2330;
--panel: #0e1117; --panel2: #131822;
font-family: "Zen", ui-sans-serif, system-ui, sans-serif; -webkit-font-smoothing: antialiased; }
.field { position: absolute; inset: 0;
background:
radial-gradient(120% 80% at 50% -10%, color-mix(in srgb, var(--accent) 20%, transparent), transparent 62%),
#06070a; }
.grid { position: absolute; inset: 0;
background-image:
linear-gradient(to right, rgba(255,255,255,.025) 1px, transparent 1px),
linear-gradient(to bottom, rgba(255,255,255,.025) 1px, transparent 1px);
background-size: 60px 60px; }
/* Each beat is centred on the frame, so a short beat and a tall one share
one optical middle and a responsive crop takes background from both
edges rather than emptying the bottom half. */
.safe { position: absolute; left: 100px; top: 0;
width: 880px; height: 1200px; }
.beat { position: absolute; left: 0; width: 100%; top: 50%;
transform: translateY(-50%); }
/* beat 1 — the mode row */
.modes {
display: flex; gap: 10px; justify-content: center; }
.mode { height: 52px; padding: 0 26px; display: flex; align-items: center;
border: 1px solid var(--line); border-radius: 999px; background: var(--panel2);
color: var(--dim); font-size: 19px; font-weight: 600; }
.mode.on { border-color: color-mix(in srgb, var(--accent) 55%, transparent);
background: color-mix(in srgb, var(--accent) 16%, var(--panel2));
color: var(--ink); }
.cursor { position: absolute; width: 22px; height: 22px; opacity: 0;
border-radius: 50%; background: #fff; box-shadow: 0 0 0 6px rgba(255,255,255,.14); }
/* beat 2 — the request */
.ask { opacity: 0;
border: 1px solid var(--line); border-radius: 20px; background: var(--panel);
padding: 26px 28px; }
.chips { display: flex; gap: 10px; margin-bottom: 20px; }
.chip { height: 38px; padding: 0 16px; display: flex; align-items: center; gap: 8px;
border: 1px solid var(--line); border-radius: 10px; background: var(--panel2);
color: var(--dim); font-size: 15px; font-weight: 600; opacity: .35; }
.chip.on { opacity: 1; color: var(--ink);
border-color: color-mix(in srgb, var(--accent) 45%, transparent); }
.chip i { width: 7px; height: 7px; border-radius: 50%; background: var(--accent); }
.line { font-size: 27px; line-height: 1.35; color: var(--ink); min-height: 74px; }
.caret { display: inline-block; width: 3px; height: 27px; background: var(--accent);
vertical-align: -4px; margin-left: 3px; }
/* beat 3 — the work */
.work { opacity: 0; }
.step { display: flex; align-items: center; gap: 16px; height: 74px;
border-bottom: 1px solid #161c27; opacity: .3; }
.step.on { opacity: 1; }
.tick { width: 26px; height: 26px; flex: 0 0 26px; border-radius: 50%;
border: 1px solid var(--line); position: relative; }
.step.on .tick { border-color: var(--accent);
background: color-mix(in srgb, var(--accent) 22%, transparent); }
.verb { font-family: "Zen Mono", ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 15px;
color: var(--accent); width: 52px; flex: 0 0 52px; }
.path { font-family: "Zen Mono", ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 19px; color: var(--ink); }
/* beat 4 — the artifact */
.made { opacity: 0; }
.shop { border: 1px solid var(--line); border-radius: 18px; background: var(--panel);
padding: 22px; display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
.card { height: 152px; border-radius: 12px;
background: linear-gradient(150deg, color-mix(in srgb, var(--accent) 26%, #1b2231), #171d29);
opacity: 0; }
.cost { margin-top: 22px; display: flex; align-items: center; gap: 12px;
font-family: "Zen Mono", ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 17px; color: var(--dim); }
.cost b { color: var(--accent); font-weight: 600; }
/* beat 5 — the line */
.close { opacity: 0;
text-align: center; font-size: 40px; font-weight: 600; line-height: 1.25;
color: var(--ink); letter-spacing: -.6px; }
</style>
</head>
<body>
<div id="root" data-composition-id="promo" data-start="0" data-duration="26"
data-fps="30" data-width="1080" data-height="1200">
<div class="field"></div>
<div class="grid"></div>
<div class="safe">
<div class="beat modes" id="modes">
<div class="mode" id="mode0">Chat</div>
<div class="mode" id="mode1">Agent</div>
<div class="mode" id="mode2">Batch</div>
</div>
<div class="cursor" id="cursor"></div>
<div class="beat ask" id="ask">
<div class="chips">
<div class="chip" id="chip0"><i></i>Sandboxes</div>
<div class="chip" id="chip1"><i></i>Vector</div>
<div class="chip" id="chip2"><i></i>Sites</div>
</div>
<div class="line"><span id="typed"></span><span class="caret" id="caret"></span></div>
</div>
<div class="beat work" id="work">
<div class="step" id="step0">
<span class="tick"></span><span class="verb">POST</span><span class="path">/v1/sandboxes</span>
</div>
<div class="step" id="step1">
<span class="tick"></span><span class="verb">POST</span><span class="path">/v1/vector</span>
</div>
<div class="step" id="step2">
<span class="tick"></span><span class="verb">POST</span><span class="path">/v1/sites/deploy</span>
</div>
<div class="step" id="step3">
<span class="tick"></span><span class="verb">POST</span><span class="path">/v1/sites/{slug}/publish</span>
</div>
</div>
<div class="beat made" id="made">
<div class="shop">
<div class="card" id="card0"></div>
<div class="card" id="card1"></div>
<div class="card" id="card2"></div>
<div class="card" id="card3"></div>
<div class="card" id="card4"></div>
<div class="card" id="card5"></div>
</div>
<div class="cost"><b>GET /v1/billing/usage</b><span>Watch what it cost.</span></div>
</div>
<div class="beat close" id="close">A store that did not exist this morning.</div>
</div>
</div>
<script>
(function () {
var tl = gsap.timeline({ paused: true });
var ASK_TEXT = "Build a spaceship shop.";
var $ = function (id) { return document.getElementById(id); };
// BEAT 1 · 0.0–3.0 — one control, one cursor, one choice.
gsap.set('#cursor', { x: 700, y: 470 });
tl.to('#cursor', { opacity: 1, duration: .35 }, 0.35)
.to('#cursor', { x: 452, y: 326, duration: 1.1, ease: 'power2.inOut' }, 0.8)
.to('#mode1', { onStart: function () { $('mode1').classList.add('on'); }, duration: .01 }, 2.0)
.to('#cursor', { scale: .82, duration: .12, yoyo: true, repeat: 1 }, 2.0)
.to('#cursor', { opacity: 0, duration: .3 }, 2.5)
// BEAT 2 · 3.0–8.0 — the surface, its three capabilities, the request.
.to('#modes', { opacity: 0, y: -18, duration: .45, ease: 'power2.in' }, 2.9)
.to('#ask', { opacity: 1, y: 0, duration: .5, ease: 'power2.out' }, 3.2)
.to('#chip0', { onStart: function () { $('chip0').classList.add('on'); }, duration: .01 }, 3.70)
.to('#chip1', { onStart: function () { $('chip1').classList.add('on'); }, duration: .01 }, 4.10)
.to('#chip2', { onStart: function () { $('chip2').classList.add('on'); }, duration: .01 }, 4.50)
.to({}, {
duration: 2.4,
onUpdate: function () {
var n = Math.round(this.progress() * ASK_TEXT.length);
$('typed').textContent = ASK_TEXT.slice(0, n);
},
}, 5.0)
.to('#caret', { opacity: 0, duration: .2 }, 7.6)
// BEAT 3 · 8.0–16.0 — four real calls, one focal point at a time.
.to('#ask', { opacity: 0, y: -22, duration: .5, ease: 'power2.in' }, 7.9)
.to('#work', { opacity: 1, duration: .5, ease: 'power2.out' }, 8.3)
.to('#step0', { onStart: function () { $('step0').classList.add('on'); }, duration: .01 }, 8.80)
.to('#step1', { onStart: function () { $('step1').classList.add('on'); }, duration: .01 }, 10.50)
.to('#step2', { onStart: function () { $('step2').classList.add('on'); }, duration: .01 }, 12.20)
.to('#step3', { onStart: function () { $('step3').classList.add('on'); }, duration: .01 }, 13.90)
// BEAT 4 · 16.0–21.0 — the thing it made, and what it cost.
.to('#work', { opacity: 0, y: -26, duration: .5, ease: 'power2.in' }, 15.7)
.to('#made', { opacity: 1, duration: .5, ease: 'power2.out' }, 16.1)
.to('#card0', { opacity: 1, duration: .4, ease: 'power2.out' }, 16.50)
.to('#card1', { opacity: 1, duration: .4, ease: 'power2.out' }, 16.66)
.to('#card2', { opacity: 1, duration: .4, ease: 'power2.out' }, 16.82)
.to('#card3', { opacity: 1, duration: .4, ease: 'power2.out' }, 16.98)
.to('#card4', { opacity: 1, duration: .4, ease: 'power2.out' }, 17.14)
.to('#card5', { opacity: 1, duration: .4, ease: 'power2.out' }, 17.30)
// BEAT 5 · 21.0–24.0 — the line, alone.
.to('#made', { opacity: 0, duration: .6, ease: 'power2.in' }, 20.6)
.to('#close', { opacity: 1, duration: .7, ease: 'power2.out' }, 21.1)
// BEAT 6 · 24.0–26.0 — back to the frame it opened on, so the loop has
// no seam: the line leaves, the mode row returns unselected, and the
// last frame is the first.
.to('#close', { opacity: 0, duration: .6, ease: 'power2.in' }, 23.8)
.to('#mode1', { onStart: function () { $('mode1').classList.remove('on'); }, duration: .01 }, 24.2)
.to('#modes', { opacity: 1, y: 0, duration: .7, ease: 'power2.out' }, 24.4);
window.__timelines = window.__timelines || {};
window.__timelines['promo'] = tl;
})();
</script>
</body>
</html>