domotion-svg 0.19.0 → 0.19.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/animation/cursor-overlay.js +33 -12
- package/package.json +1 -1
|
@@ -267,7 +267,7 @@ export function cursorOverlayMarkup(positions, clicks, style, totalDurationMs, c
|
|
|
267
267
|
kf.push(`@keyframes ${posName}{${positions.map((p) => `${pct(p.t / totalDurationMs)}{transform:translate(${num(p.x)}px,${num(p.y)}px)}`).join("")}}`);
|
|
268
268
|
const posAnim = `${posName} ${totalSec}s linear infinite`;
|
|
269
269
|
// Pulse fragments — one per click; each pushes its own keyframes into `kf`.
|
|
270
|
-
const pulseMarkup = clicks.map((c, i) => buildPulseFragment(c, i, uid, kf)).join("\n");
|
|
270
|
+
const pulseMarkup = clicks.map((c, i) => buildPulseFragment(c, i, uid, kf, totalDurationMs)).join("\n");
|
|
271
271
|
let pointerGroup;
|
|
272
272
|
if (cursorTimeline != null && cursorTimeline.length > 0) {
|
|
273
273
|
// DM-1106: one glyph per distinct keyword, each toggled by a DISCRETE opacity
|
|
@@ -303,28 +303,49 @@ ${pulseMarkup}
|
|
|
303
303
|
}
|
|
304
304
|
/** Build the SVG fragment for a single click pulse, pushing its keyframes into
|
|
305
305
|
* `kf`. The expanding ring is `transform:scale` with `non-scaling-stroke` (so
|
|
306
|
-
* it matches the old SMIL `r` growth but on the CSS timeline)
|
|
307
|
-
*
|
|
308
|
-
*
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
306
|
+
* it matches the old SMIL `r` growth but on the CSS timeline).
|
|
307
|
+
*
|
|
308
|
+
* DM-1510: the pulse animation spans the WHOLE loop (`totalDurationMs`) and runs
|
|
309
|
+
* `infinite`, exactly like the cursor position track — NOT a `durSec`-long
|
|
310
|
+
* one-shot with a start delay. The pulse curve occupies its click window
|
|
311
|
+
* `[c.t, c.t + pulseDurationMs]` as a fraction of the loop and holds invisible
|
|
312
|
+
* (opacity 0, scale reset) the rest of the loop. A one-shot `forwards`
|
|
313
|
+
* animation only played on the first loop iteration, so the click rings
|
|
314
|
+
* vanished after the SVG looped even though the cursor kept moving. */
|
|
315
|
+
function buildPulseFragment(c, idx, uid, kf, totalDurationMs) {
|
|
316
|
+
const totalSec = totalDurationMs / 1000;
|
|
317
|
+
const durMs = c.style.pulseDurationMs;
|
|
312
318
|
const r0 = 4;
|
|
313
319
|
const r1 = c.style.pulseRadius;
|
|
314
320
|
const innerR = r1 * 0.55;
|
|
315
321
|
const outerName = `co-pulse-${uid}-${idx}o`;
|
|
316
322
|
const innerName = `co-pulse-${uid}-${idx}i`;
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
const
|
|
323
|
+
// Fractions of the full loop where this pulse's window lands. The window's
|
|
324
|
+
// start/peak/end map to the old one-shot's 0% / 15% / 100% keyframes.
|
|
325
|
+
const fStart = c.t / totalDurationMs;
|
|
326
|
+
const fPeak = Math.min(1, (c.t + 0.15 * durMs) / totalDurationMs);
|
|
327
|
+
const fEnd = Math.min(1, (c.t + durMs) / totalDurationMs);
|
|
328
|
+
// Hold scale(1)+opacity 0 before the click (only needed when the click is not
|
|
329
|
+
// at t=0) and after the window (reset for the next loop, unless the window
|
|
330
|
+
// already reaches the loop end).
|
|
331
|
+
const lead = fStart > 0 ? `${pct(fStart)}{transform:scale(1);opacity:0}` : "";
|
|
332
|
+
const tail = fEnd < 1 ? `100%{transform:scale(1);opacity:0}` : "";
|
|
333
|
+
const peak = (op) => (fPeak < fEnd ? `${pct(fPeak)}{opacity:${op}}` : "");
|
|
334
|
+
kf.push(`@keyframes ${outerName}{0%{transform:scale(1);opacity:0}${lead}${peak(0.9)}${pct(fEnd)}{transform:scale(${num(r1 / r0)});opacity:0}${tail}}`);
|
|
335
|
+
kf.push(`@keyframes ${innerName}{0%{transform:scale(1);opacity:0}${lead}${peak(0.95)}${pct(fEnd)}{transform:scale(${num((r1 - 1) / r0)});opacity:0}${tail}}`);
|
|
336
|
+
const ringStyle = (name) => `transform-box:fill-box;transform-origin:center;vector-effect:non-scaling-stroke;animation:${name} ${totalSec}s linear infinite`;
|
|
320
337
|
// Right-half-disc fill for secondary clicks.
|
|
321
338
|
let secondaryHalf = "";
|
|
322
339
|
if (c.button === "secondary") {
|
|
323
340
|
const halfPath = `M ${num(c.x)} ${num(c.y - innerR)} A ${num(innerR)} ${num(innerR)} 0 0 1 ${num(c.x)} ${num(c.y + innerR)} Z`;
|
|
324
341
|
const halfName = `co-pulse-${uid}-${idx}h`;
|
|
325
|
-
|
|
342
|
+
const fHalfPeak = Math.min(1, (c.t + 0.20 * durMs) / totalDurationMs);
|
|
343
|
+
const leadH = fStart > 0 ? `${pct(fStart)}{opacity:0}` : "";
|
|
344
|
+
const tailH = fEnd < 1 ? `100%{opacity:0}` : "";
|
|
345
|
+
const peakH = fHalfPeak < fEnd ? `${pct(fHalfPeak)}{opacity:1}` : "";
|
|
346
|
+
kf.push(`@keyframes ${halfName}{0%{opacity:0}${leadH}${peakH}${pct(fEnd)}{opacity:0}${tailH}}`);
|
|
326
347
|
secondaryHalf = `
|
|
327
|
-
<path d="${halfPath}" fill="rgba(0,0,0,0.2)" opacity="0" style="animation:${halfName} ${
|
|
348
|
+
<path d="${halfPath}" fill="rgba(0,0,0,0.2)" opacity="0" style="animation:${halfName} ${totalSec}s linear infinite" />`;
|
|
328
349
|
}
|
|
329
350
|
return ` <g class="cursor-click cursor-click-${idx}">
|
|
330
351
|
<circle cx="${num(c.x)}" cy="${num(c.y)}" r="${r0}" fill="none" stroke="${c.style.pulseStrokeOuter}" stroke-width="2" opacity="0" style="${ringStyle(outerName)}" />
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "domotion-svg",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.1",
|
|
4
4
|
"description": "DOM-to-animated-SVG renderer. Captures HTML/CSS via Playwright Chromium and converts it to self-contained SVG with CSS animations — pixel-faithful demos that scale crisply and load lazily.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Brian Westphal",
|