@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,242 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: mockup
|
|
3
|
+
description: Create framework-aware UI mockups with feasibility checks. Prevents wasted effort by validating that proposed designs work within the target framework's constraints.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Mockup Skill
|
|
7
|
+
|
|
8
|
+
## Purpose
|
|
9
|
+
|
|
10
|
+
Create visual mockups (HTML/SVG) for UI features with **mandatory framework feasibility validation**. This skill exists because of a real incident: a beautiful HTML mockup was created for a Streamlit app using `position: fixed` — which doesn't work in Streamlit's DOM. 24 hours were wasted discovering this.
|
|
11
|
+
|
|
12
|
+
**This skill prevents that class of failure for ANY framework.**
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## When to Use
|
|
17
|
+
|
|
18
|
+
- Creating a UI mockup for a proposed feature
|
|
19
|
+
- Visualizing a design before implementation
|
|
20
|
+
- Producing a reference artifact for implementing agents
|
|
21
|
+
- When `@ux-designer` routes mockup creation here
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Steps
|
|
26
|
+
|
|
27
|
+
### Step 1: Read Project Context
|
|
28
|
+
|
|
29
|
+
1. Read `project-config.md` → identify:
|
|
30
|
+
- **Frontend framework** (Streamlit, React, Vue, Angular, Next.js, etc.)
|
|
31
|
+
- **Brand colors** (from profile or placeholder values)
|
|
32
|
+
- **CSS conventions** (scoping method, custom properties, etc.)
|
|
33
|
+
2. Read `copilot-instructions.md` → identify:
|
|
34
|
+
- Existing component patterns
|
|
35
|
+
- Framework-specific conventions already documented
|
|
36
|
+
3. If a `chain_id` is provided, read the Intent Document from `agent-docs/intents/`
|
|
37
|
+
|
|
38
|
+
### Step 2: Framework Feasibility Check (MANDATORY)
|
|
39
|
+
|
|
40
|
+
**Before creating ANY mockup, validate that the proposed design is feasible in the target framework.**
|
|
41
|
+
|
|
42
|
+
Run through this checklist:
|
|
43
|
+
|
|
44
|
+
| Check | Question | If NO |
|
|
45
|
+
|-------|----------|-------|
|
|
46
|
+
| **DOM Control** | Does the framework give you full control over the DOM? | Identify which HTML/CSS features are restricted |
|
|
47
|
+
| **Positioning** | Does `position: fixed/absolute` work as expected? | Document the limitation and alternative approach |
|
|
48
|
+
| **Custom HTML** | Can you inject arbitrary HTML/JS? | Identify the framework's component/extension API |
|
|
49
|
+
| **Event Handling** | Can you attach custom JS event listeners? | Document how events work in this framework |
|
|
50
|
+
| **Styling** | Can you use global CSS freely? | Document CSS scoping constraints |
|
|
51
|
+
| **Third-party Libraries** | Can you load external JS/CSS? | Check if air-gapped deployment restricts this |
|
|
52
|
+
|
|
53
|
+
#### Known Framework Constraints
|
|
54
|
+
|
|
55
|
+
| Framework | Key Constraints |
|
|
56
|
+
|-----------|----------------|
|
|
57
|
+
| **Streamlit** | No `position: fixed/absolute` (DOM wrapping breaks it). No arbitrary HTML injection without `components.html()` or `declare_component()`. CSS is scoped — use `st.container(key=)` + `.st-key-` selectors. No external CDN in air-gapped environments. |
|
|
58
|
+
| **React** | JSX required. CSS-in-JS or CSS Modules typical. Virtual DOM diffing may interfere with manual DOM manipulation. |
|
|
59
|
+
| **Vue** | Template syntax. Scoped styles by default. `v-html` for raw HTML but XSS risk. |
|
|
60
|
+
| **Gradio** | Similar DOM constraints to Streamlit. Limited CSS control. Use `gr.HTML()` for custom elements. |
|
|
61
|
+
| **Next.js** | SSR considerations. `useEffect` needed for client-only code. CSS Modules or Tailwind typical. |
|
|
62
|
+
|
|
63
|
+
> **If ANY constraint would prevent the proposed design from working:**
|
|
64
|
+
> 1. **STOP** — do not create a mockup that can't be implemented
|
|
65
|
+
> 2. **WARN** the user with specific details about what won't work and why
|
|
66
|
+
> 3. **PROPOSE ALTERNATIVES** that work within the framework's constraints
|
|
67
|
+
> 4. **Get confirmation** before proceeding with an alternative approach
|
|
68
|
+
|
|
69
|
+
### Step 3: Create the Mockup
|
|
70
|
+
|
|
71
|
+
**Output format**: HTML file (preferred for interactive mockups) or SVG (for static layouts)
|
|
72
|
+
|
|
73
|
+
**Requirements:**
|
|
74
|
+
- Use brand colors from `project-config.md` (never hardcode colors — use CSS custom properties)
|
|
75
|
+
- Include responsive behavior if applicable
|
|
76
|
+
- Add comments explaining key layout decisions
|
|
77
|
+
- If the mockup uses workarounds for framework constraints, document them clearly in comments
|
|
78
|
+
|
|
79
|
+
**HTML mockup template:**
|
|
80
|
+
```html
|
|
81
|
+
<!DOCTYPE html>
|
|
82
|
+
<html lang="en">
|
|
83
|
+
<head>
|
|
84
|
+
<meta charset="UTF-8">
|
|
85
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
86
|
+
<title>{Feature Name} — Mockup</title>
|
|
87
|
+
<style>
|
|
88
|
+
/* Brand colors from project-config.md */
|
|
89
|
+
:root {
|
|
90
|
+
--brand-primary: {<BRAND_PRIMARY>};
|
|
91
|
+
--brand-dark: {<BRAND_DARK>};
|
|
92
|
+
--brand-light: {<BRAND_LIGHT>};
|
|
93
|
+
/* Additional colors from profile */
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* Mockup styles */
|
|
97
|
+
/* ... */
|
|
98
|
+
</style>
|
|
99
|
+
</head>
|
|
100
|
+
<body>
|
|
101
|
+
<!-- MOCKUP: {Feature description} -->
|
|
102
|
+
<!-- FRAMEWORK: {Target framework} -->
|
|
103
|
+
<!-- CONSTRAINTS: {Any framework constraints that shaped this design} -->
|
|
104
|
+
<!-- ALTERNATIVES: {If using a workaround, what was the original approach and why it wouldn't work} -->
|
|
105
|
+
|
|
106
|
+
<!-- Mockup content -->
|
|
107
|
+
</body>
|
|
108
|
+
</html>
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Step 3b: Implementation-Annotated Mockup (MANDATORY for handoff to implementing agents)
|
|
112
|
+
|
|
113
|
+
When the mockup will be handed off to `@implement`, `@frontend-developer`, or `@streamlit-developer`, embed **implementation annotations** throughout the HTML so the implementing agent knows exactly what to build without guessing.
|
|
114
|
+
|
|
115
|
+
#### Annotation Types
|
|
116
|
+
|
|
117
|
+
Use these four comment patterns consistently:
|
|
118
|
+
|
|
119
|
+
**1. Component annotations** — Which framework component implements this visual element:
|
|
120
|
+
```html
|
|
121
|
+
<!-- React: <KpiFlipCard product="broadband" data={surveys.broadband}
|
|
122
|
+
trendData={trend.historicalNPS.byProduct.broadband} /> -->
|
|
123
|
+
<!-- React: <ResponsiveContainer width="100%" height={260}>
|
|
124
|
+
<AreaChart data={trendData}><Area type="monotone" dataKey="nps" ... /></AreaChart>
|
|
125
|
+
</ResponsiveContainer> -->
|
|
126
|
+
<!-- React: import { ScoreChip, DeltaBadge, SparkBar, TrendDot } from '@/components/table-cells' -->
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**2. Data source annotations** — Which JSON key / API response field provides the data:
|
|
130
|
+
```html
|
|
131
|
+
<!-- DATA-SOURCE: surveys.broadband.nps, surveys.broadband.responses -->
|
|
132
|
+
<!-- DATA-SOURCE: trend.historicalNPS.byProduct.broadband (array of monthly values) -->
|
|
133
|
+
<!-- DATA-SOURCE: ceoInsight.headline, ceoInsight.keyFindings[] (objects with title, detail) -->
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**3. Implementation notes** — Broader guidance for the implementing agent:
|
|
137
|
+
```html
|
|
138
|
+
<!-- IMPLEMENTATION NOTE [DATA-SOURCE]: This section uses hardcoded mock values for
|
|
139
|
+
layout reference. React implementation must fetch period-filtered data from the
|
|
140
|
+
/api/nps/overview endpoint and compute all KPIs dynamically. -->
|
|
141
|
+
<!-- IMPLEMENTATION NOTE [CHART]: Use Recharts AreaChart with gradient fill.
|
|
142
|
+
Spread CHART_DEFAULTS from chartTheme.ts. No axes/grid for sparkline variant. -->
|
|
143
|
+
<!-- IMPLEMENTATION NOTE [ANIMATION]: Use framer-motion rotateY for card flip.
|
|
144
|
+
duration: 0.52, ease: [0.4, 0.2, 0.2, 1]. backfaceVisibility: "hidden". -->
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**4. Styling annotations** — Which design tokens, CSS vars, or Tailwind classes to use:
|
|
148
|
+
```html
|
|
149
|
+
<!-- STYLE: bg-surface-card (var(--surface-card)), shadow-card, rounded-lg (var(--r-lg)) -->
|
|
150
|
+
<!-- STYLE: Fill color uses var(--status-red) for detractor, var(--status-green) for promoter -->
|
|
151
|
+
<!-- STYLE: Tailwind: text-ink-primary font-heading text-2xl font-bold -->
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
#### Annotation Placement Rules
|
|
155
|
+
|
|
156
|
+
1. Place **component annotations** directly above or inside the HTML element that the component replaces
|
|
157
|
+
2. Place **data source annotations** on the element that displays the data value
|
|
158
|
+
3. Place **implementation notes** at the top of each major section (tab, panel, card group)
|
|
159
|
+
4. Place **styling annotations** on elements where the visual appearance matters and the token name isn't obvious from the CSS
|
|
160
|
+
5. Every hardcoded value in the mockup (numbers, text, colors) that will be dynamic in production MUST have a data source annotation
|
|
161
|
+
|
|
162
|
+
#### Example: Annotated KPI Card
|
|
163
|
+
|
|
164
|
+
```html
|
|
165
|
+
<!-- React: <KpiFlipCard product="broadband" data={surveys.broadband}
|
|
166
|
+
trendData={trend.historicalNPS.byProduct.broadband} color={COLORS.broadband} /> -->
|
|
167
|
+
<!-- DATA-SOURCE: surveys.broadband.nps (current score), surveys.broadband.responses (count) -->
|
|
168
|
+
<!-- STYLE: var(--surface-card) bg, var(--shadow-card) shadow, 3px left border using COLORS.broadband -->
|
|
169
|
+
<div class="kpi-card">
|
|
170
|
+
<div class="kpi-score">+32</div> <!-- DATA-SOURCE: surveys.broadband.nps -->
|
|
171
|
+
<div class="kpi-delta">▲ 2.1</div> <!-- DATA-SOURCE: deriveMom(trend.historicalNPS.byProduct.broadband) -->
|
|
172
|
+
<div class="kpi-responses">1,247 responses</div> <!-- DATA-SOURCE: surveys.broadband.responses -->
|
|
173
|
+
<div class="sparkline">
|
|
174
|
+
<!-- React: <Sparkline data={trend.historicalNPS.byProduct.broadband}
|
|
175
|
+
color={COLORS.broadband} height={72} /> -->
|
|
176
|
+
<!-- IMPLEMENTATION NOTE [CHART]: Recharts AreaChart, no axes/grid/tooltip,
|
|
177
|
+
gradient fill from color at opacity 0.3 to opacity 0. -->
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
#### Completeness Check
|
|
183
|
+
|
|
184
|
+
Before saving the mockup, verify:
|
|
185
|
+
- [ ] Every visual section has at least one **component annotation**
|
|
186
|
+
- [ ] Every dynamic value has a **data source annotation** with the exact JSON path
|
|
187
|
+
- [ ] Every chart/graph has an **implementation note** specifying the chart library and config
|
|
188
|
+
- [ ] The implementing agent can build the feature using ONLY the mockup + annotations (no guessing)
|
|
189
|
+
|
|
190
|
+
### Step 4: Write Artifact
|
|
191
|
+
|
|
192
|
+
1. Save the mockup file to `agent-docs/ux/mockups/{feature-slug}.html` (or `.svg`)
|
|
193
|
+
2. Add YAML header to a companion markdown file or embed as HTML comment:
|
|
194
|
+
```yaml
|
|
195
|
+
agent: ux-designer
|
|
196
|
+
created: {date}
|
|
197
|
+
status: current
|
|
198
|
+
chain_id: {chain_id if provided}
|
|
199
|
+
approval: pending
|
|
200
|
+
framework: {target framework}
|
|
201
|
+
feasibility_warnings: {list any constraints encountered}
|
|
202
|
+
```
|
|
203
|
+
3. Update `agent-docs/ARTIFACTS.md` manifest with the new entry
|
|
204
|
+
|
|
205
|
+
### Step 5: Report
|
|
206
|
+
|
|
207
|
+
Provide:
|
|
208
|
+
- Path to the mockup file
|
|
209
|
+
- Framework feasibility summary (what was checked, any warnings)
|
|
210
|
+
- Implementation notes for the agent that will build this (which API/component to use, what CSS approach works)
|
|
211
|
+
- If alternatives were chosen, explain why the original approach wouldn't work
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## Framework Feasibility Quick Reference
|
|
216
|
+
|
|
217
|
+
### Streamlit-Specific
|
|
218
|
+
|
|
219
|
+
| Want to do | Don't use | Instead use |
|
|
220
|
+
|-----------|-----------|-------------|
|
|
221
|
+
| Floating element (chat widget, FAB) | `position: fixed/absolute` | `declare_component()` with custom HTML/JS |
|
|
222
|
+
| Custom styled container | Global CSS | `st.container(key="name")` + `.st-key-name` selector |
|
|
223
|
+
| Modal/overlay | `z-index` + absolute positioning | `st.dialog()` (Streamlit 1.33+) or `st.modal()` |
|
|
224
|
+
| Sidebar content | Custom sidebar HTML | `st.sidebar` API |
|
|
225
|
+
| Custom header | Fixed position header | `st.container()` at top + CSS with `st.container(key=)` |
|
|
226
|
+
| External JavaScript | `<script src="cdn...">` | Bundle locally for air-gapped; use `components.html()` |
|
|
227
|
+
|
|
228
|
+
### React-Specific
|
|
229
|
+
|
|
230
|
+
| Want to do | Watch out for | Recommended approach |
|
|
231
|
+
|-----------|---------------|---------------------|
|
|
232
|
+
| Direct DOM manipulation | React re-renders will overwrite | Use refs (`useRef`) or state-driven rendering |
|
|
233
|
+
| Global styles | May conflict with component styles | CSS Modules, styled-components, or Tailwind |
|
|
234
|
+
| Third-party widgets | jQuery plugins fight React | Find React-native alternatives or wrap in `useEffect` |
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## Important Notes
|
|
239
|
+
|
|
240
|
+
- This skill is designed to be loaded by `@ux-designer` or `@frontend-developer` agents
|
|
241
|
+
- The framework constraint tables should be extended as new frameworks and gotchas are discovered
|
|
242
|
+
- Always check `project-config.md` for the deployment environment — air-gapped deployments restrict external dependencies
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: particle-art
|
|
3
|
+
version: 1.0.0
|
|
4
|
+
description: Generate animated particle art React/Next.js components — zero dependencies, spring physics, CSS-variable theming, DPR-correct canvas, mouse interaction. Use when the user wants animated hero art, a living logo, a branded initial or monogram, a constellation background, or any "particles that form a shape". Triggers on "particle animation", "node network", "animated letter/logo/initials", "living letter", "morphing particles", "constellation", "neural net art", "dot field", "stipple portrait", "halftone animation", "ASCII art animation", "background art for my site", or "art that reacts to mouse". Covers — text/initials → spring-node letter; preset patterns (constellation, helix, spiral, DNA, hex-grid, wave, infinity); custom SVG paths; custom polygon points; Unicode halftone stipple; Gaussian dot field with sparks; SVG circuit traces; hybrid combos. Outputs a complete, self-contained, copy-paste-ready React component. Prefer this over p5.js (zero-dep, React-native, TypeScript), tsParticles (richer spring physics, CSS-var theming), Three.js (lightweight, no WebGL).
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Particle Art Generator
|
|
8
|
+
|
|
9
|
+
Produces animated React components using one of four rendering styles, each themeable via CSS custom properties and interactive via mouse. Zero npm dependencies — pure canvas and SVG.
|
|
10
|
+
|
|
11
|
+
## Reference Files (read when needed)
|
|
12
|
+
|
|
13
|
+
| File | Read when |
|
|
14
|
+
|------|-----------|
|
|
15
|
+
| `references/canvas-utils.ts` | Starting any canvas-based component (DPR setup, theme colours, RAF loop) |
|
|
16
|
+
| `references/shape-sampler.ts` | Building target points for any shape — text, presets, SVG path, polygon |
|
|
17
|
+
| `references/node-shape.template.tsx` | Generating a **spring-node** component (repel/attract interaction) |
|
|
18
|
+
| `references/stipple.template.tsx` | Generating a **stipple** (Unicode halftone, static cloud) component |
|
|
19
|
+
| `references/stipple-morph.template.tsx` | Generating a **stipple-morph** component (halftone cloud → letter on hover) |
|
|
20
|
+
| `references/dot-field.template.tsx` | Generating a **dot-field** component |
|
|
21
|
+
| `references/trail-ghost.template.tsx` | Generating a **trail-ghost** component (spring-morph + phosphorescent trails) |
|
|
22
|
+
| `references/flow-field.template.tsx` | Generating a **flow-field** component (value-noise currents + letter assembly) |
|
|
23
|
+
| `references/usage-examples.md` | Checking finished examples before delivering |
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Step 0 — Gather Requirements
|
|
28
|
+
|
|
29
|
+
Ask (or infer from context) these five things before writing any code:
|
|
30
|
+
|
|
31
|
+
1. **Shape** — What should the particles form?
|
|
32
|
+
- Text / initials: `"j"`, `"JS"`, `"AI"`, `"hello"`, any Unicode char or emoji
|
|
33
|
+
- Preset: `constellation` `helix` `spiral` `dna` `hexgrid` `wave` `infinity`
|
|
34
|
+
- Custom: SVG `d` path string, or `[x,y]` polygon coordinate array
|
|
35
|
+
- Abstract: no fixed shape (ambient cloud)
|
|
36
|
+
|
|
37
|
+
2. **Style** — Which rendering engine?
|
|
38
|
+
- `spring-nodes` — canvas nodes spring to shape + ambient network (best for letters/logos, repel interaction)
|
|
39
|
+
- `morph-nodes` — nodes scatter in idle, coalesce into letter/shape on hover (best for "appear on demand")
|
|
40
|
+
- `stipple` — animated Unicode character halftone static cloud (best for atmospheric dot portraits)
|
|
41
|
+
- `stipple-morph` — halftone cloud that resolves into a letter/shape on hover (best for atmospheric + interactive)
|
|
42
|
+
- `dot-field` — Gaussian canvas dot cloud with sparks (best for cosmic/diffuse feel)
|
|
43
|
+
- `trail-ghost` — spring-morph nodes with phosphorescent trailing smears (best for ethereal/ghostly feel)
|
|
44
|
+
- `flow-field` — value-noise particle currents, assembles into letter on hover (best for organic/fluid feel)
|
|
45
|
+
- `circuit` — SVG grid traces with traveling pulses (best for tech/infrastructure feel)
|
|
46
|
+
- `hybrid` — spring-nodes foreground + dot-field background (most dramatic)
|
|
47
|
+
|
|
48
|
+
3. **Size** — `sm` (240px) · `md` (300px) · `lg` (380px) · `xl` (480px) · or a number
|
|
49
|
+
|
|
50
|
+
4. **Colors** — `theme-vars` (default, CSS custom props) · `[primary, secondary]` hex pair · `monochrome`
|
|
51
|
+
|
|
52
|
+
5. **Interaction** — `repel` (default) · `attract` · `orbit` · `none`
|
|
53
|
+
|
|
54
|
+
If the user hasn't stated a style, recommend `spring-nodes` for text/initials, `dot-field` for abstract, `stipple` for atmospheric, `circuit` for technical backgrounds.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Step 1 — Select and Read Reference Files
|
|
59
|
+
|
|
60
|
+
Read the canvas-utils and the template that matches the chosen style. Always read canvas-utils first — it has the DPR setup and theme colour helpers used by all canvas components.
|
|
61
|
+
|
|
62
|
+
For shape types:
|
|
63
|
+
- **text / initials / preset / polygon / SVG path** → read `shape-sampler.ts` for the correct `sampleShape()` call
|
|
64
|
+
- **abstract** → skip shape-sampler; use ambient-only config in the node-shape template
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Step 2 — Build the Component
|
|
69
|
+
|
|
70
|
+
### Naming Convention
|
|
71
|
+
```
|
|
72
|
+
<Subject><Style>Art.tsx
|
|
73
|
+
// Examples:
|
|
74
|
+
JSpringArt.tsx // letter "j", spring-nodes
|
|
75
|
+
ConstellationArt.tsx // preset constellation
|
|
76
|
+
HeroNodeArt.tsx // abstract spring-nodes for a hero section
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### File Structure
|
|
80
|
+
```tsx
|
|
81
|
+
"use client";
|
|
82
|
+
// 1. Imports (React only — no external deps)
|
|
83
|
+
// 2. Constants (physics params, sizing)
|
|
84
|
+
// 3. Shape builder function (calls sampleShape or defines points inline)
|
|
85
|
+
// 4. Component (useEffect → canvas setup → RAF loop → cleanup)
|
|
86
|
+
// 5. Export
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Physics Parameters by Use-Case
|
|
90
|
+
|
|
91
|
+
| Use-case | spring | friction | drift | linkDist | ambientN |
|
|
92
|
+
|----------|--------|----------|-------|----------|----------|
|
|
93
|
+
| Single letter | 0.036 | 0.88 | 0.06 | 72 | 14 |
|
|
94
|
+
| Short word (2-4 chars) | 0.030 | 0.90 | 0.04 | 60 | 8 |
|
|
95
|
+
| Long word / phrase | 0.025 | 0.92 | 0.03 | 50 | 4 |
|
|
96
|
+
| Abstract / no shape | — | 0.985 | — | 82 | all |
|
|
97
|
+
| Constellation preset | 0.012 | 0.94 | 0.05 | 90 | 6 |
|
|
98
|
+
|
|
99
|
+
### Node Count Guidelines
|
|
100
|
+
|
|
101
|
+
| Shape | Recommended node count |
|
|
102
|
+
|-------|----------------------|
|
|
103
|
+
| Single letter (j, A) | 22–30 letter + 14–18 ambient |
|
|
104
|
+
| Two letters (JS, AI) | 35–45 letter + 12 ambient |
|
|
105
|
+
| Word ≤6 chars | 50–70 letter + 8 ambient |
|
|
106
|
+
| Preset shape | 28–40 |
|
|
107
|
+
| Abstract cloud | 32–48 ambient only |
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Step 3 — Adapt for the Project's Theme System
|
|
112
|
+
|
|
113
|
+
### CSS-Variable Theming (default, always prefer this)
|
|
114
|
+
```ts
|
|
115
|
+
// Read in the RAF loop — auto-updates on theme change
|
|
116
|
+
function themeRgb(v: string): [number, number, number] {
|
|
117
|
+
const ch = getComputedStyle(document.documentElement)
|
|
118
|
+
.getPropertyValue(v).trim().split(/\s+/).map(Number);
|
|
119
|
+
return [ch[0], ch[1], ch[2]];
|
|
120
|
+
}
|
|
121
|
+
// Usage: themeRgb("--accent-strong") → [79, 209, 197]
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Standard variable names (work with Tailwind CSS-variable setups):
|
|
125
|
+
- `--accent-strong` — primary / brightest nodes, tittle glow
|
|
126
|
+
- `--accent-soft` — secondary / lines and ambient nodes
|
|
127
|
+
- `--fg` — foreground text colour (fallback for monochrome)
|
|
128
|
+
- `--bg` — background (for any fill-behind effects)
|
|
129
|
+
|
|
130
|
+
If the project uses different variable names, ask the user or check `globals.css`.
|
|
131
|
+
|
|
132
|
+
### Hex / Custom Colors
|
|
133
|
+
```ts
|
|
134
|
+
function hexToRgb(hex: string): [number, number, number] {
|
|
135
|
+
const n = parseInt(hex.replace('#',''), 16);
|
|
136
|
+
return [(n >> 16) & 255, (n >> 8) & 255, n & 255];
|
|
137
|
+
}
|
|
138
|
+
// Hard-code in the RAF loop instead of reading CSS vars
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Step 4 — Interaction Modes
|
|
144
|
+
|
|
145
|
+
### Repel (default)
|
|
146
|
+
```ts
|
|
147
|
+
const mdx = p.x - mx, mdy = p.y - my;
|
|
148
|
+
const md = Math.sqrt(mdx*mdx + mdy*mdy);
|
|
149
|
+
if (md < REPEL_R && md > 0) {
|
|
150
|
+
const f = ((REPEL_R - md) / REPEL_R) * REPEL_F;
|
|
151
|
+
p.vx += (mdx / md) * f;
|
|
152
|
+
p.vy += (mdy / md) * f;
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Attract
|
|
157
|
+
```ts
|
|
158
|
+
// Same but subtract instead of add
|
|
159
|
+
p.vx -= (mdx / md) * f;
|
|
160
|
+
p.vy -= (mdy / md) * f;
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Orbit
|
|
164
|
+
```ts
|
|
165
|
+
// Apply tangential force (perpendicular to cursor vector)
|
|
166
|
+
p.vx += (-mdy / md) * f * 0.6;
|
|
167
|
+
p.vy += ( mdx / md) * f * 0.6;
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Spring-back (always apply for letter nodes)
|
|
171
|
+
```ts
|
|
172
|
+
p.vx += (p.tx - p.x) * SPRING;
|
|
173
|
+
p.vy += (p.ty - p.y) * SPRING;
|
|
174
|
+
p.vx *= FRICTION;
|
|
175
|
+
p.vy *= FRICTION;
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Step 5 — Accessibility & Performance
|
|
181
|
+
|
|
182
|
+
Always include:
|
|
183
|
+
```tsx
|
|
184
|
+
<canvas aria-hidden="true" /> // decorative, not content
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Reduced-motion support — draw a static frame then return; never return before drawing:
|
|
188
|
+
```ts
|
|
189
|
+
const reduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
190
|
+
// ... build pts, set up canvas, build mouse listeners ...
|
|
191
|
+
if (reduced) {
|
|
192
|
+
// Snap shape nodes to their targets and draw one static frame
|
|
193
|
+
pts.forEach(p => { if (p.isShape) { p.x = p.tx; p.y = p.ty; } });
|
|
194
|
+
tick(); // draw once
|
|
195
|
+
cancelAnimationFrame(raf); // immediately cancel the RAF tick queued inside
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
// Full animation loop starts here
|
|
199
|
+
```
|
|
200
|
+
⚠️ Never `return` before drawing for canvas components — that leaves the canvas blank, which is worse than any animation.
|
|
201
|
+
|
|
202
|
+
Performance rules:
|
|
203
|
+
- Read `getComputedStyle` **inside** the RAF loop (theme changes auto-apply, ~0.02ms/call)
|
|
204
|
+
- Use `devicePixelRatio` for crisp retina rendering (see canvas-utils)
|
|
205
|
+
- Clean up with `cancelAnimationFrame` and remove event listeners in the return callback
|
|
206
|
+
- Cap velocity: `if (spd > MAX_SPEED) { vx *= MAX_SPEED/spd; vy *= MAX_SPEED/spd; }`
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## Step 6 — Deliver
|
|
211
|
+
|
|
212
|
+
Output the complete component file, then show the import + usage:
|
|
213
|
+
|
|
214
|
+
```tsx
|
|
215
|
+
// Usage in any React/Next.js component:
|
|
216
|
+
import { MyArt } from "@/components/art/MyArt";
|
|
217
|
+
|
|
218
|
+
// In JSX:
|
|
219
|
+
<MyArt size={360} />
|
|
220
|
+
|
|
221
|
+
// For Next.js App Router pages — component already has "use client"
|
|
222
|
+
// For hero sections — wrap in a hidden-on-mobile div:
|
|
223
|
+
<div className="hidden lg:flex items-center justify-center">
|
|
224
|
+
<MyArt size={360} />
|
|
225
|
+
</div>
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## Quality Checklist
|
|
231
|
+
|
|
232
|
+
Before delivering, verify:
|
|
233
|
+
- [ ] `"use client"` at the top
|
|
234
|
+
- [ ] Canvas size set with `devicePixelRatio` scaling (from canvas-utils)
|
|
235
|
+
- [ ] `cancelAnimationFrame` + `removeEventListener` in useEffect cleanup
|
|
236
|
+
- [ ] `aria-hidden="true"` on the canvas/container
|
|
237
|
+
- [ ] `themeRgb()` reads CSS vars inside RAF loop (not outside)
|
|
238
|
+
- [ ] Mouse leaves handled (`mouseleave` resets to off-screen coords)
|
|
239
|
+
- [ ] Velocity capped to `MAX_SPEED`
|
|
240
|
+
- [ ] For letter nodes: spring force applied + damped FRICTION (not AMBIENT_FRICTION)
|
|
241
|
+
- [ ] TypeScript types — no `any`, interfaces for Pt/Dot/Spark
|
|
242
|
+
- [ ] Prefers-reduced-motion check
|
|
243
|
+
- [ ] Component is self-contained (no imports beyond React)
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* canvas-utils.ts — Shared canvas helpers for all particle-art components.
|
|
3
|
+
*
|
|
4
|
+
* Copy the functions you need directly into your component file.
|
|
5
|
+
* These are NOT meant to be imported as a module — they are reference implementations.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// ─── DPR-Correct Canvas Setup ────────────────────────────────────────────────
|
|
9
|
+
//
|
|
10
|
+
// Must be called in useEffect, AFTER the canvas ref is attached.
|
|
11
|
+
// Returns the 2D context; the canvas is scaled so all drawing coordinates
|
|
12
|
+
// are in CSS pixels (no need to multiply by dpr elsewhere).
|
|
13
|
+
//
|
|
14
|
+
// Usage:
|
|
15
|
+
// const ctx = setupCanvas(ref.current!, size);
|
|
16
|
+
//
|
|
17
|
+
export function setupCanvas(
|
|
18
|
+
el: HTMLCanvasElement,
|
|
19
|
+
size: number,
|
|
20
|
+
): CanvasRenderingContext2D {
|
|
21
|
+
const ctx = el.getContext("2d")!;
|
|
22
|
+
const dpr = window.devicePixelRatio || 1;
|
|
23
|
+
el.width = size * dpr;
|
|
24
|
+
el.height = size * dpr;
|
|
25
|
+
ctx.scale(dpr, dpr);
|
|
26
|
+
return ctx;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ─── Theme-Aware Colour Reading ───────────────────────────────────────────────
|
|
30
|
+
//
|
|
31
|
+
// Reads a CSS custom property (e.g. "--accent-strong") and returns [R, G, B].
|
|
32
|
+
// The value must be space-separated channels: "79 209 197".
|
|
33
|
+
// Call this INSIDE the RAF loop so theme changes (light/dark toggle) auto-apply.
|
|
34
|
+
//
|
|
35
|
+
// Standard variable names for projects using CSS-variable design tokens:
|
|
36
|
+
// "--accent-strong" → primary colour (brightest nodes, tittle glow)
|
|
37
|
+
// "--accent-soft" → secondary colour (lines, ambient nodes)
|
|
38
|
+
// "--fg" → foreground / text colour
|
|
39
|
+
// "--bg" → page background
|
|
40
|
+
//
|
|
41
|
+
export function themeRgb(varName: string): [number, number, number] {
|
|
42
|
+
const raw = getComputedStyle(document.documentElement)
|
|
43
|
+
.getPropertyValue(varName)
|
|
44
|
+
.trim()
|
|
45
|
+
.split(/\s+/)
|
|
46
|
+
.map(Number);
|
|
47
|
+
return [raw[0] ?? 100, raw[1] ?? 100, raw[2] ?? 100];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Convenience: build a CSS rgba() string from a themeRgb result + alpha
|
|
51
|
+
export function rgba(
|
|
52
|
+
[r, g, b]: [number, number, number],
|
|
53
|
+
alpha: number,
|
|
54
|
+
): string {
|
|
55
|
+
return `rgba(${r},${g},${b},${alpha.toFixed(3)})`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ─── Hex Colour Fallback ──────────────────────────────────────────────────────
|
|
59
|
+
//
|
|
60
|
+
// Use when the project does NOT have CSS custom properties.
|
|
61
|
+
// Pass hex strings in the component's props and convert once.
|
|
62
|
+
//
|
|
63
|
+
export function hexToRgb(hex: string): [number, number, number] {
|
|
64
|
+
const n = parseInt(hex.replace("#", ""), 16);
|
|
65
|
+
return [(n >> 16) & 255, (n >> 8) & 255, n & 255];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ─── Mouse Tracking ───────────────────────────────────────────────────────────
|
|
69
|
+
//
|
|
70
|
+
// Returns a ref-compatible object and attaches listeners to the canvas.
|
|
71
|
+
// The position is in CSS pixels (matches drawing coordinates).
|
|
72
|
+
// Returns a cleanup function — call it in the useEffect return.
|
|
73
|
+
//
|
|
74
|
+
// Usage:
|
|
75
|
+
// const mouse = { x: -9999, y: -9999 };
|
|
76
|
+
// const cleanupMouse = attachMouseTracking(el, mouse);
|
|
77
|
+
// return () => { cancelAnimationFrame(raf); cleanupMouse(); };
|
|
78
|
+
//
|
|
79
|
+
export function attachMouseTracking(
|
|
80
|
+
el: HTMLCanvasElement,
|
|
81
|
+
mouse: { x: number; y: number },
|
|
82
|
+
): () => void {
|
|
83
|
+
const onMove = (e: MouseEvent) => {
|
|
84
|
+
const r = el.getBoundingClientRect();
|
|
85
|
+
mouse.x = e.clientX - r.left;
|
|
86
|
+
mouse.y = e.clientY - r.top;
|
|
87
|
+
};
|
|
88
|
+
const onLeave = () => {
|
|
89
|
+
mouse.x = -9999;
|
|
90
|
+
mouse.y = -9999;
|
|
91
|
+
};
|
|
92
|
+
el.addEventListener("mousemove", onMove);
|
|
93
|
+
el.addEventListener("mouseleave", onLeave);
|
|
94
|
+
return () => {
|
|
95
|
+
el.removeEventListener("mousemove", onMove);
|
|
96
|
+
el.removeEventListener("mouseleave", onLeave);
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ─── Velocity Cap ─────────────────────────────────────────────────────────────
|
|
101
|
+
//
|
|
102
|
+
// Prevents particles from flying off-screen after strong mouse interactions.
|
|
103
|
+
// Apply AFTER adding forces, BEFORE updating position.
|
|
104
|
+
//
|
|
105
|
+
export function capVelocity(
|
|
106
|
+
p: { vx: number; vy: number },
|
|
107
|
+
maxSpeed: number,
|
|
108
|
+
): void {
|
|
109
|
+
const spd = Math.sqrt(p.vx * p.vx + p.vy * p.vy);
|
|
110
|
+
if (spd > maxSpeed) {
|
|
111
|
+
p.vx *= maxSpeed / spd;
|
|
112
|
+
p.vy *= maxSpeed / spd;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ─── Reduced-Motion Guard ─────────────────────────────────────────────────────
|
|
117
|
+
//
|
|
118
|
+
// ⚠️ DO NOT use this as an early `return` before drawing — that leaves the
|
|
119
|
+
// canvas completely blank, which is WORSE than showing animation.
|
|
120
|
+
//
|
|
121
|
+
// Correct pattern for canvas components:
|
|
122
|
+
// draw one static frame (nodes at target positions), then skip the RAF loop.
|
|
123
|
+
//
|
|
124
|
+
// const reduced = prefersReducedMotion();
|
|
125
|
+
// // ... build pts, set up canvas ...
|
|
126
|
+
// if (reduced) {
|
|
127
|
+
// pts.forEach(p => { p.x = p.tx; p.y = p.ty; }); // snap to targets
|
|
128
|
+
// drawOneFrame(ctx, pts, size); // render static
|
|
129
|
+
// return; // no RAF
|
|
130
|
+
// }
|
|
131
|
+
// tick(); // full animation
|
|
132
|
+
//
|
|
133
|
+
// For HTML/stipple components the early return IS fine because HTML content
|
|
134
|
+
// is already visible in the DOM without JavaScript.
|
|
135
|
+
//
|
|
136
|
+
export function prefersReducedMotion(): boolean {
|
|
137
|
+
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ─── Connection Lines ─────────────────────────────────────────────────────────
|
|
141
|
+
//
|
|
142
|
+
// Draw faded lines between all particle pairs within linkDist.
|
|
143
|
+
// Call this BEFORE drawing nodes so lines appear underneath.
|
|
144
|
+
//
|
|
145
|
+
// pts must have { x, y } properties.
|
|
146
|
+
//
|
|
147
|
+
export function drawConnections(
|
|
148
|
+
ctx: CanvasRenderingContext2D,
|
|
149
|
+
pts: { x: number; y: number }[],
|
|
150
|
+
linkDist: number,
|
|
151
|
+
lineColor: [number, number, number],
|
|
152
|
+
maxAlpha = 0.32,
|
|
153
|
+
): void {
|
|
154
|
+
const [r, g, b] = lineColor;
|
|
155
|
+
for (let i = 0; i < pts.length; i++) {
|
|
156
|
+
for (let j = i + 1; j < pts.length; j++) {
|
|
157
|
+
const dx = pts[i].x - pts[j].x;
|
|
158
|
+
const dy = pts[i].y - pts[j].y;
|
|
159
|
+
const d = Math.sqrt(dx * dx + dy * dy);
|
|
160
|
+
if (d < linkDist) {
|
|
161
|
+
const a = (1 - d / linkDist) * maxAlpha;
|
|
162
|
+
ctx.beginPath();
|
|
163
|
+
ctx.moveTo(pts[i].x, pts[i].y);
|
|
164
|
+
ctx.lineTo(pts[j].x, pts[j].y);
|
|
165
|
+
ctx.strokeStyle = `rgba(${r},${g},${b},${a.toFixed(3)})`;
|
|
166
|
+
ctx.lineWidth = 0.65;
|
|
167
|
+
ctx.stroke();
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|