mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
368 lines
16 KiB
HTML
368 lines
16 KiB
HTML
<!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 - vertical (1080×1920)</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: 44px;
|
||
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: 560px; height: 560px; }
|
||
.rc-hero__mark {
|
||
width: 560px;
|
||
height: 560px;
|
||
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: 36px; }
|
||
.pk-column { --pk-column-top: 280px; width: 690px; }
|
||
.pk-column .pk-card { position: static; margin-bottom: 26px; }
|
||
.rc-sub {
|
||
margin-top: 12px;
|
||
font-size: 21px;
|
||
line-height: 1.35;
|
||
color: var(--pk-text-muted);
|
||
}
|
||
|
||
/* Kinetic stat beat, right-anchored under the cards. */
|
||
.rc-stats {
|
||
position: absolute;
|
||
right: 64px;
|
||
bottom: 330px;
|
||
text-align: right;
|
||
font-family: var(--pk-font-display);
|
||
}
|
||
.rc-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;
|
||
}
|
||
.rc-stats__line--accent { color: var(--pk-accent); }
|
||
/* The stat block yields before the toast/outro close. */
|
||
.rc-stats { animation: rc-stats-out 0.5s var(--pk-ease) 15.2s forwards; }
|
||
@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: 150px; }
|
||
|
||
/* Closing lockup: small tile + domain. */
|
||
.pk-outro {
|
||
--pk-outro-bottom: 64px;
|
||
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="1920" 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: 500px; --pk-cursor-x1: 240px; --pk-cursor-y1: 1210px;">
|
||
<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 itself has no
|
||
base-visible content (children are individually base-hidden,
|
||
entering at 13/13.3/13.6s), and the composition-local
|
||
rc-stats-out rule below already fades the whole group before
|
||
the toast - 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.4s;">
|
||
<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>
|