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.
6.8 KiB
name, description, metadata
| name | description | metadata | ||
|---|---|---|---|---|
| context-sensitive-cursor | Cursor color and styling that adapt to the current text segment being typed — accent color on highlights, dim on placeholders, etc. |
|
Context-Sensitive Cursor
In a typewriter sequence, the cursor's color (and optionally height / blink behavior) matches the active text segment — brand accent while typing the brand name, dim on placeholders, success color on the completion mark. The eye lands on the keyword being typed because the cursor shifts with it; a fixed single-color cursor is visual noise by comparison. Layers on top of discrete-text-sequence's SEQUENCE pattern.
How It Works
The text is authored as a SEQUENCE of { t, text, segment, color } entries; a linear driver's onUpdate reverse-searches for the current entry and writes both the visible text and the cursor's background (the cursor is a colored block, so background, NOT color). A second linear tween sweeps a phase p through 2π × BLINK_CYCLES_PER_SCENE and gates cursor opacity on sin(p) > 0 — a deterministic square-wave blink on the timeline.
Recipe
<!-- inside a standard scene clip (frames-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>
.terminal {
font-family: {monoFont}; /* proportional fonts drift the cursor mid-segment */
display: flex;
align-items: baseline;
white-space: pre; /* preserve trailing spaces — cursor sits at segment end */
}
.text {
white-space: pre;
}
.cursor {
display: inline-block; /* inline ignores width/height */
width: {cursorWidth}px;
height: {cursorHeight}px;
background: {textColor}; /* default — overridden per segment in onUpdate */
vertical-align: {cursorBaselineFix}px; /* small negative — anchor to baseline, not line-height */
}
// Adjacent entries usually share a text prefix but may differ in `segment` —
// that's what shifts the cursor color mid-line.
const SEQUENCE = [
{ t: 0, text: "", segment: "main", color: "{mainColor}" },
{ t: T_LEADIN_END, text: "{leadInChunk}", segment: "main", color: "{mainColor}" },
{ t: T_BRAND_IN, text: "{leadInBrandPrefix}", segment: "brand", color: "{brandColor}" },
{ t: T_BRAND_OUT, text: "{leadInBrandFull}", segment: "main", color: "{mainColor}" },
{ t: T_CMD_IN, text: "{leadInCmdPrefix}", segment: "cmd", color: "{cmdColor}" },
{ t: T_SUCCESS, text: "{leadInDone}", segment: "success", color: "{successColor}" },
];
function entryAt(time) {
for (let i = SEQUENCE.length - 1; i >= 0; i--) {
if (time >= SEQUENCE[i].t) return SEQUENCE[i];
}
return SEQUENCE[0];
}
const textEl = document.getElementById("text");
const cursorEl = document.getElementById("cursor");
const driver = { t: 0 };
tl.to(
driver,
{
t: DURATION,
duration: DURATION,
ease: "none",
onUpdate: () => {
const entry = entryAt(driver.t);
textEl.textContent = entry.text;
cursorEl.style.background = entry.color;
},
},
0,
);
// Deterministic square-wave blink
const blink = { p: 0 };
tl.to(
blink,
{
p: Math.PI * 2 * BLINK_CYCLES_PER_SCENE,
duration: DURATION,
ease: "none",
onUpdate: () => {
cursorEl.style.opacity = Math.sin(blink.p) > 0 ? "1" : "0";
},
},
0,
);
Variations
- Non-blinking during active typing — suppress blink while letters are appearing (solid cursor), resume on idle. This MUST be a pure function of the driver's time: tracking a mutable
lastChangeTimeinonUpdateis not reverse-seek-safe (scrubbing backwards leaves the stale forward-pass value behind and the cursor blinks — or holds solid — at the wrong frames). Bake the change times from the SEQUENCE instead — every entry whosetextdiffers from its predecessor is a typing event:
// Baked once at build time — no runtime state.
const CHANGE_TIMES = SEQUENCE.filter((e, i) => i > 0 && e.text !== SEQUENCE[i - 1].text).map(
(e) => e.t,
);
// In onUpdate — identical result at any seek, either direction:
const isTyping = CHANGE_TIMES.some((t) => t <= driver.t && driver.t - t < TYPING_GRACE);
cursorEl.style.opacity = isTyping ? "1" : Math.sin(blink.p) > 0 ? "1" : "0";
- Cursor HEIGHT shifts on segment — larger cursor on the brand segment:
cursorEl.style.height = entry.segment === "brand" ? cursorHeightEmphasis : cursorHeight(1.1–1.25×; more reads as glitch). - Contrast reversal — a dark-text-on-light segment needs a dark cursor too; keep
entry.coloras the single source of truth and read from it.
Values
| token | range | notes |
|---|---|---|
| DURATION | 4–8s per typed line | ≥ SEQUENCE[last].t + closing dwell |
entry t spacing |
0.2–0.5s micro-additions | ascending, non-uniform — slow down on highlights |
| segment palette | 3–4 colors max | more reads as random; brand vs success should differ in saturation/luminance |
| cursorWidth / Height | 8–24px / 0.85–1.0× fontSize | too thin vanishes in render compression; too tall outranks the text |
| cursorBaselineFix | small negative px | drop the block to the text baseline |
| BLINK_CYCLES_PER_SCENE | period ≈ 0.6–1.2s | whole number — otherwise the sin sweep ends mid-cycle and the cursor pops on the last frame |
| TYPING_GRACE | 0.15–0.3s | < shortest dwell between adjacent entries — otherwise the cursor never blinks |
Critical Constraints
- Cursor color goes on
background— it's a colored block, not a glyph. - Blink is timeline-driven sin, pure of any mutable tracker — the typing-grace variation shows the seek-safe form.
white-space: preon text and container — collapsed trailing spaces park the cursor in the wrong column.- Monospace font +
display: inline-blockcursor — proportional faces drift the cursor mid-segment; inline ignores the block geometry. - BLINK_CYCLES_PER_SCENE is a whole number for the fixed DURATION.
See also
discrete-text-sequence (the underlying SEQUENCE pattern) · camera-cursor-tracking (camera follows the cursor) · press-release-spring (post-typing confirm press).