@caddis/cli 0.0.0 → 0.1.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 (153) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +150 -1
  3. package/bundles/antigravity-plugin/agents/code-reviewer.md +47 -0
  4. package/bundles/antigravity-plugin/agents/preflight.md +53 -0
  5. package/bundles/antigravity-plugin/guard_agy.py +338 -0
  6. package/bundles/antigravity-plugin/hooks.json +36 -0
  7. package/bundles/antigravity-plugin/mcp_config.json +8 -0
  8. package/bundles/antigravity-plugin/mcp_ping_server.py +55 -0
  9. package/bundles/antigravity-plugin/plugin.json +5 -0
  10. package/bundles/antigravity-plugin/session_end_agy.py +57 -0
  11. package/bundles/antigravity-plugin/skills/_registry.md +115 -0
  12. package/bundles/antigravity-plugin/skills/add-rules/SKILL.md +45 -0
  13. package/bundles/antigravity-plugin/skills/api-design/SKILL.md +249 -0
  14. package/bundles/antigravity-plugin/skills/backend-development/SKILL.md +305 -0
  15. package/bundles/antigravity-plugin/skills/best-practices/SKILL.md +500 -0
  16. package/bundles/antigravity-plugin/skills/best-practices/agents/best-practices-referencer.md +263 -0
  17. package/bundles/antigravity-plugin/skills/best-practices/agents/codebase-context-builder.md +326 -0
  18. package/bundles/antigravity-plugin/skills/best-practices/agents/task-intent-analyzer.md +245 -0
  19. package/bundles/antigravity-plugin/skills/best-practices/references/anti-patterns.md +571 -0
  20. package/bundles/antigravity-plugin/skills/best-practices/references/before-after-examples.md +1114 -0
  21. package/bundles/antigravity-plugin/skills/best-practices/references/best-practices-guide.md +513 -0
  22. package/bundles/antigravity-plugin/skills/best-practices/references/common-workflows.md +692 -0
  23. package/bundles/antigravity-plugin/skills/best-practices/references/prompt-patterns.md +547 -0
  24. package/bundles/antigravity-plugin/skills/brainstorming/SKILL.md +57 -0
  25. package/bundles/antigravity-plugin/skills/ci-cd-pipeline/SKILL.md +315 -0
  26. package/bundles/antigravity-plugin/skills/code-documentation/SKILL.md +271 -0
  27. package/bundles/antigravity-plugin/skills/code-review/SKILL.md +122 -0
  28. package/bundles/antigravity-plugin/skills/codebase-audit/SKILL.md +204 -0
  29. package/bundles/antigravity-plugin/skills/context-curator/SKILL.md +157 -0
  30. package/bundles/antigravity-plugin/skills/cross-review/SKILL.md +40 -0
  31. package/bundles/antigravity-plugin/skills/css-architecture/SKILL.md +305 -0
  32. package/bundles/antigravity-plugin/skills/css-architecture/references/RESPONSIVE-DESIGN.md +604 -0
  33. package/bundles/antigravity-plugin/skills/database-design/SKILL.md +177 -0
  34. package/bundles/antigravity-plugin/skills/db-diagram/SKILL.md +148 -0
  35. package/bundles/antigravity-plugin/skills/db-diagram/scripts/sql_to_graph.py +1212 -0
  36. package/bundles/antigravity-plugin/skills/digress/SKILL.md +61 -0
  37. package/bundles/antigravity-plugin/skills/draw-io/SKILL.md +162 -0
  38. package/bundles/antigravity-plugin/skills/draw-io/references/aws-icons.md +677 -0
  39. package/bundles/antigravity-plugin/skills/draw-io/references/layout-guidelines.md +142 -0
  40. package/bundles/antigravity-plugin/skills/draw-io/references/troubleshooting.md +118 -0
  41. package/bundles/antigravity-plugin/skills/draw-io/references/workflows.md +103 -0
  42. package/bundles/antigravity-plugin/skills/draw-io/scripts/convert-drawio-to-png.sh +25 -0
  43. package/bundles/antigravity-plugin/skills/draw-io/scripts/find_aws_icon.py +79 -0
  44. package/bundles/antigravity-plugin/skills/error-handling/SKILL.md +260 -0
  45. package/bundles/antigravity-plugin/skills/excalidraw-db/SKILL.md +38 -0
  46. package/bundles/antigravity-plugin/skills/fastapi-dev/SKILL.md +300 -0
  47. package/bundles/antigravity-plugin/skills/feature-plan/SKILL.md +198 -0
  48. package/bundles/antigravity-plugin/skills/frontend-design/SKILL.md +193 -0
  49. package/bundles/antigravity-plugin/skills/frontend-design/references/my-tech-stack.md +127 -0
  50. package/bundles/antigravity-plugin/skills/gh-cli/SKILL.md +195 -0
  51. package/bundles/antigravity-plugin/skills/git-commit/SKILL.md +285 -0
  52. package/bundles/antigravity-plugin/skills/golden-plan/SKILL.md +577 -0
  53. package/bundles/antigravity-plugin/skills/handoff/SKILL.md +100 -0
  54. package/bundles/antigravity-plugin/skills/implement/SKILL.md +114 -0
  55. package/bundles/antigravity-plugin/skills/javascript-typescript/SKILL.md +142 -0
  56. package/bundles/antigravity-plugin/skills/kb/SKILL.md +60 -0
  57. package/bundles/antigravity-plugin/skills/mermaid-db/SKILL.md +33 -0
  58. package/bundles/antigravity-plugin/skills/mermaid-diagrams/SKILL.md +236 -0
  59. package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/ENHANCEMENTS.md +264 -0
  60. package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/MERMAID-SUMMARY.md +137 -0
  61. package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/advanced-features.md +556 -0
  62. package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/architecture-diagrams.md +192 -0
  63. package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/c4-diagrams.md +410 -0
  64. package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/class-diagrams.md +361 -0
  65. package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/erd-diagrams.md +510 -0
  66. package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/flowcharts.md +450 -0
  67. package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/sequence-diagrams.md +394 -0
  68. package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/troubleshooting.md +335 -0
  69. package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/workflows.md +418 -0
  70. package/bundles/antigravity-plugin/skills/migrate-dir/SKILL.md +68 -0
  71. package/bundles/antigravity-plugin/skills/mockup/SKILL.md +242 -0
  72. package/bundles/antigravity-plugin/skills/particle-art/SKILL.md +243 -0
  73. package/bundles/antigravity-plugin/skills/particle-art/references/canvas-utils.ts +171 -0
  74. package/bundles/antigravity-plugin/skills/particle-art/references/dot-field.template.tsx +203 -0
  75. package/bundles/antigravity-plugin/skills/particle-art/references/flow-field.template.tsx +263 -0
  76. package/bundles/antigravity-plugin/skills/particle-art/references/node-shape.template.tsx +261 -0
  77. package/bundles/antigravity-plugin/skills/particle-art/references/shape-sampler.ts +281 -0
  78. package/bundles/antigravity-plugin/skills/particle-art/references/stipple-morph.template.tsx +167 -0
  79. package/bundles/antigravity-plugin/skills/particle-art/references/stipple.template.tsx +175 -0
  80. package/bundles/antigravity-plugin/skills/particle-art/references/trail-ghost.template.tsx +266 -0
  81. package/bundles/antigravity-plugin/skills/particle-art/references/usage-examples.md +320 -0
  82. package/bundles/antigravity-plugin/skills/playwright/API_REFERENCE.md +653 -0
  83. package/bundles/antigravity-plugin/skills/playwright/SKILL.md +454 -0
  84. package/bundles/antigravity-plugin/skills/playwright/lib/helpers.js +441 -0
  85. package/bundles/antigravity-plugin/skills/playwright/package.json +26 -0
  86. package/bundles/antigravity-plugin/skills/playwright/run.js +228 -0
  87. package/bundles/antigravity-plugin/skills/prd/SKILL.md +107 -0
  88. package/bundles/antigravity-plugin/skills/preflight/SKILL.md +435 -0
  89. package/bundles/antigravity-plugin/skills/python/SKILL.md +388 -0
  90. package/bundles/antigravity-plugin/skills/react-best-practices/SKILL.md +269 -0
  91. package/bundles/antigravity-plugin/skills/react-dev/README.md +404 -0
  92. package/bundles/antigravity-plugin/skills/react-dev/SKILL.md +459 -0
  93. package/bundles/antigravity-plugin/skills/react-dev/examples/generic-components.md +579 -0
  94. package/bundles/antigravity-plugin/skills/react-dev/examples/server-components.md +579 -0
  95. package/bundles/antigravity-plugin/skills/react-dev/references/event-handlers.md +574 -0
  96. package/bundles/antigravity-plugin/skills/react-dev/references/hooks.md +456 -0
  97. package/bundles/antigravity-plugin/skills/react-dev/references/react-19-patterns.md +638 -0
  98. package/bundles/antigravity-plugin/skills/react-dev/references/react-router.md +1002 -0
  99. package/bundles/antigravity-plugin/skills/react-dev/references/tanstack-router.md +587 -0
  100. package/bundles/antigravity-plugin/skills/refactoring/SKILL.md +486 -0
  101. package/bundles/antigravity-plugin/skills/resume/SKILL.md +36 -0
  102. package/bundles/antigravity-plugin/skills/security-review/SKILL.md +196 -0
  103. package/bundles/antigravity-plugin/skills/setup-project-ai/SKILL.md +61 -0
  104. package/bundles/antigravity-plugin/skills/ship/SKILL.md +107 -0
  105. package/bundles/antigravity-plugin/skills/ship-merge/SKILL.md +103 -0
  106. package/bundles/antigravity-plugin/skills/ship-pr/SKILL.md +102 -0
  107. package/bundles/antigravity-plugin/skills/skill-creator/LICENSE.txt +202 -0
  108. package/bundles/antigravity-plugin/skills/skill-creator/SKILL.md +491 -0
  109. package/bundles/antigravity-plugin/skills/skill-creator/agents/analyzer.md +274 -0
  110. package/bundles/antigravity-plugin/skills/skill-creator/agents/comparator.md +202 -0
  111. package/bundles/antigravity-plugin/skills/skill-creator/agents/grader.md +223 -0
  112. package/bundles/antigravity-plugin/skills/skill-creator/assets/eval_review.html +146 -0
  113. package/bundles/antigravity-plugin/skills/skill-creator/eval-viewer/generate_review.py +471 -0
  114. package/bundles/antigravity-plugin/skills/skill-creator/eval-viewer/viewer.html +1325 -0
  115. package/bundles/antigravity-plugin/skills/skill-creator/references/schemas.md +430 -0
  116. package/bundles/antigravity-plugin/skills/skill-creator/scripts/__init__.py +0 -0
  117. package/bundles/antigravity-plugin/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
  118. package/bundles/antigravity-plugin/skills/skill-creator/scripts/generate_report.py +326 -0
  119. package/bundles/antigravity-plugin/skills/skill-creator/scripts/improve_description.py +247 -0
  120. package/bundles/antigravity-plugin/skills/skill-creator/scripts/package_skill.py +136 -0
  121. package/bundles/antigravity-plugin/skills/skill-creator/scripts/quick_validate.py +103 -0
  122. package/bundles/antigravity-plugin/skills/skill-creator/scripts/run_eval.py +310 -0
  123. package/bundles/antigravity-plugin/skills/skill-creator/scripts/run_loop.py +328 -0
  124. package/bundles/antigravity-plugin/skills/skill-creator/scripts/utils.py +47 -0
  125. package/bundles/antigravity-plugin/skills/sql/SKILL.md +321 -0
  126. package/bundles/antigravity-plugin/skills/tdd/SKILL.md +37 -0
  127. package/bundles/antigravity-plugin/skills/tdd-workflow/SKILL.md +188 -0
  128. package/bundles/antigravity-plugin/skills/technical-writing/SKILL.md +286 -0
  129. package/bundles/antigravity-plugin/skills/test-strategy/SKILL.md +155 -0
  130. package/bundles/antigravity-plugin/skills/ui-brief/SKILL.md +84 -0
  131. package/bundles/antigravity-plugin/skills/ui-review/SKILL.md +176 -0
  132. package/bundles/antigravity-plugin/skills/ui-review/references/framework-fixes.md +471 -0
  133. package/bundles/antigravity-plugin/skills/ui-review/references/visual-checklist.md +236 -0
  134. package/bundles/antigravity-plugin/skills/usage-review/SKILL.md +77 -0
  135. package/bundles/antigravity-plugin/skills/use-model/SKILL.md +64 -0
  136. package/bundles/antigravity-plugin/skills/using-git-worktrees/SKILL.md +217 -0
  137. package/bundles/antigravity-plugin/skills/version/SKILL.md +18 -0
  138. package/bundles/antigravity-plugin/skills/warm-editorial-ui/DESIGN_TOKENS.md +487 -0
  139. package/bundles/antigravity-plugin/skills/warm-editorial-ui/IMPLEMENTATION_GUIDE.md +177 -0
  140. package/bundles/antigravity-plugin/skills/warm-editorial-ui/SKILL.md +732 -0
  141. package/bundles/antigravity-plugin/skills/webapp-testing/LICENSE.txt +202 -0
  142. package/bundles/antigravity-plugin/skills/webapp-testing/SKILL.md +97 -0
  143. package/bundles/antigravity-plugin/skills/webapp-testing/examples/console_logging.py +35 -0
  144. package/bundles/antigravity-plugin/skills/webapp-testing/examples/element_discovery.py +40 -0
  145. package/bundles/antigravity-plugin/skills/webapp-testing/examples/static_html_automation.py +33 -0
  146. package/bundles/antigravity-plugin/skills/webapp-testing/scripts/with_server.py +106 -0
  147. package/bundles/antigravity-plugin/skills/windows-deployment/SKILL.md +880 -0
  148. package/bundles/antigravity-plugin/skills/writing-plans/SKILL.md +384 -0
  149. package/bundles/antigravity-plugin/statusline-command-agy.sh +91 -0
  150. package/bundles/antigravity-plugin/warm_start_agy.py +149 -0
  151. package/bundles/manifest.json +6 -0
  152. package/dist/cli.js +5363 -0
  153. package/package.json +61 -4
@@ -0,0 +1,261 @@
1
+ "use client";
2
+ /**
3
+ * node-shape.template.tsx — Spring-constrained particle component template.
4
+ *
5
+ * USAGE:
6
+ * 1. Copy this file and rename it (e.g. JSpringArt.tsx, ConstellationArt.tsx)
7
+ * 2. Replace the `buildTargets()` body with your shape points (use shape-sampler.ts)
8
+ * 3. Tune the physics constants for your use-case (see SKILL.md physics table)
9
+ * 4. Export and import into your page
10
+ *
11
+ * DEPENDENCIES: none (copy themeRgb from canvas-utils.ts if needed)
12
+ */
13
+ import { useEffect, useRef } from "react";
14
+
15
+ // ── Physics constants — tune these per SKILL.md table ─────────────────────────
16
+ const LINK = 72; // max distance between connected nodes (px)
17
+ const MAX_SPEED = 1.4; // velocity cap
18
+ const FRICTION = 0.88; // damping for letter/shape nodes
19
+ const AMBIENT_FRICTION = 0.985; // damping for free-floating ambient nodes
20
+ const REPEL_R = 85; // mouse repulsion radius (px)
21
+ const REPEL_F = 0.5; // repulsion force strength
22
+ const DRIFT = 0.06; // random jitter per frame on shape nodes
23
+
24
+ // ── Target point type ──────────────────────────────────────────────────────────
25
+ interface Target {
26
+ tx: number; // x target (CSS px)
27
+ ty: number; // y target
28
+ spring: number; // spring constant — how strongly node returns to target
29
+ r: number; // visual radius
30
+ alpha: number; // visual opacity
31
+ isDot?: boolean; // special node with pulsing ring (e.g. tittle of "j")
32
+ }
33
+
34
+ // ── REPLACE THIS with your shape ──────────────────────────────────────────────
35
+ // Options:
36
+ // - Call sampleText("J", size) from shape-sampler.ts
37
+ // - Call sampleConstellation(size) or any other preset
38
+ // - Define points manually (bezier, grids, SVG)
39
+ //
40
+ // The function receives the canvas size in CSS pixels and returns targets.
41
+ function buildTargets(size: number): Target[] {
42
+ const cx = size * 0.5;
43
+ const cy = size * 0.5;
44
+ const targets: Target[] = [];
45
+
46
+ // Example: a simple cross / plus pattern
47
+ const ARM = 5;
48
+ for (let i = -ARM; i <= ARM; i++) {
49
+ const step = size * 0.06;
50
+ // Horizontal arm
51
+ targets.push({ tx: cx + i * step, ty: cy, spring: 0.036, r: 2.2, alpha: 0.9 });
52
+ // Vertical arm (avoid duplicate centre)
53
+ if (i !== 0) {
54
+ targets.push({ tx: cx, ty: cy + i * step, spring: 0.036, r: 2.2, alpha: 0.9 });
55
+ }
56
+ }
57
+ // Centre node — marked as dot for pulse ring
58
+ targets.push({ tx: cx, ty: cy, spring: 0.038, r: 4.5, alpha: 1.0, isDot: true });
59
+
60
+ return targets;
61
+ }
62
+
63
+ // ── Particle ───────────────────────────────────────────────────────────────────
64
+ interface Pt {
65
+ x: number; y: number;
66
+ vx: number; vy: number;
67
+ tx: number; ty: number;
68
+ spring: number;
69
+ r: number;
70
+ alpha: number;
71
+ isDot: boolean;
72
+ isShape: boolean; // true = letter/shape node; false = ambient
73
+ }
74
+
75
+ // CSS-variable colour reader — keeps theme changes instant
76
+ function themeRgb(v: string): [number, number, number] {
77
+ const ch = getComputedStyle(document.documentElement)
78
+ .getPropertyValue(v).trim().split(/\s+/).map(Number);
79
+ return [ch[0] ?? 100, ch[1] ?? 100, ch[2] ?? 100];
80
+ }
81
+
82
+ // ── Component ──────────────────────────────────────────────────────────────────
83
+ //
84
+ // Props: size (px), ambientCount (free-floating halo nodes)
85
+ //
86
+ export function NodeShapeArt({
87
+ size = 300,
88
+ ambientCount = 14,
89
+ }: {
90
+ size?: number;
91
+ ambientCount?: number;
92
+ }) {
93
+ const ref = useRef<HTMLCanvasElement>(null);
94
+
95
+ useEffect(() => {
96
+ const el = ref.current;
97
+ if (!el) return;
98
+
99
+ // DPR-correct canvas
100
+ const ctx = el.getContext("2d")!;
101
+ const dpr = window.devicePixelRatio || 1;
102
+ el.width = size * dpr;
103
+ el.height = size * dpr;
104
+ ctx.scale(dpr, dpr);
105
+
106
+ // ── Build shape nodes ──────────────────────────────────────────────────
107
+ const shapeTargets = buildTargets(size);
108
+ const pts: Pt[] = shapeTargets.map(t => ({
109
+ x: t.tx + (Math.random() - 0.5) * 20,
110
+ y: t.ty + (Math.random() - 0.5) * 20,
111
+ vx: 0, vy: 0,
112
+ tx: t.tx, ty: t.ty,
113
+ spring: t.spring,
114
+ r: t.r,
115
+ alpha: t.alpha,
116
+ isDot: t.isDot ?? false,
117
+ isShape: true,
118
+ }));
119
+
120
+ // ── Add ambient halo nodes ─────────────────────────────────────────────
121
+ const cx = size * 0.5, cy = size * 0.48;
122
+ for (let i = 0; i < ambientCount; i++) {
123
+ const angle = (i / ambientCount) * Math.PI * 2;
124
+ const spread = size * (0.28 + Math.random() * 0.17);
125
+ const ax = cx + Math.cos(angle) * spread * 0.75;
126
+ const ay = cy + Math.sin(angle) * spread;
127
+ pts.push({
128
+ x: ax, y: ay,
129
+ vx: (Math.random() - 0.5) * 0.3,
130
+ vy: (Math.random() - 0.5) * 0.3,
131
+ tx: ax, ty: ay,
132
+ spring: 0,
133
+ r: Math.random() * 1.2 + 0.6,
134
+ alpha: 0.35 + Math.random() * 0.3,
135
+ isDot: false,
136
+ isShape: false,
137
+ });
138
+ }
139
+
140
+ // ── Mouse tracking ─────────────────────────────────────────────────────
141
+ const mouse = { x: -9999, y: -9999 };
142
+ const onMove = (e: MouseEvent) => {
143
+ const r = el.getBoundingClientRect();
144
+ mouse.x = e.clientX - r.left;
145
+ mouse.y = e.clientY - r.top;
146
+ };
147
+ const onLeave = () => { mouse.x = -9999; mouse.y = -9999; };
148
+ el.addEventListener("mousemove", onMove);
149
+ el.addEventListener("mouseleave", onLeave);
150
+
151
+ let raf: number;
152
+ let frame = 0;
153
+
154
+ const tick = () => {
155
+ ctx.clearRect(0, 0, size, size);
156
+ const [nr, ng, nb] = themeRgb("--accent-strong");
157
+ const [lr, lg, lb] = themeRgb("--accent-soft");
158
+ const { x: mx, y: my } = mouse;
159
+
160
+ // ── Connection lines ───────────────────────────────────────────────
161
+ for (let i = 0; i < pts.length; i++) {
162
+ for (let j = i + 1; j < pts.length; j++) {
163
+ const dx = pts[i].x - pts[j].x;
164
+ const dy = pts[i].y - pts[j].y;
165
+ const d = Math.sqrt(dx * dx + dy * dy);
166
+ if (d < LINK) {
167
+ const a = (1 - d / LINK) * 0.28;
168
+ ctx.beginPath();
169
+ ctx.moveTo(pts[i].x, pts[i].y);
170
+ ctx.lineTo(pts[j].x, pts[j].y);
171
+ ctx.strokeStyle = `rgba(${lr},${lg},${lb},${a.toFixed(3)})`;
172
+ ctx.lineWidth = 0.65;
173
+ ctx.stroke();
174
+ }
175
+ }
176
+ }
177
+
178
+ // ── Update and draw nodes ──────────────────────────────────────────
179
+ pts.forEach(p => {
180
+ // Mouse repulsion
181
+ const mdx = p.x - mx, mdy = p.y - my;
182
+ const md = Math.sqrt(mdx * mdx + mdy * mdy);
183
+ if (md < REPEL_R && md > 0) {
184
+ const f = ((REPEL_R - md) / REPEL_R) * REPEL_F;
185
+ p.vx += (mdx / md) * f;
186
+ p.vy += (mdy / md) * f;
187
+ }
188
+
189
+ if (p.isShape) {
190
+ // Spring-back to target
191
+ p.vx += (p.tx - p.x) * p.spring + (Math.random() - 0.5) * DRIFT;
192
+ p.vy += (p.ty - p.y) * p.spring + (Math.random() - 0.5) * DRIFT;
193
+ p.vx *= FRICTION;
194
+ p.vy *= FRICTION;
195
+ } else {
196
+ // Free float with soft wall bounce
197
+ p.vx *= AMBIENT_FRICTION;
198
+ p.vy *= AMBIENT_FRICTION;
199
+ if (p.x < 8 || p.x > size - 8) p.vx *= -1;
200
+ if (p.y < 8 || p.y > size - 8) p.vy *= -1;
201
+ }
202
+
203
+ // Velocity cap
204
+ const spd = Math.sqrt(p.vx * p.vx + p.vy * p.vy);
205
+ if (spd > MAX_SPEED) { p.vx *= MAX_SPEED / spd; p.vy *= MAX_SPEED / spd; }
206
+
207
+ p.x += p.vx;
208
+ p.y += p.vy;
209
+
210
+ // Draw
211
+ const near = md < REPEL_R;
212
+ let nodeR = near ? p.r * 1.3 : p.r;
213
+ const nodeA = near ? Math.min(1, p.alpha * 1.15) : p.alpha;
214
+
215
+ // Special dot: pulsing ring
216
+ if (p.isDot) {
217
+ nodeR = p.r + Math.sin(frame * 0.04) * 0.8;
218
+ ctx.beginPath();
219
+ ctx.arc(p.x, p.y, nodeR + 5 + Math.sin(frame * 0.04) * 2, 0, Math.PI * 2);
220
+ ctx.strokeStyle = `rgba(${nr},${ng},${nb},0.18)`;
221
+ ctx.lineWidth = 1;
222
+ ctx.stroke();
223
+ }
224
+
225
+ ctx.beginPath();
226
+ ctx.arc(p.x, p.y, nodeR, 0, Math.PI * 2);
227
+ ctx.fillStyle = `rgba(${nr},${ng},${nb},${nodeA.toFixed(3)})`;
228
+ ctx.fill();
229
+ });
230
+
231
+ frame++;
232
+ raf = requestAnimationFrame(tick);
233
+ };
234
+
235
+ // Reduced-motion: draw one static frame at target positions, skip the loop.
236
+ // Never return early before drawing — that leaves the canvas blank (worse than animation).
237
+ if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
238
+ pts.forEach(p => {
239
+ if (p.isShape) { p.x = p.tx; p.y = p.ty; }
240
+ });
241
+ tick();
242
+ cancelAnimationFrame(raf);
243
+ return;
244
+ }
245
+
246
+ tick();
247
+ return () => {
248
+ cancelAnimationFrame(raf);
249
+ el.removeEventListener("mousemove", onMove);
250
+ el.removeEventListener("mouseleave", onLeave);
251
+ };
252
+ }, [size, ambientCount]);
253
+
254
+ return (
255
+ <canvas
256
+ ref={ref}
257
+ style={{ width: size, height: size, cursor: "crosshair" }}
258
+ aria-hidden="true"
259
+ />
260
+ );
261
+ }
@@ -0,0 +1,281 @@
1
+ /**
2
+ * shape-sampler.ts — Target-point generators for all particle-art shapes.
3
+ *
4
+ * Each function returns an array of [x, y] coordinates in CSS pixel space,
5
+ * ready to use as spring-node target positions.
6
+ *
7
+ * Copy the function(s) you need into your component file.
8
+ */
9
+
10
+ export type Point = [number, number];
11
+
12
+ // ─── Text / Initials → Points ─────────────────────────────────────────────────
13
+ //
14
+ // Renders text onto an off-screen canvas at large size, samples non-transparent
15
+ // pixels, then scales back to the target size with optional density control.
16
+ //
17
+ // Usage:
18
+ // const pts = sampleText("j", 300, { nodeCount: 28, font: "bold 200px serif" });
19
+ //
20
+ export function sampleText(
21
+ text: string,
22
+ size: number,
23
+ opts: {
24
+ nodeCount?: number; // approximate number of nodes to return
25
+ font?: string; // CSS font string (default: bold 180px sans-serif)
26
+ offsetX?: number; // manual x offset if letter drifts
27
+ offsetY?: number; // manual y offset
28
+ } = {},
29
+ ): Point[] {
30
+ const { nodeCount = 30, font = `bold 180px sans-serif`, offsetX = 0, offsetY = 0 } = opts;
31
+
32
+ // Render at 4× size for better sampling resolution
33
+ const SCALE = 4;
34
+ const offW = size * SCALE;
35
+ const offH = size * SCALE;
36
+
37
+ const oc = document.createElement("canvas");
38
+ oc.width = offW;
39
+ oc.height = offH;
40
+ const oc2 = oc.getContext("2d")!;
41
+ oc2.fillStyle = "#fff";
42
+ oc2.font = font.replace(/(\d+)px/, (_, n) => `${parseInt(n) * SCALE}px`);
43
+ oc2.textAlign = "center";
44
+ oc2.textBaseline = "middle";
45
+ oc2.fillText(text, offW / 2 + offsetX * SCALE, offH / 2 + offsetY * SCALE);
46
+
47
+ const { data } = oc2.getImageData(0, 0, offW, offH);
48
+ const candidates: Point[] = [];
49
+
50
+ // Sample every Nth pixel so we don't collect millions of points
51
+ const step = Math.max(1, Math.floor(offW / 80));
52
+ for (let y = 0; y < offH; y += step) {
53
+ for (let x = 0; x < offW; x += step) {
54
+ if (data[(y * offW + x) * 4] > 128) {
55
+ candidates.push([x / SCALE, y / SCALE]);
56
+ }
57
+ }
58
+ }
59
+
60
+ if (candidates.length === 0) return [];
61
+
62
+ // Downsample to nodeCount using spatial stratification
63
+ return stratifiedSample(candidates, nodeCount);
64
+ }
65
+
66
+ // ─── Preset Shapes ────────────────────────────────────────────────────────────
67
+
68
+ export function sampleConstellation(size: number, nodeCount = 32): Point[] {
69
+ const pts: Point[] = [];
70
+ const cx = size / 2, cy = size / 2;
71
+ const r = size * 0.38;
72
+ // Main ring of nodes
73
+ for (let i = 0; i < Math.floor(nodeCount * 0.6); i++) {
74
+ const a = (i / Math.floor(nodeCount * 0.6)) * Math.PI * 2;
75
+ const jitter = size * 0.06;
76
+ pts.push([
77
+ cx + Math.cos(a) * (r + (srand(i, 0) - 0.5) * jitter),
78
+ cy + Math.sin(a) * (r + (srand(i, 1) - 0.5) * jitter),
79
+ ]);
80
+ }
81
+ // Inner cluster
82
+ const inner = nodeCount - pts.length;
83
+ for (let i = 0; i < inner; i++) {
84
+ const a = srand(i + 100, 0) * Math.PI * 2;
85
+ const d = srand(i + 100, 1) * r * 0.5;
86
+ pts.push([cx + Math.cos(a) * d, cy + Math.sin(a) * d]);
87
+ }
88
+ return pts;
89
+ }
90
+
91
+ export function sampleHelix(size: number, nodeCount = 36): Point[] {
92
+ const pts: Point[] = [];
93
+ const cx = size / 2;
94
+ const spread = size * 0.20;
95
+ for (let i = 0; i < nodeCount; i++) {
96
+ const t = i / (nodeCount - 1);
97
+ const y = size * 0.1 + t * size * 0.8;
98
+ const a = t * Math.PI * 4; // 2 full turns
99
+ pts.push(
100
+ [cx + Math.cos(a) * spread, y],
101
+ [cx - Math.cos(a) * spread, y],
102
+ );
103
+ }
104
+ return pts.slice(0, nodeCount);
105
+ }
106
+
107
+ export function sampleSpiral(size: number, nodeCount = 40): Point[] {
108
+ const pts: Point[] = [];
109
+ const cx = size / 2, cy = size / 2;
110
+ const maxR = size * 0.42;
111
+ for (let i = 0; i < nodeCount; i++) {
112
+ const t = i / nodeCount;
113
+ const a = t * Math.PI * 6; // 3 turns
114
+ const r = t * maxR;
115
+ pts.push([cx + Math.cos(a) * r, cy + Math.sin(a) * r]);
116
+ }
117
+ return pts;
118
+ }
119
+
120
+ export function sampleDNA(size: number, nodeCount = 40): Point[] {
121
+ const pts: Point[] = [];
122
+ const cx = size / 2;
123
+ const spread = size * 0.18;
124
+ const rungs = Math.floor(nodeCount / 3);
125
+ for (let i = 0; i < rungs; i++) {
126
+ const t = i / (rungs - 1);
127
+ const y = size * 0.08 + t * size * 0.84;
128
+ const a = t * Math.PI * 5;
129
+ const lx = cx + Math.cos(a) * spread;
130
+ const rx = cx - Math.cos(a) * spread;
131
+ pts.push([lx, y], [rx, y]);
132
+ // Cross-rung node at midpoint every other step
133
+ if (i % 2 === 0 && pts.length < nodeCount) {
134
+ pts.push([(lx + rx) / 2, y]);
135
+ }
136
+ }
137
+ return pts.slice(0, nodeCount);
138
+ }
139
+
140
+ export function sampleHexGrid(size: number, nodeCount = 36): Point[] {
141
+ const pts: Point[] = [];
142
+ const rows = 5, cols = 7;
143
+ const hx = size / (cols + 0.5);
144
+ const hy = size / (rows + 0.5) * 0.87;
145
+ const ox = hx * 0.5, oy = hy * 0.5;
146
+
147
+ for (let r = 0; r < rows; r++) {
148
+ for (let c = 0; c < cols; c++) {
149
+ const x = ox + c * hx + (r % 2 === 0 ? 0 : hx / 2);
150
+ const y = oy + r * hy;
151
+ if (x > 0 && x < size && y > 0 && y < size) pts.push([x, y]);
152
+ }
153
+ }
154
+ return stratifiedSample(pts, nodeCount);
155
+ }
156
+
157
+ export function sampleWave(size: number, nodeCount = 36): Point[] {
158
+ const pts: Point[] = [];
159
+ const rows = 4;
160
+ const perRow = Math.ceil(nodeCount / rows);
161
+ for (let r = 0; r < rows; r++) {
162
+ const baseY = size * (0.2 + r * 0.18);
163
+ for (let i = 0; i < perRow; i++) {
164
+ const t = i / (perRow - 1);
165
+ const x = size * 0.08 + t * size * 0.84;
166
+ const y = baseY + Math.sin(t * Math.PI * 2 + r) * size * 0.06;
167
+ pts.push([x, y]);
168
+ }
169
+ }
170
+ return pts.slice(0, nodeCount);
171
+ }
172
+
173
+ export function sampleInfinity(size: number, nodeCount = 36): Point[] {
174
+ const pts: Point[] = [];
175
+ const cx = size / 2, cy = size / 2;
176
+ const a = size * 0.38, b = size * 0.18;
177
+ for (let i = 0; i < nodeCount; i++) {
178
+ const t = (i / nodeCount) * Math.PI * 2;
179
+ // Lemniscate of Bernoulli
180
+ const denom = 1 + Math.sin(t) ** 2;
181
+ const x = cx + (a * Math.cos(t)) / denom;
182
+ const y = cy + (b * Math.sin(t) * Math.cos(t)) / denom;
183
+ pts.push([x, y]);
184
+ }
185
+ return pts;
186
+ }
187
+
188
+ // ─── SVG Path → Points ────────────────────────────────────────────────────────
189
+ //
190
+ // Samples points along an SVG path element (must be in the DOM or created
191
+ // via document.createElementNS). Pass the path's `d` attribute string.
192
+ //
193
+ // Usage:
194
+ // const pts = sampleSVGPath("M10,80 C40,10 65,10 95,80 S150,150 180,80", 300, 28);
195
+ //
196
+ export function sampleSVGPath(d: string, size: number, nodeCount = 30): Point[] {
197
+ const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
198
+ svg.style.position = "absolute";
199
+ svg.style.opacity = "0";
200
+ svg.style.pointerEvents = "none";
201
+ const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
202
+ path.setAttribute("d", d);
203
+ svg.appendChild(path);
204
+ document.body.appendChild(svg);
205
+
206
+ const len = path.getTotalLength();
207
+ const pts: Point[] = [];
208
+ // Get bounding box to normalize to [0, size]
209
+ const bbox = path.getBBox();
210
+ const scaleX = size / (bbox.width || 1);
211
+ const scaleY = size / (bbox.height || 1);
212
+ const s = Math.min(scaleX, scaleY) * 0.85; // 85% padding
213
+ const offX = (size - bbox.width * s) / 2 - bbox.x * s;
214
+ const offY = (size - bbox.height * s) / 2 - bbox.y * s;
215
+
216
+ for (let i = 0; i < nodeCount; i++) {
217
+ const pt = path.getPointAtLength((i / (nodeCount - 1)) * len);
218
+ pts.push([pt.x * s + offX, pt.y * s + offY]);
219
+ }
220
+
221
+ document.body.removeChild(svg);
222
+ return pts;
223
+ }
224
+
225
+ // ─── Polygon / Custom Coordinate Array ────────────────────────────────────────
226
+ //
227
+ // Takes raw [x, y] pairs (in any coordinate space) and normalizes to [0, size].
228
+ //
229
+ // Usage:
230
+ // const pts = samplePolygon([[0,0],[1,0],[0.5,1]], 300, 30);
231
+ //
232
+ export function samplePolygon(
233
+ coords: Point[],
234
+ size: number,
235
+ nodeCount = 30,
236
+ ): Point[] {
237
+ if (coords.length === 0) return [];
238
+
239
+ // Normalize to [0, 1]
240
+ const xs = coords.map(p => p[0]);
241
+ const ys = coords.map(p => p[1]);
242
+ const minX = Math.min(...xs), maxX = Math.max(...xs);
243
+ const minY = Math.min(...ys), maxY = Math.max(...ys);
244
+ const rangeX = maxX - minX || 1;
245
+ const rangeY = maxY - minY || 1;
246
+ const s = Math.min(size / rangeX, size / rangeY) * 0.85;
247
+
248
+ // Interpolate along polygon edges
249
+ const edges: Point[] = [];
250
+ for (let i = 0; i < coords.length; i++) {
251
+ const a = coords[i];
252
+ const b = coords[(i + 1) % coords.length];
253
+ const steps = Math.max(2, Math.ceil(nodeCount / coords.length));
254
+ for (let j = 0; j < steps; j++) {
255
+ const t = j / steps;
256
+ edges.push([
257
+ ((a[0] + (b[0] - a[0]) * t) - minX) * s + (size - rangeX * s) / 2,
258
+ ((a[1] + (b[1] - a[1]) * t) - minY) * s + (size - rangeY * s) / 2,
259
+ ]);
260
+ }
261
+ }
262
+ return stratifiedSample(edges, nodeCount);
263
+ }
264
+
265
+ // ─── Internal Helpers ─────────────────────────────────────────────────────────
266
+
267
+ // Deterministic pseudo-random (avoids hydration mismatch)
268
+ function srand(x: number, y: number): number {
269
+ return (Math.abs(Math.sin(x * 127.1 + y * 311.7) * 43758.5453)) % 1;
270
+ }
271
+
272
+ // Spatial stratified downsampling — spreads chosen points evenly across the space
273
+ function stratifiedSample(pts: Point[], n: number): Point[] {
274
+ if (pts.length <= n) return pts;
275
+ const step = pts.length / n;
276
+ const result: Point[] = [];
277
+ for (let i = 0; i < n; i++) {
278
+ result.push(pts[Math.floor(i * step)]);
279
+ }
280
+ return result;
281
+ }