Skill 18 · Hyperframes Animation
Subchapter 18.101
rules/theme-crossfade-morph.mdMarkdown9 KBView on GitHub
The whole world re-skins while one thing holds still. A composer box cycles through four IDE themes; a checkout widget flips through brand skins — background, typography, corner radii, toolbar icons, footer logos all change at once, in place, in ~0.3s, N times — and through every flip one anchor element (the prompt string, the widget layout, the wordmark) never moves. The anchor’s stillness is the rhetorical claim: everything changes, this doesn’t.
Boundary: card-morph-anchor.md morphs one container between two shots — its dimensions, radius, and surface tween continuously. This rule re-skins an entire scene through N discrete states: nothing tweens property-by-property (fonts, icons, and logos can’t interpolate); the “morph” is a fast simultaneous crossfade of complete pre-styled layers. (scale-swap-transition.md swaps an element at center; here the surroundings swap and the element holds.)
position: absolute; inset: 0) containing everything that changes: background, shell/chrome, toolbar icons, footer logos, typography. All N_SKINS layers exist in the DOM from t=0, stacked; skin 0 starts visible, the rest at opacity: 0.MORPH_DUR (~0.3s): outgoing 1 → 0, incoming 0 → 1. Because both layers are complete, every property “blends” simultaneously for free — including the un-tweenable ones (font families, icon glyphs, logos), which read as morphing precisely because everything else is mid-blend around them.T_k = CYCLE_START + k × (SKIN_HOLD + MORPH_DUR). Steady cadence by default; hold the final skin longest when it’s the resolve.The only animated property is opacity — which is why this rule is seek-safe with zero special machinery.
<!-- inside a standard scene clip (hyperframes-core) -->
<div class="theme-stage">
<!-- One complete pre-styled layer per skin; skin-0 visible at t=0 -->
<div class="skin skin-0"><div class="shell">…terminal chrome, mono type, footer badge…</div></div>
<div class="skin skin-1">
<div class="shell">…rounded composer, sans type, toolbar pills, logo…</div>
</div>
<div class="skin skin-2"><div class="shell">…dark shell, its own chrome and footer…</div></div>
<!-- The anchor: rendered ONCE, above every skin. It never moves. -->
<div class="anchor" id="anchor">{anchorText}</div>
</div>.theme-stage {
position: absolute;
inset: 0;
}
.skin {
position: absolute;
inset: 0;
opacity: 0;
/* Each skin fully self-styled: its own background, fonts, radii,
icons, chrome, logos. Nothing inherited across skins. */
}
.skin-0 {
opacity: 1; /* the opening state — matches the timeline's fromTo */
}
.shell {
/* CRITICAL: shared geometry. The shell box (and any element that
"persists" across skins — toolbar row, footer row) sits at the SAME
coordinates in every skin, so mid-blend frames read as one UI
changing clothes, not two UIs ghosting. */
position: absolute;
left: SHELL_LEFT;
top: SHELL_TOP;
width: SHELL_WIDTH;
height: SHELL_HEIGHT;
}
.anchor {
position: absolute;
z-index: 10; /* above every skin */
left: ANCHOR_LEFT;
top: ANCHOR_TOP;
/* No transforms, no transitions — the stillness is load-bearing. */
}const skins = gsap.utils.toArray(".skin");
// Boundary k→k+1 at T_k: outgoing fades down as incoming fades up —
// ONE simultaneous crossfade, everything blends at once.
skins.forEach((skin, k) => {
if (k === 0) return; // skin-0 is the opening state
const at = CYCLE_START + k * (SKIN_HOLD + MORPH_DUR);
tl.fromTo(skin, { opacity: 0 }, { opacity: 1, duration: MORPH_DUR, ease: "power2.inOut" }, at);
tl.to(
skins[k - 1],
{ opacity: 0, duration: MORPH_DUR, ease: "power2.inOut" },
at, // same position — the blend is simultaneous, never sequential
);
});
// The anchor gets NO tweens. Its absence from the timeline is the point.MORPH_DUR). The paired swap sells “same product, every brand.”N−1 skins, then hold the final skin 2–3× SKIN_HOLD; the cycle demonstrates breadth, the brake lands the resolve. Precompute the hold array; don’t drift the cadence without cause.| token | range | notes |
|---|---|---|
| N_SKINS | 3–5 | two is a before/after (consider card-morph-anchor); past five the cycle pads |
| SKIN_HOLD | 0.8–1.5s | long enough to register the logo/footer identity, short enough to keep the churn rhetorical |
| MORPH_DUR | 0.25–0.4s, ~0.3s canonical | faster reads as a hard cut; slower reads as a mushy dissolve with lingering double-exposure |
| CYCLE_START | ≥ anchor settle + a beat | after the anchor and skin-0 have fully registered |
| SHELL geometry | — | shell / toolbar / footer coordinates identical across skins; contents inside the slots differ freely |
| ANCHOR position | — | identical to the pixel across the scene (per-layer form: identical in every skin) |
| washout / brake | shell ~0.2–0.3 opacity; hold 2–3× SKIN_HOLD | — |
opacity — no borderRadius / background tweens; radii and colors change by being different in the next layer. Visibility via opacity only, never display / visibility toggles (they can’t blend mid-fade).discrete-text-sequence territory; the ~0.3s blend is specifically the “morph” read.context-sensitive-cursor (caret color switches at each T_k) · discrete-text-sequence (type the anchor first; or the hard-cut alternative) · card-morph-anchor (the single-container sibling) · spring-pop-entrance (the lockup that joins the anchor at the resolve) · sine-wave-loop (drifting field under the cycle — never on the anchor).