Files
zooqueen 62bbc7766c skills: give each skill the directory its name declares
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.
2026-08-13 18:17:24 -07:00

5.5 KiB
Raw Permalink Blame History

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.
tags
3d, cards, split, tilt, comparison, symmetric, layout

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 rotateY and 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-stage reads 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

  • perspective on the scene root is REQUIRED; preserve-3d on 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).