Files
hyperframes/skills/hyperframes-animation/rules/discrete-text-sequence.md
T
WaterrrForever 853256403b 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.
2026-07-21 17:28:55 +08:00

147 lines
6.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: discrete-text-sequence
description: Replace entire text states at frame thresholds for non-linear typing effects — typos, bulk additions, pauses, backspaces, simulated thinking.
metadata:
tags: text, typing, discrete, threshold, non-linear, sequence
---
# Discrete Text Sequence
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
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.
## Recipe
```html
<!-- 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
.terminal {
font-family: {monoFont}; /* monospace required — proportional jitters even in a fixed box */
display: flex;
align-items: baseline;
font-size: TERMINAL_FONT_SIZE;
}
.text-wrap {
display: inline-flex;
align-items: baseline;
min-width: TEXT_WRAP_MIN_WIDTH; /* ≥ widest state — stops right-edge jitter */
white-space: nowrap;
}
.cursor {
display: inline-block; /* inline ignores width */
width: CURSOR_WIDTH;
}
```
```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
];
// 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 driver = { t: 0 };
tl.to(
driver,
{
t: TOTAL_DURATION,
duration: TOTAL_DURATION,
ease: "none",
onUpdate: () => {
textEl.textContent = textAt(driver.t);
},
},
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,
);
```
## Variations
- **Smooth character slice** (continuous typewriter — no pauses, no edits): faster to author but uniformly "machine-typed", missing the human realism:
```js
const fullText = "{fullPhrase}";
const len = { v: 0 };
tl.to(
len,
{
v: fullText.length,
duration: TYPE_DUR,
ease: "power1.inOut",
onUpdate: () => {
textEl.textContent = fullText.substring(0, Math.floor(len.v));
},
},
0,
);
```
- **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.
## Values
| 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
- **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.
## See also
`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).