Files
roboco/motion/compositions/release-recap/square.html
T
6188cce58c [e0bc418f] Video: v0.19.0 - our newest release (#333)
* [e0bc418f] add v019-release HyperFrames composition with vertical/square cuts, theme, props, and smoke tests

* [e0bc418f] fix(v019-release): remove local artifacts, replace em dashes, add captions.json and regression tests

* [e0bc418f] chore: delete .venv symlink and tighten .gitignore for symlink leakage

* [e0bc418f] chore(.gitignore): remove duplicate .pnpm-store line

* [e0bc418f] fix(v019-release): clean internal em dashes and add package-lock regression test

* [e0bc418f] chore(v019-release): remove committed vitest cache artifact from node_modules

* [e0bc418f] fix(.gitignore): restore .pnpm-store/ next to its GH001 comment and remove stray duplicate

* [e0bc418f] docs(motion): document v019-release composition and captions convention

* [e0bc418f] feat(release-recap): rebuild v0.19.0 video as panel-demo three-release recap

CEO rejected the text-card v019-release composition and rescoped this
to a recap of three releases (0.18.0-0.20.0) shipped in six days,
built in the panel-demo register per the CEO's brief. Brings
motion/kit/ and motion/compositions/panel-demo/ into this branch
(byte-identical to master, which merged them after this branch
diverged and cannot be rebased onto directly), deletes the obsolete
motion/compositions/v019-release/ text-card composition, and adds
motion/compositions/release-recap/: the panel frame, one intake typing
reveal, three release cards (v0.18.0/v0.19.0/v0.20.0) cycling through
a single shared kanban slot with their own progress-to-completed pill
flip, a cursor that glides in once and click-pulses at each flip, a
closing toast, and the roboco.tech outro, both at 1080x1920 and
1080x1080. Includes props.js, captions.json (X 136/280 chars, TikTok
365/2200 chars, no em dash), and a vitest smoke test (11/11 passing
across all three compositions in the package). Also refreshes
motion/README.md with the current design-bar/panel-demo-kit sections
and a release-recap example replacing the deleted v019-release one.

* [e0bc418f] fix(release-recap): polish pass — accumulate the three releases, fix facts, robust typewriter

Cards now stack and accumulate instead of cycling one absolute slot (the
three-release story never landed visually); duration tightened 20s to
15s; per-release copy corrected (0.18.0 fleet doctrine + design bar,
0.19.0 video engine + sandboxes, 0.20.0 root-owned coverage + the kit)
along with the same fixes in captions.json; one cursor with one glide
and one click landing at the pill edge instead of parking on pill text
for 17s; the per-char typing reveal (which partially froze) replaced by
a single width-steps mono typewriter sized to the supplied text.

* [e0bc418f] feat(release-recap): identity redesign — real logo, real-UI sidebar, explainers, stat beat

CEO review round 2: no logo, no similarity with the actual UI, explains
nothing. Rebuilt: branded cold open (the real logo on an app-icon tile
with a ring sweep, wordmark, tagline), a labeled sidebar mirroring the
real panel (nav labels + active state + search + Live dot + CEO chip),
a one-line explainer under each release title, a kinetic stat beat
(3 releases / 6 days / 1 human — full-frame interstitial on the square
cut where no side zone exists), and a logo lockup outro; 18s. The logo
PNG is vendored into motion/public (offline constraint, same pattern
as the fonts).

* [e0bc418f] fix(release-recap): darkmode logo hero — crisp 1024px mark replaces the pixelated 64px tile

The cold open now uses logos/roboco-logo-darkmode.png (vendored into
motion/public), built for dark backgrounds with the wordmark included:
full-size mark with a soft glow and the ring sweep, no white tile, no
separate typed wordmark. The small app-icon tiles in the sidebar and
outro keep the 64px icon where tiny sizes favor it.

---------

Co-authored-by: UX/UI Developer 1 <ux-dev-1@roboco.tech>
Co-authored-by: UX/UI Documenter <ux-doc@roboco.tech>
Co-authored-by: UX/UI Developer 2 <ux-dev-2@roboco.tech>
Co-authored-by: Renn F <rennf93@users.noreply.github.com>
2026-07-09 18:48:46 +02:00

348 lines
15 KiB
HTML
Raw 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; } }
.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. -->
<div class="pk-intake clip" data-start="4.2" data-duration="13.8" data-track-index="2" 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. -->
<div class="pk-column">
<div class="pk-column__header">Shipped</div>
<div class="pk-card clip" data-start="6" data-duration="12" data-track-index="3" 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 clip" data-start="6" data-duration="1.8" data-track-index="4" 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 clip" data-start="7.8" data-duration="10.2" data-track-index="5" style="--pk-pill-enter-delay: 7.8s;">completed</span>
</div>
</div>
<div class="pk-card clip" data-start="8.2" data-duration="9.8" data-track-index="6" 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 clip" data-start="8.2" data-duration="1.8" data-track-index="7" 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 clip" data-start="10" data-duration="8" data-track-index="8" style="--pk-pill-enter-delay: 10s;">completed</span>
</div>
</div>
<div class="pk-card clip" data-start="10.4" data-duration="7.6" data-track-index="9" 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 clip" data-start="10.4" data-duration="2.4" data-track-index="10" 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 clip" data-start="12.6" data-duration="5.4" data-track-index="11" style="--pk-pill-enter-delay: 12.6s;">completed</span>
</div>
</div>
</div>
<!-- One cursor, one glide, one click at the newest pill's edge. -->
<div class="pk-cursor clip" data-start="11" data-duration="7" data-track-index="12" 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. -->
<div class="rc-stats clip" data-start="13" data-duration="5" data-track-index="16">
<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 clip" data-start="14.8" data-duration="3.2" data-track-index="13" 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 clip" data-start="15.4" data-duration="2.6" data-track-index="14" 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>