Files
roboco/motion/compositions/release-0.27.0/vertical.html
T
cbddfc7cb3 [c71959c3] Video: release 0.27.0 (#698)
* [c71959c3] feat(motion): add release-0.27.0 panel-demo clip

Extends the panel-demo kit register (kit/) with a v0.27.0 release
composition: identity cold open, four feature cards (WAF real-IP
resolution, Telegram Mini App V6, Codex/Gemini providers, cost-tiered
routing) flipping from in-progress to completed, a camera that pushes
per beat via pk-camera data-shots, a cursor that clicks and travels
via pk-cursor data-waypoints, a receipt stats overlay, a shipped
toast, and the roboco.tech outro. Ships vertical.html, square.html,
props.js, captions.json, and a vitest smoke test mirroring
release-0.26.0's invariants.

* [c71959c3] fix(motion): implement genuinely non-uniform card-beat gaps in release-0.27.0 clip

The four .pk-card animation-delay values were uniform 5.0s/10.0s/15.0s/20.0s
in both vertical.html and square.html -- byte-for-byte identical to
release-0.26.0's metronomic spacing the craft bar bans -- despite the prior
decision_log claiming 5.5s/6.4s/5.3s varied gaps had been added. Card delays
are now 5.0/10.5/16.9/22.2 (real 5.5s/6.4s/5.3s gaps). Camera data-shots,
cursor data-waypoints, and the stats/toast/outro tail in both files are
re-warped with a shared piecewise-linear time function anchored at each
card's new beat, so nothing desyncs and the tail still fits inside the
fixed 40s runtime. Added a regression test asserting the four card gaps
are not all identical.

* [c71959c3] docs(motion): add release-0.27.0 composition section to README

---------

Co-authored-by: UX/UI Developer 1 <ux-dev-1@roboco.tech>
Co-authored-by: UX/UI Documenter <ux-doc@roboco.tech>
2026-07-25 03:39:07 +00:00

380 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-0.27.0" data-composition-duration="40" data-fps="30">
<head>
<meta charset="utf-8" />
<title>RoboCo v0.27.0 Release - vertical (1080×1920)</title>
<link rel="stylesheet" href="../../kit/kit.css" />
<script src="props.js"></script>
<script src="../../kit/kit.js"></script>
<style>
/* v0.27.0 release clip - panel-demo register, same shape as
release-0.26.0: identity cold open, panel assembles, four feature
cards flip to completed one per scene, cursor click, receipt
stats, toast, lockup. Total 40s. */
/* ---- Identity cold open -------------------------------------------- */
.r27-hero {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 38px;
background: var(--pk-bg);
z-index: 30;
animation: r27-hero-out 0.6s var(--pk-ease) 2.6s forwards;
}
@keyframes r27-hero-out { to { opacity: 0; visibility: hidden; } }
.r27-hero__markwrap { position: relative; width: 520px; height: 520px; }
.r27-hero__mark {
width: 520px;
height: 520px;
transform: scale(0.82);
opacity: 0;
filter: drop-shadow(0 12px 60px rgba(255, 90, 31, 0.18));
animation: r27-mark-in 1s var(--pk-ease) 0.2s forwards;
}
@keyframes r27-mark-in { to { transform: scale(1); opacity: 1; } }
.r27-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: r27-ring-in 0.3s linear 0.9s forwards, r27-ring-spin 1.4s cubic-bezier(0.5, 0, 0.3, 1) 0.9s forwards;
}
@keyframes r27-ring-in { to { opacity: 1; } }
@keyframes r27-ring-spin { to { transform: rotate(360deg); opacity: 0; } }
.r27-hero__version {
font-family: var(--pk-font-display);
font-size: 120px;
color: var(--pk-text);
opacity: 0;
transform: translateY(18px);
animation: pk-enter-rise 0.7s var(--pk-ease) 1.2s forwards;
}
.r27-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 --------------------------------------------------- */
.pk-frame { animation-delay: 2.6s; }
.pk-frame__sidebar {
width: 250px;
align-items: stretch;
gap: 6px;
padding: 28px 18px 0 18px;
transform: translateX(-40px);
opacity: 0;
animation: r27-side-in 0.7s var(--pk-ease) 2.8s forwards;
}
@keyframes r27-side-in { to { transform: translateX(0); opacity: 1; } }
.r27-side__logo {
width: 44px;
height: 44px;
border-radius: 12px;
background: #f4f6f8;
display: flex;
align-items: center;
justify-content: center;
margin: 0 0 22px 4px;
}
.r27-side__logo img { width: 34px; height: 34px; }
.r27-nav {
display: flex;
align-items: center;
height: 52px;
padding: 0 16px;
border-radius: 10px;
font-size: 21px;
color: var(--pk-text-muted);
}
.r27-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; }
.r27-user {
display: flex;
align-items: center;
gap: 12px;
color: var(--pk-text-muted);
font-size: 20px;
}
.r27-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: 32px; }
.pk-column { --pk-column-top: 220px; width: 720px; }
.pk-column .pk-card {
position: static;
margin-bottom: 20px;
/* Springy entrance with a hint of overshoot; a flat fade on every
card is the metronome that makes the cut feel like a slideshow. */
animation-duration: 0.55s;
animation-timing-function: cubic-bezier(0.34, 1.4, 0.64, 1);
}
.r27-sub {
margin-top: 10px;
font-size: 22px;
line-height: 1.35;
color: var(--pk-text-muted);
}
/* Receipt stats overlay. */
.r27-stats {
position: absolute;
right: 64px;
bottom: 320px;
text-align: right;
font-family: var(--pk-font-display);
}
.r27-stats__line {
font-size: 64px;
line-height: 1.22;
color: var(--pk-text);
opacity: 0;
transform: translateY(24px) scale(0.96);
animation: pk-enter-rise-scale 0.55s var(--pk-ease) forwards;
}
.r27-stats__line--accent { color: var(--pk-accent); }
.r27-stats { animation: r27-stats-out 0.5s var(--pk-ease) 33.0s forwards; }
@keyframes r27-stats-out { to { opacity: 0; } }
.pk-toast { --pk-toast-bottom: 140px; }
/* Closing lockup. */
.pk-outro {
--pk-outro-bottom: 56px;
display: flex;
align-items: center;
gap: 20px;
}
.r27-outro__logo {
width: 52px;
height: 52px;
border-radius: 14px;
background: #f4f6f8;
display: flex;
align-items: center;
justify-content: center;
}
.r27-outro__logo img { width: 40px; height: 40px; }
#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-0.27.0" data-width="1080" data-height="1920" data-start="0" data-duration="40" data-fps="30">
<!-- Beat 1: identity cold open. -->
<div class="r27-hero clip" data-start="0" data-duration="3.2" data-track-index="16">
<div class="r27-hero__markwrap">
<img class="r27-hero__mark" src="../../public/roboco-logo-darkmode.png" alt="RoboCo" />
<div class="r27-hero__ring"></div>
</div>
<div class="r27-hero__version">v0.27.0</div>
<div class="r27-hero__tag">Four brains, zero bans</div>
</div>
<!-- Beat 2: the panel assembles, and the camera lives on it: start a
touch tight, push toward each card as it completes, pull wide for
the receipt, ease onto the toast, settle for the lockup. -->
<div class="pk-camera"
data-shots="2.6 0 14 1.025; 4.8 0 8 1.03;
6.5 34 96 1.055; 9.6 30 88 1.05;
11.0 30 30 1.06; 15.1 26 24 1.055;
17.3 32 -52 1.065; 20.7 28 -58 1.06;
22.6 30 -150 1.065; 24.5 26 -156 1.06;
26.6 0 0 1.0; 30.3 0 0 1.0;
31.7 -26 -120 1.035; 34.0 -22 -116 1.03;
37.2 0 0 1.0; 39.6 0 6 1.005">
<div class="pk-frame clip" data-start="0" data-duration="40" data-track-index="1">
<aside class="pk-frame__sidebar">
<div class="r27-side__logo"><img src="../../public/roboco-logo.png" alt="" /></div>
<div class="r27-nav">Overview</div>
<div class="r27-nav">Business</div>
<div class="r27-nav">Social</div>
<div class="r27-nav r27-nav--active">Tasks</div>
<div class="r27-nav">Kanban</div>
<div class="r27-nav">Git</div>
<div class="r27-nav">Agents</div>
<div class="r27-nav">Metrics</div>
</aside>
<header class="pk-frame__topbar">
<div class="pk-frame__search">Search tasks, agents...</div>
<div class="r27-user">
<div class="pk-frame__status clip" data-start="0" data-duration="40" data-track-index="0">
<span class="pk-frame__statusdot"></span>
<span>Live</span>
</div>
<span class="r27-user__chip">CEO · Renzo</span>
</div>
</header>
<div class="pk-frame__content">
<!-- Beat 3: the hook types into intake. -->
<div class="pk-intake" style="animation-delay: 3.6s;">
<div class="pk-intake__label">New task</div>
<div class="pk-intake__field"><span id="typeIntro">Four brains, zero bans</span><span class="pk-caret"></span></div>
</div>
<!-- Beat 4: four feature cards on varied beat spacing (5.5s/6.4s/5.3s
gaps, not the metronomic identical 5.0s the craft bar bans),
each fully visible before the next enters. -->
<div class="pk-column">
<div class="pk-column__header">Shipped in v0.27.0</div>
<div class="pk-card" style="animation-delay: 5.0s;">
<div class="pk-card__title">WAF knows the real IP</div>
<div class="r27-sub">Tailscale hops don't get banned anymore.</div>
<div class="pk-card__meta">
<span class="pk-chip pk-chip--gray">fastapi-guard</span>
</div>
<div class="pk-card__status">
<span class="pk-pill pk-pill--progress pk-pill--swap-out" style="--pk-pill-enter-delay: 5.0s; --pk-pill-exit-delay: 6.4s;">in progress</span>
<span class="pk-pill pk-pill--completed pk-pill--swap-in" style="--pk-pill-enter-delay: 6.6s;">completed</span>
</div>
</div>
<div class="pk-card" style="animation-delay: 10.5s;">
<div class="pk-card__title">Telegram Mini App V6</div>
<div class="r27-sub">Real chat scopes, wallet-style metrics.</div>
<div class="pk-card__meta">
<span class="pk-chip pk-chip--gray">telegram</span>
</div>
<div class="pk-card__status">
<span class="pk-pill pk-pill--progress pk-pill--swap-out" style="--pk-pill-enter-delay: 10.5s; --pk-pill-exit-delay: 11.9s;">in progress</span>
<span class="pk-pill pk-pill--completed pk-pill--swap-in" style="--pk-pill-enter-delay: 12.1s;">completed</span>
</div>
</div>
<div class="pk-card" style="animation-delay: 16.9s;">
<div class="pk-card__title">Codex and Gemini join</div>
<div class="r27-sub">Four model providers, one gateway now.</div>
<div class="pk-card__meta">
<span class="pk-chip pk-chip--gray">providers</span>
</div>
<div class="pk-card__status">
<span class="pk-pill pk-pill--progress pk-pill--swap-out" style="--pk-pill-enter-delay: 16.9s; --pk-pill-exit-delay: 18.3s;">in progress</span>
<span class="pk-pill pk-pill--completed pk-pill--swap-in" style="--pk-pill-enter-delay: 18.5s;">completed</span>
</div>
</div>
<div class="pk-card" style="animation-delay: 22.2s;">
<div class="pk-card__title">Cost-tiered routing</div>
<div class="r27-sub">Save a routing mix, restore it anytime.</div>
<div class="pk-card__meta">
<span class="pk-chip pk-chip--gray">routing</span>
</div>
<div class="pk-card__status">
<span class="pk-pill pk-pill--progress pk-pill--swap-out" style="--pk-pill-enter-delay: 22.2s; --pk-pill-exit-delay: 23.6s;">in progress</span>
<span class="pk-pill pk-pill--completed pk-pill--swap-in" style="--pk-pill-enter-delay: 23.8s;">completed</span>
</div>
</div>
</div>
<!-- The cursor is the CEO's hand: settle on the intake while it
types, ONE submit click just before the org takes over, then
travel to witness each card completing (the agents do the
clicking-free work), drift to the receipt, acknowledge the
toast, and leave the frame; never freeze, never blink out. -->
<div class="pk-cursor"
data-waypoints="4.0 640 210; 4.5 552 152; 4.8 545 148 click;
5.7 300 330; 6.5 160 405; 7.9 178 420;
10.1 260 520; 11.0 190 628; 13.1 172 642;
16.4 250 760; 17.3 162 848; 19.0 180 862;
21.8 255 985; 22.6 190 1068; 24.0 174 1082;
25.4 420 1180; 26.4 560 1250; 29.8 585 1268;
30.7 615 1420; 31.7 612 1462 click;
33.8 700 1520; 35.4 940 1660">
<div class="pk-cursor__glyph"></div>
</div>
<!-- Beat 5: receipt stats. -->
<div class="r27-stats">
<div class="r27-stats__line" style="animation-delay: 25.7s;">1 release</div>
<div class="r27-stats__line r27-stats__line--accent" style="animation-delay: 26.0s;">4 providers</div>
<div class="r27-stats__line" style="animation-delay: 26.3s;">0 bans</div>
</div>
<!-- Beat 6: confirmation toast, then lockup. -->
<div class="pk-toast" style="animation-delay: 31.0s;">
<div class="pk-toast__icon"></div>
<div class="pk-toast__body">
<div class="pk-toast__title" id="toastTitle">v0.27.0 shipped</div>
<div class="pk-toast__text" id="toastBody">I approved once. 25 agents shipped it.</div>
</div>
</div>
<div class="pk-outro" style="animation-delay: 36.3s;">
<span class="r27-outro__logo"><img src="../../public/roboco-logo.png" alt="" /></span>
<span>roboco.tech</span>
</div>
</div>
</div>
</div>
</div>
<script>
window.__timelines = window.__timelines || {};
window.__timelines["release-0.27.0"] = {
id: "release-0.27.0",
duration: 40,
fps: 30,
animations: []
};
(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);
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.0s forwards";
}
var typeEl = document.getElementById("typeIntro");
if (typeEl) {
if (typeof props.introText === "string" && props.introText) {
typeEl.textContent = props.introText;
}
typeReveal(typeEl);
}
window.PanelKit.choreographAllCursors();
window.PanelKit.choreographAllCameras();
})();
</script>
</body>
</html>