@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,167 @@
1
+ "use client";
2
+ /**
3
+ * stipple-morph.template.tsx — Halftone dot cloud that morphs into a letter/shape on hover.
4
+ *
5
+ * USAGE:
6
+ * 1. Copy and rename (e.g. StippleMorph.tsx, HeroHalftone.tsx)
7
+ * 2. Pass `text` prop to set the target letter/word (default "j")
8
+ * 3. Adjust COLS × ROWS for dot density
9
+ * 4. The morph is driven by React synthetic onMouseMove/onMouseLeave — no addEventListener needed
10
+ *
11
+ * RENDERING: HTML spans (SSR-safe). Grid A built at module level; Grid B from canvas in useEffect.
12
+ * INTERACTION: hover canvas → cloud resolves into letter; leave → drifts back
13
+ * THEMING: --accent-strong CSS var for dot colour
14
+ */
15
+ import { useEffect, useRef } from "react";
16
+
17
+ const COLS = 24;
18
+ const ROWS = 18;
19
+ const SCAN_PERIOD = 480;
20
+ const MORPH_SPEED = 0.045; // lerp speed; higher = snappier
21
+
22
+ function srand(x: number, y: number): number {
23
+ return (Math.abs(Math.sin(x * 127.1 + y * 311.7) * 43758.5453)) % 1;
24
+ }
25
+
26
+ function charFor(v: number): string {
27
+ if (v > 0.68) return "●";
28
+ if (v > 0.35) return "•";
29
+ return "·";
30
+ }
31
+
32
+ // ── Grid A: Gaussian cloud — module-level, SSR-safe ───────────────────────────
33
+ const GRID_A: number[][] = Array.from({ length: ROWS }, (_, yi) =>
34
+ Array.from({ length: COLS }, (_, xi) => {
35
+ const nx = (xi / (COLS - 1)) * 2 - 1;
36
+ const ny = (yi / (ROWS - 1)) * 2 - 1;
37
+ const dist = Math.sqrt(nx * nx + ny * ny);
38
+ const base = Math.exp(-(dist * dist) / (2 * 0.28 * 0.28));
39
+ return Math.max(0, Math.min(1, base + srand(xi * 53, yi * 97) * 0.12 - 0.06));
40
+ })
41
+ );
42
+
43
+ // Per-cell breathing params
44
+ const B_PHASE = Array.from({ length: ROWS }, (_, yi) =>
45
+ Array.from({ length: COLS }, (_, xi) => srand(xi * 71, yi * 113) * Math.PI * 2)
46
+ );
47
+ const B_SPEED = Array.from({ length: ROWS }, (_, yi) =>
48
+ Array.from({ length: COLS }, (_, xi) => 0.008 + srand(xi, yi) * 0.006)
49
+ );
50
+ const B_AMP = Array.from({ length: ROWS }, (_, yi) =>
51
+ Array.from({ length: COLS }, (_, xi) => 0.04 + srand(xi + 50, yi) * 0.10)
52
+ );
53
+
54
+ // ── Grid B: sample canvas text → density map ──────────────────────────────────
55
+ //
56
+ // REPLACE `text` param or call with any string. Single chars work best.
57
+ // For multi-char (e.g. "JS"), reduce font size:
58
+ // const fontSize = Math.floor(Math.min(ROWS*S*0.8, COLS*S*0.35));
59
+ //
60
+ function buildLetterGrid(text: string): number[][] {
61
+ const S = 8;
62
+ const oc = document.createElement("canvas");
63
+ oc.width = COLS * S;
64
+ oc.height = ROWS * S;
65
+ const ctx = oc.getContext("2d")!;
66
+ const fontSize = Math.floor(Math.min(ROWS * S * 0.88, COLS * S * 0.55));
67
+ ctx.fillStyle = "#fff";
68
+ ctx.font = `bold ${fontSize}px sans-serif`;
69
+ ctx.textAlign = "center";
70
+ ctx.textBaseline = "middle";
71
+ ctx.fillText(text, (COLS * S) / 2, (ROWS * S) / 2);
72
+ const { data } = ctx.getImageData(0, 0, COLS * S, ROWS * S);
73
+ return Array.from({ length: ROWS }, (_, yi) =>
74
+ Array.from({ length: COLS }, (_, xi) => {
75
+ let sum = 0;
76
+ for (let dy = 0; dy < S; dy++)
77
+ for (let dx = 0; dx < S; dx++)
78
+ sum += data[((yi * S + dy) * COLS * S + (xi * S + dx)) * 4] / 255;
79
+ return Math.min(1, (sum / (S * S)) * 2.2);
80
+ })
81
+ );
82
+ }
83
+
84
+ // ── Component ──────────────────────────────────────────────────────────────────
85
+ export function StippleMorphArt({ text = "j", className = "" }: { text?: string; className?: string }) {
86
+ const containerRef = useRef<HTMLDivElement>(null);
87
+ // Refs for cross-closure state — never stale in the RAF loop
88
+ const active = useRef(false);
89
+ const morphRef = useRef(0);
90
+
91
+ useEffect(() => {
92
+ const container = containerRef.current;
93
+ if (!container) return;
94
+
95
+ const GRID_B = buildLetterGrid(text);
96
+ const spans = Array.from(container.querySelectorAll<HTMLElement>("span[data-xi]"));
97
+
98
+ let raf: number;
99
+ let frame = 0;
100
+
101
+ const tick = () => {
102
+ // Lerp morphStrength toward 0 (idle) or 1 (letter)
103
+ const m = morphRef.current + ((active.current ? 1 : 0) - morphRef.current) * MORPH_SPEED;
104
+ morphRef.current = m;
105
+
106
+ const cx = COLS / 2, cy = ROWS / 2;
107
+ const maxR = Math.sqrt(cx * cx + cy * cy);
108
+ const waveR = ((frame % SCAN_PERIOD) / SCAN_PERIOD) * (maxR + 1);
109
+
110
+ spans.forEach(el => {
111
+ const xi = +el.dataset.xi!;
112
+ const yi = +el.dataset.yi!;
113
+ const vA = GRID_A[yi][xi];
114
+ const vB = GRID_B[yi][xi];
115
+
116
+ const breathe = Math.sin(frame * B_SPEED[yi][xi] + B_PHASE[yi][xi]) * B_AMP[yi][xi] * (1 - m * 0.85);
117
+
118
+ const dist = Math.sqrt((xi - cx) ** 2 + (yi - cy) ** 2);
119
+ const diff = dist - waveR;
120
+ const wave = (Math.abs(diff) < 1.6 ? 0.38 : diff < 0 && diff > -3.5 ? 0.11 : 0) * (1 - m);
121
+
122
+ const v = Math.max(0, Math.min(1, vA * (1 - m) + vB * m + breathe + wave));
123
+ el.style.opacity = v.toFixed(3);
124
+ const c = charFor(v);
125
+ if (el.textContent !== c) el.textContent = c;
126
+ });
127
+
128
+ frame++;
129
+ raf = requestAnimationFrame(tick);
130
+ };
131
+ tick();
132
+ return () => cancelAnimationFrame(raf);
133
+ }, [text]);
134
+
135
+ return (
136
+ // React synthetic events on the container — reliable across all React modes
137
+ <div
138
+ ref={containerRef}
139
+ className={className}
140
+ style={{ userSelect: "none", lineHeight: 1.15, cursor: "crosshair" }}
141
+ onMouseMove={() => { active.current = true; }}
142
+ onMouseLeave={() => { active.current = false; }}
143
+ aria-hidden="true"
144
+ >
145
+ <div style={{ textAlign: "center", fontSize: "1.1rem", marginBottom: "0.3em", color: "rgb(var(--accent-strong))", opacity: 0.82 }}>
146
+
147
+ </div>
148
+ {Array.from({ length: ROWS }, (_, yi) => (
149
+ <div key={yi} style={{ display: "flex", justifyContent: "center", gap: "0.18em" }}>
150
+ {Array.from({ length: COLS }, (_, xi) => {
151
+ const v = GRID_A[yi][xi];
152
+ return (
153
+ <span
154
+ key={xi}
155
+ data-xi={xi}
156
+ data-yi={yi}
157
+ style={{ fontSize: "0.68rem", color: "rgb(var(--accent-strong))", opacity: v, transition: "none" }}
158
+ >
159
+ {charFor(v)}
160
+ </span>
161
+ );
162
+ })}
163
+ </div>
164
+ ))}
165
+ </div>
166
+ );
167
+ }
@@ -0,0 +1,175 @@
1
+ "use client";
2
+ /**
3
+ * stipple.template.tsx — Animated Unicode halftone dot component template.
4
+ *
5
+ * USAGE:
6
+ * 1. Copy and rename (e.g. StipplePortrait.tsx, HeroStipple.tsx)
7
+ * 2. Adjust COLS × ROWS to control dot density
8
+ * 3. Replace the GRID density function with your own if desired
9
+ * 4. The scan wave and breathing animation work automatically
10
+ *
11
+ * RENDERING: Pure HTML/CSS spans — no canvas. Works with SSR.
12
+ * THEMING: Uses --accent-strong and --accent-soft CSS vars.
13
+ *
14
+ * DEPENDENCIES: none
15
+ */
16
+ import { useEffect, useRef } from "react";
17
+
18
+ // ── Grid parameters ────────────────────────────────────────────────────────────
19
+ const COLS = 24; // horizontal dot count
20
+ const ROWS = 18; // vertical dot count
21
+ const SCAN_PERIOD = 480; // frames per scan wave cycle
22
+
23
+ // ── Dot character selection ────────────────────────────────────────────────────
24
+ function charFor(v: number): string {
25
+ if (v > 0.68) return "●";
26
+ if (v > 0.35) return "•";
27
+ return "·";
28
+ }
29
+
30
+ // ── Deterministic pseudo-random (SSR-safe, no hydration mismatch) ──────────────
31
+ function srand(x: number, y: number): number {
32
+ return (Math.abs(Math.sin(x * 127.1 + y * 311.7) * 43758.5453)) % 1;
33
+ }
34
+
35
+ // ── Pre-compute density grid at module level (stable across re-renders) ────────
36
+ //
37
+ // REPLACE THIS with your own density function.
38
+ // Current: Gaussian falloff from centre — denser at middle, dispersing at edges.
39
+ //
40
+ // `v` is in [0, 1]: 1 = densest (●), 0 = emptiest (·)
41
+ //
42
+ const GRID: number[][] = Array.from({ length: ROWS }, (_, yi) =>
43
+ Array.from({ length: COLS }, (_, xi) => {
44
+ // Normalised distance from centre [0, 1]
45
+ const nx = (xi / (COLS - 1)) * 2 - 1;
46
+ const ny = (yi / (ROWS - 1)) * 2 - 1;
47
+ const dist = Math.sqrt(nx * nx + ny * ny);
48
+
49
+ // Gaussian envelope
50
+ const base = Math.exp(-(dist * dist) / (2 * 0.28 * 0.28));
51
+
52
+ // Jitter so dots don't look perfectly smooth
53
+ const jitter = srand(xi * 53, yi * 97) * 0.12 - 0.06;
54
+
55
+ return Math.max(0, Math.min(1, base + jitter));
56
+ })
57
+ );
58
+
59
+ // ── Animation state per dot ────────────────────────────────────────────────────
60
+ interface DotAnim {
61
+ el: HTMLElement;
62
+ v: number; // density value [0, 1]
63
+ x: number; // column index
64
+ y: number; // row index
65
+ phase: number; // breathing phase offset
66
+ speed: number; // breathing speed
67
+ amp: number; // breathing amplitude
68
+ }
69
+
70
+ // ── Component ──────────────────────────────────────────────────────────────────
71
+ //
72
+ // Renders COLS×ROWS dots as animated spans.
73
+ // The scan wave sweeps outward from centre periodically.
74
+ // Each dot breathes (opacity oscillation) independently.
75
+ //
76
+ export function StippleArt({ className = "" }: { className?: string }) {
77
+ const containerRef = useRef<HTMLDivElement>(null);
78
+
79
+ useEffect(() => {
80
+ const container = containerRef.current;
81
+ if (!container) return;
82
+
83
+ // Reduced-motion: render static dots, skip animation
84
+ if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
85
+
86
+ // Collect all dot spans with their attributes
87
+ const spans = container.querySelectorAll<HTMLElement>("span[data-v]");
88
+ const MAX_DOT_R = Math.sqrt(COLS * COLS + ROWS * ROWS) / 2;
89
+
90
+ const dots: DotAnim[] = Array.from(spans).map(el => ({
91
+ el,
92
+ v: parseFloat(el.dataset.v ?? "0"),
93
+ x: parseFloat(el.dataset.x ?? "0"),
94
+ y: parseFloat(el.dataset.y ?? "0"),
95
+ phase: srand(parseFloat(el.dataset.x ?? "0") * 71, parseFloat(el.dataset.y ?? "0") * 113) * Math.PI * 2,
96
+ speed: 0.008 + srand(parseFloat(el.dataset.x ?? "0"), parseFloat(el.dataset.y ?? "0")) * 0.006,
97
+ amp: 0.04 + srand(parseFloat(el.dataset.x ?? "0") + 50, parseFloat(el.dataset.y ?? "0")) * 0.10,
98
+ }));
99
+
100
+ let raf: number;
101
+ let frame = 0;
102
+ const cx = COLS / 2, cy = ROWS / 2;
103
+
104
+ const tick = () => {
105
+ const waveR = ((frame % SCAN_PERIOD) / SCAN_PERIOD) * (MAX_DOT_R + 1);
106
+
107
+ dots.forEach(d => {
108
+ const dist = Math.sqrt((d.x - cx) ** 2 + (d.y - cy) ** 2);
109
+ const diff = dist - waveR;
110
+
111
+ // Scan wave ring boost
112
+ const ringBoost = Math.abs(diff) < 1.6 ? 0.40 : 0;
113
+ // Trailing glow behind the wave
114
+ const trailBoost = diff < 0 && diff > -3.5 ? 0.12 : 0;
115
+ // Per-dot breathing
116
+ const breathe = Math.sin(frame * d.speed + d.phase) * d.amp;
117
+
118
+ const opacity = Math.max(0, Math.min(1, d.v + breathe + ringBoost + trailBoost));
119
+ d.el.style.opacity = opacity.toFixed(3);
120
+ });
121
+
122
+ frame++;
123
+ raf = requestAnimationFrame(tick);
124
+ };
125
+ tick();
126
+ return () => cancelAnimationFrame(raf);
127
+ }, []);
128
+
129
+ return (
130
+ <div
131
+ ref={containerRef}
132
+ className={className}
133
+ style={{ userSelect: "none", lineHeight: 1.15 }}
134
+ aria-hidden="true"
135
+ >
136
+ {/* ✦ glyph above the dot grid */}
137
+ <div
138
+ style={{
139
+ textAlign: "center",
140
+ fontSize: "1.1rem",
141
+ letterSpacing: "0.05em",
142
+ marginBottom: "0.3em",
143
+ color: "rgb(var(--accent-strong))",
144
+ opacity: 0.82,
145
+ }}
146
+ >
147
+
148
+ </div>
149
+
150
+ {/* Dot grid */}
151
+ {GRID.map((row, yi) => (
152
+ <div key={yi} style={{ display: "flex", justifyContent: "center", gap: "0.18em" }}>
153
+ {row.map((v, xi) => (
154
+ <span
155
+ key={xi}
156
+ data-v={v.toFixed(4)}
157
+ data-x={xi}
158
+ data-y={yi}
159
+ style={{
160
+ fontSize: "0.68rem",
161
+ color: v > 0.55
162
+ ? "rgb(var(--accent-strong))"
163
+ : "rgb(var(--accent-soft))",
164
+ opacity: v,
165
+ transition: "none",
166
+ }}
167
+ >
168
+ {charFor(v)}
169
+ </span>
170
+ ))}
171
+ </div>
172
+ ))}
173
+ </div>
174
+ );
175
+ }
@@ -0,0 +1,266 @@
1
+ "use client";
2
+ /**
3
+ * trail-ghost.template.tsx — Spring-morph nodes with phosphorescent trailing effect.
4
+ *
5
+ * USAGE:
6
+ * 1. Copy and rename (e.g. TrailGhost.tsx, HeroTrails.tsx)
7
+ * 2. Replace `buildTargets` with any shape from shape-sampler.ts
8
+ * 3. Tune TRAIL_DECAY (lower → shorter trails) and TRAIL_MAX (higher → longer tails)
9
+ * 4. Background colour is baked into the persistence rect — change "0,0,0" for light mode
10
+ *
11
+ * KEY TECHNIQUE: instead of ctx.clearRect() each frame, fill with a semi-transparent rect
12
+ * (rgba(0,0,0,0.10)). Old frames fade rather than vanish, creating ghostly smeared trails.
13
+ *
14
+ * INTERACTION: mousemove → nodes coalesce into letter; mouseleave → drift apart
15
+ * THEMING: --accent-strong (node colour), --accent-soft (connection lines)
16
+ */
17
+ import { useEffect, useRef } from "react";
18
+
19
+ // ── Bezier helper ─────────────────────────────────────────────────────────────
20
+ function cubicBezier(
21
+ t: number,
22
+ p0: [number, number], p1: [number, number],
23
+ p2: [number, number], p3: [number, number],
24
+ ): [number, number] {
25
+ const m = 1 - t;
26
+ return [
27
+ m**3*p0[0] + 3*m**2*t*p1[0] + 3*m*t**2*p2[0] + t**3*p3[0],
28
+ m**3*p0[1] + 3*m**2*t*p1[1] + 3*m*t**2*p2[1] + t**3*p3[1],
29
+ ];
30
+ }
31
+
32
+ // ── Target shape — replace or import from shape-sampler.ts ───────────────────
33
+ //
34
+ // CUSTOMISE: swap this function for any sampleText / sampleSVGPath / sampleConstellation call.
35
+ // Return an array of { tx, ty, spring, r, alpha, isDot } objects.
36
+ //
37
+ interface Target { tx: number; ty: number; spring: number; r: number; alpha: number; isDot: boolean }
38
+
39
+ function buildTargets(size: number): Target[] {
40
+ const cx = size * 0.50;
41
+ const stemTop = size * 0.210, stemBot = size * 0.660;
42
+ const targets: Target[] = [];
43
+
44
+ // Tittle (dot above stem)
45
+ targets.push({ tx: cx, ty: size * 0.130, spring: 0.038, r: 4.5, alpha: 1.0, isDot: true });
46
+
47
+ // Vertical stem
48
+ for (let i = 0; i <= 12; i++)
49
+ targets.push({ tx: cx, ty: stemTop + (i / 12) * (stemBot - stemTop), spring: 0.036, r: 2.0, alpha: 0.92, isDot: false });
50
+
51
+ // Hook (cubic bezier descender)
52
+ const P0: [number,number] = [cx, stemBot];
53
+ const P1: [number,number] = [cx, stemBot + size * 0.115];
54
+ const P2: [number,number] = [cx - size * 0.115, stemBot + size * 0.145];
55
+ const P3: [number,number] = [cx - size * 0.155, stemBot + size * 0.040];
56
+ for (let i = 1; i <= 9; i++) {
57
+ const [bx, by] = cubicBezier(i / 9, P0, P1, P2, P3);
58
+ targets.push({ tx: bx, ty: by, spring: 0.034, r: 2.0, alpha: 0.88, isDot: false });
59
+ }
60
+ return targets;
61
+ }
62
+
63
+ // ── Types & theme helper ──────────────────────────────────────────────────────
64
+ interface Pt {
65
+ x: number; y: number; vx: number; vy: number;
66
+ tx: number; ty: number; springBase: number;
67
+ r: number; alpha: number; isDot: boolean; isLetter: boolean;
68
+ trail: { x: number; y: number; a: number }[];
69
+ }
70
+
71
+ function themeRgb(v: string): [number, number, number] {
72
+ const ch = getComputedStyle(document.documentElement)
73
+ .getPropertyValue(v).trim().split(/\s+/).map(Number);
74
+ return [ch[0], ch[1], ch[2]];
75
+ }
76
+
77
+ // ── Constants ─────────────────────────────────────────────────────────────────
78
+ const LINK = 68; // connection line distance threshold (px)
79
+ const MAX_SPEED = 1.5;
80
+ const IDLE_FRICTION = 0.984; // low friction → nodes drift organically in idle
81
+ const ACTIVE_FRICTION = 0.88; // snappy convergence on hover
82
+ const DRIFT = 0.055; // random brownian nudge in idle
83
+ const MORPH_SPEED = 0.05; // lerp rate 0→1; higher = faster morph
84
+ const TRAIL_DECAY = 0.91; // per-frame alpha multiplier — lower = shorter trails (try 0.80–0.96)
85
+ const TRAIL_MAX = 50; // max trail history length per node
86
+
87
+ // ── Component ─────────────────────────────────────────────────────────────────
88
+ export function TrailGhostArt({ size = 300 }: { size?: number }) {
89
+ const ref = useRef<HTMLCanvasElement>(null);
90
+ // useRef for cross-RAF state — never stale in the animation loop
91
+ const active = useRef(false);
92
+ const morph = useRef(0);
93
+
94
+ useEffect(() => {
95
+ const el = ref.current;
96
+ if (!el) return;
97
+
98
+ const ctx = el.getContext("2d")!;
99
+ const dpr = window.devicePixelRatio || 1;
100
+ el.width = size * dpr;
101
+ el.height = size * dpr;
102
+ ctx.scale(dpr, dpr);
103
+
104
+ const cx = size * 0.50, cy = size * 0.46;
105
+
106
+ // Letter nodes — start scattered, attracted to targets on hover
107
+ const pts: Pt[] = buildTargets(size).map(t => {
108
+ const angle = Math.random() * Math.PI * 2;
109
+ const dist = size * (0.10 + Math.random() * 0.32);
110
+ return {
111
+ x: cx + Math.cos(angle) * dist,
112
+ y: cy + Math.sin(angle) * dist,
113
+ vx: (Math.random() - 0.5) * 0.6,
114
+ vy: (Math.random() - 0.5) * 0.6,
115
+ tx: t.tx, ty: t.ty, springBase: t.spring,
116
+ r: t.r, alpha: t.alpha, isDot: t.isDot, isLetter: true,
117
+ trail: [],
118
+ };
119
+ });
120
+
121
+ // Ambient nodes — no letter target, add atmosphere
122
+ // CUSTOMISE: increase count, adjust alpha, starting radius
123
+ for (let i = 0; i < 10; i++) {
124
+ const angle = (i / 10) * Math.PI * 2;
125
+ const dist = size * (0.24 + Math.random() * 0.18);
126
+ pts.push({
127
+ x: cx + Math.cos(angle) * dist * 0.75,
128
+ y: cy + Math.sin(angle) * dist,
129
+ vx: (Math.random() - 0.5) * 0.3,
130
+ vy: (Math.random() - 0.5) * 0.3,
131
+ tx: cx, ty: cy, springBase: 0,
132
+ r: Math.random() * 0.9 + 0.5,
133
+ alpha: 0.30 + Math.random() * 0.20,
134
+ isDot: false, isLetter: false,
135
+ trail: [],
136
+ });
137
+ }
138
+
139
+ // mousemove is more reliable than mouseenter on canvas in React 18
140
+ const onMove = () => { active.current = true; };
141
+ const onLeave = () => { active.current = false; };
142
+ el.addEventListener("mousemove", onMove);
143
+ el.addEventListener("mouseleave", onLeave);
144
+
145
+ let raf: number;
146
+ let frame = 0;
147
+
148
+ const tick = () => {
149
+ // PERSISTENCE ghost — fill instead of clearRect; old pixels fade gradually
150
+ // CUSTOMISE: increase alpha (e.g. 0.20) for shorter trails; decrease (0.05) for longer
151
+ ctx.fillStyle = "rgba(0,0,0,0.10)";
152
+ ctx.fillRect(0, 0, size, size);
153
+
154
+ const [nr, ng, nb] = themeRgb("--accent-strong");
155
+ const [lr, lg, lb] = themeRgb("--accent-soft");
156
+
157
+ // Lerp morph 0 (idle) → 1 (letter formed)
158
+ const m = morph.current + ((active.current ? 1 : 0) - morph.current) * MORPH_SPEED;
159
+ morph.current = m;
160
+
161
+ // Connection lines — faint web between nearby nodes
162
+ for (let i = 0; i < pts.length; i++) {
163
+ for (let j = i + 1; j < pts.length; j++) {
164
+ const dx = pts[i].x - pts[j].x, dy = pts[i].y - pts[j].y;
165
+ const d = Math.sqrt(dx * dx + dy * dy);
166
+ if (d < LINK) {
167
+ ctx.beginPath();
168
+ ctx.moveTo(pts[i].x, pts[i].y);
169
+ ctx.lineTo(pts[j].x, pts[j].y);
170
+ ctx.strokeStyle = `rgba(${lr},${lg},${lb},${((1 - d / LINK) * 0.14).toFixed(3)})`;
171
+ ctx.lineWidth = 0.5;
172
+ ctx.stroke();
173
+ }
174
+ }
175
+ }
176
+
177
+ pts.forEach(p => {
178
+ // 1. Record trail before moving, decay all entries, trim to TRAIL_MAX
179
+ p.trail.unshift({ x: p.x, y: p.y, a: p.alpha * 0.65 });
180
+ p.trail.forEach(t => { t.a *= TRAIL_DECAY; });
181
+ while (p.trail.length > TRAIL_MAX) p.trail.pop();
182
+
183
+ // 2. Draw trail — fading, shrinking circles back through history
184
+ p.trail.forEach((t, i) => {
185
+ const frac = i / TRAIL_MAX;
186
+ const r = Math.max(0.2, p.r * (1 - frac * 0.88));
187
+ ctx.beginPath();
188
+ ctx.arc(t.x, t.y, r, 0, Math.PI * 2);
189
+ ctx.fillStyle = `rgba(${nr},${ng},${nb},${Math.max(0, t.a).toFixed(3)})`;
190
+ ctx.fill();
191
+ });
192
+
193
+ // 3. Physics
194
+ if (p.isLetter) {
195
+ // Spring toward target (scales with morph)
196
+ p.vx += (p.tx - p.x) * p.springBase * m;
197
+ p.vy += (p.ty - p.y) * p.springBase * m;
198
+ // Brownian drift fades out as letter forms
199
+ p.vx += (Math.random() - 0.5) * DRIFT * (1 - m);
200
+ p.vy += (Math.random() - 0.5) * DRIFT * (1 - m);
201
+ // Gentle centre gravity in idle so nodes don't escape
202
+ p.vx += (cx - p.x) * 0.0009 * (1 - m);
203
+ p.vy += (cy - p.y) * 0.0009 * (1 - m);
204
+ // Friction blends from idle to active
205
+ const fr = IDLE_FRICTION + (ACTIVE_FRICTION - IDLE_FRICTION) * m;
206
+ p.vx *= fr; p.vy *= fr;
207
+ // Soft wall bounce in idle
208
+ if (m < 0.15) {
209
+ if (p.x < 6 || p.x > size - 6) p.vx *= -0.8;
210
+ if (p.y < 6 || p.y > size - 6) p.vy *= -0.8;
211
+ }
212
+ } else {
213
+ p.vx += (Math.random() - 0.5) * DRIFT * 0.5;
214
+ p.vy += (Math.random() - 0.5) * DRIFT * 0.5;
215
+ p.vx += (cx - p.x) * 0.0006;
216
+ p.vy += (cy - p.y) * 0.0006;
217
+ p.vx *= IDLE_FRICTION; p.vy *= IDLE_FRICTION;
218
+ if (p.x < 6 || p.x > size - 6) p.vx *= -0.8;
219
+ if (p.y < 6 || p.y > size - 6) p.vy *= -0.8;
220
+ }
221
+
222
+ const spd = Math.sqrt(p.vx * p.vx + p.vy * p.vy);
223
+ if (spd > MAX_SPEED) { p.vx *= MAX_SPEED / spd; p.vy *= MAX_SPEED / spd; }
224
+ p.x += p.vx; p.y += p.vy;
225
+
226
+ // 4. Node head — bright dot on top of trail
227
+ let nodeR = p.r;
228
+ if (p.isDot && m > 0.15) {
229
+ // Pulsing glow ring for the tittle (dot above "j" stem)
230
+ nodeR = p.r + Math.sin(frame * 0.04) * 0.8;
231
+ ctx.beginPath();
232
+ ctx.arc(p.x, p.y, nodeR + 5 + Math.sin(frame * 0.04) * 2, 0, Math.PI * 2);
233
+ ctx.strokeStyle = `rgba(${nr},${ng},${nb},${(0.20 * m).toFixed(3)})`;
234
+ ctx.lineWidth = 1;
235
+ ctx.stroke();
236
+ }
237
+ ctx.beginPath();
238
+ ctx.arc(p.x, p.y, nodeR, 0, Math.PI * 2);
239
+ ctx.fillStyle = `rgba(${nr},${ng},${nb},${p.alpha.toFixed(3)})`;
240
+ ctx.fill();
241
+ });
242
+
243
+ frame++;
244
+ raf = requestAnimationFrame(tick);
245
+ };
246
+ tick();
247
+
248
+ // ⚠️ prefers-reduced-motion: do NOT early-return before drawing (leaves canvas blank).
249
+ // Correct approach would be: snap all pts to targets, call tick() once, then cancelRAF.
250
+ // This component always animates — add static-snap logic here if reduced motion is required.
251
+
252
+ return () => {
253
+ cancelAnimationFrame(raf);
254
+ el.removeEventListener("mousemove", onMove);
255
+ el.removeEventListener("mouseleave", onLeave);
256
+ };
257
+ }, [size]);
258
+
259
+ return (
260
+ <canvas
261
+ ref={ref}
262
+ style={{ width: size, height: size, cursor: "crosshair" }}
263
+ aria-hidden="true"
264
+ />
265
+ );
266
+ }