mirror of
https://github.com/EveryInc/compound-engineering-plugin.git
synced 2026-10-10 01:49:05 +02:00
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
393 lines
25 KiB
JavaScript
393 lines
25 KiB
JavaScript
/*
|
|
* "What is Compound Engineering?" — the homepage explainer animation.
|
|
*
|
|
* Ported from the Claude Design project "Compound Engineering Explainer"
|
|
* (ce-video.jsx on the animations-v3 engine). The site loads nothing
|
|
* third-party, so instead of React this file carries a ~60-line patcher:
|
|
* the whole 1920x1080 frame is a pure function of one time axis T, rebuilt
|
|
* as a lightweight node tree each frame and patched onto the DOM by position.
|
|
* The tree's shape never changes (absent nodes render as comment
|
|
* placeholders), so positional patching is enough.
|
|
*
|
|
* Mount point: <div class="ce-explainer" data-skills="N" data-hosts="N">.
|
|
*/
|
|
(function () {
|
|
'use strict';
|
|
|
|
var script = document.currentScript;
|
|
var LOGO = script ? new URL('../logo.png', script.src).href : '';
|
|
|
|
// ── Tiny DOM patcher ──────────────────────────────────────────────────────
|
|
var SVG_NS = 'http://www.w3.org/2000/svg';
|
|
var UNITLESS = { opacity: 1, zIndex: 1, fontWeight: 1, flex: 1, flexShrink: 1, lineHeight: 1 };
|
|
var CAMEL_ATTRS = { pathLength: 1, viewBox: 1 };
|
|
var kebab = function (s) { return s.replace(/[A-Z]/g, function (m) { return '-' + m.toLowerCase(); }); };
|
|
|
|
function h(t, p) {
|
|
var c = [];
|
|
(function flat(a) {
|
|
for (var i = 0; i < a.length; i++) Array.isArray(a[i]) ? flat(a[i]) : c.push(a[i]);
|
|
})(Array.prototype.slice.call(arguments, 2));
|
|
if (typeof t === 'function') return t(Object.assign({}, p, { children: c }));
|
|
return { t: t, p: p || {}, c: c };
|
|
}
|
|
|
|
function kindOf(v) {
|
|
if (v == null || v === false || v === true) return '#comment';
|
|
if (typeof v !== 'object') return '#text';
|
|
return v.t.toUpperCase();
|
|
}
|
|
|
|
function patch(parent, i, v, svg) {
|
|
var el = parent.childNodes[i];
|
|
var kind = kindOf(v);
|
|
if (!el || el.nodeName.toUpperCase() !== kind) {
|
|
var next = kind === '#comment' ? document.createComment('')
|
|
: kind === '#text' ? document.createTextNode('')
|
|
: (svg || v.t === 'svg') ? document.createElementNS(SVG_NS, v.t) : document.createElement(v.t);
|
|
el ? parent.replaceChild(next, el) : parent.appendChild(next);
|
|
el = next;
|
|
}
|
|
if (kind === '#comment') return;
|
|
if (kind === '#text') {
|
|
var s = String(v);
|
|
if (el.data !== s) el.data = s;
|
|
return;
|
|
}
|
|
var isSvg = svg || v.t === 'svg';
|
|
var prev = el.__p || {};
|
|
var props = v.p;
|
|
for (var k in props) {
|
|
if (k === 'style') {
|
|
var st = props.style, ps = prev.style || {};
|
|
for (var sk in st) {
|
|
if (st[sk] === ps[sk]) continue;
|
|
var val = typeof st[sk] === 'number' && !UNITLESS[sk] ? st[sk] + 'px' : String(st[sk]);
|
|
el.style.setProperty(kebab(sk), val);
|
|
}
|
|
for (var rk in ps) if (!(rk in st)) el.style.removeProperty(kebab(rk));
|
|
} else if (props[k] !== prev[k]) {
|
|
var name = k === 'className' ? 'class' : isSvg && !CAMEL_ATTRS[k] ? kebab(k) : k;
|
|
el.setAttribute(name, props[k]);
|
|
}
|
|
}
|
|
el.__p = props;
|
|
var kids = v.c;
|
|
for (var j = 0; j < kids.length; j++) patch(el, j, kids[j], isSvg && v.t !== 'foreignObject');
|
|
while (el.childNodes.length > kids.length) el.removeChild(el.lastChild);
|
|
}
|
|
|
|
// ── Motion vocabulary (from the design) ───────────────────────────────────
|
|
var Easing = {
|
|
easeOutCubic: function (t) { return (--t) * t * t + 1; },
|
|
easeInOutCubic: function (t) { return t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1; },
|
|
easeOutBack: function (t) { var c1 = 1.70158, c3 = c1 + 1; return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2); },
|
|
};
|
|
var clamp = function (v, a, b) { return Math.max(a, Math.min(b, v)); };
|
|
var prog = function (T, s, d) { return clamp((T - s) / d, 0, 1); };
|
|
var MOTION = {
|
|
enter: function (T, s, d) { return Easing.easeOutCubic(prog(T, s, d == null ? 0.7 : d)); },
|
|
draw: function (T, s, d) { return Easing.easeInOutCubic(prog(T, s, d == null ? 1 : d)); },
|
|
pop: function (T, s, d) { return Easing.easeOutBack(prog(T, s, d == null ? 0.5 : d)); },
|
|
};
|
|
var win = function (T, a, b, fi, fo) {
|
|
fi = fi == null ? 0.6 : fi; fo = fo == null ? 0.5 : fo;
|
|
return MOTION.enter(T, a, fi) * (1 - MOTION.draw(T, b - fo, fo));
|
|
};
|
|
var lerp = function (a, b, t) { return a + (b - a) * t; };
|
|
|
|
var C = { paper: '#1B1B1F', ink: '#DFDFD6', mute: '#98989F', line: '#2E2E32', term: '#161618', onAcc: '#111111' };
|
|
var ACC = ['#DCEFAE', '#5C7F14'];
|
|
var SERIF = "ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Helvetica, Arial, sans-serif";
|
|
var SANS = "system-ui, -apple-system, 'Segoe UI', Helvetica, Arial, sans-serif";
|
|
var MONO = "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace";
|
|
|
|
var SCENES = [
|
|
['Debt', 5.5], ['Invert', 4.5], ['Loop', 11.5], ['Return', 5.5],
|
|
['Remember', 7.5], ['Ratio', 4.5], ['Outro', 5.5],
|
|
];
|
|
var CUES = {}, TOTAL = 0;
|
|
SCENES.forEach(function (s) { CUES[s[0]] = TOTAL; TOTAL += s[1]; });
|
|
|
|
function Rise(o) {
|
|
var fi = o.fi == null ? 0.7 : o.fi, fo = o.fo == null ? 0.5 : o.fo, dy = o.dy == null ? 28 : o.dy;
|
|
var e = MOTION.enter(o.T, o.a, fi);
|
|
var op = win(o.T, o.a, o.b, fi, fo);
|
|
return h('div', { style: Object.assign({ position: 'absolute', opacity: op, transform: 'translateY(' + (1 - e) * dy + 'px)' }, o.style) }, o.children);
|
|
}
|
|
|
|
function Headline(o) {
|
|
var size = o.size || 76;
|
|
return h(Rise, { T: o.T, a: o.a, b: o.b, style: { left: o.x || 140, top: o.y || 130, width: o.width || 1700 } },
|
|
h('div', { style: { font: '500 22px ' + MONO, letterSpacing: '0.14em', textTransform: 'uppercase', color: C.mute, marginBottom: 18 } }, o.eyebrow),
|
|
h('div', { style: { font: '600 ' + size + 'px/1.08 ' + SERIF, color: C.ink, letterSpacing: '-0.025em', textWrap: 'pretty' } }, o.text));
|
|
}
|
|
|
|
// ── 1+2. The curve: debt rises, compounding falls ─────────────────────────
|
|
function Chart(o) {
|
|
var T = o.T, a = CUES.Debt, inv = CUES.Invert, end = CUES.Loop + 0.6;
|
|
var op = win(T, a + 0.3, end, 0.7, 0.8);
|
|
var W = 1640, H = 500, n = 9;
|
|
var p = MOTION.draw(T, a + 1.0, 3.4);
|
|
var m = MOTION.draw(T, inv + 0.5, 1.8);
|
|
var debt = function (t) { return 0.1 + 0.82 * (Math.pow(1.45, t) - 1) / (Math.pow(1.45, n) - 1); };
|
|
var comp = function (t) { return 0.1 + 0.5 * Math.pow(0.7, t); };
|
|
var f = function (t) { return lerp(debt(t), comp(t), m); };
|
|
var X = function (t) { return 70 + t * (W - 140) / n; };
|
|
var Y = function (v) { return H - 30 - v * (H - 60); };
|
|
var d = '';
|
|
for (var k = 0; k <= 90; k++) { var t = k / 10; d += (k ? 'L' : 'M') + X(t).toFixed(1) + ' ' + Y(f(t)).toFixed(1); }
|
|
var area = d + 'L' + X(n) + ' ' + (H - 30) + 'L' + X(0) + ' ' + (H - 30) + 'Z';
|
|
var endE = MOTION.enter(T, a + 4.2, 0.6);
|
|
var dots = [];
|
|
for (var i = 0; i <= n; i++) {
|
|
var s = MOTION.pop(T, a + 1.0 + (i / n) * 3.2, 0.4);
|
|
dots.push(h('circle', { cx: X(i), cy: Y(f(i)), r: Math.max(0, 9 * s), fill: C.paper, stroke: C.ink, strokeWidth: 4 }));
|
|
}
|
|
return h('div', { style: { position: 'absolute', left: 140, top: 440, width: W, height: H + 60, opacity: op } },
|
|
h('svg', { width: W, height: H, style: { position: 'absolute', left: 0, top: 0, overflow: 'visible' } },
|
|
h('line', { x1: 40, y1: 0, x2: 40, y2: H - 30, stroke: C.line, strokeWidth: 2 }),
|
|
h('line', { x1: 40, y1: H - 30, x2: W, y2: H - 30, stroke: C.line, strokeWidth: 2 }),
|
|
h('path', { d: area, fill: o.acc[0], opacity: m * 0.22 }),
|
|
h('path', { d: d, fill: 'none', stroke: C.ink, strokeWidth: 5, strokeLinecap: 'round', strokeLinejoin: 'round', pathLength: 1, strokeDasharray: 1, strokeDashoffset: 1 - p }),
|
|
dots),
|
|
h('div', { style: { position: 'absolute', left: -6, top: H / 2 - 30, transform: 'rotate(-90deg) translateX(-50%)', transformOrigin: '0 0', font: '500 22px ' + SANS, color: C.mute, whiteSpace: 'nowrap' } }, 'Effort per change'),
|
|
h('div', { style: { position: 'absolute', right: 0, top: H - 10, font: '500 22px ' + SANS, color: C.mute, whiteSpace: 'nowrap' } }, 'Changes over time →'),
|
|
h('div', { style: { position: 'absolute', left: X(n) - 360, top: Y(f(n)) - 70, width: 330, textAlign: 'right', font: '500 26px ' + SANS, color: C.ink, opacity: endE } },
|
|
h('span', { style: { opacity: 1 - m, position: 'absolute', right: 0, top: 0, whiteSpace: 'nowrap' } }, 'Next change: slower'),
|
|
h('span', { style: { opacity: m, position: 'absolute', right: 0, top: 0, whiteSpace: 'nowrap' } }, 'Next change: easier')));
|
|
}
|
|
|
|
// ── 3+4. The loop and its return arrow ────────────────────────────────────
|
|
var STEPS = [
|
|
{ k: 'Brainstorm', cmd: '/ce-brainstorm', d: 'Think the problem through. Write down the requirements.' },
|
|
{ k: 'Plan', cmd: '/ce-plan', d: 'Turn requirements into an implementation-ready plan.' },
|
|
{ k: 'Work', cmd: '/ce-work', d: 'Execute the plan. Verify and commit.' },
|
|
{ k: 'Simplify', cmd: '/ce-simplify-code', d: 'Refine the fresh code for clarity and reuse.' },
|
|
{ k: 'Review', cmd: '/ce-code-review', d: 'Multi-agent review against the plan.' },
|
|
{ k: 'Compound', cmd: '/ce-compound', d: 'Capture what you learned, so the next loop starts smarter.' },
|
|
];
|
|
var CX = 960, CY = 560, R = 300, NR = 64;
|
|
var ang = function (i) { return (-90 + i * 60) * Math.PI / 180; };
|
|
var pt = function (a, r) { r = r == null ? R : r; return [CX + r * Math.cos(a), CY + r * Math.sin(a)]; };
|
|
function arcPath(a0, a1) {
|
|
var d = '';
|
|
for (var k = 0; k <= 64; k++) { var q = pt(lerp(a0, a1, k / 64)); d += (k ? 'L' : 'M') + q[0].toFixed(1) + ' ' + q[1].toFixed(1); }
|
|
return d;
|
|
}
|
|
|
|
function Loop(o) {
|
|
var T = o.T, acc = o.acc, L = CUES.Loop, Rt = CUES.Return, M = CUES.Remember;
|
|
var op = MOTION.enter(T, L + 0.1, 0.8) * (1 - MOTION.draw(T, M - 0.2, 0.7));
|
|
var s0 = L + 2.0, step = 1.45;
|
|
var si = function (i) { return s0 + i * step; };
|
|
var pos = -1;
|
|
for (var i = 0; i < 6; i++) pos += MOTION.draw(T, si(i) - 0.5, 0.5);
|
|
pos += MOTION.draw(T, Rt + 0.7, 1.4);
|
|
var dotA = ang(0) + (Math.max(pos, 0) * Math.PI) / 3;
|
|
var ringP = MOTION.draw(T, L + 0.3, 1.3);
|
|
var active = {};
|
|
if (T >= si(0) - 0.25 && T < Rt + 0.7) active[Math.min(5, Math.round(pos))] = true;
|
|
if (T >= Rt + 2.1) { active[0] = true; active[1] = true; }
|
|
var z = MOTION.draw(T, Rt - 0.3, 1.6) * (1 - MOTION.draw(T, M - 0.3, 1.0));
|
|
var sc = 1 + 0.45 * z, tx = z * (900 - 1.45 * 810), ty = z * (560 - 1.45 * 330);
|
|
var retP = clamp(pos - 5, 0, 1);
|
|
var card = MOTION.pop(T, Rt + 0.15, 0.5);
|
|
var cardPt = pt(dotA, R - 140);
|
|
var headA = ang(5) + (Math.PI / 3) * 0.78;
|
|
var hp = pt(headA);
|
|
var hRot = (headA * 180) / Math.PI + 90;
|
|
var dotPt = pt(dotA);
|
|
|
|
var ripples = [0, 1].map(function (i) {
|
|
var e = MOTION.enter(T, Rt + 2.1 + i * 0.3, 1.0);
|
|
var q = pt(ang(i));
|
|
return h('circle', { cx: q[0], cy: q[1], r: NR + 60 * e, fill: 'none', stroke: acc[0], strokeWidth: 4, opacity: e > 0 ? 1 - e : 0 });
|
|
});
|
|
|
|
var nodes = STEPS.map(function (st, i) {
|
|
var q = pt(ang(i)), lq = pt(ang(i), R + 158);
|
|
var pp = MOTION.pop(T, L + 0.5 + i * 0.13, 0.5);
|
|
var on = !!active[i];
|
|
return [
|
|
h('div', { style: { position: 'absolute', left: q[0] - NR, top: q[1] - NR, width: NR * 2, height: NR * 2, borderRadius: '50%', background: on ? acc[0] : C.paper, border: '3px solid ' + C.ink, boxSizing: 'border-box', transform: 'scale(' + pp * (on ? 1.1 : 1) + ')', display: 'flex', alignItems: 'center', justifyContent: 'center', font: '600 44px ' + SERIF, color: on ? C.onAcc : C.ink } }, i + 1),
|
|
h('div', { style: { position: 'absolute', left: lq[0], top: lq[1], transform: 'translate(-50%,-50%)', textAlign: 'center', opacity: MOTION.enter(T, L + 0.7 + i * 0.13, 0.5), whiteSpace: 'nowrap' } },
|
|
h('div', { style: { font: '600 34px/1 ' + SERIF, color: C.ink } }, st.k),
|
|
h('div', { style: { font: '500 20px ' + MONO, color: on ? acc[0] : C.mute, marginTop: 8 } }, st.cmd)),
|
|
];
|
|
});
|
|
|
|
var centers = STEPS.map(function (st, i) {
|
|
var b = i < 5 ? si(i + 1) : Rt + 0.4;
|
|
var e = MOTION.enter(T, si(i), 0.45);
|
|
return h('div', { style: { position: 'absolute', inset: 0, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', textAlign: 'center', gap: 14, opacity: win(T, si(i), b, 0.45, 0.3), transform: 'translateY(' + (1 - e) * 16 + 'px)' } },
|
|
h('div', { style: { font: '600 64px/1 ' + SERIF, letterSpacing: '-0.02em', color: C.ink } }, st.k),
|
|
h('div', { style: { font: '400 26px/1.35 ' + SANS, color: C.mute, textWrap: 'balance' } }, st.d));
|
|
});
|
|
|
|
return h('div', { style: { position: 'absolute', inset: 0, opacity: op } },
|
|
h('div', { style: { position: 'absolute', left: 0, top: 0, width: 1920, height: 1080, transformOrigin: '0 0', transform: 'translate(' + tx + 'px,' + ty + 'px) scale(' + sc + ')' } },
|
|
h('svg', { width: 1920, height: 1080, style: { position: 'absolute', inset: 0 } },
|
|
h('circle', { cx: CX, cy: CY, r: R, fill: 'none', stroke: C.line, strokeWidth: 3, pathLength: 1, strokeDasharray: 1, strokeDashoffset: 1 - ringP, transform: 'rotate(-90 ' + CX + ' ' + CY + ')' }),
|
|
pos > 0 && h('path', { d: arcPath(ang(0), ang(0) + Math.min(pos, 5) * Math.PI / 3), fill: 'none', stroke: C.ink, strokeWidth: 4, strokeLinecap: 'round' }),
|
|
retP > 0 && h('path', { d: arcPath(ang(5), ang(5) + retP * Math.PI / 3), fill: 'none', stroke: acc[0], strokeWidth: 9, strokeLinecap: 'round' }),
|
|
h('polygon', { points: '-14,10 0,-12 14,10', fill: acc[0], transform: 'translate(' + hp[0] + ' ' + hp[1] + ') rotate(' + hRot + ') scale(' + (retP >= 0.78 ? 1 : 0) + ')' }),
|
|
ripples),
|
|
pos >= -0.5 && h('div', { style: { position: 'absolute', left: dotPt[0] - 11, top: dotPt[1] - 11, width: 22, height: 22, borderRadius: '50%', background: C.ink, opacity: MOTION.enter(T, si(0) - 0.5, 0.3) * (1 - prog(T, Rt + 2.1, 0.3)) } }),
|
|
nodes,
|
|
h('div', { style: { position: 'absolute', left: CX - 210, top: CY - 150, width: 420, height: 300 } },
|
|
h('div', { style: { position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', font: '500 22px ' + MONO, letterSpacing: '0.14em', textTransform: 'uppercase', color: C.mute, opacity: win(T, L + 0.9, si(0), 0.5, 0.35) } }, 'The loop'),
|
|
centers),
|
|
h('div', { style: { position: 'absolute', left: cardPt[0], top: cardPt[1], transform: 'translate(-50%,-50%) scale(' + card + ')', background: C.paper, border: '3px solid ' + C.ink, borderRadius: 12, padding: '12px 18px', boxShadow: '0 10px 30px rgba(0,0,0,0.4)', whiteSpace: 'nowrap', opacity: T >= Rt ? 1 : 0 } },
|
|
h('div', { style: { font: '500 14px ' + MONO, color: C.mute, letterSpacing: '0.06em' } }, 'docs/solutions/'),
|
|
h('div', { style: { font: '600 20px ' + MONO, color: C.ink, marginTop: 4 } }, 'env-var-trap.md'))),
|
|
h('div', { style: { position: 'absolute', left: 0, right: 0, bottom: 0, height: 330, background: 'linear-gradient(to bottom, rgba(27,27,31,0), ' + C.paper + ' 38%)', opacity: win(T, Rt + 2.2, M + 0.2, 0.7, 0.5) } }),
|
|
h(Headline, { T: T, a: Rt + 2.4, b: M + 0.2, eyebrow: 'Compound feeds the next brainstorm and plan', text: 'That return arrow is the whole point.', y: 860, size: 64 }));
|
|
}
|
|
|
|
// ── 5. Run one teaches it. Run two remembers. ─────────────────────────────
|
|
function Term(o) {
|
|
var T = o.T;
|
|
var dots = [0, 1, 2].map(function () { return h('div', { style: { width: 14, height: 14, borderRadius: '50%', background: '#3A3A3A' } }); });
|
|
var lines = o.lines.map(function (ln) {
|
|
var text = ln.type ? ln.t.slice(0, Math.floor(ln.t.length * prog(T, ln.at, ln.dur || 0.8))) : ln.t;
|
|
return h('div', { style: { font: (ln.b ? 600 : 400) + ' 26px/1.5 ' + MONO, color: ln.hl ? o.acc[0] : ln.dim ? '#8E897E' : '#EDEAE2', whiteSpace: 'pre', opacity: ln.type ? (T >= ln.at ? 1 : 0) : MOTION.enter(T, ln.at, 0.35), minHeight: 39 } }, text);
|
|
});
|
|
return h('div', { style: { position: 'absolute', left: o.x, top: o.y, width: o.w, height: o.h, background: C.term, borderRadius: 18, opacity: o.op, transform: 'translateX(' + o.dx + 'px)', overflow: 'hidden', border: '1px solid ' + C.line, boxShadow: '0 30px 60px rgba(0,0,0,0.35)' } },
|
|
h('div', { style: { height: 56, display: 'flex', alignItems: 'center', gap: 10, padding: '0 22px', borderBottom: '1px solid #2A2A2A' } },
|
|
dots,
|
|
h('div', { style: { font: '500 18px ' + MONO, color: '#A29D92', marginLeft: 14, whiteSpace: 'nowrap' } }, o.title)),
|
|
h('div', { style: { padding: '28px 34px', display: 'flex', flexDirection: 'column', gap: 6 } }, lines));
|
|
}
|
|
|
|
function Remember(o) {
|
|
var T = o.T, M = CUES.Remember, end = CUES.Ratio + 0.4;
|
|
var drift = 1 + 0.025 * prog(T, M, CUES.Ratio - M);
|
|
var l = win(T, M + 0.3, end, 0.8, 0.6), r = win(T, M + 2.4, end, 0.8, 0.6);
|
|
return h('div', { style: { position: 'absolute', inset: 0, transform: 'scale(' + drift + ')', transformOrigin: '50% 60%' } },
|
|
h(Headline, { T: T, a: M + 0.2, b: end, eyebrow: 'Two real sessions, 18 days apart', text: 'Run one teaches it. Run two remembers.' }),
|
|
h(Term, { T: T, acc: o.acc, x: 140, y: 390, w: 800, h: 500, op: l, dx: (1 - MOTION.enter(T, M + 0.3, 0.8)) * -40, title: 'run 1 · day 0',
|
|
lines: [
|
|
{ at: M + 0.9, t: '$ /ce-compound', type: true, dur: 0.6 },
|
|
{ at: M + 1.7, t: '✓ Captured 1 learning' },
|
|
{ at: M + 2.0, t: ' docs/solutions/env-var-trap.md', hl: true, b: true },
|
|
{ at: M + 2.4, t: ' Workers read env vars at boot —', dim: true },
|
|
{ at: M + 2.6, t: ' restart them after config changes.', dim: true },
|
|
] }),
|
|
h(Term, { T: T, acc: o.acc, x: 980, y: 390, w: 800, h: 500, op: r, dx: (1 - MOTION.enter(T, M + 2.4, 0.8)) * 40, title: 'run 2 · day 18 · unrelated work',
|
|
lines: [
|
|
{ at: M + 3.0, t: '$ /ce-plan add a job timeout', type: true, dur: 0.8 },
|
|
{ at: M + 4.1, t: '→ Searching docs/solutions/ …', dim: true },
|
|
{ at: M + 4.7, t: '✓ Found 1 relevant learning' },
|
|
{ at: M + 5.0, t: ' env-var-trap.md', hl: true, b: true },
|
|
{ at: M + 5.5, t: ' Plan: restart workers on deploy', dim: true },
|
|
] }));
|
|
}
|
|
|
|
// ── 6. 80 / 20 ────────────────────────────────────────────────────────────
|
|
function Ratio(o) {
|
|
var T = o.T, acc = o.acc, Ra = CUES.Ratio, end = CUES.Outro + 0.3;
|
|
var op = win(T, Ra + 0.2, end, 0.6, 0.6);
|
|
var a = MOTION.draw(T, Ra + 0.8, 1.3), b = MOTION.draw(T, Ra + 1.8, 0.8);
|
|
var W = 1640, w80 = W * 0.8, w20 = W * 0.2 - 12;
|
|
return h('div', { style: { position: 'absolute', inset: 0, opacity: op } },
|
|
h(Headline, { T: T, a: Ra + 0.2, b: end, eyebrow: 'Where the effort goes', text: '80% planning and review. 20% execution.' }),
|
|
h('div', { style: { position: 'absolute', left: 140, top: 520, width: W, height: 170, display: 'flex', gap: 12 } },
|
|
h('div', { style: { width: w80 * a, height: '100%', background: C.ink, borderRadius: 16, overflow: 'hidden', display: 'flex', flexDirection: 'column', justifyContent: 'center', padding: '0 40px', boxSizing: 'border-box' } },
|
|
h('div', { style: { font: '600 60px/1 ' + SERIF, color: C.paper, opacity: MOTION.enter(T, Ra + 1.6, 0.5), whiteSpace: 'nowrap' } }, '80%'),
|
|
h('div', { style: { font: '500 22px ' + MONO, color: '#3C3C43', marginTop: 12, opacity: MOTION.enter(T, Ra + 1.8, 0.5), whiteSpace: 'nowrap' } }, 'brainstorm · plan · review · compound')),
|
|
h('div', { style: { width: w20 * b, height: '100%', background: acc[0], borderRadius: 16, overflow: 'hidden', display: 'flex', flexDirection: 'column', justifyContent: 'center', padding: '0 32px', boxSizing: 'border-box' } },
|
|
h('div', { style: { font: '600 60px/1 ' + SERIF, color: C.onAcc, opacity: MOTION.enter(T, Ra + 2.3, 0.5), whiteSpace: 'nowrap' } }, '20%'),
|
|
h('div', { style: { font: '500 22px ' + MONO, color: C.onAcc, marginTop: 12, opacity: MOTION.enter(T, Ra + 2.4, 0.5), whiteSpace: 'nowrap' } }, 'work'))),
|
|
h(Rise, { T: T, a: Ra + 2.8, b: end, style: { left: 140, top: 760, font: '400 34px ' + SANS, color: C.mute, whiteSpace: 'nowrap' } }, 'The point is leverage, not ceremony.'));
|
|
}
|
|
|
|
// ── 7. Outro ──────────────────────────────────────────────────────────────
|
|
function Outro(o) {
|
|
var T = o.T, O = CUES.Outro;
|
|
var op = MOTION.enter(T, O + 0.1, 0.6) * (1 - MOTION.draw(T, TOTAL - 0.8, 0.8));
|
|
var drift = 1 + 0.03 * prog(T, O, TOTAL - O);
|
|
var lg = MOTION.pop(T, O + 0.2, 0.7);
|
|
var facts = [o.skills && o.skills + ' skills', o.hosts && o.hosts + ' agent hosts', 'github.com/EveryInc/compound-engineering-plugin'].filter(Boolean).join(' · ');
|
|
return h('div', { style: { position: 'absolute', inset: 0, opacity: op, transform: 'scale(' + drift + ')', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', whiteSpace: 'nowrap' } },
|
|
h('img', { src: LOGO, alt: '', style: { width: 170, height: 170, borderRadius: 36, transform: 'scale(' + lg + ')', marginBottom: 40 } }),
|
|
h('div', { style: { font: '600 112px/1 ' + SERIF, letterSpacing: '-0.03em', color: C.ink, opacity: MOTION.enter(T, O + 0.5, 0.6), transform: 'translateY(' + (1 - MOTION.enter(T, O + 0.5, 0.7)) * 24 + 'px)' } }, 'Compound Engineering'),
|
|
h('div', { style: { font: '400 34px ' + SANS, color: C.mute, marginTop: 26, opacity: MOTION.enter(T, O + 0.9, 0.6) } }, 'Each unit of engineering work should make the next one easier.'),
|
|
h('div', { style: { font: '500 30px ' + MONO, color: C.ink, background: C.term, border: '1px solid ' + C.line, borderRadius: 14, padding: '20px 32px', marginTop: 56, opacity: MOTION.enter(T, O + 1.4, 0.6) } }, '/plugin install compound-engineering'),
|
|
h('div', { style: { font: '500 22px ' + MONO, color: C.mute, marginTop: 40, opacity: MOTION.enter(T, O + 1.8, 0.6), letterSpacing: '0.04em' } }, facts));
|
|
}
|
|
|
|
function Piece(o) {
|
|
var T = o.T, acc = ACC, D = CUES.Debt, I = CUES.Invert;
|
|
return h('div', { style: { position: 'absolute', left: 0, top: 0, width: 1920, height: 1080, background: C.paper, overflow: 'hidden' } },
|
|
h(Headline, { T: T, a: D + 0.3, b: I + 0.45, eyebrow: 'Traditional development', text: 'Every change makes the next one harder.' }),
|
|
h(Headline, { T: T, a: I + 0.55, b: CUES.Loop + 0.4, eyebrow: 'Compound engineering', text: 'Every change makes the next one easier.' }),
|
|
h(Chart, { T: T, acc: acc }),
|
|
h(Loop, { T: T, acc: acc }),
|
|
h(Remember, { T: T, acc: acc }),
|
|
h(Ratio, { T: T, acc: acc }),
|
|
h(Outro, { T: T, skills: o.skills, hosts: o.hosts }));
|
|
}
|
|
|
|
// ── Player ────────────────────────────────────────────────────────────────
|
|
// Plays while on screen, pauses when scrolled away or the tab is hidden.
|
|
// Reduced-motion visitors get a still frame and a play button instead of
|
|
// autoplay. The button pauses and resumes for everyone.
|
|
function mount(root) {
|
|
var skills = +root.getAttribute('data-skills') || 0;
|
|
var hosts = +root.getAttribute('data-hosts') || 0;
|
|
var stage = document.createElement('div');
|
|
stage.className = 'ce-explainer-stage';
|
|
stage.setAttribute('aria-hidden', 'true');
|
|
var btn = document.createElement('button');
|
|
btn.type = 'button';
|
|
btn.className = 'ce-explainer-toggle';
|
|
root.appendChild(stage);
|
|
root.appendChild(btn);
|
|
|
|
var reduced = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
// Still frame: the loop, zoomed in on the return arrow.
|
|
var T = reduced ? CUES.Remember - 1.2 : 0;
|
|
var wanted = !reduced, visible = false, raf = 0, last = 0;
|
|
|
|
function render() {
|
|
patch(stage, 0, Piece({ T: T, skills: skills, hosts: hosts }), false);
|
|
}
|
|
function fit() {
|
|
var s = root.clientWidth / 1920;
|
|
stage.firstChild && (stage.firstChild.style.transform = 'scale(' + s + ')');
|
|
}
|
|
function frame(ts) {
|
|
if (last) T = (T + Math.min(0.1, (ts - last) / 1000)) % TOTAL;
|
|
last = ts;
|
|
render();
|
|
fit();
|
|
raf = requestAnimationFrame(frame);
|
|
}
|
|
function sync() {
|
|
var run = wanted && visible && !document.hidden;
|
|
if (run && !raf) { last = 0; raf = requestAnimationFrame(frame); }
|
|
if (!run && raf) { cancelAnimationFrame(raf); raf = 0; }
|
|
btn.setAttribute('aria-label', wanted ? 'Pause animation' : 'Play animation');
|
|
btn.setAttribute('data-state', wanted ? 'playing' : 'paused');
|
|
}
|
|
|
|
btn.addEventListener('click', function () { wanted = !wanted; sync(); });
|
|
document.addEventListener('visibilitychange', sync);
|
|
if ('IntersectionObserver' in window) {
|
|
new IntersectionObserver(function (es) { visible = es[0].isIntersecting; sync(); }, { threshold: 0.2 }).observe(root);
|
|
} else {
|
|
visible = true;
|
|
}
|
|
if ('ResizeObserver' in window) new ResizeObserver(fit).observe(root);
|
|
else window.addEventListener('resize', fit);
|
|
|
|
render();
|
|
fit();
|
|
sync();
|
|
}
|
|
|
|
var roots = document.querySelectorAll('.ce-explainer');
|
|
for (var i = 0; i < roots.length; i++) mount(roots[i]);
|
|
})();
|