Every SKILL.md already declared the new name — name: frames, frames-cli, frames-core, and so on — but the directories were never moved, so the tree carried frames-* in content and hyperframes-* on disk. Everything that resolves a skill by path missed: the core-skill list, the plugin marketplace pin, and the docs the CLI reads at runtime. Eight tests failed on main because the files they open do not exist. The directories now match what the files say they are, and the guide the claude-design contract reads takes the name that contract already used.
5.4 KiB
name, description, metadata
| name | description | metadata | ||
|---|---|---|---|---|
| scale-swap-transition | Coordinated shrink-out + spring pop-in morph-like transition between two elements — no SVG path interpolation needed. |
|
Scale-Swap Transition
Simulates a "morph" between two DOM elements by overlapping exit and entrance scale animations. Lighter weight than card-morph-anchor.md (which morphs container dimensions — use that for SHAPE changes; this rule is for SAME-shape state swaps) and easier than SVG path interpolation.
At a single trigger, two coordinated tweens fire:
- Outgoing: scale
1.0 → EXIT_SCALE+ opacity1 → 0, fastpower2.in(rushing away). - Incoming: scale
EXIT_SCALE → 1.0+ opacity0 → 1,back.out(BOUNCE_FACTOR)(arriving with weight).
A small OVERLAP window during which both are mid-tween creates the morph illusion; the incoming sits on top via z-index so the outgoing's fade-tail doesn't bleed through.
Recipe
<!-- Both cards position: absolute; inset: 0 in one fixed-size wrapper — same
footprint, same transform-origin: 50% 50%. Incoming starts opacity: 0,
transform: scale(EXIT_SCALE), z-index above the outgoing. -->
<div class="swap-wrap">
<div class="card outgoing" id="outgoing">{outgoingIcon} {outgoingLabel}</div>
<div class="card incoming" id="incoming">
{incomingIcon} {incomingLabel}
<div class="sub" id="sub">{incomingSubline}</div>
</div>
</div>
// Outgoing: shrink + fade fast
tl.to(
"#outgoing",
{ scale: EXIT_SCALE, opacity: 0, duration: EXIT_DUR, ease: "power2.in" },
TRIGGER,
);
// Incoming: pops in with overshoot, starting OVERLAP before the exit finishes
tl.to(
"#incoming",
{ scale: 1.0, opacity: 1, duration: ENTER_DUR, ease: `back.out(${BOUNCE_FACTOR})` },
TRIGGER + EXIT_DUR - OVERLAP,
);
// Inner content reveals AFTER the incoming settles
tl.fromTo(
"#sub",
{ opacity: 0, y: SUB_REVEAL_Y_PX },
{ opacity: 1, y: 0, duration: SUB_REVEAL_DUR, ease: "power3.out" },
TRIGGER + EXIT_DUR + SUB_REVEAL_DELAY,
);
Variations
- Delayed inner content reveal — the classic pattern above: morph the container, then reveal inner text once it settles; the 0.2–0.4 s gap lets the eye land on the new shape before reading.
- Triple swap (3-state cycle) — chain A→B→C with triggers
TRIGGER_AB/TRIGGER_BC; each transition is its own tween pair, the previous incoming becoming the next outgoing. State-evolution narratives (early → mid → final labels). - Color-shift transition (no scale) — for a flat morph between same-shape states, drop the scale and keep opacity + a brief background hue tween; less dramatic, more product-UI tone.
Values
| token | range | notes |
|---|---|---|
| TRIGGER | ≥ outgoing settled + a presence-dwell | the outgoing must "land" before transforming |
| EXIT_DUR | 0.3–0.5 s | |
| ENTER_DUR | 0.45–0.7 s | longer than EXIT_DUR so the overshoot can settle |
| OVERLAP | 0.1–0.2 s | >0.3 s both are clearly visible together (no morph); <0.05 s leaves a visible empty gap |
| EXIT_SCALE | 0.6–0.8 | smaller exits feel dramatic but risk reading as "vanish" instead of "morph" |
| BOUNCE_FACTOR | 1.4 soft · 1.8 firm · 2.2 cartoony | |
| SUB_REVEAL_DELAY | 0.2–0.4 s | reveals during the morph compete with the swap for attention |
| BRAND_REVEAL_AT | < TRIGGER | context (brand, eyebrow) sets the stage early; revealed AT the swap it competes with the headline beat |
Critical Constraints
- Incoming z-index ABOVE outgoing — otherwise the outgoing's fade-tail (opacity 0.3–0.5) bleeds through and double-exposes the frame.
- Both elements share
transform-origin: 50% 50%— different origins make the morph read as one thing teleporting elsewhere. - Bouncy ease ONLY on the incoming — outgoing
power2.in, incomingback.out; reversed, the swap feels mechanical. - Both cards
position: absolute; inset: 0in the same fixed-size wrapper (sized to fit both states; the wrap never resizes). - Don't
display: nonethe outgoing after the fade — leave it atopacity: 0so layout doesn't reflow. - Inner content reveals after the container settles; climax dwell ≥ 1 s after the final state + subline land.
See also
press-release-spring (a button press TRIGGERS the swap — cause and effect) · card-morph-anchor (shape-changing alternative) · reactive-displacement (when the replacement should read as a causal collision) · sine-wave-loop (idle breathing on the final state).