@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.
- package/LICENSE +21 -0
- package/README.md +150 -1
- package/bundles/antigravity-plugin/agents/code-reviewer.md +47 -0
- package/bundles/antigravity-plugin/agents/preflight.md +53 -0
- package/bundles/antigravity-plugin/guard_agy.py +338 -0
- package/bundles/antigravity-plugin/hooks.json +36 -0
- package/bundles/antigravity-plugin/mcp_config.json +8 -0
- package/bundles/antigravity-plugin/mcp_ping_server.py +55 -0
- package/bundles/antigravity-plugin/plugin.json +5 -0
- package/bundles/antigravity-plugin/session_end_agy.py +57 -0
- package/bundles/antigravity-plugin/skills/_registry.md +115 -0
- package/bundles/antigravity-plugin/skills/add-rules/SKILL.md +45 -0
- package/bundles/antigravity-plugin/skills/api-design/SKILL.md +249 -0
- package/bundles/antigravity-plugin/skills/backend-development/SKILL.md +305 -0
- package/bundles/antigravity-plugin/skills/best-practices/SKILL.md +500 -0
- package/bundles/antigravity-plugin/skills/best-practices/agents/best-practices-referencer.md +263 -0
- package/bundles/antigravity-plugin/skills/best-practices/agents/codebase-context-builder.md +326 -0
- package/bundles/antigravity-plugin/skills/best-practices/agents/task-intent-analyzer.md +245 -0
- package/bundles/antigravity-plugin/skills/best-practices/references/anti-patterns.md +571 -0
- package/bundles/antigravity-plugin/skills/best-practices/references/before-after-examples.md +1114 -0
- package/bundles/antigravity-plugin/skills/best-practices/references/best-practices-guide.md +513 -0
- package/bundles/antigravity-plugin/skills/best-practices/references/common-workflows.md +692 -0
- package/bundles/antigravity-plugin/skills/best-practices/references/prompt-patterns.md +547 -0
- package/bundles/antigravity-plugin/skills/brainstorming/SKILL.md +57 -0
- package/bundles/antigravity-plugin/skills/ci-cd-pipeline/SKILL.md +315 -0
- package/bundles/antigravity-plugin/skills/code-documentation/SKILL.md +271 -0
- package/bundles/antigravity-plugin/skills/code-review/SKILL.md +122 -0
- package/bundles/antigravity-plugin/skills/codebase-audit/SKILL.md +204 -0
- package/bundles/antigravity-plugin/skills/context-curator/SKILL.md +157 -0
- package/bundles/antigravity-plugin/skills/cross-review/SKILL.md +40 -0
- package/bundles/antigravity-plugin/skills/css-architecture/SKILL.md +305 -0
- package/bundles/antigravity-plugin/skills/css-architecture/references/RESPONSIVE-DESIGN.md +604 -0
- package/bundles/antigravity-plugin/skills/database-design/SKILL.md +177 -0
- package/bundles/antigravity-plugin/skills/db-diagram/SKILL.md +148 -0
- package/bundles/antigravity-plugin/skills/db-diagram/scripts/sql_to_graph.py +1212 -0
- package/bundles/antigravity-plugin/skills/digress/SKILL.md +61 -0
- package/bundles/antigravity-plugin/skills/draw-io/SKILL.md +162 -0
- package/bundles/antigravity-plugin/skills/draw-io/references/aws-icons.md +677 -0
- package/bundles/antigravity-plugin/skills/draw-io/references/layout-guidelines.md +142 -0
- package/bundles/antigravity-plugin/skills/draw-io/references/troubleshooting.md +118 -0
- package/bundles/antigravity-plugin/skills/draw-io/references/workflows.md +103 -0
- package/bundles/antigravity-plugin/skills/draw-io/scripts/convert-drawio-to-png.sh +25 -0
- package/bundles/antigravity-plugin/skills/draw-io/scripts/find_aws_icon.py +79 -0
- package/bundles/antigravity-plugin/skills/error-handling/SKILL.md +260 -0
- package/bundles/antigravity-plugin/skills/excalidraw-db/SKILL.md +38 -0
- package/bundles/antigravity-plugin/skills/fastapi-dev/SKILL.md +300 -0
- package/bundles/antigravity-plugin/skills/feature-plan/SKILL.md +198 -0
- package/bundles/antigravity-plugin/skills/frontend-design/SKILL.md +193 -0
- package/bundles/antigravity-plugin/skills/frontend-design/references/my-tech-stack.md +127 -0
- package/bundles/antigravity-plugin/skills/gh-cli/SKILL.md +195 -0
- package/bundles/antigravity-plugin/skills/git-commit/SKILL.md +285 -0
- package/bundles/antigravity-plugin/skills/golden-plan/SKILL.md +577 -0
- package/bundles/antigravity-plugin/skills/handoff/SKILL.md +100 -0
- package/bundles/antigravity-plugin/skills/implement/SKILL.md +114 -0
- package/bundles/antigravity-plugin/skills/javascript-typescript/SKILL.md +142 -0
- package/bundles/antigravity-plugin/skills/kb/SKILL.md +60 -0
- package/bundles/antigravity-plugin/skills/mermaid-db/SKILL.md +33 -0
- package/bundles/antigravity-plugin/skills/mermaid-diagrams/SKILL.md +236 -0
- package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/ENHANCEMENTS.md +264 -0
- package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/MERMAID-SUMMARY.md +137 -0
- package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/advanced-features.md +556 -0
- package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/architecture-diagrams.md +192 -0
- package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/c4-diagrams.md +410 -0
- package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/class-diagrams.md +361 -0
- package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/erd-diagrams.md +510 -0
- package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/flowcharts.md +450 -0
- package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/sequence-diagrams.md +394 -0
- package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/troubleshooting.md +335 -0
- package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/workflows.md +418 -0
- package/bundles/antigravity-plugin/skills/migrate-dir/SKILL.md +68 -0
- package/bundles/antigravity-plugin/skills/mockup/SKILL.md +242 -0
- package/bundles/antigravity-plugin/skills/particle-art/SKILL.md +243 -0
- package/bundles/antigravity-plugin/skills/particle-art/references/canvas-utils.ts +171 -0
- package/bundles/antigravity-plugin/skills/particle-art/references/dot-field.template.tsx +203 -0
- package/bundles/antigravity-plugin/skills/particle-art/references/flow-field.template.tsx +263 -0
- package/bundles/antigravity-plugin/skills/particle-art/references/node-shape.template.tsx +261 -0
- package/bundles/antigravity-plugin/skills/particle-art/references/shape-sampler.ts +281 -0
- package/bundles/antigravity-plugin/skills/particle-art/references/stipple-morph.template.tsx +167 -0
- package/bundles/antigravity-plugin/skills/particle-art/references/stipple.template.tsx +175 -0
- package/bundles/antigravity-plugin/skills/particle-art/references/trail-ghost.template.tsx +266 -0
- package/bundles/antigravity-plugin/skills/particle-art/references/usage-examples.md +320 -0
- package/bundles/antigravity-plugin/skills/playwright/API_REFERENCE.md +653 -0
- package/bundles/antigravity-plugin/skills/playwright/SKILL.md +454 -0
- package/bundles/antigravity-plugin/skills/playwright/lib/helpers.js +441 -0
- package/bundles/antigravity-plugin/skills/playwright/package.json +26 -0
- package/bundles/antigravity-plugin/skills/playwright/run.js +228 -0
- package/bundles/antigravity-plugin/skills/prd/SKILL.md +107 -0
- package/bundles/antigravity-plugin/skills/preflight/SKILL.md +435 -0
- package/bundles/antigravity-plugin/skills/python/SKILL.md +388 -0
- package/bundles/antigravity-plugin/skills/react-best-practices/SKILL.md +269 -0
- package/bundles/antigravity-plugin/skills/react-dev/README.md +404 -0
- package/bundles/antigravity-plugin/skills/react-dev/SKILL.md +459 -0
- package/bundles/antigravity-plugin/skills/react-dev/examples/generic-components.md +579 -0
- package/bundles/antigravity-plugin/skills/react-dev/examples/server-components.md +579 -0
- package/bundles/antigravity-plugin/skills/react-dev/references/event-handlers.md +574 -0
- package/bundles/antigravity-plugin/skills/react-dev/references/hooks.md +456 -0
- package/bundles/antigravity-plugin/skills/react-dev/references/react-19-patterns.md +638 -0
- package/bundles/antigravity-plugin/skills/react-dev/references/react-router.md +1002 -0
- package/bundles/antigravity-plugin/skills/react-dev/references/tanstack-router.md +587 -0
- package/bundles/antigravity-plugin/skills/refactoring/SKILL.md +486 -0
- package/bundles/antigravity-plugin/skills/resume/SKILL.md +36 -0
- package/bundles/antigravity-plugin/skills/security-review/SKILL.md +196 -0
- package/bundles/antigravity-plugin/skills/setup-project-ai/SKILL.md +61 -0
- package/bundles/antigravity-plugin/skills/ship/SKILL.md +107 -0
- package/bundles/antigravity-plugin/skills/ship-merge/SKILL.md +103 -0
- package/bundles/antigravity-plugin/skills/ship-pr/SKILL.md +102 -0
- package/bundles/antigravity-plugin/skills/skill-creator/LICENSE.txt +202 -0
- package/bundles/antigravity-plugin/skills/skill-creator/SKILL.md +491 -0
- package/bundles/antigravity-plugin/skills/skill-creator/agents/analyzer.md +274 -0
- package/bundles/antigravity-plugin/skills/skill-creator/agents/comparator.md +202 -0
- package/bundles/antigravity-plugin/skills/skill-creator/agents/grader.md +223 -0
- package/bundles/antigravity-plugin/skills/skill-creator/assets/eval_review.html +146 -0
- package/bundles/antigravity-plugin/skills/skill-creator/eval-viewer/generate_review.py +471 -0
- package/bundles/antigravity-plugin/skills/skill-creator/eval-viewer/viewer.html +1325 -0
- package/bundles/antigravity-plugin/skills/skill-creator/references/schemas.md +430 -0
- package/bundles/antigravity-plugin/skills/skill-creator/scripts/__init__.py +0 -0
- package/bundles/antigravity-plugin/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/bundles/antigravity-plugin/skills/skill-creator/scripts/generate_report.py +326 -0
- package/bundles/antigravity-plugin/skills/skill-creator/scripts/improve_description.py +247 -0
- package/bundles/antigravity-plugin/skills/skill-creator/scripts/package_skill.py +136 -0
- package/bundles/antigravity-plugin/skills/skill-creator/scripts/quick_validate.py +103 -0
- package/bundles/antigravity-plugin/skills/skill-creator/scripts/run_eval.py +310 -0
- package/bundles/antigravity-plugin/skills/skill-creator/scripts/run_loop.py +328 -0
- package/bundles/antigravity-plugin/skills/skill-creator/scripts/utils.py +47 -0
- package/bundles/antigravity-plugin/skills/sql/SKILL.md +321 -0
- package/bundles/antigravity-plugin/skills/tdd/SKILL.md +37 -0
- package/bundles/antigravity-plugin/skills/tdd-workflow/SKILL.md +188 -0
- package/bundles/antigravity-plugin/skills/technical-writing/SKILL.md +286 -0
- package/bundles/antigravity-plugin/skills/test-strategy/SKILL.md +155 -0
- package/bundles/antigravity-plugin/skills/ui-brief/SKILL.md +84 -0
- package/bundles/antigravity-plugin/skills/ui-review/SKILL.md +176 -0
- package/bundles/antigravity-plugin/skills/ui-review/references/framework-fixes.md +471 -0
- package/bundles/antigravity-plugin/skills/ui-review/references/visual-checklist.md +236 -0
- package/bundles/antigravity-plugin/skills/usage-review/SKILL.md +77 -0
- package/bundles/antigravity-plugin/skills/use-model/SKILL.md +64 -0
- package/bundles/antigravity-plugin/skills/using-git-worktrees/SKILL.md +217 -0
- package/bundles/antigravity-plugin/skills/version/SKILL.md +18 -0
- package/bundles/antigravity-plugin/skills/warm-editorial-ui/DESIGN_TOKENS.md +487 -0
- package/bundles/antigravity-plugin/skills/warm-editorial-ui/IMPLEMENTATION_GUIDE.md +177 -0
- package/bundles/antigravity-plugin/skills/warm-editorial-ui/SKILL.md +732 -0
- package/bundles/antigravity-plugin/skills/webapp-testing/LICENSE.txt +202 -0
- package/bundles/antigravity-plugin/skills/webapp-testing/SKILL.md +97 -0
- package/bundles/antigravity-plugin/skills/webapp-testing/examples/console_logging.py +35 -0
- package/bundles/antigravity-plugin/skills/webapp-testing/examples/element_discovery.py +40 -0
- package/bundles/antigravity-plugin/skills/webapp-testing/examples/static_html_automation.py +33 -0
- package/bundles/antigravity-plugin/skills/webapp-testing/scripts/with_server.py +106 -0
- package/bundles/antigravity-plugin/skills/windows-deployment/SKILL.md +880 -0
- package/bundles/antigravity-plugin/skills/writing-plans/SKILL.md +384 -0
- package/bundles/antigravity-plugin/statusline-command-agy.sh +91 -0
- package/bundles/antigravity-plugin/warm_start_agy.py +149 -0
- package/bundles/manifest.json +6 -0
- package/dist/cli.js +5363 -0
- 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
|
+
}
|