Files
hyperframes/skills/marker-highlight/references/examples.md
T
Vance Ingalls cb0b17062a feat(skills): add marker-highlight skill for animated text highlighting (#190)
## Summary

- **New skill:** **`marker-highlight`** — integrates [MarkerHighlight.js](https://github.com/Robincodes-Sandbox/marker-highlight) into HyperFrames compositions. Canvas-based animated text highlighting with 5 drawing modes: marker pen, circle, burst, scribble, and sketchout.
- **Studio fix:** added missing `captionSync` to useEffect dependency array (oxlint exhaustive-deps)
- **Studio fix:** `loadOverrides` now checks `res.ok` before parsing, preventing 404 console noise on projects without captions

## Skill details

The skill documents the non-obvious GSAP integration pattern discovered during development:

1. **One highlighter per container** — the library clears ALL `.highlight` divs from the shared parent on init, so multiple instances on sibling marks conflict
2. **`data-color`** **\+** **`data-original-bgcolor`** — prevents the CSS background-color flash that occurs when the library reads and clears the mark's background
3. **Canvas pre-draw + clear + reanimate** — `animate: false` pre-draws statically, canvases are hidden, then cleared and shown with `reanimateMark()` at trigger time for clean animated reveals
4. **`onReverseComplete`** **for rewind** — hides highlight divs when the timeline seeks backward past the trigger point

## Test plan

- [ ] `npx hyperframes lint` passes on test-composition
- [ ] Studio preview shows marker highlight on "something" at 1s, circle on "love" at 2.2s
- [ ] Rewind past trigger points hides highlights
- [ ] No 404 console errors for caption-overrides.json on non-caption projects



[Screen Recording 2026-04-02 at 1.56.30 AM.mov <span class="graphite__hidden">(uploaded via Graphite)</span> <img class="graphite__hidden" src="https://app.graphite.com/user-attachments/thumbnails/53b03f4e-538e-477a-b738-7a033b99a84e.mov" />](https://app.graphite.com/user-attachments/video/53b03f4e-538e-477a-b738-7a033b99a84e.mov)



🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-04-02 13:48:24 -07:00

147 lines
3.8 KiB
Markdown

# Marker Highlight Examples
## Recipes
### Underline
```html
<mark
data-height="0.15"
data-offset="0.8"
data-padding="0"
data-highlight='{"amplitude":0.2,"wavelength":5,"roughEnds":0}'
data-color="rgba(30, 136, 229, 0.6)"
>important</mark
>
```
### Strikethrough
```html
<mark
data-drawing-mode="highlight"
data-height="0.1"
data-offset="0"
data-highlight='{"amplitude":0.1,"wavelength":3}'
data-color="rgba(229, 57, 53, 0.8)"
>wrong answer</mark
>
```
### Circled Annotation
```html
<mark
data-drawing-mode="circle"
data-circle='{"curve":0.8,"wobble":0.4,"loops":2,"thickness":3}'
data-animation-speed="1200"
data-color="rgba(229, 57, 53, 0.6)"
>this one</mark
>
```
## Full Example in a Composition
```html
<div data-composition-id="highlight-demo" data-width="1920" data-height="1080">
<div
id="content"
style="
position: absolute; inset: 0;
display: flex; align-items: center; justify-content: center;
font-family: 'Inter', sans-serif; font-size: 72px; color: #fff;
background: #111;
"
>
<p id="hero">
The <mark id="m1" data-color="rgba(255, 220, 50, 0.5)">fastest</mark> way to
<mark
id="m2"
data-drawing-mode="circle"
data-circle='{"curve":0.8,"wobble":0.3,"loops":2,"thickness":3}'
data-color="rgba(229, 57, 53, 0.6)"
>ship</mark
>
</p>
</div>
<style>
[data-composition-id="highlight-demo"] mark {
background-color: transparent;
color: inherit;
}
</style>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<script src="marker-highlight.global.js"></script>
<script>
window.__timelines = window.__timelines || {};
var tl = gsap.timeline({ paused: true });
// Set colors via data attribute (no visible flash)
document.querySelectorAll("mark[data-color]").forEach(function (m) {
m.setAttribute("data-original-bgcolor", m.getAttribute("data-color"));
});
// Init once after fonts, then hide all canvases
var hl;
document.fonts.ready.then(function () {
setTimeout(function () {
hl = new MarkerHighlighter(document.getElementById("hero"), {
animate: false,
animationSpeed: 800,
padding: 0.3,
highlight: { amplitude: 0.3, wavelength: 5 },
});
setTimeout(function () {
document.querySelectorAll(".highlight").forEach(function (d) {
d.style.opacity = "0";
});
}, 100);
}, 50);
});
function addHighlight(markId, time) {
tl.to(
{},
{
duration: 0.001,
onStart: function () {
var mark = document.getElementById(markId);
var ref = mark.getAttribute("data-mark-ref");
if (!ref || !hl) return;
mark.parentElement
.querySelectorAll('.highlight[data-mark-id="' + ref + '"]')
.forEach(function (div) {
var c = div.querySelector("canvas");
if (c) c.getContext("2d").clearRect(0, 0, c.width, c.height);
div.style.opacity = "1";
});
hl.reanimateMark(mark);
},
onReverseComplete: function () {
var mark = document.getElementById(markId);
var ref = mark.getAttribute("data-mark-ref");
if (!ref) return;
mark.parentElement
.querySelectorAll('.highlight[data-mark-id="' + ref + '"]')
.forEach(function (div) {
div.style.opacity = "0";
});
},
},
time,
);
}
gsap.set("#hero", { opacity: 0 });
tl.to("#hero", { opacity: 1, duration: 0.6 }, 0);
addHighlight("m1", 0.8);
addHighlight("m2", 1.6);
window.__timelines["highlight-demo"] = tl;
</script>
</div>
```