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.5 KiB
name, description, metadata
| name | description | metadata | ||
|---|---|---|---|---|
| split-tilt-cards | Two cards side-by-side with opposing Y-rotation creating a symmetric 3D split-screen layout for comparisons or feature pairs. |
|
Split Tilt Cards
Two cards side-by-side with opposing rotateY (left +TILT, right −TILT) — a symmetric "book-open" 3D split for comparisons, before/after, feature pairs. Each card slides in from its own side (reinforcing "they came from their own worlds and met here"), then the pair idles in counter-phase.
How It Works
perspective on the scene root (REQUIRED — without it rotateY flattens to a 2D layout) and transform-style: preserve-3d on the stage and both cards. Entry starts each card off-axis with TILT + TILT_OVERSHOOT, settling to TILT — a pivot-into-place. Idle is a gentle counter-phase y-bob (the two yoyo tweens run in opposite directions); copy fades up during the cards' settle, not after.
Recipe
<!-- inside a standard scene clip (frames-core) -->
<div class="split-stage">
<div class="card card-left">
<div class="card-eyebrow">{leftEyebrow}</div>
<div class="card-headline">{leftHeadline}</div>
<div class="card-body">{leftBody}</div>
</div>
<div class="card card-right">…</div>
</div>
.scene-root {
display: grid;
place-items: center;
perspective: SCENE_PERSPECTIVE; /* REQUIRED */
}
.split-stage {
display: flex;
gap: STAGE_GAP;
transform-style: preserve-3d;
}
.card {
width: CARD_WIDTH;
transform-style: preserve-3d;
will-change: transform;
}
/* Shadow falls WITH the facing direction: left card faces right → shadow right. */
.card-left {
box-shadow: -CARD_SHADOW_OFFSET CARD_SHADOW_DROP CARD_SHADOW_BLUR {shadowColor};
}
.card-right {
box-shadow: CARD_SHADOW_OFFSET CARD_SHADOW_DROP CARD_SHADOW_BLUR {shadowColor};
}
// Entry — from outside, opposing tilts settle with a small pivot
tl.fromTo(
".card-left",
{ x: -ENTRY_SLIDE_DIST, rotateY: TILT + TILT_OVERSHOOT, opacity: 0 },
{ x: 0, rotateY: TILT, opacity: 1, duration: ENTRY_DUR, ease: "power3.out" },
LEFT_AT,
);
tl.fromTo(
".card-right",
{ x: ENTRY_SLIDE_DIST, rotateY: -TILT - TILT_OVERSHOOT, opacity: 0 },
{ x: 0, rotateY: -TILT, opacity: 1, duration: ENTRY_DUR, ease: "power3.out" },
RIGHT_AT,
);
// Counter-phase idle bob — opposite signs = alive; synchronized = conveyor belt
tl.to(
".card-left",
{ y: -FLOAT_AMP, duration: FLOAT_DURATION / 2, ease: "sine.inOut", yoyo: true, repeat: 1 },
IDLE_START,
);
tl.to(
".card-right",
{ y: FLOAT_AMP, duration: FLOAT_DURATION / 2, ease: "sine.inOut", yoyo: true, repeat: 1 },
IDLE_START,
);
// Copy fades up during the settle
tl.from(
".card-eyebrow, .card-headline, .card-body",
{ opacity: 0, y: COPY_RISE, stagger: COPY_STAGGER, duration: COPY_DUR, ease: "power2.out" },
COPY_REVEAL_AT,
);
Variations
- Badges / floating labels: position them on the PARENT, never inside a card — inside they inherit the
rotateYand tilt off-axis. - 3+ cards: center card stays flat (
rotateY: 0), outer two tilt inward — "old way / nothing / our way." - Zoom-through: a separate camera tween scaling
.split-stagereads as the viewer crossing the gap between the tilted pair.
Values
| token | range | notes |
|---|---|---|
| SCENE_PERSPECTIVE | 1000–2400px | lower exaggerates the tilt; higher reads near-isometric |
| TILT | 10–18° | < 10 reads almost flat; > 18 folds shut and copy blurs |
| TILT_OVERSHOOT | 4–12° | the pivot-into-place feel |
| STAGE_GAP | 40–120px (~0.06–0.15×CARD_WIDTH) | small = fused pair; large = compared-but-separate |
| CARD_WIDTH | 480–820px @1920 | 2×CARD_WIDTH + STAGE_GAP ≤ 0.95×stage at full tilt |
| ENTRY_SLIDE_DIST | 200–500px (~0.3–0.6×CARD_WIDTH) | |
| ENTRY_DUR | 0.6–1.2s | |
| RIGHT_AT | LEFT_AT + 0–0.3s | zero feels mechanical; large fragments the pair |
| FLOAT_AMP | 3–8px | subtle is the point |
| FLOAT_DURATION | 1.6–3.2s round trip | breathing cadence; IDLE_START ≥ entry end |
| COPY_REVEAL_AT | during the entry tail | copy popping in after cards are idle reads disconnected |
Critical Constraints
perspectiveon the scene root is REQUIRED;preserve-3don the stage AND each card.- Shadow direction matches tilt — left card faces right → shadow falls right (and mirrored). Wrong sign reads as broken 3D.
- Counter-phase idle — the two bobs run with opposite signs at the same position.
- Badges outside the card divs (they'd inherit the rotation).
- Body copy ≤ 2 lines per card — tilted long paragraphs collapse into perspective blur.
- Symmetric weight — same width, same vertical center, similar line counts; asymmetry breaks the comparison metaphor.
See also
card-morph-anchor (the pair can morph into one unified shape afterward) · counting-dynamic-scale (numbers as each side's headline) · sine-wave-loop (the idle form).