@birdapi/velinstyle 1.2.2 → 1.3.0

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.
Files changed (109) hide show
  1. package/README.de.md +18 -4
  2. package/README.md +42 -7
  3. package/cli/cli-manifest.json +441 -174
  4. package/cli/docgen/extract-cli.js +2 -1
  5. package/cli/docs-generate.js +9 -0
  6. package/cli/experience.js +173 -0
  7. package/cli/index.js +1294 -417
  8. package/cli/motion.js +738 -0
  9. package/cli/production/extract.js +46 -6
  10. package/cli/production/report.js +104 -14
  11. package/cli/production/run.js +44 -1
  12. package/cli/security.js +190 -0
  13. package/cli/skills.js +53 -10
  14. package/cli/transparency.js +140 -41
  15. package/cli/workflow.js +32 -17
  16. package/components/velin-theme-toggle.js +15 -1
  17. package/core/a11y/component-contracts.json +391 -274
  18. package/core/attributes/registry.js +1 -1
  19. package/core/motion/analyze.js +38 -0
  20. package/core/motion/blueprint.js +51 -0
  21. package/core/motion/composer.js +114 -0
  22. package/core/motion/data/registry.json +352 -0
  23. package/core/motion/effects.js +17 -1
  24. package/core/motion/index.js +112 -9
  25. package/core/motion/optimize.js +44 -0
  26. package/core/motion/policy.js +77 -0
  27. package/core/motion/registry.js +131 -0
  28. package/core/motion/review.js +59 -0
  29. package/core/motion/scan.js +150 -0
  30. package/core/motion/timeline.js +68 -0
  31. package/core/motion/transitions.js +49 -0
  32. package/core/motion/triggers.js +168 -0
  33. package/core/security/checks/fs.js +92 -0
  34. package/core/security/detect.js +35 -0
  35. package/core/security/engine.js +116 -0
  36. package/core/security/index.js +18 -0
  37. package/core/security/registry.js +85 -0
  38. package/core/security/report/report.js +113 -0
  39. package/core/security/rules/ci/index.js +146 -0
  40. package/core/security/rules/consumer/index.js +174 -0
  41. package/core/security/rules/deps/index.js +182 -0
  42. package/core/security/rules/index.js +24 -0
  43. package/core/security/rules/publish/index.js +230 -0
  44. package/core/security/rules/repo/index.js +143 -0
  45. package/core/security/rules/secrets/index.js +133 -0
  46. package/core/security/score/dependency-health.js +53 -0
  47. package/core/security/score/release-health.js +66 -0
  48. package/core/security/score/score.js +102 -0
  49. package/core/security/timeline/timeline.js +76 -0
  50. package/dist/chunks/attributes-HK7VIOLA.js +1080 -0
  51. package/dist/chunks/attributes-VRHHVNDO.js +1080 -0
  52. package/dist/chunks/chunk-MYKUI364.js +116 -0
  53. package/dist/chunks/chunk-NEVBPD5T.js +116 -0
  54. package/dist/chunks/runtime-entry.js +1 -1
  55. package/dist/chunks/velin-theme-toggle-J2NHC7IM.js +304 -0
  56. package/dist/llms.txt +2 -2
  57. package/dist/search-index.json +166 -2
  58. package/dist/velin-agent.json +156 -32
  59. package/dist/velinstyle-components.iife.js +862 -17
  60. package/dist/velinstyle-components.js +862 -17
  61. package/dist/velinstyle-components.min.js +126 -126
  62. package/dist/velinstyle.css +42 -7
  63. package/dist/velinstyle.min.css +1 -1
  64. package/package.json +158 -143
  65. package/packages/velinstyle-cli-core/package.json +11 -0
  66. package/packages/velinstyle-cli-core/src/config.js +84 -0
  67. package/packages/velinstyle-cli-core/src/contract.js +56 -0
  68. package/packages/velinstyle-cli-core/src/event-bus.js +50 -0
  69. package/packages/velinstyle-cli-core/src/index.js +18 -0
  70. package/packages/velinstyle-cli-core/src/lifecycle.js +33 -0
  71. package/packages/velinstyle-cli-core/src/runner.js +129 -0
  72. package/packages/velinstyle-cli-registry/data/commands.json +1216 -0
  73. package/packages/velinstyle-cli-registry/data/layouts.json +33 -0
  74. package/packages/velinstyle-cli-registry/data/widgets.json +35 -0
  75. package/packages/velinstyle-cli-registry/package.json +13 -0
  76. package/packages/velinstyle-cli-registry/src/index.js +88 -0
  77. package/packages/velinstyle-cli-renderer/package.json +8 -0
  78. package/packages/velinstyle-cli-renderer/src/index.js +286 -0
  79. package/packages/velinstyle-cli-theme/package.json +8 -0
  80. package/packages/velinstyle-cli-theme/src/index.js +84 -0
  81. package/packages/velinstyle-cli-ui/package.json +8 -0
  82. package/packages/velinstyle-cli-ui/src/index.js +34 -0
  83. package/packages/velinstyle-motion/README.md +81 -0
  84. package/packages/velinstyle-motion/package.json +37 -0
  85. package/packages/velinstyle-motion/src/ai/mini-prompt.js +119 -0
  86. package/packages/velinstyle-motion/src/ai/provider.js +59 -0
  87. package/packages/velinstyle-motion/src/ai/providers/pixverse-setup.js +402 -0
  88. package/packages/velinstyle-motion/src/ai/providers/pixverse.js +245 -0
  89. package/packages/velinstyle-motion/src/config.js +257 -0
  90. package/packages/velinstyle-motion/src/export/html-clip.js +141 -0
  91. package/packages/velinstyle-motion/src/index.js +304 -0
  92. package/packages/velinstyle-motion/src/jobs/cache.js +30 -0
  93. package/packages/velinstyle-motion/src/jobs/queue.js +69 -0
  94. package/packages/velinstyle-motion/src/jobs/usage.js +53 -0
  95. package/packages/velinstyle-motion/src/local/effects.js +139 -0
  96. package/packages/velinstyle-motion/src/local/engine.js +96 -0
  97. package/packages/velinstyle-motion/src/providers-catalog.js +44 -0
  98. package/packages/velinstyle-motion/src/registry/presets.js +25 -0
  99. package/packages/velinstyle-motion/src/registry/presets.json +85 -0
  100. package/packages/velinstyle-motion/src/ux/cost-gate.js +37 -0
  101. package/packages/velinstyle-motion/src/ux/pixverse-gate.js +153 -0
  102. package/packages/velinstyle-motion/src/ux/wizard.js +171 -0
  103. package/packages/velinstyle-motion/src/validate-image.js +91 -0
  104. package/packages/velinstyle-skills/catalog.json +1 -1
  105. package/packages/velinstyle-skills/registry.json +1 -142
  106. package/packages/velinstyle-skills/skills/velin-motion-reduced-safe/SKILL.md +1 -1
  107. package/packages/velinstyle-skills/skills/velin-motion-reveal-feedback/SKILL.md +1 -1
  108. package/src/tokens/motion.css +7 -0
  109. package/src/utilities/scroll-animation.css +66 -0
@@ -240,7 +240,7 @@ function registerBuiltins() {
240
240
  },
241
241
  });
242
242
 
243
- for (const motion of ['velin-reveal', 'velin-fade', 'velin-slide', 'velin-scale', 'velin-parallax', 'velin-hover', 'velin-stagger', 'velin-scroll']) {
243
+ for (const motion of ['velin-motion', 'velin-trigger', 'velin-duration', 'velin-delay', 'velin-ease', 'velin-timeline', 'velin-motion-sequence', 'velin-reveal', 'velin-fade', 'velin-slide', 'velin-scale', 'velin-parallax', 'velin-hover', 'velin-stagger', 'velin-scroll']) {
244
244
  registerAttribute(motion, { enhance() {} });
245
245
  }
246
246
  }
@@ -0,0 +1,38 @@
1
+ import { scanMotionHtml, doctorMotionHtml, collectMotionElements } from './scan.js';
2
+
3
+ /**
4
+ * Aggregate metrics for `velinstyle motion analyze`.
5
+ * @param {string} html
6
+ */
7
+ export function analyzeMotionHtml(html) {
8
+ const { elements } = collectMotionElements(html);
9
+ const scan = scanMotionHtml(html);
10
+ const durations = elements.map((e) => e.duration).filter((d) => d > 0);
11
+ const sum = durations.reduce((a, b) => a + b, 0);
12
+ const avg = durations.length ? Math.round(sum / durations.length) : 0;
13
+ const longest = durations.length ? Math.max(...durations) : 0;
14
+ const doctor = doctorMotionHtml(html);
15
+ const reducedPass = !doctor.findings.some((f) => f.code === 'motion.missing-reduced' || f.code === 'motion.excessive-duration');
16
+
17
+ let layoutShiftRisk = 'LOW';
18
+ if (elements.some((e) => e.preset === 'parallax') || scan.summary.infinite > 2) layoutShiftRisk = 'MEDIUM';
19
+ if (scan.summary.infinite > 5 || longest > 2000) layoutShiftRisk = 'HIGH';
20
+
21
+ return {
22
+ ok: doctor.ok,
23
+ metrics: {
24
+ animations: elements.length || scan.summary.classHits,
25
+ durationMs: sum,
26
+ durationLabel: `${(sum / 1000).toFixed(1)} s`,
27
+ averageMs: avg,
28
+ longestMs: longest,
29
+ reducedMotion: reducedPass ? 'PASS' : 'FAIL',
30
+ infinite: scan.summary.infinite,
31
+ layoutShiftRisk,
32
+ },
33
+ summary: scan.summary,
34
+ findings: doctor.findings,
35
+ };
36
+ }
37
+
38
+ export { doctorMotionHtml, scanMotionHtml };
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Emit motion blueprint HTML snippets from registry.
3
+ */
4
+ import { getBlueprint, getPreset, getTimeline, getPolicy } from './registry.js';
5
+
6
+ /**
7
+ * @param {string} kind
8
+ * @returns {{ ok: boolean, html?: string, error?: string, blueprint?: object }}
9
+ */
10
+ export function emitMotionBlueprint(kind) {
11
+ const bp = getBlueprint(kind);
12
+ if (!bp) {
13
+ return {
14
+ ok: false,
15
+ error: `Unknown motion blueprint "${kind}". Use: landing|dashboard|pricing|portfolio|shop|forum|docs|blog|crm|login`,
16
+ };
17
+ }
18
+ const policy = getPolicy(bp.policy || 'safe');
19
+ const timeline = bp.timeline ? getTimeline(bp.timeline) : null;
20
+ const presetBlocks = (bp.presets || [])
21
+ .map((id) => {
22
+ const p = getPreset(id);
23
+ return ` <!-- preset: ${id}${p ? ` — ${p.title}` : ''} -->
24
+ <div velin-motion="${id === 'heroReveal' ? 'fade' : id}" velin-trigger="visible" class="velin-mb-4">
25
+ <p class="velin-text-muted">${p?.title || id}</p>
26
+ </div>`;
27
+ })
28
+ .join('\n');
29
+
30
+ const timelineBlock = timeline
31
+ ? `
32
+ <section velin-timeline="${timeline.id}" class="velin-stack velin-gap-3" aria-label="${timeline.title}">
33
+ ${(timeline.steps || [])
34
+ .map(
35
+ (step, i) =>
36
+ ` <div data-velin-step="${step}" velin-motion="fade" velin-trigger="load" velin-delay="${i * 100}">${step}</div>`,
37
+ )
38
+ .join('\n')}
39
+ </section>`
40
+ : '';
41
+
42
+ const html = `<!-- Velin Motion blueprint: ${bp.id} · policy ${policy?.id || 'safe'} -->
43
+ <!-- ${bp.description} -->
44
+ <div data-velin-motion-policy="${policy?.id || 'safe'}" data-velin-reveal-auto>
45
+ ${presetBlocks}
46
+ ${timelineBlock}
47
+ </div>
48
+ `;
49
+
50
+ return { ok: true, html, blueprint: bp };
51
+ }
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Motion Composer — sequence API (Studio-ready).
3
+ */
4
+ import { prefersReducedMotion, markVisible } from './effects.js';
5
+ import { resolvePresetId } from './registry.js';
6
+ import { applyEffects } from './effects.js';
7
+
8
+ /**
9
+ * @typedef {{ type: 'fade'|'slide'|'wait'|'rotate'|'blur'|'preset', preset?: string, ms?: number, value?: string }} MotionStep
10
+ */
11
+
12
+ export function fade(ms = 400) {
13
+ return { type: 'fade', ms };
14
+ }
15
+
16
+ export function slide(ms = 500, value = 'up') {
17
+ return { type: 'slide', ms, value };
18
+ }
19
+
20
+ export function wait(ms = 200) {
21
+ return { type: 'wait', ms };
22
+ }
23
+
24
+ export function rotate(ms = 400) {
25
+ return { type: 'rotate', ms };
26
+ }
27
+
28
+ export function blur(ms = 400) {
29
+ return { type: 'blur', ms };
30
+ }
31
+
32
+ export function preset(id, ms) {
33
+ return { type: 'preset', preset: id, ms };
34
+ }
35
+
36
+ /**
37
+ * @param {MotionStep[]} steps
38
+ * @param {{ root?: HTMLElement, target?: HTMLElement }} [ctx]
39
+ */
40
+ export async function sequence(steps = [], ctx = {}) {
41
+ const target = ctx.target || ctx.root;
42
+ const reduced = prefersReducedMotion();
43
+ const ran = [];
44
+
45
+ for (const step of steps) {
46
+ if (step.type === 'wait') {
47
+ if (!reduced && step.ms) await delay(step.ms);
48
+ ran.push(step);
49
+ continue;
50
+ }
51
+ if (target && typeof target.classList?.add === 'function') {
52
+ if (step.type === 'preset' && step.preset) {
53
+ const p = resolvePresetId(step.preset);
54
+ if (p?.cssClass) target.classList.add(...String(p.cssClass).split(/\s+/));
55
+ applyEffects(target, { [step.preset]: 'true' });
56
+ } else if (step.type === 'fade') {
57
+ applyEffects(target, { fade: 'true' });
58
+ } else if (step.type === 'slide') {
59
+ applyEffects(target, { slide: step.value || 'up' });
60
+ } else if (step.type === 'blur') {
61
+ applyEffects(target, { blur: 'true' });
62
+ } else if (step.type === 'rotate') {
63
+ applyEffects(target, { flip: 'true' });
64
+ }
65
+ markVisible(target);
66
+ }
67
+ if (!reduced && step.ms) await delay(step.ms);
68
+ ran.push(step);
69
+ }
70
+ return { ok: true, ran };
71
+ }
72
+
73
+ function delay(ms) {
74
+ return new Promise((resolve) => {
75
+ if (typeof setTimeout === 'undefined') return resolve();
76
+ setTimeout(resolve, ms);
77
+ });
78
+ }
79
+
80
+ /**
81
+ * Declarative `<section velin-motion-sequence>` — children play in order.
82
+ */
83
+ export function enhanceSequences(root) {
84
+ if (!root?.querySelectorAll) return () => {};
85
+ const nodes = [...root.querySelectorAll('[velin-motion-sequence]')];
86
+ for (const section of nodes) {
87
+ const children = [...section.children];
88
+ let i = 0;
89
+ const stepMs = Number(section.getAttribute('velin-sequence-gap') || 120);
90
+ for (const child of children) {
91
+ child.classList.add('velin-animate-on-scroll', 'velin-animate-on-scroll--fade');
92
+ if (prefersReducedMotion()) {
93
+ markVisible(child);
94
+ } else if (typeof setTimeout !== 'undefined') {
95
+ const delayMs = i * stepMs;
96
+ setTimeout(() => markVisible(child), delayMs);
97
+ } else {
98
+ markVisible(child);
99
+ }
100
+ i += 1;
101
+ }
102
+ }
103
+ return () => {};
104
+ }
105
+
106
+ export const Motion = {
107
+ sequence,
108
+ fade,
109
+ slide,
110
+ wait,
111
+ rotate,
112
+ blur,
113
+ preset,
114
+ };
@@ -0,0 +1,352 @@
1
+ {
2
+ "schema": "velinstyle.motion.registry",
3
+ "version": 1,
4
+ "presets": [
5
+ {
6
+ "id": "fade",
7
+ "title": "Fade",
8
+ "description": "Opacity entrance; layout-safe default reveal.",
9
+ "kind": "preset",
10
+ "category": "hero",
11
+ "tags": ["entrance"],
12
+ "duration": 400,
13
+ "fps": "60",
14
+ "reducedMotion": true,
15
+ "layoutSafe": true,
16
+ "status": "stable",
17
+ "cssClass": "velin-animate-on-scroll--fade",
18
+ "compatibility": { "cssOnly": true, "runtime": true, "webComponents": true, "studio": true, "production": true, "review": true, "cli": true }
19
+ },
20
+ {
21
+ "id": "slide",
22
+ "title": "Slide Up",
23
+ "description": "Slide from below into view.",
24
+ "kind": "preset",
25
+ "category": "cards",
26
+ "tags": ["entrance", "slide"],
27
+ "duration": 500,
28
+ "fps": "60",
29
+ "reducedMotion": true,
30
+ "layoutSafe": true,
31
+ "status": "stable",
32
+ "cssClass": "velin-animate-on-scroll--slide-up",
33
+ "compatibility": { "cssOnly": true, "runtime": true, "webComponents": true, "studio": true, "production": true, "review": true, "cli": true }
34
+ },
35
+ {
36
+ "id": "slide-down",
37
+ "title": "Slide Down",
38
+ "description": "Slide from above into view.",
39
+ "kind": "preset",
40
+ "category": "navigation",
41
+ "tags": ["entrance", "slide"],
42
+ "duration": 500,
43
+ "reducedMotion": true,
44
+ "layoutSafe": true,
45
+ "status": "stable",
46
+ "cssClass": "velin-animate-on-scroll--slide-down",
47
+ "compatibility": { "cssOnly": true, "runtime": true, "webComponents": true, "studio": true, "production": true, "review": true, "cli": true }
48
+ },
49
+ {
50
+ "id": "slide-left",
51
+ "title": "Slide Left",
52
+ "description": "Slide from the inline-end.",
53
+ "kind": "preset",
54
+ "category": "lists",
55
+ "tags": ["entrance", "slide"],
56
+ "duration": 500,
57
+ "reducedMotion": true,
58
+ "layoutSafe": true,
59
+ "status": "stable",
60
+ "cssClass": "velin-animate-on-scroll--slide-left",
61
+ "compatibility": { "cssOnly": true, "runtime": true, "webComponents": true, "studio": true, "production": true, "review": true, "cli": true }
62
+ },
63
+ {
64
+ "id": "slide-right",
65
+ "title": "Slide Right",
66
+ "description": "Slide from the inline-start.",
67
+ "kind": "preset",
68
+ "category": "lists",
69
+ "tags": ["entrance", "slide"],
70
+ "duration": 500,
71
+ "reducedMotion": true,
72
+ "layoutSafe": true,
73
+ "status": "stable",
74
+ "cssClass": "velin-animate-on-scroll--slide-right",
75
+ "compatibility": { "cssOnly": true, "runtime": true, "webComponents": true, "studio": true, "production": true, "review": true, "cli": true }
76
+ },
77
+ {
78
+ "id": "zoom",
79
+ "title": "Zoom / Scale",
80
+ "description": "Scale-in entrance for cards and media.",
81
+ "kind": "preset",
82
+ "category": "media",
83
+ "tags": ["entrance", "scale"],
84
+ "duration": 450,
85
+ "reducedMotion": true,
86
+ "layoutSafe": true,
87
+ "status": "stable",
88
+ "cssClass": "velin-animate-on-scroll--scale",
89
+ "compatibility": { "cssOnly": true, "runtime": true, "webComponents": true, "studio": true, "production": true, "review": true, "cli": true }
90
+ },
91
+ {
92
+ "id": "blur",
93
+ "title": "Blur In",
94
+ "description": "Soft fade/blur entrance (maps to fade-in utility).",
95
+ "kind": "preset",
96
+ "category": "overlay",
97
+ "tags": ["entrance"],
98
+ "duration": 500,
99
+ "reducedMotion": true,
100
+ "layoutSafe": true,
101
+ "status": "beta",
102
+ "cssClass": "velin-animate-fade-in",
103
+ "compatibility": { "cssOnly": true, "runtime": true, "webComponents": true, "studio": true, "production": true, "review": true, "cli": true }
104
+ },
105
+ {
106
+ "id": "flip",
107
+ "title": "Flip / Scale In",
108
+ "description": "Attention scale-in for emphasis.",
109
+ "kind": "preset",
110
+ "category": "feedback",
111
+ "tags": ["attention"],
112
+ "duration": 400,
113
+ "reducedMotion": true,
114
+ "layoutSafe": true,
115
+ "status": "beta",
116
+ "cssClass": "velin-animate-scale-in",
117
+ "compatibility": { "cssOnly": true, "runtime": true, "webComponents": true, "studio": true, "production": true, "review": true, "cli": true }
118
+ },
119
+ {
120
+ "id": "lift",
121
+ "title": "Hover Lift",
122
+ "description": "Subtle lift on hover for interactive cards and buttons.",
123
+ "kind": "preset",
124
+ "category": "buttons",
125
+ "tags": ["hover"],
126
+ "duration": 200,
127
+ "reducedMotion": true,
128
+ "layoutSafe": true,
129
+ "status": "stable",
130
+ "cssClass": "velin-animate-hover",
131
+ "compatibility": { "cssOnly": true, "runtime": true, "webComponents": true, "studio": true, "production": true, "review": true, "cli": true }
132
+ },
133
+ {
134
+ "id": "pulse",
135
+ "title": "Pulse",
136
+ "description": "Attention pulse; prefer finite loops under performance policy.",
137
+ "kind": "preset",
138
+ "category": "feedback",
139
+ "tags": ["attention"],
140
+ "duration": 1000,
141
+ "reducedMotion": true,
142
+ "layoutSafe": true,
143
+ "status": "stable",
144
+ "cssClass": "velin-animate-pulse",
145
+ "compatibility": { "cssOnly": true, "runtime": true, "webComponents": true, "studio": true, "production": true, "review": true, "cli": true }
146
+ },
147
+ {
148
+ "id": "shake",
149
+ "title": "Shake",
150
+ "description": "Error/attention shake feedback.",
151
+ "kind": "preset",
152
+ "category": "forms",
153
+ "tags": ["attention", "feedback"],
154
+ "duration": 400,
155
+ "reducedMotion": true,
156
+ "layoutSafe": true,
157
+ "status": "stable",
158
+ "cssClass": "velin-animate-shake",
159
+ "compatibility": { "cssOnly": true, "runtime": true, "webComponents": true, "studio": true, "production": true, "review": true, "cli": true }
160
+ },
161
+ {
162
+ "id": "bounce",
163
+ "title": "Bounce",
164
+ "description": "Playful bounce entrance or attention.",
165
+ "kind": "preset",
166
+ "category": "feedback",
167
+ "tags": ["attention"],
168
+ "duration": 600,
169
+ "reducedMotion": true,
170
+ "layoutSafe": true,
171
+ "status": "stable",
172
+ "cssClass": "velin-animate-bounce",
173
+ "compatibility": { "cssOnly": true, "runtime": true, "webComponents": true, "studio": true, "production": true, "review": true, "cli": true }
174
+ },
175
+ {
176
+ "id": "shimmer",
177
+ "title": "Shimmer",
178
+ "description": "Loading shimmer surface (skeleton companion).",
179
+ "kind": "preset",
180
+ "category": "loading",
181
+ "tags": ["loading"],
182
+ "duration": 1200,
183
+ "reducedMotion": true,
184
+ "layoutSafe": true,
185
+ "status": "stable",
186
+ "cssClass": "velin-skeleton",
187
+ "compatibility": { "cssOnly": true, "runtime": true, "webComponents": true, "studio": true, "production": true, "review": true, "cli": true }
188
+ },
189
+ {
190
+ "id": "heroReveal",
191
+ "title": "Hero Reveal",
192
+ "description": "Staggered entrance for hero content; layout-safe; reduced-motion fallback.",
193
+ "kind": "preset",
194
+ "category": "hero",
195
+ "tags": ["entrance", "stagger"],
196
+ "duration": 600,
197
+ "fps": "60",
198
+ "reducedMotion": true,
199
+ "layoutSafe": true,
200
+ "status": "stable",
201
+ "cssClass": "velin-animate-on-scroll--fade",
202
+ "compatibility": { "cssOnly": true, "runtime": true, "webComponents": true, "studio": true, "production": true, "review": true, "cli": true }
203
+ },
204
+ {
205
+ "id": "parallax",
206
+ "title": "Parallax",
207
+ "description": "Scroll-driven parallax shift.",
208
+ "kind": "preset",
209
+ "category": "media",
210
+ "tags": ["scroll"],
211
+ "duration": 0,
212
+ "reducedMotion": true,
213
+ "layoutSafe": false,
214
+ "status": "stable",
215
+ "cssClass": "velin-parallax",
216
+ "compatibility": { "cssOnly": true, "runtime": true, "webComponents": true, "studio": true, "production": true, "review": true, "cli": true }
217
+ },
218
+ {
219
+ "id": "fadeOut",
220
+ "title": "Fade Out",
221
+ "description": "Exit fade (planned composition with timelines).",
222
+ "kind": "preset",
223
+ "category": "overlay",
224
+ "tags": ["exit"],
225
+ "duration": 300,
226
+ "reducedMotion": true,
227
+ "layoutSafe": true,
228
+ "status": "beta",
229
+ "cssClass": "velin-animate-fade-out",
230
+ "compatibility": { "cssOnly": true, "runtime": true, "webComponents": true, "studio": true, "production": true, "review": true, "cli": true }
231
+ }
232
+ ],
233
+ "timelines": [
234
+ {
235
+ "id": "hero-intro",
236
+ "title": "Hero Intro",
237
+ "description": "Title → subtitle → buttons → image",
238
+ "kind": "timeline",
239
+ "category": "hero",
240
+ "steps": ["title", "subtitle", "buttons", "image"],
241
+ "status": "beta",
242
+ "compatibility": { "cssOnly": false, "runtime": true, "webComponents": true, "studio": true, "production": true, "review": true, "cli": true }
243
+ },
244
+ {
245
+ "id": "card-stagger",
246
+ "title": "Card Stagger",
247
+ "description": "Sequential card entrance labels.",
248
+ "kind": "timeline",
249
+ "category": "cards",
250
+ "steps": ["card-1", "card-2", "card-3"],
251
+ "status": "beta",
252
+ "compatibility": { "cssOnly": false, "runtime": true, "webComponents": true, "studio": true, "production": true, "review": true, "cli": true }
253
+ }
254
+ ],
255
+ "transitions": [
256
+ {
257
+ "id": "cross-fade",
258
+ "title": "Cross Fade",
259
+ "description": "Page/view cross-fade (View Transition friendly).",
260
+ "kind": "transition",
261
+ "category": "page",
262
+ "status": "planned",
263
+ "compatibility": { "cssOnly": true, "runtime": true, "webComponents": true, "studio": true, "production": true, "review": true, "cli": true }
264
+ },
265
+ {
266
+ "id": "modal-enter",
267
+ "title": "Modal Enter",
268
+ "description": "Overlay modal entrance transition.",
269
+ "kind": "transition",
270
+ "category": "modal",
271
+ "status": "planned",
272
+ "compatibility": { "cssOnly": true, "runtime": true, "webComponents": true, "studio": true, "production": true, "review": true, "cli": true }
273
+ },
274
+ {
275
+ "id": "drawer-enter",
276
+ "title": "Drawer Enter",
277
+ "description": "Drawer slide transition.",
278
+ "kind": "transition",
279
+ "category": "drawer",
280
+ "status": "planned",
281
+ "compatibility": { "cssOnly": true, "runtime": true, "webComponents": true, "studio": true, "production": true, "review": true, "cli": true }
282
+ }
283
+ ],
284
+ "physics": [
285
+ {
286
+ "id": "spring",
287
+ "title": "Spring",
288
+ "description": "Spring easing token mapping (runtime simulation later).",
289
+ "kind": "physics",
290
+ "category": "feedback",
291
+ "status": "planned",
292
+ "token": "--velin-ease-spring",
293
+ "compatibility": { "cssOnly": true, "runtime": false, "webComponents": false, "studio": true, "production": true, "review": true, "cli": true }
294
+ },
295
+ {
296
+ "id": "elastic",
297
+ "title": "Elastic",
298
+ "description": "Elastic easing token mapping.",
299
+ "kind": "physics",
300
+ "category": "feedback",
301
+ "status": "planned",
302
+ "token": "--velin-ease-elastic",
303
+ "compatibility": { "cssOnly": true, "runtime": false, "webComponents": false, "studio": true, "production": true, "review": true, "cli": true }
304
+ }
305
+ ],
306
+ "composers": [
307
+ {
308
+ "id": "sequence",
309
+ "title": "Sequence Composer",
310
+ "description": "Ordered fade/wait/slide composition API.",
311
+ "kind": "composer",
312
+ "category": "page",
313
+ "status": "planned",
314
+ "compatibility": { "cssOnly": false, "runtime": true, "webComponents": true, "studio": true, "production": true, "review": true, "cli": true }
315
+ }
316
+ ],
317
+ "triggers": [
318
+ { "id": "load", "title": "Load", "description": "Start when document/module initializes.", "kind": "trigger", "category": "page", "status": "stable", "compatibility": { "cssOnly": false, "runtime": true, "webComponents": true, "studio": true, "production": true, "review": true, "cli": true } },
319
+ { "id": "visible", "title": "Visible", "description": "Start when element enters the viewport (IntersectionObserver).", "kind": "trigger", "category": "page", "status": "stable", "compatibility": { "cssOnly": false, "runtime": true, "webComponents": true, "studio": true, "production": true, "review": true, "cli": true } },
320
+ { "id": "hidden", "title": "Hidden", "description": "Fire when element leaves the viewport. (Planned — runtime falls back to visible.)", "kind": "trigger", "category": "page", "status": "planned", "compatibility": { "cssOnly": false, "runtime": false, "webComponents": true, "studio": true, "production": true, "review": true, "cli": true } },
321
+ { "id": "hover", "title": "Hover", "description": "Pointer enter / hover.", "kind": "trigger", "category": "buttons", "status": "stable", "compatibility": { "cssOnly": true, "runtime": true, "webComponents": true, "studio": true, "production": true, "review": true, "cli": true } },
322
+ { "id": "focus", "title": "Focus", "description": "Keyboard/pointer focus.", "kind": "trigger", "category": "forms", "status": "stable", "compatibility": { "cssOnly": true, "runtime": true, "webComponents": true, "studio": true, "production": true, "review": true, "cli": true } },
323
+ { "id": "click", "title": "Click", "description": "Pointer activation.", "kind": "trigger", "category": "buttons", "status": "stable", "compatibility": { "cssOnly": false, "runtime": true, "webComponents": true, "studio": true, "production": true, "review": true, "cli": true } },
324
+ { "id": "scroll", "title": "Scroll", "description": "Scroll-linked or scroll-progress trigger.", "kind": "trigger", "category": "page", "status": "stable", "compatibility": { "cssOnly": true, "runtime": true, "webComponents": true, "studio": true, "production": true, "review": true, "cli": true } },
325
+ { "id": "idle", "title": "Idle", "description": "After user idle timeout. (Planned — runtime falls back to visible.)", "kind": "trigger", "category": "feedback", "status": "planned", "compatibility": { "cssOnly": false, "runtime": false, "webComponents": true, "studio": true, "production": true, "review": true, "cli": true } },
326
+ { "id": "media", "title": "Media", "description": "Match media query condition. (Planned — runtime falls back to visible.)", "kind": "trigger", "category": "page", "status": "planned", "compatibility": { "cssOnly": false, "runtime": false, "webComponents": true, "studio": true, "production": true, "review": true, "cli": true } },
327
+ { "id": "container", "title": "Container", "description": "Container query / size trigger. (Planned — runtime falls back to visible.)", "kind": "trigger", "category": "cards", "status": "planned", "compatibility": { "cssOnly": false, "runtime": false, "webComponents": true, "studio": true, "production": true, "review": true, "cli": true } },
328
+ { "id": "theme", "title": "Theme", "description": "Theme change trigger. (Planned — runtime falls back to visible.)", "kind": "trigger", "category": "page", "status": "planned", "compatibility": { "cssOnly": false, "runtime": false, "webComponents": true, "studio": true, "production": true, "review": true, "cli": true } },
329
+ { "id": "custom", "title": "Custom", "description": "Custom event name via velin-trigger-event. (Planned — runtime falls back to visible.)", "kind": "trigger", "category": "page", "status": "planned", "compatibility": { "cssOnly": false, "runtime": false, "webComponents": true, "studio": true, "production": true, "review": true, "cli": true } }
330
+ ],
331
+ "policies": [
332
+ { "id": "safe", "title": "Safe", "description": "Default layout-safe motion with capped durations.", "kind": "policy", "category": "page", "status": "stable", "maxDurationMs": 800, "allowInfinite": false, "compatibility": { "cssOnly": true, "runtime": true, "webComponents": true, "studio": true, "production": true, "review": true, "cli": true } },
333
+ { "id": "reduced", "title": "Reduced", "description": "Honors prefers-reduced-motion first; shorten or skip decorative motion.", "kind": "policy", "category": "page", "status": "stable", "maxDurationMs": 200, "forceReduced": true, "compatibility": { "cssOnly": true, "runtime": true, "webComponents": true, "studio": true, "production": true, "review": true, "cli": true } },
334
+ { "id": "marketing", "title": "Marketing", "description": "Expressive hero motion allowed within caps.", "kind": "policy", "category": "hero", "status": "stable", "maxDurationMs": 1200, "allowInfinite": false, "compatibility": { "cssOnly": true, "runtime": true, "webComponents": true, "studio": true, "production": true, "review": true, "cli": true } },
335
+ { "id": "minimal", "title": "Minimal", "description": "Very little motion; feedback only.", "kind": "policy", "category": "page", "status": "stable", "maxDurationMs": 300, "allowInfinite": false, "compatibility": { "cssOnly": true, "runtime": true, "webComponents": true, "studio": true, "production": true, "review": true, "cli": true } },
336
+ { "id": "accessibility", "title": "Accessibility", "description": "Strict reduced-motion + focus-safe interactions.", "kind": "policy", "category": "page", "status": "stable", "maxDurationMs": 200, "forceReduced": true, "compatibility": { "cssOnly": true, "runtime": true, "webComponents": true, "studio": true, "production": true, "review": true, "cli": true } },
337
+ { "id": "performance", "title": "Performance", "description": "FPS-friendly; ban infinite decorative loops.", "kind": "policy", "category": "page", "status": "stable", "maxDurationMs": 600, "allowInfinite": false, "compatibility": { "cssOnly": true, "runtime": true, "webComponents": true, "studio": true, "production": true, "review": true, "cli": true } },
338
+ { "id": "disabled", "title": "Disabled", "description": "All motion off.", "kind": "policy", "category": "page", "status": "stable", "disabled": true, "compatibility": { "cssOnly": true, "runtime": true, "webComponents": true, "studio": true, "production": true, "review": true, "cli": true } }
339
+ ],
340
+ "blueprints": [
341
+ { "id": "landing", "title": "Landing", "description": "Hero reveal + staggered sections + CTA hover.", "kind": "blueprint", "category": "hero", "status": "beta", "presets": ["heroReveal", "fade", "lift"], "timeline": "hero-intro", "policy": "marketing", "compatibility": { "cssOnly": true, "runtime": true, "webComponents": true, "studio": true, "production": true, "review": true, "cli": true } },
342
+ { "id": "dashboard", "title": "Dashboard", "description": "Minimal card stagger for admin shells.", "kind": "blueprint", "category": "cards", "status": "beta", "presets": ["fade", "slide"], "timeline": "card-stagger", "policy": "minimal", "compatibility": { "cssOnly": true, "runtime": true, "webComponents": true, "studio": true, "production": true, "review": true, "cli": true } },
343
+ { "id": "pricing", "title": "Pricing", "description": "Pricing cards with stagger and lift.", "kind": "blueprint", "category": "cards", "status": "beta", "presets": ["slide", "lift"], "policy": "marketing", "compatibility": { "cssOnly": true, "runtime": true, "webComponents": true, "studio": true, "production": true, "review": true, "cli": true } },
344
+ { "id": "portfolio", "title": "Portfolio", "description": "Media zoom + parallax-safe reveals.", "kind": "blueprint", "category": "media", "status": "beta", "presets": ["zoom", "fade"], "policy": "marketing", "compatibility": { "cssOnly": true, "runtime": true, "webComponents": true, "studio": true, "production": true, "review": true, "cli": true } },
345
+ { "id": "shop", "title": "Shop", "description": "Product cards and feedback pulses.", "kind": "blueprint", "category": "cards", "status": "planned", "presets": ["fade", "lift"], "policy": "safe", "compatibility": { "cssOnly": true, "runtime": true, "webComponents": true, "studio": true, "production": true, "review": true, "cli": true } },
346
+ { "id": "forum", "title": "Forum", "description": "List slides and form feedback.", "kind": "blueprint", "category": "lists", "status": "planned", "presets": ["slide", "shake"], "policy": "safe", "compatibility": { "cssOnly": true, "runtime": true, "webComponents": true, "studio": true, "production": true, "review": true, "cli": true } },
347
+ { "id": "docs", "title": "Docs", "description": "Minimal fade for documentation pages.", "kind": "blueprint", "category": "page", "status": "beta", "presets": ["fade"], "policy": "minimal", "compatibility": { "cssOnly": true, "runtime": true, "webComponents": true, "studio": true, "production": true, "review": true, "cli": true } },
348
+ { "id": "blog", "title": "Blog", "description": "Article hero fade and card slides.", "kind": "blueprint", "category": "hero", "status": "planned", "presets": ["fade", "slide"], "policy": "safe", "compatibility": { "cssOnly": true, "runtime": true, "webComponents": true, "studio": true, "production": true, "review": true, "cli": true } },
349
+ { "id": "crm", "title": "CRM", "description": "Table-safe minimal motion.", "kind": "blueprint", "category": "tables", "status": "planned", "presets": ["fade"], "policy": "performance", "compatibility": { "cssOnly": true, "runtime": true, "webComponents": true, "studio": true, "production": true, "review": true, "cli": true } },
350
+ { "id": "login", "title": "Login", "description": "Form feedback and focus-safe motion.", "kind": "blueprint", "category": "forms", "status": "beta", "presets": ["fade", "shake"], "policy": "accessibility", "compatibility": { "cssOnly": true, "runtime": true, "webComponents": true, "studio": true, "production": true, "review": true, "cli": true } }
351
+ ]
352
+ }
@@ -9,16 +9,24 @@ const EFFECT_MAP = {
9
9
  'slide-left': ['velin-animate-on-scroll', 'velin-animate-on-scroll--slide-left'],
10
10
  'slide-right': ['velin-animate-on-scroll', 'velin-animate-on-scroll--slide-right'],
11
11
  scale: ['velin-animate-on-scroll', 'velin-animate-on-scroll--scale'],
12
+ zoom: ['velin-animate-on-scroll', 'velin-animate-on-scroll--scale'],
12
13
  parallax: ['velin-parallax'],
13
14
  'parallax-slow': ['velin-parallax', 'velin-parallax--slow'],
14
15
  hover: ['velin-animate-hover'],
15
16
  'hover-lift': ['velin-animate-hover'],
17
+ lift: ['velin-animate-hover'],
16
18
  flip: ['velin-animate-scale-in'],
17
19
  blur: ['velin-animate-fade-in'],
20
+ pulse: ['velin-animate-pulse'],
21
+ shake: ['velin-animate-shake'],
22
+ bounce: ['velin-animate-bounce'],
23
+ shimmer: ['velin-skeleton'],
24
+ heroReveal: ['velin-animate-on-scroll', 'velin-animate-on-scroll--fade'],
25
+ fadeOut: ['velin-animate-fade-out'],
18
26
  };
19
27
 
20
28
  /**
21
- * Apply motion effect classes from velin-* attribute values.
29
+ * Apply motion effect classes from velin-* attribute values or velin-motion preset.
22
30
  * @param {HTMLElement} el
23
31
  * @param {Record<string, string>} attrs keyed by effect name
24
32
  */
@@ -26,6 +34,12 @@ export function applyEffects(el, attrs = {}) {
26
34
  const classes = new Set();
27
35
  for (const [name, value] of Object.entries(attrs)) {
28
36
  if (value === 'false' || value === 'off') continue;
37
+ if (name === 'motion' && value && value !== 'true') {
38
+ const presetKey = value;
39
+ const mapped = EFFECT_MAP[presetKey];
40
+ if (mapped) mapped.forEach((c) => classes.add(c));
41
+ continue;
42
+ }
29
43
  const mapped = EFFECT_MAP[name];
30
44
  if (mapped) mapped.forEach((c) => classes.add(c));
31
45
  if (name === 'slide' && value && value !== 'true' && value !== '') {
@@ -52,3 +66,5 @@ export function prefersReducedMotion() {
52
66
  window.matchMedia('(prefers-reduced-motion: reduce)').matches
53
67
  );
54
68
  }
69
+
70
+ export { EFFECT_MAP };