* feat(skills): c2v mining pass over animation blueprints and rules Compacts ~45 existing animation rules/blueprints into tighter recipe form (net -3.4k lines) and adds 17 mined from the c2v corpus: - 7 blueprints: agent-progress-theater, camera-journey, fixed-anchor-cycle, panel-edit-live-sync, prompt-type-submit-generate, transcript-scroll-artifact-reveal, zoom-out-workspace-reveal - 10 rules: 3d-camera-flight, anchored-layout-expand, chart-scrub-readout, chromatic-glitch, control-target-sync, cursor-drag, gradient-text-sweep, multi-cursor-choreography, particle-burst, theme-crossfade-morph Both indexes updated. * feat(skills): sync product-launch script bank with mined blueprint roles The role->blueprint script bank in product-launch-video/story-design.md is kept 1:1 with blueprints-index role declarations, which the c2v mining pass expanded. Adds the 25 missing entries (script-shape descriptor + example lines + pattern): 13 for the 7 new blueprints, 12 for role widenings on 7 existing ones (cursor-ui-demo, dataviz-countup, titlecard-reveal, et al.), and states the 1:1 sync contract in the bank's intro. * docs(skills): cover constellation-hub scatter-drift variant in the script bank Review follow-up on #2680: the SOCIAL_PROOF constellation-hub entry patterned only the orbit shape; the c2v pass added a scatter-drift end-card variant with the opposite geometry (no hub, no ring). Adds an example line and extends the pattern so a scatter-drift beat's VO isn't steered toward the orbit shape.
6.6 KiB
name, description, metadata
| name | description | metadata | ||
|---|---|---|---|---|
| 3d-text-depth-layers | Multiple offset text layers create a stacked 3D shadow / extrusion effect on large typography — more impactful than CSS text-shadow because each layer is a full DOM element. |
|
3D Text Depth Layers
The same text rendered N times at increasing offsets — back layers translucent, front layer full opacity and brand color — creates a physical "stacked extrusion" depth illusion on large typography. Distinct from text-shadow (which can't have per-layer hue / opacity / animation): each layer is a real DOM element.
How It Works
A build script appends LAYER_COUNT copies back-to-front; each back layer sits at translate(i × OFFSET_X, i × OFFSET_Y) with alpha stepping down per layer, while the front copy (i = 0) is position: relative so it defines the container size (back layers stack absolutely behind it). The default entrance cascades the layers' fades back-to-front while a proxy tween grows the offsets from 0 → full, so the depth "builds forward" and lands as the last layer fades in.
Recipe
<!-- inside a standard scene clip (hyperframes-core) -->
<div class="depth-stack">
<!-- layers injected by script — LAYER_COUNT copies of {label} -->
</div>
.depth-stack {
position: relative; /* front layer defines size; back layers stack behind */
}
.depth-text {
font-weight: 900; /* black weight — thin text loses the illusion */
font-size: HERO_FONT_SIZE;
letter-spacing: HERO_LETTER_SPACING;
line-height: 1;
color: {frontColor};
}
.depth-text.is-back {
position: absolute;
top: 0;
left: 0;
pointer-events: none; /* decorative */
}
.depth-text.is-front {
position: relative;
z-index: 10;
}
const stack = document.querySelector(".depth-stack");
// Build back-to-front so the FRONT (i=0) is appended LAST
for (let i = LAYER_COUNT - 1; i >= 0; i--) {
const el = document.createElement("div");
el.className = "depth-text " + (i === 0 ? "is-front" : "is-back");
el.textContent = "{label}";
if (i > 0) {
const alpha = Math.max(BACK_ALPHA_MAX - i * BACK_ALPHA_STEP, BACK_ALPHA_MIN);
el.style.color = `rgba({backHueRGB}, ${alpha})`; // rgba in color, NOT element opacity
el.style.transform = `translate(${i * OFFSET_X}px, ${i * OFFSET_Y}px)`;
}
el.dataset.layer = String(i);
stack.appendChild(el);
}
// Cascade entry — back layers fade in first, building forward
stack.querySelectorAll(".depth-text").forEach((el) => {
const i = Number(el.dataset.layer);
const finalAlpha = i === 0 ? 1 : Math.max(BACK_ALPHA_MAX - i * BACK_ALPHA_STEP, BACK_ALPHA_MIN);
tl.fromTo(
el,
{ opacity: 0 },
{ opacity: finalAlpha, duration: LAYER_FADE_DUR, ease: "power2.out" },
LAYER_CASCADE_START + (LAYER_COUNT - 1 - i) * LAYER_CASCADE_STEP,
);
});
// Depth grows on entry — offsets interpolate 0 → full
const depthState = { p: 0 };
tl.to(
depthState,
{
p: 1,
duration: DEPTH_GROW_DUR,
ease: "power2.out",
onUpdate: () => {
stack.querySelectorAll(".depth-text.is-back").forEach((el) => {
const i = Number(el.dataset.layer);
el.style.transform = `translate(${i * OFFSET_X * depthState.p}px, ${i * OFFSET_Y * depthState.p}px)`;
});
},
},
LAYER_CASCADE_START, // align with the cascade so depth lands as the last layer fades in
);
Variations
- Static depth (single hero shot) — render all layers at final positions from t=0; optionally fade the whole stack in with a subtle scale (0.94–0.98 → 1, 0.5–0.8s).
- Dynamic depth pulse — after the grow completes, modulate the offsets with a sine multiplier
1 + sin(p) × BEAT_AMP(BEAT_AMP 0.2–0.6; one beat per 0.7–1.5s reads as a heartbeat). - Color-shift back layers — instead of fading to translucent, step hue/lightness per layer:
hsla(HUE_BASE − i × HUE_STEP, SAT_PCT%, LIGHT_BASE − i × LIGHT_STEP%, 1)(HUE_STEP 4–12°; larger reads as glitch). Depth reads as a colored cast shadow.
Values
| token | range | notes |
|---|---|---|
| LAYER_COUNT | 4–6 | <4 doesn't read as 3D; >6 clutters on tight kerning |
| OFFSET_X / OFFSET_Y | 1–3px each | >4px reads as glitch / chromatic aberration, not depth |
| BACK_ALPHA_MAX | 0.6–0.85 | nearest back layer; >0.9 fights the front for dominance |
| BACK_ALPHA_STEP | 0.08–0.15 | small = soft gradient; large = discrete plates |
| BACK_ALPHA_MIN | 0.1–0.2 | floor — below 0.1 the deepest layer vanishes on dark backgrounds |
| HERO_FONT_SIZE | 60px min; 200–340px hero | thin/small text loses the layered illusion |
| HERO_LETTER_SPACING | −0.03em–0 | tighter makes offsets read as depth, not repetition |
| LAYER_CASCADE_STEP | 0.04–0.10s | smaller ≈ simultaneous; larger feels stepped |
| LAYER_FADE_DUR | 0.3–0.6s | per-layer fade |
| DEPTH_GROW_DUR | 0.4–0.8s | ≈ LAYER_FADE_DUR × LAYER_COUNT / 2 so depth lands with the last fade |
Critical Constraints
- Offset direction implies light direction —
(+x, +y)= light upper-left,(-x, +y)= upper-right; one sign convention for the whole composition. - Back layers translucent OR darker — never more saturated than the front (reads as a halo, not depth).
- Set back-layer color via
rgba()incolor, not elementopacity— opacity fades the whole rendered glyph including any shadow. - Front layer
position: relativedefines container size; back layers absolute withpointer-events: none; offsets viatransform: translate(), nevertop/left. - No CSS
text-shadowalongside layered depth — they compound and over-extrude. - No per-letter animation on top of the stack — hacker-flip / typewriter over 6-layer depth is chaos; drop to 2–3 layers or apply depth only to the static post-reveal state.
See also
counting-dynamic-scale (counter rendered with depth layers) · sine-wave-loop (idle breathing on the front layer post-reveal) · center-outward-expansion (depth-stacked wordmark after the burst lands).