hyperframes 0.2.2 → 0.2.3-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +7926 -7426
- package/dist/skills/gsap/SKILL.md +222 -0
- package/dist/skills/gsap/references/effects.md +304 -0
- package/dist/skills/gsap/references/frameworks.md +56 -0
- package/dist/skills/gsap/references/plugins.md +194 -0
- package/dist/skills/gsap/references/react.md +80 -0
- package/dist/skills/gsap/references/scrolltrigger.md +147 -0
- package/dist/skills/gsap/references/utils.md +91 -0
- package/dist/skills/gsap/scripts/extract-audio-data.py +188 -0
- package/dist/skills/{hyperframes-compose → hyperframes}/SKILL.md +43 -43
- package/dist/skills/hyperframes/references/audio-reactive.md +76 -0
- package/dist/skills/hyperframes/references/captions.md +132 -0
- package/dist/skills/hyperframes/references/css-patterns.md +371 -0
- package/dist/skills/hyperframes/references/examples.md +146 -0
- package/dist/skills/hyperframes/references/marker-highlight.md +158 -0
- package/dist/skills/hyperframes/references/transitions/catalog.md +132 -0
- package/dist/skills/hyperframes/references/transitions/css-3d.md +12 -0
- package/dist/skills/hyperframes/references/transitions/css-blur.md +51 -0
- package/dist/skills/hyperframes/references/transitions/css-cover.md +43 -0
- package/dist/skills/hyperframes/references/transitions/css-destruction.md +95 -0
- package/dist/skills/hyperframes/references/transitions/css-dissolve.md +66 -0
- package/dist/skills/hyperframes/references/transitions/css-distortion.md +45 -0
- package/dist/skills/hyperframes/references/transitions/css-grid.md +10 -0
- package/dist/skills/hyperframes/references/transitions/css-light.md +49 -0
- package/dist/skills/hyperframes/references/transitions/css-mechanical.md +30 -0
- package/dist/skills/hyperframes/references/transitions/css-other.md +36 -0
- package/dist/skills/hyperframes/references/transitions/css-push.md +41 -0
- package/dist/skills/hyperframes/references/transitions/css-radial.md +37 -0
- package/dist/skills/hyperframes/references/transitions/css-scale.md +24 -0
- package/dist/skills/hyperframes/references/transitions/shader-setup.md +463 -0
- package/dist/skills/hyperframes/references/transitions/shader-transitions.md +329 -0
- package/dist/skills/hyperframes/references/transitions.md +96 -0
- package/dist/skills/hyperframes/references/tts.md +56 -0
- package/dist/skills/hyperframes-cli/SKILL.md +114 -0
- package/dist/templates/_shared/CLAUDE.md +5 -7
- package/dist/templates/blank/index.html +8 -10
- package/package.json +2 -4
- package/dist/skills/hyperframes-captions/SKILL.md +0 -212
- package/dist/skills/hyperframes-tts/SKILL.md +0 -79
- package/dist/templates/blank/compositions/captions.html +0 -95
- /package/dist/skills/{hyperframes-compose → hyperframes}/data-in-motion.md +0 -0
- /package/dist/skills/{hyperframes-compose → hyperframes}/house-style.md +0 -0
- /package/dist/skills/{hyperframes-compose → hyperframes}/palettes/bold-energetic.md +0 -0
- /package/dist/skills/{hyperframes-compose → hyperframes}/palettes/clean-corporate.md +0 -0
- /package/dist/skills/{hyperframes-compose → hyperframes}/palettes/dark-premium.md +0 -0
- /package/dist/skills/{hyperframes-compose → hyperframes}/palettes/jewel-rich.md +0 -0
- /package/dist/skills/{hyperframes-compose → hyperframes}/palettes/monochrome.md +0 -0
- /package/dist/skills/{hyperframes-compose → hyperframes}/palettes/nature-earth.md +0 -0
- /package/dist/skills/{hyperframes-compose → hyperframes}/palettes/neon-electric.md +0 -0
- /package/dist/skills/{hyperframes-compose → hyperframes}/palettes/pastel-soft.md +0 -0
- /package/dist/skills/{hyperframes-compose → hyperframes}/palettes/warm-editorial.md +0 -0
- /package/dist/skills/{hyperframes-compose → hyperframes}/patterns.md +0 -0
- /package/dist/skills/{hyperframes-captions → hyperframes/references}/dynamic-techniques.md +0 -0
- /package/dist/skills/{hyperframes-captions → hyperframes/references}/transcript-guide.md +0 -0
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
# CSS Patterns for Marker Highlighting
|
|
2
|
+
|
|
3
|
+
Pure CSS + GSAP implementations of all five MarkerHighlight.js drawing modes. Use these for deterministic rendering in HyperFrames compositions — no external library dependency, full GSAP timeline control.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [1. Highlight Mode](#1-highlight-mode) — Yellow marker sweep behind text
|
|
8
|
+
- [2. Circle Mode](#2-circle-mode) — Hand-drawn ellipse around text
|
|
9
|
+
- [3. Burst Mode](#3-burst-mode) — Radiating lines from text
|
|
10
|
+
- [4. Scribble Mode](#4-scribble-mode) — Chaotic scribble over text
|
|
11
|
+
- [5. Sketchout Mode](#5-sketchout-mode) — Rough rectangle outline
|
|
12
|
+
|
|
13
|
+
## 1. Highlight Mode
|
|
14
|
+
|
|
15
|
+
Yellow marker sweep behind text. The most common mode.
|
|
16
|
+
|
|
17
|
+
```html
|
|
18
|
+
<div class="mh-highlight-wrap">
|
|
19
|
+
<div class="mh-highlight-bar" id="hl-1"></div>
|
|
20
|
+
<span class="mh-highlight-text">highlighted text</span>
|
|
21
|
+
</div>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
```css
|
|
25
|
+
.mh-highlight-wrap {
|
|
26
|
+
position: relative;
|
|
27
|
+
display: inline-block;
|
|
28
|
+
}
|
|
29
|
+
.mh-highlight-bar {
|
|
30
|
+
position: absolute;
|
|
31
|
+
top: 0;
|
|
32
|
+
left: -6px;
|
|
33
|
+
right: -6px;
|
|
34
|
+
bottom: 0;
|
|
35
|
+
background: #fdd835;
|
|
36
|
+
opacity: 0.35;
|
|
37
|
+
transform: scaleX(0);
|
|
38
|
+
transform-origin: left center;
|
|
39
|
+
border-radius: 3px;
|
|
40
|
+
z-index: 0;
|
|
41
|
+
}
|
|
42
|
+
.mh-highlight-text {
|
|
43
|
+
position: relative;
|
|
44
|
+
z-index: 1;
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
```js
|
|
49
|
+
// Sweep in from left
|
|
50
|
+
tl.to("#hl-1", { scaleX: 1, duration: 0.5, ease: "power2.out" }, 0.6);
|
|
51
|
+
|
|
52
|
+
// Optional: skew for hand-drawn feel
|
|
53
|
+
// gsap.set("#hl-1", { skewX: -2 });
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Multi-line Highlight
|
|
57
|
+
|
|
58
|
+
Stagger bars across multiple lines:
|
|
59
|
+
|
|
60
|
+
```js
|
|
61
|
+
tl.to(
|
|
62
|
+
".mh-highlight-bar",
|
|
63
|
+
{
|
|
64
|
+
scaleX: 1,
|
|
65
|
+
duration: 0.5,
|
|
66
|
+
ease: "power2.out",
|
|
67
|
+
stagger: 0.3,
|
|
68
|
+
},
|
|
69
|
+
0.6,
|
|
70
|
+
);
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## 2. Circle Mode
|
|
74
|
+
|
|
75
|
+
Hand-drawn circle around text. Use `border-radius: 50%` with a slight rotation for organic feel.
|
|
76
|
+
|
|
77
|
+
```html
|
|
78
|
+
<div class="mh-circle-wrap">
|
|
79
|
+
<span class="mh-circle-text" id="circle-word">IMPORTANT</span>
|
|
80
|
+
<div class="mh-circle-ring" id="circle-1"></div>
|
|
81
|
+
</div>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
```css
|
|
85
|
+
.mh-circle-wrap {
|
|
86
|
+
position: relative;
|
|
87
|
+
display: inline-block;
|
|
88
|
+
}
|
|
89
|
+
.mh-circle-text {
|
|
90
|
+
position: relative;
|
|
91
|
+
z-index: 1;
|
|
92
|
+
}
|
|
93
|
+
.mh-circle-ring {
|
|
94
|
+
position: absolute;
|
|
95
|
+
top: 50%;
|
|
96
|
+
left: 50%;
|
|
97
|
+
width: 130%;
|
|
98
|
+
height: 160%;
|
|
99
|
+
transform: translate(-50%, -50%) rotate(-3deg) scale(0);
|
|
100
|
+
border: 3px solid #e53935;
|
|
101
|
+
border-radius: 50%;
|
|
102
|
+
pointer-events: none;
|
|
103
|
+
z-index: 0;
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
```js
|
|
108
|
+
// Circle scales in with a wobble
|
|
109
|
+
tl.to(
|
|
110
|
+
"#circle-1",
|
|
111
|
+
{
|
|
112
|
+
scale: 1,
|
|
113
|
+
rotation: -3,
|
|
114
|
+
duration: 0.6,
|
|
115
|
+
ease: "back.out(1.7)",
|
|
116
|
+
transformOrigin: "center center",
|
|
117
|
+
},
|
|
118
|
+
0.7,
|
|
119
|
+
);
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Variations
|
|
123
|
+
|
|
124
|
+
```css
|
|
125
|
+
/* Tighter circle (for short words) */
|
|
126
|
+
.mh-circle-ring.tight {
|
|
127
|
+
width: 150%;
|
|
128
|
+
height: 180%;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/* Squared circle (rounded rectangle) */
|
|
132
|
+
.mh-circle-ring.rounded {
|
|
133
|
+
border-radius: 30%;
|
|
134
|
+
width: 120%;
|
|
135
|
+
height: 140%;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/* Ellipse (wider than tall) */
|
|
139
|
+
.mh-circle-ring.ellipse {
|
|
140
|
+
width: 150%;
|
|
141
|
+
height: 130%;
|
|
142
|
+
border-radius: 50%;
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## 3. Burst Mode
|
|
147
|
+
|
|
148
|
+
Radiating lines from text center. Each line is a positioned div rotated to its angle.
|
|
149
|
+
|
|
150
|
+
```html
|
|
151
|
+
<div class="mh-burst-wrap">
|
|
152
|
+
<span class="mh-burst-text">WOW</span>
|
|
153
|
+
<div class="mh-burst-container" id="burst-1">
|
|
154
|
+
<div class="mh-burst-line" style="--angle: 0deg; --len: 70px;"></div>
|
|
155
|
+
<div class="mh-burst-line" style="--angle: 30deg; --len: 55px;"></div>
|
|
156
|
+
<div class="mh-burst-line" style="--angle: 60deg; --len: 80px;"></div>
|
|
157
|
+
<div class="mh-burst-line" style="--angle: 90deg; --len: 45px;"></div>
|
|
158
|
+
<div class="mh-burst-line" style="--angle: 120deg; --len: 65px;"></div>
|
|
159
|
+
<div class="mh-burst-line" style="--angle: 150deg; --len: 75px;"></div>
|
|
160
|
+
<div class="mh-burst-line" style="--angle: 180deg; --len: 50px;"></div>
|
|
161
|
+
<div class="mh-burst-line" style="--angle: 210deg; --len: 60px;"></div>
|
|
162
|
+
<div class="mh-burst-line" style="--angle: 240deg; --len: 80px;"></div>
|
|
163
|
+
<div class="mh-burst-line" style="--angle: 270deg; --len: 40px;"></div>
|
|
164
|
+
<div class="mh-burst-line" style="--angle: 300deg; --len: 70px;"></div>
|
|
165
|
+
<div class="mh-burst-line" style="--angle: 330deg; --len: 55px;"></div>
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
```css
|
|
171
|
+
.mh-burst-wrap {
|
|
172
|
+
position: relative;
|
|
173
|
+
display: inline-block;
|
|
174
|
+
}
|
|
175
|
+
.mh-burst-text {
|
|
176
|
+
position: relative;
|
|
177
|
+
z-index: 2;
|
|
178
|
+
}
|
|
179
|
+
.mh-burst-container {
|
|
180
|
+
position: absolute;
|
|
181
|
+
top: 50%;
|
|
182
|
+
left: 50%;
|
|
183
|
+
width: 0;
|
|
184
|
+
height: 0;
|
|
185
|
+
z-index: 1;
|
|
186
|
+
}
|
|
187
|
+
.mh-burst-line {
|
|
188
|
+
position: absolute;
|
|
189
|
+
width: 3px;
|
|
190
|
+
height: var(--len);
|
|
191
|
+
background: #1e88e5;
|
|
192
|
+
left: -1.5px;
|
|
193
|
+
top: calc(-1 * var(--len));
|
|
194
|
+
transform: rotate(var(--angle));
|
|
195
|
+
transform-origin: bottom center;
|
|
196
|
+
opacity: 0;
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
```js
|
|
201
|
+
// All lines burst outward simultaneously with slight stagger
|
|
202
|
+
tl.fromTo(
|
|
203
|
+
"#burst-1 .mh-burst-line",
|
|
204
|
+
{ scaleY: 0, opacity: 0 },
|
|
205
|
+
{ scaleY: 1, opacity: 1, duration: 0.4, ease: "power2.out", stagger: 0.03 },
|
|
206
|
+
0.7,
|
|
207
|
+
);
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
**Vary line lengths** (40-80px range) for an organic, hand-drawn feel. Equal lengths look mechanical.
|
|
211
|
+
|
|
212
|
+
## 4. Scribble Mode
|
|
213
|
+
|
|
214
|
+
Wavy SVG underlines and strikethroughs that draw themselves via `stroke-dashoffset`.
|
|
215
|
+
|
|
216
|
+
```html
|
|
217
|
+
<div class="mh-scribble-wrap">
|
|
218
|
+
<span class="mh-scribble-text">underlined text</span>
|
|
219
|
+
<svg class="mh-scribble-svg" viewBox="0 0 500 24" preserveAspectRatio="none">
|
|
220
|
+
<path
|
|
221
|
+
id="scribble-1"
|
|
222
|
+
d="M0,12 Q31,0 62,12 Q93,24 125,12 Q156,0 187,12 Q218,24 250,12 Q281,0 312,12 Q343,24 375,12 Q406,0 437,12 Q468,24 500,12"
|
|
223
|
+
fill="none"
|
|
224
|
+
stroke="#FDD835"
|
|
225
|
+
stroke-width="3"
|
|
226
|
+
stroke-linecap="round"
|
|
227
|
+
/>
|
|
228
|
+
</svg>
|
|
229
|
+
</div>
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
```css
|
|
233
|
+
.mh-scribble-wrap {
|
|
234
|
+
position: relative;
|
|
235
|
+
display: inline-block;
|
|
236
|
+
}
|
|
237
|
+
.mh-scribble-text {
|
|
238
|
+
position: relative;
|
|
239
|
+
z-index: 1;
|
|
240
|
+
}
|
|
241
|
+
.mh-scribble-svg {
|
|
242
|
+
position: absolute;
|
|
243
|
+
left: 0;
|
|
244
|
+
bottom: -6px;
|
|
245
|
+
width: 100%;
|
|
246
|
+
height: 24px;
|
|
247
|
+
z-index: 0;
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
```js
|
|
252
|
+
// Measure path length and set initial dash state
|
|
253
|
+
var path = document.querySelector("#scribble-1");
|
|
254
|
+
var len = path.getTotalLength();
|
|
255
|
+
gsap.set(path, { strokeDasharray: len, strokeDashoffset: len });
|
|
256
|
+
|
|
257
|
+
// Draw the line
|
|
258
|
+
tl.to(
|
|
259
|
+
"#scribble-1",
|
|
260
|
+
{
|
|
261
|
+
strokeDashoffset: 0,
|
|
262
|
+
duration: 0.8,
|
|
263
|
+
ease: "power1.inOut",
|
|
264
|
+
},
|
|
265
|
+
0.7,
|
|
266
|
+
);
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### Strikethrough Variant
|
|
270
|
+
|
|
271
|
+
Position the SVG at `top: 50%; transform: translateY(-50%)` instead of `bottom: -6px`.
|
|
272
|
+
|
|
273
|
+
### Wavy Path Generator
|
|
274
|
+
|
|
275
|
+
Scale the path's viewBox width to match text width. The wave pattern `Q x1,y1 x2,y2` alternates between `y=0` and `y=24` for a natural wobble. Adjust the control points for tighter or looser waves:
|
|
276
|
+
|
|
277
|
+
- **Tight waves**: smaller x-increments (25px per half-wave)
|
|
278
|
+
- **Loose waves**: larger x-increments (50px per half-wave)
|
|
279
|
+
- **Amplitude**: change the y range (0-24 for standard, 0-16 for subtle)
|
|
280
|
+
|
|
281
|
+
## 5. Sketchout Mode
|
|
282
|
+
|
|
283
|
+
Cross-hatch lines over de-emphasized text. Multiple angled lines create a "crossed out" effect.
|
|
284
|
+
|
|
285
|
+
```html
|
|
286
|
+
<div class="mh-sketchout-wrap">
|
|
287
|
+
<span class="mh-sketchout-text">old price</span>
|
|
288
|
+
<div class="mh-sketchout-lines" id="sketchout-1">
|
|
289
|
+
<div class="mh-sketchout-line mh-sketchout-fwd"></div>
|
|
290
|
+
<div class="mh-sketchout-line mh-sketchout-bwd"></div>
|
|
291
|
+
</div>
|
|
292
|
+
</div>
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
```css
|
|
296
|
+
.mh-sketchout-wrap {
|
|
297
|
+
position: relative;
|
|
298
|
+
display: inline-block;
|
|
299
|
+
}
|
|
300
|
+
.mh-sketchout-text {
|
|
301
|
+
position: relative;
|
|
302
|
+
z-index: 0;
|
|
303
|
+
}
|
|
304
|
+
.mh-sketchout-lines {
|
|
305
|
+
position: absolute;
|
|
306
|
+
top: 0;
|
|
307
|
+
left: -4px;
|
|
308
|
+
right: -4px;
|
|
309
|
+
bottom: 0;
|
|
310
|
+
overflow: hidden;
|
|
311
|
+
z-index: 1;
|
|
312
|
+
}
|
|
313
|
+
.mh-sketchout-line {
|
|
314
|
+
position: absolute;
|
|
315
|
+
top: 50%;
|
|
316
|
+
left: 0;
|
|
317
|
+
width: 100%;
|
|
318
|
+
height: 2px;
|
|
319
|
+
background: #e53935;
|
|
320
|
+
transform-origin: left center;
|
|
321
|
+
transform: scaleX(0);
|
|
322
|
+
}
|
|
323
|
+
.mh-sketchout-fwd {
|
|
324
|
+
transform: scaleX(0) rotate(-12deg);
|
|
325
|
+
}
|
|
326
|
+
.mh-sketchout-bwd {
|
|
327
|
+
transform: scaleX(0) rotate(12deg);
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
```js
|
|
332
|
+
// Forward slash draws first
|
|
333
|
+
tl.to(
|
|
334
|
+
"#sketchout-1 .mh-sketchout-fwd",
|
|
335
|
+
{
|
|
336
|
+
scaleX: 1,
|
|
337
|
+
duration: 0.3,
|
|
338
|
+
ease: "power2.out",
|
|
339
|
+
},
|
|
340
|
+
1.0,
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
// Backward slash follows
|
|
344
|
+
tl.to(
|
|
345
|
+
"#sketchout-1 .mh-sketchout-bwd",
|
|
346
|
+
{
|
|
347
|
+
scaleX: 1,
|
|
348
|
+
duration: 0.3,
|
|
349
|
+
ease: "power2.out",
|
|
350
|
+
},
|
|
351
|
+
1.15,
|
|
352
|
+
);
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
## Combining Modes in Captions
|
|
356
|
+
|
|
357
|
+
Use mode cycling for visual variety across caption groups:
|
|
358
|
+
|
|
359
|
+
```js
|
|
360
|
+
var MODES = ["highlight", "circle", "burst", "scribble"];
|
|
361
|
+
|
|
362
|
+
GROUPS.forEach(function (group, gi) {
|
|
363
|
+
var mode = MODES[gi % MODES.length];
|
|
364
|
+
// Apply the mode's CSS pattern to emphasis words in this group
|
|
365
|
+
group.emphasisWords.forEach(function (word) {
|
|
366
|
+
applyMode(word.el, mode, tl, word.start);
|
|
367
|
+
});
|
|
368
|
+
});
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
Cycle every 2-3 groups for high energy, every 3-4 for medium, every 4-5 for low.
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Marker Highlight Examples
|
|
2
|
+
|
|
3
|
+
## Recipes
|
|
4
|
+
|
|
5
|
+
### Underline
|
|
6
|
+
|
|
7
|
+
```html
|
|
8
|
+
<mark
|
|
9
|
+
data-height="0.15"
|
|
10
|
+
data-offset="0.8"
|
|
11
|
+
data-padding="0"
|
|
12
|
+
data-highlight='{"amplitude":0.2,"wavelength":5,"roughEnds":0}'
|
|
13
|
+
data-color="rgba(30, 136, 229, 0.6)"
|
|
14
|
+
>important</mark
|
|
15
|
+
>
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### Strikethrough
|
|
19
|
+
|
|
20
|
+
```html
|
|
21
|
+
<mark
|
|
22
|
+
data-drawing-mode="highlight"
|
|
23
|
+
data-height="0.1"
|
|
24
|
+
data-offset="0"
|
|
25
|
+
data-highlight='{"amplitude":0.1,"wavelength":3}'
|
|
26
|
+
data-color="rgba(229, 57, 53, 0.8)"
|
|
27
|
+
>wrong answer</mark
|
|
28
|
+
>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Circled Annotation
|
|
32
|
+
|
|
33
|
+
```html
|
|
34
|
+
<mark
|
|
35
|
+
data-drawing-mode="circle"
|
|
36
|
+
data-circle='{"curve":0.8,"wobble":0.4,"loops":2,"thickness":3}'
|
|
37
|
+
data-animation-speed="1200"
|
|
38
|
+
data-color="rgba(229, 57, 53, 0.6)"
|
|
39
|
+
>this one</mark
|
|
40
|
+
>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Full Example in a Composition
|
|
44
|
+
|
|
45
|
+
```html
|
|
46
|
+
<div data-composition-id="highlight-demo" data-width="1920" data-height="1080">
|
|
47
|
+
<div
|
|
48
|
+
id="content"
|
|
49
|
+
style="
|
|
50
|
+
position: absolute; inset: 0;
|
|
51
|
+
display: flex; align-items: center; justify-content: center;
|
|
52
|
+
font-family: 'Inter', sans-serif; font-size: 72px; color: #fff;
|
|
53
|
+
background: #111;
|
|
54
|
+
"
|
|
55
|
+
>
|
|
56
|
+
<p id="hero">
|
|
57
|
+
The <mark id="m1" data-color="rgba(255, 220, 50, 0.5)">fastest</mark> way to
|
|
58
|
+
<mark
|
|
59
|
+
id="m2"
|
|
60
|
+
data-drawing-mode="circle"
|
|
61
|
+
data-circle='{"curve":0.8,"wobble":0.3,"loops":2,"thickness":3}'
|
|
62
|
+
data-color="rgba(229, 57, 53, 0.6)"
|
|
63
|
+
>ship</mark
|
|
64
|
+
>
|
|
65
|
+
</p>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<style>
|
|
69
|
+
[data-composition-id="highlight-demo"] mark {
|
|
70
|
+
background-color: transparent;
|
|
71
|
+
color: inherit;
|
|
72
|
+
}
|
|
73
|
+
</style>
|
|
74
|
+
|
|
75
|
+
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
|
76
|
+
<script src="marker-highlight.global.js"></script>
|
|
77
|
+
<script>
|
|
78
|
+
window.__timelines = window.__timelines || {};
|
|
79
|
+
var tl = gsap.timeline({ paused: true });
|
|
80
|
+
|
|
81
|
+
// Set colors via data attribute (no visible flash)
|
|
82
|
+
document.querySelectorAll("mark[data-color]").forEach(function (m) {
|
|
83
|
+
m.setAttribute("data-original-bgcolor", m.getAttribute("data-color"));
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Init once after fonts, then hide all canvases
|
|
87
|
+
var hl;
|
|
88
|
+
document.fonts.ready.then(function () {
|
|
89
|
+
setTimeout(function () {
|
|
90
|
+
hl = new MarkerHighlighter(document.getElementById("hero"), {
|
|
91
|
+
animate: false,
|
|
92
|
+
animationSpeed: 800,
|
|
93
|
+
padding: 0.3,
|
|
94
|
+
highlight: { amplitude: 0.3, wavelength: 5 },
|
|
95
|
+
});
|
|
96
|
+
setTimeout(function () {
|
|
97
|
+
document.querySelectorAll(".highlight").forEach(function (d) {
|
|
98
|
+
d.style.opacity = "0";
|
|
99
|
+
});
|
|
100
|
+
}, 100);
|
|
101
|
+
}, 50);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
function addHighlight(markId, time) {
|
|
105
|
+
tl.to(
|
|
106
|
+
{},
|
|
107
|
+
{
|
|
108
|
+
duration: 0.001,
|
|
109
|
+
onStart: function () {
|
|
110
|
+
var mark = document.getElementById(markId);
|
|
111
|
+
var ref = mark.getAttribute("data-mark-ref");
|
|
112
|
+
if (!ref || !hl) return;
|
|
113
|
+
mark.parentElement
|
|
114
|
+
.querySelectorAll('.highlight[data-mark-id="' + ref + '"]')
|
|
115
|
+
.forEach(function (div) {
|
|
116
|
+
var c = div.querySelector("canvas");
|
|
117
|
+
if (c) c.getContext("2d").clearRect(0, 0, c.width, c.height);
|
|
118
|
+
div.style.opacity = "1";
|
|
119
|
+
});
|
|
120
|
+
hl.reanimateMark(mark);
|
|
121
|
+
},
|
|
122
|
+
onReverseComplete: function () {
|
|
123
|
+
var mark = document.getElementById(markId);
|
|
124
|
+
var ref = mark.getAttribute("data-mark-ref");
|
|
125
|
+
if (!ref) return;
|
|
126
|
+
mark.parentElement
|
|
127
|
+
.querySelectorAll('.highlight[data-mark-id="' + ref + '"]')
|
|
128
|
+
.forEach(function (div) {
|
|
129
|
+
div.style.opacity = "0";
|
|
130
|
+
});
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
time,
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
gsap.set("#hero", { opacity: 0 });
|
|
138
|
+
tl.to("#hero", { opacity: 1, duration: 0.6 }, 0);
|
|
139
|
+
|
|
140
|
+
addHighlight("m1", 0.8);
|
|
141
|
+
addHighlight("m2", 1.6);
|
|
142
|
+
|
|
143
|
+
window.__timelines["highlight-demo"] = tl;
|
|
144
|
+
</script>
|
|
145
|
+
</div>
|
|
146
|
+
```
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# Marker Highlight
|
|
2
|
+
|
|
3
|
+
Animated canvas-based text highlighting using MarkerHighlight.js. Wraps text in `<mark>` tags and renders effects (marker pen, circle, burst, scribble, sketchout) on a canvas overlay without modifying text DOM.
|
|
4
|
+
|
|
5
|
+
The library runs its own requestAnimationFrame loop — **not** GSAP-driven. Use `tl.call()` to trigger at specific timeline points.
|
|
6
|
+
|
|
7
|
+
## Required Script
|
|
8
|
+
|
|
9
|
+
Download and convert to global script:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
curl -sL "https://cdn.jsdelivr.net/gh/Robincodes-Sandbox/marker-highlight@main/dist/marker-highlight.min.js" \
|
|
13
|
+
| sed 's/export{[^}]*};$/window.MarkerHighlighter=W;/' > marker-highlight.global.js
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
```html
|
|
17
|
+
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
|
18
|
+
<script src="marker-highlight.global.js"></script>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Color Setup
|
|
22
|
+
|
|
23
|
+
Set via `data-color`, copy to `data-original-bgcolor` before constructing. Never set `background-color` in CSS.
|
|
24
|
+
|
|
25
|
+
```css
|
|
26
|
+
mark {
|
|
27
|
+
color: inherit;
|
|
28
|
+
background-color: transparent;
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
```html
|
|
33
|
+
<mark id="m1" data-color="rgba(255, 220, 50, 0.5)">highlighted</mark>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
```js
|
|
37
|
+
document
|
|
38
|
+
.querySelectorAll("mark[data-color]")
|
|
39
|
+
.forEach((m) => m.setAttribute("data-original-bgcolor", m.getAttribute("data-color")));
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## GSAP Integration Pattern
|
|
43
|
+
|
|
44
|
+
ONE MarkerHighlighter per container with `animate: false`, hide all canvases, then clear+show+reanimate per mark at trigger time.
|
|
45
|
+
|
|
46
|
+
```js
|
|
47
|
+
var hl = new MarkerHighlighter(document.getElementById("text-container"), {
|
|
48
|
+
animate: false,
|
|
49
|
+
animationSpeed: 800,
|
|
50
|
+
padding: 0.3,
|
|
51
|
+
highlight: { amplitude: 0.3, wavelength: 5 },
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
setTimeout(function () {
|
|
55
|
+
document.querySelectorAll(".highlight").forEach((div) => (div.style.opacity = "0"));
|
|
56
|
+
}, 100);
|
|
57
|
+
|
|
58
|
+
function addHighlight(highlighter, markId, time) {
|
|
59
|
+
tl.to(
|
|
60
|
+
{},
|
|
61
|
+
{
|
|
62
|
+
duration: 0.001,
|
|
63
|
+
onStart: function () {
|
|
64
|
+
var mark = document.getElementById(markId);
|
|
65
|
+
var ref = mark.getAttribute("data-mark-ref");
|
|
66
|
+
var divs = mark.parentElement.querySelectorAll('.highlight[data-mark-id="' + ref + '"]');
|
|
67
|
+
divs.forEach(function (div) {
|
|
68
|
+
var canvas = div.querySelector("canvas");
|
|
69
|
+
if (canvas) canvas.getContext("2d").clearRect(0, 0, canvas.width, canvas.height);
|
|
70
|
+
div.style.opacity = "1";
|
|
71
|
+
});
|
|
72
|
+
highlighter.reanimateMark(mark);
|
|
73
|
+
},
|
|
74
|
+
onReverseComplete: function () {
|
|
75
|
+
var mark = document.getElementById(markId);
|
|
76
|
+
var ref = mark.getAttribute("data-mark-ref");
|
|
77
|
+
mark.parentElement
|
|
78
|
+
.querySelectorAll('.highlight[data-mark-id="' + ref + '"]')
|
|
79
|
+
.forEach((div) => (div.style.opacity = "0"));
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
time,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
addHighlight(hl, "m1", 1.0);
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Drawing Modes
|
|
90
|
+
|
|
91
|
+
| Mode | Effect | Best for |
|
|
92
|
+
| ----------- | ---------------------------- | -------------------------- |
|
|
93
|
+
| `highlight` | Wavy marker stroke (default) | Phrases, key terms |
|
|
94
|
+
| `circle` | Hand-drawn ellipse | Single words, annotations |
|
|
95
|
+
| `burst` | Radiating lines/curves/puffs | Excitement, energy |
|
|
96
|
+
| `scribble` | Chaotic scribble | Crossing out, messy energy |
|
|
97
|
+
| `sketchout` | Rough rectangle outline | Boxed callouts, blueprint |
|
|
98
|
+
|
|
99
|
+
```html
|
|
100
|
+
<mark data-drawing-mode="circle" data-color="rgba(229, 57, 53, 0.6)">critical</mark>
|
|
101
|
+
<mark
|
|
102
|
+
data-drawing-mode="burst"
|
|
103
|
+
data-burst='{"style":"cloud","count":20}'
|
|
104
|
+
data-color="rgba(255, 220, 50, 0.5)"
|
|
105
|
+
>amazing</mark
|
|
106
|
+
>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Configuration
|
|
110
|
+
|
|
111
|
+
### Global (constructor)
|
|
112
|
+
|
|
113
|
+
| Option | Default | Description |
|
|
114
|
+
| ---------------- | ------------- | ------------------------- |
|
|
115
|
+
| `animate` | `true` | `false` to defer for GSAP |
|
|
116
|
+
| `animationSpeed` | `5000` | Duration in ms |
|
|
117
|
+
| `drawingMode` | `"highlight"` | Default mode |
|
|
118
|
+
| `height` | `1` | Relative to line height |
|
|
119
|
+
| `offset` | `0` | Vertical shift |
|
|
120
|
+
| `padding` | `0` | Horizontal padding |
|
|
121
|
+
|
|
122
|
+
### Per-Mode
|
|
123
|
+
|
|
124
|
+
**highlight**: `amplitude` (0.25), `wavelength` (1), `roughEnds` (5), `jitter` (0.1)
|
|
125
|
+
**circle**: `curve` (0.5), `wobble` (0.3), `loops` (3), `thickness` (5)
|
|
126
|
+
**burst**: `style` ("lines"/"curve"/"cloud"), `count` (10), `power` (1), `randomness` (0.5)
|
|
127
|
+
|
|
128
|
+
### Named Styles
|
|
129
|
+
|
|
130
|
+
```js
|
|
131
|
+
MarkerHighlighter.defineStyle("underline", {
|
|
132
|
+
animationSpeed: 400,
|
|
133
|
+
height: 0.15,
|
|
134
|
+
offset: 0.8,
|
|
135
|
+
padding: 0,
|
|
136
|
+
highlight: { amplitude: 0.2, wavelength: 5, roughEnds: 0 },
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Mode-to-Caption Energy Mapping
|
|
141
|
+
|
|
142
|
+
| Energy | Mode | Use for |
|
|
143
|
+
| ----------- | --------------------- | ------------------- |
|
|
144
|
+
| High | `burst` + `highlight` | Launches, hype |
|
|
145
|
+
| Medium-high | `circle` | Key stats, terms |
|
|
146
|
+
| Medium | `highlight` | Standard emphasis |
|
|
147
|
+
| Medium-low | `scribble` | Subtle, tutorials |
|
|
148
|
+
| Low | `sketchout` | Contrast, blueprint |
|
|
149
|
+
|
|
150
|
+
## Notes
|
|
151
|
+
|
|
152
|
+
- One highlighter per container (clears all `.highlight` divs on init)
|
|
153
|
+
- Canvas pre-draw + clear pattern for clean reveals
|
|
154
|
+
- rAF-based — not seekable mid-stroke
|
|
155
|
+
- Use `onReverseComplete` for rewind support
|
|
156
|
+
|
|
157
|
+
For CSS+GSAP fallback (no library, fully seekable), see [css-patterns.md](css-patterns.md).
|
|
158
|
+
For full examples, see [examples.md](examples.md).
|