Files
roboco/motion/compositions/release-recap/square.html

373 lines
16 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" data-composition-id="release-recap" data-composition-duration="18" data-fps="30">
<head>
<meta charset="utf-8" />
<title>Release Recap - square (1080×1080)</title>
<link rel="stylesheet" href="../../kit/kit.css" />
<script src="props.js"></script>
<script src="../../kit/kit.js"></script>
<style>
/* Composition-local layer on top of the kit: identity cold open (the
real logo tile + wordmark), a labeled sidebar that mirrors the actual
panel, per-release explainer sublines, a kinetic stat beat, and the
closing lockup. Kit components keep the card/pill/toast/cursor look. */
/* ---- Identity cold open -------------------------------------------- */
.rc-hero {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 32px;
background: var(--pk-bg);
z-index: 30;
animation: rc-hero-out 0.6s var(--pk-ease) 3.2s forwards;
}
@keyframes rc-hero-out { to { opacity: 0; visibility: hidden; } }
.rc-hero__markwrap { position: relative; width: 440px; height: 440px; }
.rc-hero__mark {
width: 440px;
height: 440px;
transform: scale(0.82);
opacity: 0;
filter: drop-shadow(0 12px 60px rgba(120, 170, 210, 0.18));
animation: rc-mark-in 1s var(--pk-ease) 0.2s forwards;
}
@keyframes rc-mark-in { to { transform: scale(1); opacity: 1; } }
/* One accent ring sweeps the tile once - the identity flourish. */
.rc-hero__ring {
position: absolute;
inset: 6px;
border-radius: 50%;
padding: 3px;
background: conic-gradient(from 0deg, transparent 0 78%, var(--pk-accent) 92%, transparent 100%);
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
opacity: 0;
animation: rc-ring-in 0.3s linear 0.9s forwards, rc-ring-spin 1.4s cubic-bezier(0.5, 0, 0.3, 1) 0.9s forwards;
}
@keyframes rc-ring-in { to { opacity: 1; } }
@keyframes rc-ring-spin { to { transform: rotate(360deg); opacity: 0; } }
.rc-hero__tag {
font-size: 30px;
color: var(--pk-text-muted);
opacity: 0;
transform: translateY(14px);
animation: pk-enter-rise 0.7s var(--pk-ease) 1.5s forwards;
}
/* ---- Panel chrome: labeled sidebar mirroring the real panel -------- */
.pk-frame { animation-delay: 3.2s; }
.pk-frame__sidebar {
width: 250px;
align-items: stretch;
gap: 6px;
padding: 28px 18px 0 18px;
transform: translateX(-40px);
opacity: 0;
animation: rc-side-in 0.7s var(--pk-ease) 3.4s forwards;
}
@keyframes rc-side-in { to { transform: translateX(0); opacity: 1; } }
.rc-side__logo {
width: 44px;
height: 44px;
border-radius: 12px;
background: #f4f6f8;
display: flex;
align-items: center;
justify-content: center;
margin: 0 0 22px 4px;
}
.rc-side__logo img { width: 34px; height: 34px; }
.rc-nav {
display: flex;
align-items: center;
height: 52px;
padding: 0 16px;
border-radius: 10px;
font-size: 21px;
color: var(--pk-text-muted);
}
.rc-nav--active { background: var(--pk-active-bg); color: var(--pk-active-fg); font-weight: 600; }
.pk-frame__topbar { left: 250px; }
.pk-frame__content { left: 250px; }
.rc-user {
display: flex;
align-items: center;
gap: 12px;
color: var(--pk-text-muted);
font-size: 20px;
}
.rc-user__chip {
background: var(--pk-surface-raised);
border: 1px solid var(--pk-border);
border-radius: 999px;
padding: 8px 18px;
color: var(--pk-text);
}
/* ---- Content placement --------------------------------------------- */
.pk-intake { --pk-intake-top: 20px; }
.pk-column { --pk-column-top: 168px; width: 660px; }
.pk-column .pk-card { position: static; margin-bottom: 12px; padding: 18px 26px; }
.rc-sub {
margin-top: 8px;
font-size: 18px;
line-height: 1.35;
color: var(--pk-text-muted);
}
/* Kinetic stat beat, right-anchored under the cards. */
.rc-stats {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 10px;
text-align: center;
background: rgba(10, 10, 10, 0.88);
z-index: 20;
font-family: var(--pk-font-display);
opacity: 0;
animation: rc-stats-in 0.4s var(--pk-ease) 13s forwards, rc-stats-out 0.5s var(--pk-ease) 14.4s forwards;
}
@keyframes rc-stats-in { to { opacity: 1; } }
.rc-stats__line {
font-size: 84px;
line-height: 1.25;
color: var(--pk-text);
opacity: 0;
transform: translateY(24px) scale(0.96);
animation: pk-enter-rise-scale 0.55s var(--pk-ease) forwards;
}
.rc-stats__line--accent { color: var(--pk-accent); }
@keyframes rc-stats-out { to { opacity: 0; } }
/* Cursor: base-hidden + a delayed animation, per motion/README.md's
clip-window rule (the renderer's per-clip scheduler drifts on long
compositions - beats never carry the clip class). kit.css's
single-glide --pk-cursor-x0/x1 API has no opacity component of its
own, so fold a fade into a local copy of its glide keyframe rather
than touching kit.css. */
.pk-cursor {
opacity: 0;
animation: rc-cursor-glide-in 1.2s var(--pk-ease) var(--pk-cursor-delay, 0s) forwards;
}
@keyframes rc-cursor-glide-in {
from { opacity: 0; transform: translate(var(--pk-cursor-x0, 0px), var(--pk-cursor-y0, 0px)); }
to { opacity: 1; transform: translate(var(--pk-cursor-x1, 0px), var(--pk-cursor-y1, 0px)); }
}
.pk-toast { --pk-toast-bottom: 84px; width: 480px; }
/* Closing lockup: small tile + domain. */
.pk-outro {
--pk-outro-bottom: 28px;
display: flex;
align-items: center;
gap: 20px;
}
.rc-outro__logo {
width: 52px;
height: 52px;
border-radius: 14px;
background: #f4f6f8;
display: flex;
align-items: center;
justify-content: center;
}
.rc-outro__logo img { width: 40px; height: 40px; }
/* Single-animation typewriter: the mono face makes ch-widths exact, so
a width reveal in steps(n) reads as typing and cannot partially
freeze the way per-char reveals can. */
.pk-intake__field { font-family: var(--pk-font-display); letter-spacing: 0.5px; }
#typeIntro { display: inline-block; overflow: hidden; white-space: pre; vertical-align: bottom; width: 0; }
@keyframes type-reveal { to { width: var(--type-ch, 20ch); } }
</style>
</head>
<body>
<div id="stage" data-composition-id="release-recap" data-width="1080" data-height="1080" data-start="0" data-duration="18" data-fps="30">
<!-- Beat 1: identity cold open. The tile settles, the accent ring sweeps
once, the wordmark and tag rise; the whole layer yields to the
panel at 3.2s. -->
<div class="rc-hero clip" data-start="0" data-duration="3.8" data-track-index="15">
<div class="rc-hero__markwrap">
<img class="rc-hero__mark" src="../../public/roboco-logo-darkmode.png" alt="RoboCo" />
<div class="rc-hero__ring"></div>
</div>
<div class="rc-hero__tag">the AI company that ships itself</div>
</div>
<!-- Beat 2: the panel assembles - labeled sidebar like the real UI. -->
<div class="pk-frame clip" data-start="3.2" data-duration="14.8" data-track-index="1">
<aside class="pk-frame__sidebar">
<div class="rc-side__logo"><img src="../../public/roboco-logo.png" alt="" /></div>
<div class="rc-nav">Overview</div>
<div class="rc-nav">Business</div>
<div class="rc-nav">Social</div>
<div class="rc-nav rc-nav--active">Tasks</div>
<div class="rc-nav">Kanban</div>
<div class="rc-nav">Git</div>
<div class="rc-nav">Agents</div>
<div class="rc-nav">Metrics</div>
</aside>
<header class="pk-frame__topbar">
<div class="pk-frame__search">Search tasks, agents...</div>
<div class="rc-user">
<div class="pk-frame__status clip" data-start="3.2" data-duration="14.8" data-track-index="0">
<span class="pk-frame__statusdot"></span>
<span>Live</span>
</div>
<span class="rc-user__chip">CEO · Renzo</span>
</div>
</header>
<div class="pk-frame__content">
<!-- Beat 3: the recap line types into intake. Base-hidden +
delayed animation (motion/README.md's clip-window rule) -
pk-intake's own kit.css entrance already owns this. -->
<div class="pk-intake" style="animation-delay: 4.2s;">
<div class="pk-intake__label">New task</div>
<div class="pk-intake__field"><span id="typeIntro">3 releases in 6 days</span><span class="pk-caret"></span></div>
</div>
<!-- Beat 4: three releases stack and accumulate, each with a
one-line explainer; each flips completed as the next ships.
Base-hidden + animation-delay per motion/README.md's
clip-window rule - never the clip class on a beat. Pills reuse
kit.css's pk-pill--swap-out/-in, which already own their own
enter+exit via --pk-pill-enter-delay/--pk-pill-exit-delay. -->
<div class="pk-column">
<div class="pk-column__header">Shipped</div>
<div class="pk-card" style="animation-delay: 6s;">
<div class="pk-card__title">v0.18.0 · Fleet doctrine</div>
<div class="rc-sub">One behavioral doctrine and one design bar across all 25 agents.</div>
<div class="pk-card__meta">
<span class="pk-chip pk-chip--gray">Jul 4</span>
</div>
<div class="pk-card__status">
<span class="pk-pill pk-pill--progress pk-pill--swap-out" style="--pk-pill-enter-delay: 6s; --pk-pill-exit-delay: 7.6s;">in progress</span>
<span class="pk-pill pk-pill--completed pk-pill--swap-in" style="--pk-pill-enter-delay: 7.8s;">completed</span>
</div>
</div>
<div class="pk-card" style="animation-delay: 8.2s;">
<div class="pk-card__title">v0.19.0 · Video engine</div>
<div class="rc-sub">Releases become videos. Every agent gets its own dev sandbox.</div>
<div class="pk-card__meta">
<span class="pk-chip pk-chip--gray">Jul 8</span>
</div>
<div class="pk-card__status">
<span class="pk-pill pk-pill--progress pk-pill--swap-out" style="--pk-pill-enter-delay: 8.2s; --pk-pill-exit-delay: 9.8s;">in progress</span>
<span class="pk-pill pk-pill--completed pk-pill--swap-in" style="--pk-pill-enter-delay: 10s;">completed</span>
</div>
</div>
<div class="pk-card" style="animation-delay: 10.4s;">
<div class="pk-card__title">v0.20.0 · Root-owned coverage</div>
<div class="rc-sub">Acceptance criteria owned at the root. Plus the kit this video is built with.</div>
<div class="pk-card__meta">
<span class="pk-chip pk-chip--gray">Jul 9</span>
</div>
<div class="pk-card__status">
<span class="pk-pill pk-pill--progress pk-pill--swap-out" style="--pk-pill-enter-delay: 10.4s; --pk-pill-exit-delay: 12.4s;">in progress</span>
<span class="pk-pill pk-pill--completed pk-pill--swap-in" style="--pk-pill-enter-delay: 12.6s;">completed</span>
</div>
</div>
</div>
<!-- One cursor, one glide, one click at the newest pill's edge.
Base-hidden + a delayed animation (clip-window rule); see the
local .pk-cursor override in <style> above for the fade. -->
<div class="pk-cursor" style="--pk-cursor-delay: 11s; --pk-cursor-x0: 700px; --pk-cursor-y0: 340px; --pk-cursor-x1: 246px; --pk-cursor-y1: 920px;">
<div class="pk-cursor__glyph"></div>
<div class="pk-cursor__ring" style="--pk-click-delay: 12.4s;"></div>
</div>
<!-- Beat 5: the kinetic stat lines. rc-stats is fully
self-contained here (opacity:0 base + rc-stats-in/-out in
<style> above) - no clip window needed. -->
<div class="rc-stats">
<div class="rc-stats__line" style="animation-delay: 13s;">3 releases</div>
<div class="rc-stats__line" style="animation-delay: 13.3s;">6 days</div>
<div class="rc-stats__line rc-stats__line--accent" style="animation-delay: 13.6s;">1 human</div>
</div>
<!-- Beat 6: confirmation toast, then the lockup outro. -->
<div class="pk-toast" style="animation-delay: 14.8s;">
<div class="pk-toast__icon"></div>
<div class="pk-toast__body">
<div class="pk-toast__title" id="toastTitle">3 releases shipped</div>
<div class="pk-toast__text" id="toastBody">0.18.0 to 0.20.0 in six days.</div>
</div>
</div>
<div class="pk-outro" style="animation-delay: 15.4s;">
<span class="rc-outro__logo"><img src="../../public/roboco-logo.png" alt="" /></span>
<span>roboco.tech</span>
</div>
</div>
</div>
</div>
<script>
// Register the composition timeline metadata. Motion is driven by CSS
// keyframes (no GSAP - the render is offline, no CDN), but HyperFrames
// requires the registry entry to consider the composition valid.
window.__timelines = window.__timelines || {};
window.__timelines["release-recap"] = {
id: "release-recap",
duration: 18,
fps: 30,
animations: []
};
// Read sidecar-supplied props (props.js is overwritten at render time)
// and populate the DOM. Defensive: a missing prop leaves the
// placeholder text already in the markup intact rather than throwing.
(function () {
var props = (window.__PROPS__ && typeof window.__PROPS__ === "object") ? window.__PROPS__ : {};
function setText(id, value) {
if (typeof value !== "string" || !value) return;
var el = document.getElementById(id);
if (el) el.textContent = value;
}
setText("toastTitle", props.toastTitle);
setText("toastBody", props.toastBody);
// typeReveal: width-steps typewriter sized to the actual text so a
// sidecar-supplied introText of any length types out fully.
function typeReveal(el) {
var n = (el.textContent || "").length || 1;
el.style.setProperty("--type-ch", (n + 1) + "ch");
el.style.animation = "type-reveal " + (n * 0.07).toFixed(2) + "s steps(" + n + ", end) 4.6s forwards";
}
var typeEl = document.getElementById("typeIntro");
if (typeEl) {
if (typeof props.introText === "string" && props.introText) {
typeEl.textContent = props.introText;
}
typeReveal(typeEl);
}
})();
</script>
</body>
</html>