feat(skills): c2v mining pass — 7 new blueprints, 10 new rules, compacted recipe corpus (#2680)

* 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.
This commit is contained in:
WaterrrForever
2026-07-21 17:28:55 +08:00
committed by GitHub
parent 8f171433f9
commit 853256403b
68 changed files with 4911 additions and 8179 deletions
@@ -7,149 +7,99 @@ metadata:
# Discrete Text Sequence
Instead of character-by-character typewriter, replace entire string states at time thresholds. Enables non-linear effects (typos, bulk additions, pauses, "thinking" gaps) that smooth per-char typing can't achieve.
Instead of character-by-character typewriter, replace entire string states at time thresholds — enabling non-linear effects (typos, backspaces, bulk paste, "thinking" gaps) that smooth per-char typing can't achieve. If your effect is "type each character, no edits", this rule is overkill — use the smooth-slice variation below.
## How It Works
An array of `{ text, t }` pairs where `t` is a time in seconds. On every onUpdate, scan the array for the latest entry whose `t` has passed and render that text. The display jumps between states; no animation between them.
The typing is authored as a sparse array of `{ t, text }` states; on every `onUpdate` a **reverse search** finds the latest entry whose `t` has passed and renders its text. Display jumps between states with no animation between them — the realism comes from the schedule shape: fast keystroke clusters (0.060.20s apart), pauses at word breaks (0.30.6s), a typo, backspaces peeling back to the fork, then a bulk paste replacing many chars in one entry. A block cursor blinks via a deterministic sin square wave on the same timeline.
For continuous per-char typewriter (no pauses, no edits), use the **smooth-slice** variation at the bottom.
## HTML
## Recipe
```html
<div
class="scene"
id="seq-scene"
data-composition-id="seq-scene"
data-start="0"
data-duration="6"
data-track-index="0"
>
<div class="terminal">
<div class="prompt">$</div>
<div class="text-wrap">
<span class="text" id="text">|</span>
<span class="cursor" id="cursor">_</span>
</div>
<!-- inside a standard scene clip (hyperframes-core) -->
<div class="terminal">
<div class="prompt">$</div>
<div class="text-wrap">
<span class="text" id="text"></span><span class="cursor" id="cursor">_</span>
</div>
</div>
```
## CSS
```css
.scene {
position: relative;
width: 100%;
height: 100%;
display: grid;
place-items: center;
background: {bgColor};
font-family: {monoFont}; /* monospace is required — see Critical Constraints */
}
.terminal {
font-family: {monoFont}; /* monospace required — proportional jitters even in a fixed box */
display: flex;
align-items: baseline;
gap: GUTTER;
font-weight: 800;
font-size: TERMINAL_FONT_SIZE;
color: {textColor};
}
.prompt {
color: {accentColor};
}
.text-wrap {
display: inline-flex;
align-items: baseline;
/* Fixed-width container prevents the right side from jittering as
content changes length. Choose width ≥ longest state's width. */
min-width: TEXT_WRAP_MIN_WIDTH;
min-width: TEXT_WRAP_MIN_WIDTH; /* ≥ widest state — stops right-edge jitter */
white-space: nowrap;
}
.text {
color: {textColor};
}
.cursor {
display: inline-block;
display: inline-block; /* inline ignores width */
width: CURSOR_WIDTH;
color: {accentColor};
margin-left: CURSOR_GAP;
}
```
## GSAP Timeline + Discrete State Logic
```js
// Each entry shows from its t until the NEXT entry's t.
// Shape: keystrokes → typo → backspace to the fork → bulk paste → completion mark.
const SEQUENCE = [
{ t: 0.0, text: "" },
{ t: T_K1, text: "{p1}" }, // first keystrokes (~3-5 chars, 0.1-0.2s apart)
{ t: T_K2, text: "{p1 + ' ' + p2_typo}" }, // continuation containing a typo
{ t: T_BS, text: "{p1 + ' ' + p2_partial}" }, // backspace(s) — peel back to the fork
{ t: T_BULK, text: "{fullCorrectedText}" }, // bulk paste — many chars in one jump
{ t: T_DONE, text: "{fullCorrectedText + ' ✓'}" }, // completion marker
];
```html
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<script>
window.__timelines = window.__timelines || {};
// SEQUENCE — each entry shows from t to the NEXT entry's t.
// Non-linear: typos, corrections, bulk additions, pauses.
// Shape (one realization):
// [warm-up keystrokes] → [typo] → [backspaces back to fork] →
// [bulk-paste of the corrected continuation] → [completion mark]
const SEQUENCE = [
{ t: 0.0, text: "" },
{ t: T_K1, text: "{p1}" }, // first keystrokes (~3-5 chars, 0.1-0.2s apart)
{ t: T_K2, text: "{p1 + ' ' + p2_typo}" }, // continuation containing a typo
{ t: T_BS, text: "{p1 + ' ' + p2_partial}" }, // backspace(s) — peel back to the fork
{ t: T_BULK, text: "{fullCorrectedText}" }, // bulk paste — replaces several chars at once
{ t: T_DONE, text: "{fullCorrectedText + ' ✓'}" }, // completion marker
];
// Reverse-search for the latest entry whose t has passed.
function textAt(time) {
for (let i = SEQUENCE.length - 1; i >= 0; i--) {
if (time >= SEQUENCE[i].t) return SEQUENCE[i].text;
}
return "";
// Reverse-search for the latest entry whose t has passed
function textAt(time) {
for (let i = SEQUENCE.length - 1; i >= 0; i--) {
if (time >= SEQUENCE[i].t) return SEQUENCE[i].text;
}
return "";
}
const textEl = document.getElementById("text");
const cursorEl = document.getElementById("cursor");
const tl = gsap.timeline({ paused: true });
const textEl = document.getElementById("text");
const cursorEl = document.getElementById("cursor");
// Drive the discrete display via a 0→TOTAL_DURATION tween's onUpdate
const driver = { t: 0 };
tl.to(
driver,
{
t: TOTAL_DURATION,
duration: TOTAL_DURATION,
ease: "none",
onUpdate: () => {
textEl.textContent = textAt(driver.t);
},
const driver = { t: 0 };
tl.to(
driver,
{
t: TOTAL_DURATION,
duration: TOTAL_DURATION,
ease: "none",
onUpdate: () => {
textEl.textContent = textAt(driver.t);
},
0,
);
},
0,
);
// Cursor blink — deterministic via sin, not CSS animation
const blinkDriver = { p: 0 };
tl.to(
blinkDriver,
{
p: Math.PI * 2 * BLINK_CYCLES, // BLINK_CYCLES = blinks across composition
duration: TOTAL_DURATION,
ease: "none",
onUpdate: () => {
cursorEl.style.opacity = Math.sin(blinkDriver.p) > 0 ? "1" : "0";
},
// Cursor blink — deterministic sin square wave, never a CSS animation
const blink = { p: 0 };
tl.to(
blink,
{
p: Math.PI * 2 * BLINK_CYCLES,
duration: TOTAL_DURATION,
ease: "none",
onUpdate: () => {
cursorEl.style.opacity = Math.sin(blink.p) > 0 ? "1" : "0";
},
0,
);
window.__timelines["seq-scene"] = tl;
</script>
},
0,
);
```
## Variations
### Smooth character slice (continuous typewriter — no pauses, no edits)
For straight-forward typewriter without the non-linear chaos:
- **Smooth character slice** (continuous typewriter — no pauses, no edits): faster to author but uniformly "machine-typed", missing the human realism:
```js
const fullText = "{fullPhrase}";
@@ -168,106 +118,29 @@ tl.to(
);
```
This is faster to author but produces a uniform "machine-typed" feel — missing the human-typing realism.
- **Thinking pause** — hold one state for `THINK_HOLD_DUR` (0.82.0s; under 0.5s reads as a stutter, not thought) simply by leaving a gap before the next entry's `t`.
- **State pulse on completion** — when the final state lands, `tl.to(".text", { scale: 1.031.08, duration: 0.150.3, yoyo: true, repeat: 1 }, T_DONE)`.
- **Per-state color shift** — in `onUpdate`, branch on `driver.t` vs the milestones: success color after `T_DONE`, dim mid-edit, normal while typing.
### Thinking pause (extended hold on a key state)
## Values
Insert a state that holds for `THINK_HOLD_DUR` seconds without changes — feels like the user paused to think:
```js
{ t: T_PRE_PAUSE, text: '{partialPhrase}' }, // last state before the pause
// ... no entries for THINK_HOLD_DUR seconds ...
{ t: T_PRE_PAUSE + THINK_HOLD_DUR, text: '{resumedPhrase}' },
```
### State pulse on completion
When the final state lands (e.g. "✓"), pulse-scale the line briefly for emphasis:
```js
tl.to(
".text",
{ scale: COMPLETION_PULSE_SCALE, duration: COMPLETION_PULSE_DUR, yoyo: true, repeat: 1 },
T_DONE,
);
```
### Per-state color shift
Color-code states by phase (e.g. dim during edit, success color after the completion marker, optional warning color on typo):
```js
// In onUpdate after setting textContent:
if (driver.t > T_DONE) textEl.style.color = "{successColor}";
else if (driver.t < T_K2)
textEl.style.color = "{textColor}"; // normal typing
else textEl.style.color = "{mutedColor}"; // mid-edit dim
```
## How to Choose Values
### Layout
- **TERMINAL_FONT_SIZE** — font size of the typing line.
- Range: 48-96 px for full-bleed compositions; smaller for terminal-style detail
- Constraints: combined with `TEXT_WRAP_MIN_WIDTH` must fit within viewport
- **TEXT_WRAP_MIN_WIDTH** — fixed-width container holding the text.
- Constraints: must be `≥ widthOf(longest SEQUENCE state) at TERMINAL_FONT_SIZE`. Measure with a hidden probe after `document.fonts.ready` if unsure
- Effects: too small → right edge jitters as states change length; too large → unused horizontal whitespace pads the composition
- **GUTTER** — flex gap between prompt glyph (`$`, `>`) and text.
- Range: ~0.3-0.5× `TERMINAL_FONT_SIZE`
- **CURSOR_WIDTH / CURSOR_GAP** — block cursor dimensions.
- Range: width ~0.3× `TERMINAL_FONT_SIZE`; gap small (single-digit px) so the cursor feels attached to the text
### Sequence timing
- **TOTAL_DURATION** — composition length.
- Constraints: must be ≥ `T_DONE` + ~1s climax dwell so viewer sees the completion marker
- **T_K1 / T_K2 / T_BS / T_BULK / T_DONE** — milestone timestamps within the SEQUENCE.
- Range: keystrokes 0.06-0.20s apart for "human typing"; pauses 0.3-0.6s at natural word breaks; bulk paste jumps multiple characters in a single entry
- Constraints: monotonically increasing; `T_DONE ≤ TOTAL_DURATION - dwell`
- **TYPE_DUR** (smooth-slice variation) — total typing duration for continuous typewriter.
- Range: `chars × 0.06s` (fast) to `chars × 0.12s` (relaxed)
- **THINK_HOLD_DUR** (thinking-pause variation) — hold time between two SEQUENCE states.
- Range: 0.8-2.0s; under 0.5s reads as a stutter rather than thought
- **COMPLETION_PULSE_SCALE / COMPLETION_PULSE_DUR** (pulse variation).
- Range: scale 1.03-1.08 (subtle), duration 0.15-0.30s
### Cursor
- **BLINK_CYCLES** — number of full blink cycles across `TOTAL_DURATION`.
- Range: `TOTAL_DURATION / 0.8s ≤ BLINK_CYCLES ≤ TOTAL_DURATION / 0.5s` (cycle every 0.5-0.8s reads as a natural cursor)
### Color tokens
- **{bgColor} / {textColor} / {accentColor} / {successColor} / {mutedColor}** — discrete choices, not numeric ranges. Pick from the composition's palette; the prompt + cursor share `{accentColor}` so they read as the same "system" element.
## Key Principles
- **Threshold sequence drives realism** — group fast successive keystrokes (0.1-0.2s apart), then pause on word breaks (0.3-0.5s), bulk-paste in single jumps (one entry replaces many chars), include a typo or two for human-typing feel
- **Reverse-search the array each frame** — O(n) per frame, where n is small (≤30 typical). Don't try to index by frame; the sequence is sparse
- **Fixed-width container is mandatory** — without `min-width`, the right edge of the text wrap jitters as state length changes. Set width ≥ longest expected state
- **Cursor must be deterministic** — sin-based or sequence-driven blink, NOT a CSS animation. HF seeks frame-by-frame; CSS animations desync
- **No `transition` on the text element** — discrete jumps should be INSTANT. A CSS transition turns the jump into a smear and ruins the "typing" feel
- **❗ Distinguish discrete from smooth** — if your effect is "type each character, no edits" → use the smooth-slice variation. Discrete sequence is overkill for that case. Use discrete only when you need non-linear states (typos, pauses, bulk paste)
| token | range | notes |
| ------------------- | -------------------------------------------- | ---------------------------------------------------------------------- |
| TERMINAL_FONT_SIZE | 4896px | full-bleed comps; smaller for terminal-style detail |
| TEXT_WRAP_MIN_WIDTH | ≥ widest state | measure with a hidden probe after `document.fonts.ready` if unsure |
| milestone `t`s | keystrokes 0.060.20s apart; pauses 0.30.6s | monotonically increasing; `T_DONE ≤ TOTAL_DURATION ~1s` climax dwell |
| TYPE_DUR (smooth) | `chars × 0.060.12s` | fast → relaxed |
| BLINK_CYCLES | one cycle per 0.50.8s | `TOTAL_DURATION / 0.8 ≤ BLINK_CYCLES ≤ TOTAL_DURATION / 0.5` |
| CURSOR_WIDTH | ~0.3× font size | gap to text single-digit px so the cursor feels attached |
## Critical Constraints
- **Timeline must be paused**: `gsap.timeline({ paused: true })`
- **Registry key = `data-composition-id`**
- **No CSS `transition`** on the text or any of its parents
- **Cursor `display: inline-block`** — `display: inline` ignores width/transform
- **Monospace font** for terminal-style effects — proportional fonts cause visual jitter even with fixed-width container
- **Whitespace: nowrap** on text wrap — wrapping mid-state breaks the illusion
- **Reverse-search the array each frame** — O(n) with small n (≤30 typical); don't index by frame, the sequence is sparse.
- **`min-width` on the text wrap is mandatory** — without it the right edge jitters as state length changes.
- **Discrete jumps must be INSTANT** — any transition on the text turns the jump into a smear and kills the "typing" feel.
- **Cursor blink is sin/sequence-driven on the timeline**, `display: inline-block`, monospace font, `white-space: nowrap` (wrapping mid-state breaks the illusion; trailing spaces must survive).
- **Discrete vs smooth** — use discrete only for non-linear states (typos, pauses, bulk paste); plain typing takes the smooth-slice variation.
## Combinations
## See also
- [3d-text-depth-layers.md](3d-text-depth-layers.md) — discrete text rendered with layered depth (heavy, dramatic)
- [counting-dynamic-scale.md](counting-dynamic-scale.md) — discrete text for the LABEL while counter animates smoothly
- [press-release-spring.md](press-release-spring.md) — after the sequence completes, the line "presses" like a button confirming success
## Pairs with HF skills
- `/hyperframes-animation` — onUpdate-driven discrete state lookup
- `/hyperframes-core` — composition wiring
- `/hyperframes-cli``hyperframes lint`
`context-sensitive-cursor` (same SEQUENCE pattern + segment-colored cursor) · `3d-text-depth-layers` (discrete text with layered depth) · `counting-dynamic-scale` (discrete label beside a smooth counter) · `press-release-spring` (post-completion press beat).