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.4 KiB
name, description, metadata
| name | description | metadata | ||
|---|---|---|---|---|
| vertical-spring-ticker | Slot-machine style vertical scrolling using additive spring physics within a masked container — each spring contributes one "step" of scroll. |
|
Vertical Spring Ticker (Slot Machine)
Multiple spring tweens are ADDED TOGETHER to produce total Y translation — each spring contributes one discrete "step", so instead of a single linear scroll you get the slot-machine "click click click" rhythm with natural settling. Distinct from a continuous marquee: this rule's semantics are discrete steps that land; for endless linear motion see sine-wave-loop.md.
How It Works
A masked window of fixed height ITEM_HEIGHT (overflow: hidden) holds a vertical stack of items, each exactly ITEM_HEIGHT tall. Each spring holds a 0→1 progress; a shared onUpdate sums them and applies translateY(-sum × ITEM_HEIGHT). Springs fire sequentially with overlap (STEP_SPACING ≤ STEP_DUR), so each step snaps in while the previous is still settling — that overlap is what makes them additive, and the back.out overshoot is what makes each step read as a "click".
Recipe
<!-- inside a standard scene clip (frames-core) -->
<div class="ticker" id="ticker">
<div class="stack-inner" id="stack-inner">
<div class="item">{item0}</div>
<div class="item">{item1}</div>
<div class="item">{itemN}</div>
</div>
</div>
.ticker {
width: TICKER_WIDTH;
height: ITEM_HEIGHT; /* MUST match .item height exactly */
overflow: hidden; /* the mask is the window */
}
.stack-inner {
display: flex;
flex-direction: column; /* mandatory — vertical stacking */
}
.item {
height: ITEM_HEIGHT; /* MUST equal .ticker height */
display: flex;
align-items: center;
justify-content: center;
/* font-variant-numeric: tabular-nums; — for numeric tickers */
}
const innerEl = document.getElementById("stack-inner");
const springs = Array.from({ length: STEPS }, () => ({ p: 0 }));
function applyTransform() {
const sumP = springs.reduce((acc, s) => acc + s.p, 0);
innerEl.style.transform = `translateY(${-sumP * ITEM_HEIGHT}px)`;
}
applyTransform(); // initial state
springs.forEach((spring, i) => {
tl.to(
spring,
{
p: 1,
duration: STEP_DUR,
ease: `back.out(${BOUNCE_FACTOR})`,
onUpdate: applyTransform,
},
STEP_START + i * STEP_SPACING,
);
});
Variations
- Numeric ticker (price / counter rolling) — items are the digit sequence; run the same spring-step pattern per decimal position.
font-variant-numeric: tabular-numsrequired. - Reverse direction (countdown) — flip the sign (
translateY(${sumP * ITEM_HEIGHT}px)) and arrange items in reverse order. - Pause between groups — several fast steps (small
STEP_SPACING), a long pause, then one dramatic final step with a biggerBOUNCE_FACTOR. The pause is where the eye locks in. - Continuous infinite ticker — NOT this rule (this rule is discrete steps); a looping news ticker is a single linear tween with duplicated items — see sine-wave-loop.md for continuous-motion semantics.
Values
| token | range | notes |
|---|---|---|
| ITEM_HEIGHT | ~fontSize × 1.25 |
must hold capital descenders; .ticker height MUST equal it exactly |
| TICKER_WIDTH | 30–60% viewport width | wide enough for the longest item without ellipsis |
| STEPS | 1–4 | number of transitions, not items; STEPS ≤ itemCount − 1 |
| STEP_DUR | 0.3–0.7s | under 0.3 the overshoot is invisible; over 0.7 the click reads as a slide |
| STEP_SPACING | 0.3–0.5s | ≤ STEP_DUR so springs overlap (additive); wider gaps read as a lazy linear scroll |
| BOUNCE_FACTOR | 1.4–2.5 | 1.4 gentle click / 2.0 firm / 2.5+ casino spin-and-land for a climax step |
Reference: ../../examples/proof-logo-chain.html (204px, 1 step, 0.45s).
Critical Constraints
- Container height = item height, pixel-exact, all items equal — mismatches show partial item edges above/below the mask and accumulate drift across steps.
overflow: hiddenon the container, not the inner stack;flex-direction: columnon the stack.- Sum the springs in
onUpdate— never tween the final position directly. Each spring contributing its OWN snap is the slot-machine pacing. - Overlap steps and keep
back.outper step — non-overlapping steps or an out-only ease collapse into a linear scroll. - Never update items via
innerHTMLbetween steps — the ticker moves the SAME items via translate; swapping content shows the previous item AS the new one (broken illusion). - Climax dwell ≥1s after the final step (SKILL universal constraint).
tabular-numsfor numeric tickers — variable digit widths break alignment.
See also
reactive-displacement (ticker pushed by an incoming element) · scale-swap-transition (ticker scales out after settling) · press-release-spring (button press triggers the spin).