@caddis/cli 0.3.0 → 0.4.1
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/bundles/antigravity-plugin/agents/knowledge-transfer.md +17 -32
- package/bundles/antigravity-plugin/caddis_statusline.py +25 -3
- package/bundles/antigravity-plugin/guard_agy.py +41 -9
- package/bundles/antigravity-plugin/plugin.json +1 -1
- package/bundles/antigravity-plugin/scripts/caddis_inventory.py +73 -3
- package/bundles/antigravity-plugin/scripts/caddis_statusline.py +25 -3
- package/bundles/antigravity-plugin/scripts/check_doc_coverage.py +1 -1
- package/bundles/antigravity-plugin/scripts/claudster_config.py +0 -5
- package/bundles/antigravity-plugin/scripts/hook_log.py +1 -1
- package/bundles/antigravity-plugin/scripts/new-machine.ps1 +170 -0
- package/bundles/antigravity-plugin/scripts/oss_ask.py +1 -1
- package/bundles/antigravity-plugin/scripts/oss_model.py +10 -2
- package/bundles/antigravity-plugin/scripts/oss_review.py +1 -1
- package/bundles/antigravity-plugin/scripts/setup_project_ai.py +42 -10
- package/bundles/antigravity-plugin/session_end_agy.py +31 -6
- package/bundles/antigravity-plugin/skills/_registry.md +1 -0
- package/bundles/antigravity-plugin/skills/advisory-hub/SKILL.md +44 -0
- package/bundles/antigravity-plugin/skills/code-review/SKILL.md +11 -0
- package/bundles/antigravity-plugin/skills/feature-plan/SKILL.md +21 -0
- package/bundles/antigravity-plugin/skills/gate-review/SKILL.md +11 -0
- package/bundles/antigravity-plugin/skills/implement/SKILL.md +36 -0
- package/bundles/antigravity-plugin/skills/safe-cleanup/SKILL.md +186 -0
- package/bundles/antigravity-plugin/skills/security-review/SKILL.md +9 -0
- package/bundles/antigravity-plugin/skills/validate-phase/SKILL.md +31 -0
- package/bundles/antigravity-plugin/warm_start_agy.py +96 -9
- package/bundles/antigravity-plugin-extras/plugin.json +1 -1
- package/bundles/antigravity-plugin-extras/skills/_registry.md +1 -0
- package/bundles/antigravity-plugin-extras/skills/safe-cleanup/SKILL.md +186 -0
- package/bundles/codex/.codex/config.toml.example +36 -0
- package/bundles/codex/.codex/skills/coding/api-design/SKILL.md +249 -0
- package/bundles/codex/.codex/skills/coding/backend-development/SKILL.md +305 -0
- package/bundles/codex/.codex/skills/coding/code-review/SKILL.md +140 -0
- package/bundles/codex/.codex/skills/coding/codebase-audit/SKILL.md +204 -0
- package/bundles/codex/.codex/skills/coding/dashboard-perf-sweep/SKILL.md +132 -0
- package/bundles/codex/.codex/skills/coding/dashboard-perf-sweep/scripts/perf_sweep.py +249 -0
- package/bundles/codex/.codex/skills/coding/error-handling/SKILL.md +260 -0
- package/bundles/codex/.codex/skills/coding/fastapi-dev/SKILL.md +300 -0
- package/bundles/codex/.codex/skills/coding/javascript-typescript/SKILL.md +142 -0
- package/bundles/codex/.codex/skills/coding/python/SKILL.md +388 -0
- package/bundles/codex/.codex/skills/coding/refactoring/SKILL.md +486 -0
- package/bundles/codex/.codex/skills/coding/security-review/SKILL.md +205 -0
- package/bundles/codex/.codex/skills/coding/sql/SKILL.md +321 -0
- package/bundles/codex/.codex/skills/data/database-design/SKILL.md +177 -0
- package/bundles/codex/.codex/skills/data/db-diagram/SKILL.md +148 -0
- package/bundles/codex/.codex/skills/data/db-diagram/scripts/sql_to_graph.py +1212 -0
- package/bundles/codex/.codex/skills/devops/ci-cd-pipeline/SKILL.md +315 -0
- package/bundles/codex/.codex/skills/devops/gh-cli/SKILL.md +195 -0
- package/bundles/codex/.codex/skills/devops/git-commit/SKILL.md +285 -0
- package/bundles/codex/.codex/skills/devops/safe-cleanup/SKILL.md +186 -0
- package/bundles/codex/.codex/skills/devops/using-git-worktrees/SKILL.md +217 -0
- package/bundles/codex/.codex/skills/devops/windows-deployment/SKILL.md +880 -0
- package/bundles/codex/.codex/skills/docs/code-documentation/SKILL.md +271 -0
- package/bundles/codex/.codex/skills/docs/explainer-doc/SKILL.md +97 -0
- package/bundles/codex/.codex/skills/docs/technical-writing/SKILL.md +286 -0
- package/bundles/codex/.codex/skills/docs/writing-plans/SKILL.md +387 -0
- package/bundles/codex/.codex/skills/frontend/css-architecture/SKILL.md +305 -0
- package/bundles/codex/.codex/skills/frontend/css-architecture/references/RESPONSIVE-DESIGN.md +604 -0
- package/bundles/codex/.codex/skills/frontend/frontend-design/SKILL.md +193 -0
- package/bundles/codex/.codex/skills/frontend/frontend-design/references/my-tech-stack.md +127 -0
- package/bundles/codex/.codex/skills/frontend/mockup/SKILL.md +242 -0
- package/bundles/codex/.codex/skills/frontend/react-best-practices/SKILL.md +269 -0
- package/bundles/codex/.codex/skills/frontend/react-dev/README.md +404 -0
- package/bundles/codex/.codex/skills/frontend/react-dev/SKILL.md +459 -0
- package/bundles/codex/.codex/skills/frontend/react-dev/examples/generic-components.md +579 -0
- package/bundles/codex/.codex/skills/frontend/react-dev/examples/server-components.md +579 -0
- package/bundles/codex/.codex/skills/frontend/react-dev/references/event-handlers.md +574 -0
- package/bundles/codex/.codex/skills/frontend/react-dev/references/hooks.md +456 -0
- package/bundles/codex/.codex/skills/frontend/react-dev/references/react-19-patterns.md +638 -0
- package/bundles/codex/.codex/skills/frontend/react-dev/references/react-router.md +1002 -0
- package/bundles/codex/.codex/skills/frontend/react-dev/references/tanstack-router.md +587 -0
- package/bundles/codex/.codex/skills/frontend/ui-review/SKILL.md +176 -0
- package/bundles/codex/.codex/skills/frontend/ui-review/references/framework-fixes.md +471 -0
- package/bundles/codex/.codex/skills/frontend/ui-review/references/visual-checklist.md +236 -0
- package/bundles/codex/.codex/skills/frontend/warm-editorial-ui/DESIGN_TOKENS.md +487 -0
- package/bundles/codex/.codex/skills/frontend/warm-editorial-ui/IMPLEMENTATION_GUIDE.md +177 -0
- package/bundles/codex/.codex/skills/frontend/warm-editorial-ui/SKILL.md +732 -0
- package/bundles/codex/.codex/skills/media/draw-io/SKILL.md +162 -0
- package/bundles/codex/.codex/skills/media/draw-io/references/aws-icons.md +677 -0
- package/bundles/codex/.codex/skills/media/draw-io/references/layout-guidelines.md +142 -0
- package/bundles/codex/.codex/skills/media/draw-io/references/troubleshooting.md +118 -0
- package/bundles/codex/.codex/skills/media/draw-io/references/workflows.md +103 -0
- package/bundles/codex/.codex/skills/media/draw-io/scripts/convert-drawio-to-png.sh +25 -0
- package/bundles/codex/.codex/skills/media/draw-io/scripts/find_aws_icon.py +79 -0
- package/bundles/codex/.codex/skills/media/mermaid-diagrams/SKILL.md +292 -0
- package/bundles/codex/.codex/skills/media/mermaid-diagrams/references/ENHANCEMENTS.md +264 -0
- package/bundles/codex/.codex/skills/media/mermaid-diagrams/references/MERMAID-SUMMARY.md +137 -0
- package/bundles/codex/.codex/skills/media/mermaid-diagrams/references/advanced-features.md +556 -0
- package/bundles/codex/.codex/skills/media/mermaid-diagrams/references/architecture-diagrams.md +192 -0
- package/bundles/codex/.codex/skills/media/mermaid-diagrams/references/c4-diagrams.md +410 -0
- package/bundles/codex/.codex/skills/media/mermaid-diagrams/references/class-diagrams.md +361 -0
- package/bundles/codex/.codex/skills/media/mermaid-diagrams/references/erd-diagrams.md +510 -0
- package/bundles/codex/.codex/skills/media/mermaid-diagrams/references/flowcharts.md +450 -0
- package/bundles/codex/.codex/skills/media/mermaid-diagrams/references/sequence-diagrams.md +394 -0
- package/bundles/codex/.codex/skills/media/mermaid-diagrams/references/troubleshooting.md +335 -0
- package/bundles/codex/.codex/skills/media/mermaid-diagrams/references/workflows.md +418 -0
- package/bundles/codex/.codex/skills/media/particle-art/SKILL.md +243 -0
- package/bundles/codex/.codex/skills/media/particle-art/references/canvas-utils.ts +171 -0
- package/bundles/codex/.codex/skills/media/particle-art/references/dot-field.template.tsx +203 -0
- package/bundles/codex/.codex/skills/media/particle-art/references/flow-field.template.tsx +263 -0
- package/bundles/codex/.codex/skills/media/particle-art/references/node-shape.template.tsx +261 -0
- package/bundles/codex/.codex/skills/media/particle-art/references/shape-sampler.ts +281 -0
- package/bundles/codex/.codex/skills/media/particle-art/references/stipple-morph.template.tsx +167 -0
- package/bundles/codex/.codex/skills/media/particle-art/references/stipple.template.tsx +175 -0
- package/bundles/codex/.codex/skills/media/particle-art/references/trail-ghost.template.tsx +266 -0
- package/bundles/codex/.codex/skills/media/particle-art/references/usage-examples.md +320 -0
- package/bundles/codex/.codex/skills/testing/playwright/API_REFERENCE.md +653 -0
- package/bundles/codex/.codex/skills/testing/playwright/SKILL.md +454 -0
- package/bundles/codex/.codex/skills/testing/playwright/lib/helpers.js +441 -0
- package/bundles/codex/.codex/skills/testing/playwright/package.json +26 -0
- package/bundles/codex/.codex/skills/testing/playwright/run.js +228 -0
- package/bundles/codex/.codex/skills/testing/tdd-workflow/SKILL.md +188 -0
- package/bundles/codex/.codex/skills/testing/test-strategy/SKILL.md +155 -0
- package/bundles/codex/.codex/skills/testing/webapp-testing/LICENSE.txt +202 -0
- package/bundles/codex/.codex/skills/testing/webapp-testing/SKILL.md +97 -0
- package/bundles/codex/.codex/skills/testing/webapp-testing/examples/console_logging.py +35 -0
- package/bundles/codex/.codex/skills/testing/webapp-testing/examples/element_discovery.py +40 -0
- package/bundles/codex/.codex/skills/testing/webapp-testing/examples/static_html_automation.py +33 -0
- package/bundles/codex/.codex/skills/testing/webapp-testing/scripts/with_server.py +106 -0
- package/bundles/codex/.codex/skills/workflow/advisory-hub/SKILL.md +227 -0
- package/bundles/codex/.codex/skills/workflow/advisory-hub/reference/context-template.md +214 -0
- package/bundles/codex/.codex/skills/workflow/advisory-hub/reference/hub-handbook.md +233 -0
- package/bundles/codex/.codex/skills/workflow/advisory-hub/reference/report-contract.md +94 -0
- package/bundles/codex/.codex/skills/workflow/advisory-hub/reference/succession.md +118 -0
- package/bundles/codex/.codex/skills/workflow/best-practices/SKILL.md +500 -0
- package/bundles/codex/.codex/skills/workflow/best-practices/agents/best-practices-referencer.md +263 -0
- package/bundles/codex/.codex/skills/workflow/best-practices/agents/codebase-context-builder.md +326 -0
- package/bundles/codex/.codex/skills/workflow/best-practices/agents/task-intent-analyzer.md +245 -0
- package/bundles/codex/.codex/skills/workflow/best-practices/references/anti-patterns.md +571 -0
- package/bundles/codex/.codex/skills/workflow/best-practices/references/before-after-examples.md +1114 -0
- package/bundles/codex/.codex/skills/workflow/best-practices/references/best-practices-guide.md +513 -0
- package/bundles/codex/.codex/skills/workflow/best-practices/references/common-workflows.md +692 -0
- package/bundles/codex/.codex/skills/workflow/best-practices/references/prompt-patterns.md +547 -0
- package/bundles/codex/.codex/skills/workflow/brainstorming/SKILL.md +57 -0
- package/bundles/codex/.codex/skills/workflow/context-curator/SKILL.md +157 -0
- package/bundles/codex/.codex/skills/workflow/golden-plan/SKILL.md +586 -0
- package/bundles/codex/.codex/skills/workflow/preflight/SKILL.md +440 -0
- package/bundles/codex/.codex/skills/workflow/skill-creator/LICENSE.txt +202 -0
- package/bundles/codex/.codex/skills/workflow/skill-creator/SKILL.md +491 -0
- package/bundles/codex/.codex/skills/workflow/skill-creator/agents/analyzer.md +274 -0
- package/bundles/codex/.codex/skills/workflow/skill-creator/agents/comparator.md +202 -0
- package/bundles/codex/.codex/skills/workflow/skill-creator/agents/grader.md +223 -0
- package/bundles/codex/.codex/skills/workflow/skill-creator/assets/eval_review.html +146 -0
- package/bundles/codex/.codex/skills/workflow/skill-creator/eval-viewer/generate_review.py +471 -0
- package/bundles/codex/.codex/skills/workflow/skill-creator/eval-viewer/viewer.html +1325 -0
- package/bundles/codex/.codex/skills/workflow/skill-creator/references/schemas.md +430 -0
- package/bundles/codex/.codex/skills/workflow/skill-creator/scripts/__init__.py +0 -0
- package/bundles/codex/.codex/skills/workflow/skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/bundles/codex/.codex/skills/workflow/skill-creator/scripts/generate_report.py +326 -0
- package/bundles/codex/.codex/skills/workflow/skill-creator/scripts/improve_description.py +247 -0
- package/bundles/codex/.codex/skills/workflow/skill-creator/scripts/package_skill.py +136 -0
- package/bundles/codex/.codex/skills/workflow/skill-creator/scripts/quick_validate.py +103 -0
- package/bundles/codex/.codex/skills/workflow/skill-creator/scripts/run_eval.py +310 -0
- package/bundles/codex/.codex/skills/workflow/skill-creator/scripts/run_loop.py +328 -0
- package/bundles/codex/.codex/skills/workflow/skill-creator/scripts/utils.py +47 -0
- package/bundles/codex/AGENTS.md +146 -0
- package/bundles/manifest.json +4 -3
- package/dist/claude-deepseek.js +2 -2
- package/dist/claude-glm.js +2 -2
- package/dist/claude-oss.js +2 -2
- package/dist/cli.js +187 -56
- package/package.json +1 -1
- package/bundles/antigravity-plugin/scripts/dream_capture.py +0 -249
- package/bundles/antigravity-plugin/scripts/dream_memory.py +0 -438
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/ArsenalSC-OFL.txt +0 -93
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/ArsenalSC-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/BigShoulders-Bold.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/BigShoulders-OFL.txt +0 -93
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/BigShoulders-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Boldonse-OFL.txt +0 -93
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Boldonse-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/BricolageGrotesque-Bold.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/BricolageGrotesque-OFL.txt +0 -93
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/BricolageGrotesque-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/CrimsonPro-Bold.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/CrimsonPro-Italic.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/CrimsonPro-OFL.txt +0 -93
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/CrimsonPro-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/DMMono-OFL.txt +0 -93
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/DMMono-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/EricaOne-OFL.txt +0 -94
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/EricaOne-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/GeistMono-Bold.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/GeistMono-OFL.txt +0 -93
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/GeistMono-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Gloock-OFL.txt +0 -93
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Gloock-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/IBMPlexMono-Bold.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/IBMPlexMono-OFL.txt +0 -93
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/IBMPlexMono-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/IBMPlexSerif-Bold.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/IBMPlexSerif-BoldItalic.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/IBMPlexSerif-Italic.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/IBMPlexSerif-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/InstrumentSans-Bold.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/InstrumentSans-BoldItalic.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/InstrumentSans-Italic.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/InstrumentSans-OFL.txt +0 -93
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/InstrumentSans-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/InstrumentSerif-Italic.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/InstrumentSerif-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Italiana-OFL.txt +0 -93
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Italiana-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/JetBrainsMono-Bold.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/JetBrainsMono-OFL.txt +0 -93
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/JetBrainsMono-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Jura-Light.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Jura-Medium.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Jura-OFL.txt +0 -93
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/LibreBaskerville-OFL.txt +0 -93
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/LibreBaskerville-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Lora-Bold.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Lora-BoldItalic.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Lora-Italic.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Lora-OFL.txt +0 -93
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Lora-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/NationalPark-Bold.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/NationalPark-OFL.txt +0 -93
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/NationalPark-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/NothingYouCouldDo-OFL.txt +0 -93
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/NothingYouCouldDo-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Outfit-Bold.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Outfit-OFL.txt +0 -93
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Outfit-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/PixelifySans-Medium.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/PixelifySans-OFL.txt +0 -93
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/PoiretOne-OFL.txt +0 -93
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/PoiretOne-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/RedHatMono-Bold.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/RedHatMono-OFL.txt +0 -93
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/RedHatMono-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Silkscreen-OFL.txt +0 -93
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Silkscreen-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/SmoochSans-Medium.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/SmoochSans-OFL.txt +0 -93
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Tektur-Medium.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Tektur-OFL.txt +0 -93
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Tektur-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/WorkSans-Bold.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/WorkSans-BoldItalic.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/WorkSans-Italic.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/WorkSans-OFL.txt +0 -93
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/WorkSans-Regular.ttf +0 -0
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/YoungSerif-OFL.txt +0 -93
- package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/YoungSerif-Regular.ttf +0 -0
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
/**
|
|
3
|
+
* dot-field.template.tsx — Gaussian canvas dot cloud with sparks template.
|
|
4
|
+
*
|
|
5
|
+
* USAGE:
|
|
6
|
+
* 1. Copy and rename (e.g. HeroDotField.tsx, CosmicField.tsx)
|
|
7
|
+
* 2. Adjust DOT_COUNT, SIGMA (spread), spark parameters to taste
|
|
8
|
+
* 3. Mouse moves dots away; they spring back slowly to their base positions
|
|
9
|
+
*
|
|
10
|
+
* THEMING: --accent-strong (inner/bright), --accent-soft (outer/dim) CSS vars
|
|
11
|
+
* DEPENDENCIES: none
|
|
12
|
+
*/
|
|
13
|
+
import { useEffect, useRef } from "react";
|
|
14
|
+
|
|
15
|
+
// ── Constants ──────────────────────────────────────────────────────────────────
|
|
16
|
+
const DOT_COUNT = 120; // total dot count (lower for fine dot feel)
|
|
17
|
+
const SIGMA = 0.28; // Gaussian standard deviation [0–0.5]; lower = tighter cluster
|
|
18
|
+
const SPRING_BACK = 0.08; // how fast dots return to base position after repulsion
|
|
19
|
+
const REPEL_MULT = 0.2; // repulsion radius = size * REPEL_MULT
|
|
20
|
+
const SPARK_PROB = 0.008; // probability per frame to spawn a spark
|
|
21
|
+
const MAX_SPARKS = 8;
|
|
22
|
+
|
|
23
|
+
// ── Helpers ────────────────────────────────────────────────────────────────────
|
|
24
|
+
function themeRgb(v: string): [number, number, number] {
|
|
25
|
+
const ch = getComputedStyle(document.documentElement)
|
|
26
|
+
.getPropertyValue(v).trim().split(/\s+/).map(Number);
|
|
27
|
+
return [ch[0] ?? 100, ch[1] ?? 100, ch[2] ?? 100];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Box-Muller: Gaussian sample with mean=0, std=sigma
|
|
31
|
+
function gaussian(sigma: number): number {
|
|
32
|
+
let u = 0, v = 0;
|
|
33
|
+
while (u === 0) u = Math.random();
|
|
34
|
+
while (v === 0) v = Math.random();
|
|
35
|
+
return Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v) * sigma;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface Dot {
|
|
39
|
+
bx: number; by: number; // base position (centre of gravity)
|
|
40
|
+
x: number; y: number; // current position
|
|
41
|
+
r: number; // radius
|
|
42
|
+
distRatio: number; // normalised distance from centre [0, 1]
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface Spark {
|
|
46
|
+
x: number; y: number;
|
|
47
|
+
vx: number; vy: number;
|
|
48
|
+
life: number; // [1, 0] — fades and shrinks as it dies
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function buildDots(size: number): Dot[] {
|
|
52
|
+
const cx = size / 2, cy = size / 2;
|
|
53
|
+
const dots: Dot[] = [];
|
|
54
|
+
for (let i = 0; i < DOT_COUNT; i++) {
|
|
55
|
+
const bx = cx + gaussian(SIGMA) * size;
|
|
56
|
+
const by = cy + gaussian(SIGMA) * size;
|
|
57
|
+
const dx = (bx - cx) / (size * 0.5);
|
|
58
|
+
const dy = (by - cy) / (size * 0.5);
|
|
59
|
+
dots.push({
|
|
60
|
+
bx, by, x: bx, y: by,
|
|
61
|
+
r: Math.random() * 1.8 + 0.8,
|
|
62
|
+
distRatio: Math.sqrt(dx * dx + dy * dy),
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
return dots;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ── Component ──────────────────────────────────────────────────────────────────
|
|
69
|
+
export function DotFieldArt({ size = 280 }: { size?: number }) {
|
|
70
|
+
const ref = useRef<HTMLCanvasElement>(null);
|
|
71
|
+
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
const el = ref.current;
|
|
74
|
+
if (!el) return;
|
|
75
|
+
|
|
76
|
+
const ctx = el.getContext("2d")!;
|
|
77
|
+
const dpr = window.devicePixelRatio || 1;
|
|
78
|
+
el.width = size * dpr;
|
|
79
|
+
el.height = size * dpr;
|
|
80
|
+
ctx.scale(dpr, dpr);
|
|
81
|
+
|
|
82
|
+
const dots = buildDots(size);
|
|
83
|
+
const sparks: Spark[] = [];
|
|
84
|
+
const REPEL = size * REPEL_MULT;
|
|
85
|
+
|
|
86
|
+
// Mouse tracking
|
|
87
|
+
const mouse = { x: -9999, y: -9999 };
|
|
88
|
+
const onMove = (e: MouseEvent) => {
|
|
89
|
+
const r = el.getBoundingClientRect();
|
|
90
|
+
mouse.x = e.clientX - r.left;
|
|
91
|
+
mouse.y = e.clientY - r.top;
|
|
92
|
+
};
|
|
93
|
+
const onLeave = () => { mouse.x = -9999; mouse.y = -9999; };
|
|
94
|
+
el.addEventListener("mousemove", onMove);
|
|
95
|
+
el.addEventListener("mouseleave", onLeave);
|
|
96
|
+
|
|
97
|
+
let raf: number;
|
|
98
|
+
|
|
99
|
+
const tick = () => {
|
|
100
|
+
ctx.clearRect(0, 0, size, size);
|
|
101
|
+
const [sr, sg, sb] = themeRgb("--accent-strong");
|
|
102
|
+
const [lr, lg, lb] = themeRgb("--accent-soft");
|
|
103
|
+
const { x: mx, y: my } = mouse;
|
|
104
|
+
|
|
105
|
+
// ── Update dots ────────────────────────────────────────────────────
|
|
106
|
+
dots.forEach(d => {
|
|
107
|
+
// Repulsion
|
|
108
|
+
const dx = d.x - mx, dy = d.y - my;
|
|
109
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
110
|
+
if (dist < REPEL && dist > 0) {
|
|
111
|
+
const f = ((REPEL - dist) / REPEL) * 2.5;
|
|
112
|
+
d.x += (dx / dist) * f;
|
|
113
|
+
d.y += (dy / dist) * f;
|
|
114
|
+
}
|
|
115
|
+
// Spring back to base
|
|
116
|
+
d.x += (d.bx - d.x) * SPRING_BACK;
|
|
117
|
+
d.y += (d.by - d.y) * SPRING_BACK;
|
|
118
|
+
|
|
119
|
+
// Colour blend: inner → --accent-strong, outer → --accent-soft
|
|
120
|
+
const t = Math.min(1, d.distRatio * 1.6);
|
|
121
|
+
const r2 = sr + (lr - sr) * t;
|
|
122
|
+
const g2 = sg + (lg - sg) * t;
|
|
123
|
+
const b2 = sb + (lb - sb) * t;
|
|
124
|
+
const alpha = Math.max(0.12, 0.85 - d.distRatio * 0.7);
|
|
125
|
+
|
|
126
|
+
ctx.beginPath();
|
|
127
|
+
ctx.arc(d.x, d.y, d.r, 0, Math.PI * 2);
|
|
128
|
+
ctx.fillStyle = `rgba(${Math.round(r2)},${Math.round(g2)},${Math.round(b2)},${alpha.toFixed(3)})`;
|
|
129
|
+
ctx.fill();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// ── Sparks ────────────────────────────────────────────────────────
|
|
133
|
+
if (Math.random() < SPARK_PROB && sparks.length < MAX_SPARKS) {
|
|
134
|
+
const angle = Math.random() * Math.PI * 2;
|
|
135
|
+
const spawnR = size * 0.08 + Math.random() * size * 0.15;
|
|
136
|
+
sparks.push({
|
|
137
|
+
x: size / 2 + Math.cos(angle) * spawnR,
|
|
138
|
+
y: size / 2 + Math.sin(angle) * spawnR,
|
|
139
|
+
vx: (Math.random() - 0.5) * 2.2,
|
|
140
|
+
vy: (Math.random() - 0.5) * 2.2,
|
|
141
|
+
life: 1,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
for (let i = sparks.length - 1; i >= 0; i--) {
|
|
146
|
+
const s = sparks[i];
|
|
147
|
+
s.x += s.vx;
|
|
148
|
+
s.y += s.vy;
|
|
149
|
+
s.vx *= 0.96;
|
|
150
|
+
s.vy *= 0.96;
|
|
151
|
+
s.life -= 0.025;
|
|
152
|
+
if (s.life <= 0) { sparks.splice(i, 1); continue; }
|
|
153
|
+
|
|
154
|
+
ctx.beginPath();
|
|
155
|
+
ctx.arc(s.x, s.y, s.life * 1.8, 0, Math.PI * 2);
|
|
156
|
+
ctx.fillStyle = `rgba(${sr},${sg},${sb},${(s.life * 0.9).toFixed(3)})`;
|
|
157
|
+
ctx.fill();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
raf = requestAnimationFrame(tick);
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// Reduced-motion: draw one static frame of dots at base positions, skip the loop.
|
|
164
|
+
// Never return early before drawing — that leaves the canvas blank.
|
|
165
|
+
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
|
|
166
|
+
tick();
|
|
167
|
+
cancelAnimationFrame(raf);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
tick();
|
|
172
|
+
return () => {
|
|
173
|
+
cancelAnimationFrame(raf);
|
|
174
|
+
el.removeEventListener("mousemove", onMove);
|
|
175
|
+
el.removeEventListener("mouseleave", onLeave);
|
|
176
|
+
};
|
|
177
|
+
}, [size]);
|
|
178
|
+
|
|
179
|
+
return (
|
|
180
|
+
<div style={{ position: "relative", width: size, height: size }} aria-hidden="true">
|
|
181
|
+
<canvas
|
|
182
|
+
ref={ref}
|
|
183
|
+
style={{ width: size, height: size }}
|
|
184
|
+
/>
|
|
185
|
+
{/* Centre ✦ as HTML so it scales with the canvas and stays crisp */}
|
|
186
|
+
<span
|
|
187
|
+
style={{
|
|
188
|
+
position: "absolute",
|
|
189
|
+
top: "50%",
|
|
190
|
+
left: "50%",
|
|
191
|
+
transform: "translate(-50%, -50%)",
|
|
192
|
+
fontSize: "1.4rem",
|
|
193
|
+
color: "rgb(var(--accent-strong))",
|
|
194
|
+
opacity: 0.72,
|
|
195
|
+
pointerEvents: "none",
|
|
196
|
+
userSelect: "none",
|
|
197
|
+
}}
|
|
198
|
+
>
|
|
199
|
+
✦
|
|
200
|
+
</span>
|
|
201
|
+
</div>
|
|
202
|
+
);
|
|
203
|
+
}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
/**
|
|
3
|
+
* flow-field.template.tsx — Particles driven by 2D value noise; morph into shape on hover.
|
|
4
|
+
*
|
|
5
|
+
* USAGE:
|
|
6
|
+
* 1. Copy and rename (e.g. FlowField.tsx, HeroFlow.tsx)
|
|
7
|
+
* 2. Replace `buildTargets` with any shape from shape-sampler.ts
|
|
8
|
+
* 3. Tune FLOW_FORCE (current strength), FRICTION, and MORPH_SPEED
|
|
9
|
+
* 4. Ambient-only mode: remove letter targets entirely; particles just drift with noise
|
|
10
|
+
*
|
|
11
|
+
* KEY TECHNIQUE: every particle follows a `flowAngle` derived from two octaves of value noise
|
|
12
|
+
* (vnoise). On hover, spring forces toward letter targets compete with the noise field; morph
|
|
13
|
+
* controls the blend (0 = pure noise, 1 = 25% noise + 75% spring). Particles are drawn as
|
|
14
|
+
* velocity-aligned streaks to show flow direction.
|
|
15
|
+
*
|
|
16
|
+
* INTERACTION: mousemove → letter assembles from flowing particles; mouseleave → disperses back
|
|
17
|
+
* THEMING: --accent-strong (streak/node), --accent-soft (connection lines)
|
|
18
|
+
*/
|
|
19
|
+
import { useEffect, useRef } from "react";
|
|
20
|
+
|
|
21
|
+
// ── 2D value noise ─────────────────────────────────────────────────────────────
|
|
22
|
+
//
|
|
23
|
+
// Coherent noise — same sin-hash base as shape-sampler.ts.
|
|
24
|
+
// Input: continuous (x, y). Output: 0..1, smoothly varying.
|
|
25
|
+
//
|
|
26
|
+
function vnoise(x: number, y: number): number {
|
|
27
|
+
const ix = Math.floor(x), iy = Math.floor(y);
|
|
28
|
+
const fx = x - ix, fy = y - iy;
|
|
29
|
+
const ux = fx * fx * (3 - 2 * fx); // smoothstep
|
|
30
|
+
const uy = fy * fy * (3 - 2 * fy);
|
|
31
|
+
const h = (a: number, b: number) =>
|
|
32
|
+
(Math.abs(Math.sin(a * 127.1 + b * 311.7) * 43758.5453)) % 1;
|
|
33
|
+
const aa = h(ix, iy), ba = h(ix+1, iy);
|
|
34
|
+
const ab = h(ix, iy+1), bb = h(ix+1, iy+1);
|
|
35
|
+
return aa + (ba-aa)*ux + (ab-aa)*uy + (aa-ba-ab+bb)*ux*uy;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Two-octave flow angle — animates over time via `t`
|
|
39
|
+
// CUSTOMISE: adjust multipliers for scale (0.008 = large cells) and speed (0.00035 = slow drift)
|
|
40
|
+
function flowAngle(x: number, y: number, t: number): number {
|
|
41
|
+
const n1 = vnoise(x * 0.008 + t * 0.00035, y * 0.008);
|
|
42
|
+
const n2 = vnoise(x * 0.015 + t * 0.0005 + 100, y * 0.015 + 50);
|
|
43
|
+
return (n1 * 0.65 + n2 * 0.35) * Math.PI * 4;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ── Target shape — replace or import from shape-sampler.ts ───────────────────
|
|
47
|
+
function cubicBezier(
|
|
48
|
+
t: number,
|
|
49
|
+
p0: [number,number], p1: [number,number],
|
|
50
|
+
p2: [number,number], p3: [number,number],
|
|
51
|
+
): [number,number] {
|
|
52
|
+
const m = 1 - t;
|
|
53
|
+
return [
|
|
54
|
+
m**3*p0[0]+3*m**2*t*p1[0]+3*m*t**2*p2[0]+t**3*p3[0],
|
|
55
|
+
m**3*p0[1]+3*m**2*t*p1[1]+3*m*t**2*p2[1]+t**3*p3[1],
|
|
56
|
+
];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface Target { tx: number; ty: number; spring: number; r: number; alpha: number; isDot: boolean }
|
|
60
|
+
|
|
61
|
+
function buildTargets(size: number): Target[] {
|
|
62
|
+
const cx = size * 0.50;
|
|
63
|
+
const stemTop = size * 0.210, stemBot = size * 0.660;
|
|
64
|
+
const out: Target[] = [];
|
|
65
|
+
|
|
66
|
+
// Tittle
|
|
67
|
+
out.push({ tx: cx, ty: size * 0.130, spring: 0.030, r: 4.5, alpha: 1.0, isDot: true });
|
|
68
|
+
|
|
69
|
+
// Stem
|
|
70
|
+
for (let i = 0; i <= 12; i++)
|
|
71
|
+
out.push({ tx: cx, ty: stemTop + (i/12) * (stemBot-stemTop), spring: 0.028, r: 2.2, alpha: 0.92, isDot: false });
|
|
72
|
+
|
|
73
|
+
// Hook
|
|
74
|
+
const P0: [number,number] = [cx, stemBot];
|
|
75
|
+
const P1: [number,number] = [cx, stemBot + size*0.115];
|
|
76
|
+
const P2: [number,number] = [cx - size*0.115, stemBot + size*0.145];
|
|
77
|
+
const P3: [number,number] = [cx - size*0.155, stemBot + size*0.040];
|
|
78
|
+
for (let i = 1; i <= 9; i++) {
|
|
79
|
+
const [bx,by] = cubicBezier(i/9, P0, P1, P2, P3);
|
|
80
|
+
out.push({ tx: bx, ty: by, spring: 0.026, r: 2.2, alpha: 0.88, isDot: false });
|
|
81
|
+
}
|
|
82
|
+
return out;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ── Types & theme helper ──────────────────────────────────────────────────────
|
|
86
|
+
interface Pt {
|
|
87
|
+
x: number; y: number; vx: number; vy: number;
|
|
88
|
+
tx: number; ty: number; spring: number;
|
|
89
|
+
r: number; alpha: number; isDot: boolean; isLetter: boolean;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function themeRgb(v: string): [number, number, number] {
|
|
93
|
+
const ch = getComputedStyle(document.documentElement)
|
|
94
|
+
.getPropertyValue(v).trim().split(/\s+/).map(Number);
|
|
95
|
+
return [ch[0], ch[1], ch[2]];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
99
|
+
const FLOW_FORCE = 0.06; // noise field push strength; raise for wilder currents
|
|
100
|
+
const FRICTION = 0.965; // less damping than spring components — particles glide
|
|
101
|
+
const MAX_SPEED = 1.8;
|
|
102
|
+
const MORPH_SPEED = 0.048; // lerp speed 0→1; lower = longer, dreamier transition
|
|
103
|
+
const LINK = 80; // connection line distance threshold (px)
|
|
104
|
+
|
|
105
|
+
// ── Component ─────────────────────────────────────────────────────────────────
|
|
106
|
+
export function FlowFieldArt({ size = 300 }: { size?: number }) {
|
|
107
|
+
const ref = useRef<HTMLCanvasElement>(null);
|
|
108
|
+
// useRef — never stale in the RAF loop
|
|
109
|
+
const active = useRef(false);
|
|
110
|
+
const morph = useRef(0);
|
|
111
|
+
|
|
112
|
+
useEffect(() => {
|
|
113
|
+
const el = ref.current;
|
|
114
|
+
if (!el) return;
|
|
115
|
+
|
|
116
|
+
const ctx = el.getContext("2d")!;
|
|
117
|
+
const dpr = window.devicePixelRatio || 1;
|
|
118
|
+
el.width = size * dpr;
|
|
119
|
+
el.height = size * dpr;
|
|
120
|
+
ctx.scale(dpr, dpr);
|
|
121
|
+
|
|
122
|
+
const cx = size * 0.50, cy = size * 0.46;
|
|
123
|
+
|
|
124
|
+
// Letter particles — scattered initially; attracted to letter on hover
|
|
125
|
+
const pts: Pt[] = buildTargets(size).map(t => ({
|
|
126
|
+
x: cx + (Math.random() - 0.5) * size * 0.6,
|
|
127
|
+
y: cy + (Math.random() - 0.5) * size * 0.6,
|
|
128
|
+
vx: (Math.random() - 0.5) * 0.5,
|
|
129
|
+
vy: (Math.random() - 0.5) * 0.5,
|
|
130
|
+
tx: t.tx, ty: t.ty, spring: t.spring,
|
|
131
|
+
r: t.r, alpha: t.alpha, isDot: t.isDot, isLetter: true,
|
|
132
|
+
}));
|
|
133
|
+
|
|
134
|
+
// Ambient flow-only particles — no letter target; fill the field
|
|
135
|
+
// CUSTOMISE: increase count for denser streams
|
|
136
|
+
for (let i = 0; i < 22; i++) {
|
|
137
|
+
pts.push({
|
|
138
|
+
x: Math.random() * size, y: Math.random() * size,
|
|
139
|
+
vx: (Math.random() - 0.5) * 0.4,
|
|
140
|
+
vy: (Math.random() - 0.5) * 0.4,
|
|
141
|
+
tx: cx, ty: cy, spring: 0,
|
|
142
|
+
r: Math.random() * 1.2 + 0.5,
|
|
143
|
+
alpha: 0.28 + Math.random() * 0.22,
|
|
144
|
+
isDot: false, isLetter: false,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const onMove = () => { active.current = true; };
|
|
149
|
+
const onLeave = () => { active.current = false; };
|
|
150
|
+
el.addEventListener("mousemove", onMove);
|
|
151
|
+
el.addEventListener("mouseleave", onLeave);
|
|
152
|
+
|
|
153
|
+
let raf: number;
|
|
154
|
+
let frame = 0;
|
|
155
|
+
|
|
156
|
+
const tick = () => {
|
|
157
|
+
ctx.clearRect(0, 0, size, size);
|
|
158
|
+
const [nr, ng, nb] = themeRgb("--accent-strong");
|
|
159
|
+
const [lr, lg, lb] = themeRgb("--accent-soft");
|
|
160
|
+
|
|
161
|
+
const m = morph.current + ((active.current ? 1 : 0) - morph.current) * MORPH_SPEED;
|
|
162
|
+
morph.current = m;
|
|
163
|
+
|
|
164
|
+
// Connection lines — grow more visible as letter forms
|
|
165
|
+
for (let i = 0; i < pts.length; i++) {
|
|
166
|
+
for (let j = i + 1; j < pts.length; j++) {
|
|
167
|
+
const dx = pts[i].x - pts[j].x, dy = pts[i].y - pts[j].y;
|
|
168
|
+
const d = Math.sqrt(dx*dx + dy*dy);
|
|
169
|
+
if (d < LINK) {
|
|
170
|
+
const a = (1 - d/LINK) * 0.14 * (0.4 + m*0.6);
|
|
171
|
+
ctx.beginPath();
|
|
172
|
+
ctx.moveTo(pts[i].x, pts[i].y);
|
|
173
|
+
ctx.lineTo(pts[j].x, pts[j].y);
|
|
174
|
+
ctx.strokeStyle = `rgba(${lr},${lg},${lb},${a.toFixed(3)})`;
|
|
175
|
+
ctx.lineWidth = 0.6;
|
|
176
|
+
ctx.stroke();
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
pts.forEach(p => {
|
|
182
|
+
// 1. Flow field force — attenuates as morph strengthens (25% remains at m=1)
|
|
183
|
+
const angle = flowAngle(p.x, p.y, frame);
|
|
184
|
+
const flowScale = 1 - m * 0.75;
|
|
185
|
+
p.vx += Math.cos(angle) * FLOW_FORCE * flowScale;
|
|
186
|
+
p.vy += Math.sin(angle) * FLOW_FORCE * flowScale;
|
|
187
|
+
|
|
188
|
+
// 2. Spring toward letter target (only for letter particles; scales with morph)
|
|
189
|
+
if (p.isLetter && m > 0.01) {
|
|
190
|
+
p.vx += (p.tx - p.x) * p.spring * m;
|
|
191
|
+
p.vy += (p.ty - p.y) * p.spring * m;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// 3. Edge wrap in idle (disable during morph to let letter form cleanly)
|
|
195
|
+
if (m < 0.3) {
|
|
196
|
+
if (p.x < 0) p.x = size;
|
|
197
|
+
if (p.x > size) p.x = 0;
|
|
198
|
+
if (p.y < 0) p.y = size;
|
|
199
|
+
if (p.y > size) p.y = 0;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
p.vx *= FRICTION; p.vy *= FRICTION;
|
|
203
|
+
const spd = Math.sqrt(p.vx*p.vx + p.vy*p.vy);
|
|
204
|
+
if (spd > MAX_SPEED) { p.vx *= MAX_SPEED/spd; p.vy *= MAX_SPEED/spd; }
|
|
205
|
+
p.x += p.vx; p.y += p.vy;
|
|
206
|
+
|
|
207
|
+
// 4. Velocity-aligned streak — shows flow direction
|
|
208
|
+
// Length proportional to speed (particles moving faster cast longer streaks)
|
|
209
|
+
const streakLen = Math.sqrt(p.vx*p.vx + p.vy*p.vy) * 5;
|
|
210
|
+
const alpha = p.isLetter
|
|
211
|
+
? p.alpha * (0.55 + m * 0.45) // letter particles brighten as form solidifies
|
|
212
|
+
: p.alpha;
|
|
213
|
+
|
|
214
|
+
ctx.beginPath();
|
|
215
|
+
ctx.moveTo(p.x - p.vx * streakLen, p.y - p.vy * streakLen);
|
|
216
|
+
ctx.lineTo(p.x, p.y);
|
|
217
|
+
ctx.strokeStyle = `rgba(${nr},${ng},${nb},${alpha.toFixed(3)})`;
|
|
218
|
+
ctx.lineWidth = p.r * 1.4;
|
|
219
|
+
ctx.lineCap = "round";
|
|
220
|
+
ctx.stroke();
|
|
221
|
+
|
|
222
|
+
// 5. Solid node dot at head — visible only once letter starts forming
|
|
223
|
+
if (m > 0.2 && p.isLetter) {
|
|
224
|
+
let nodeR = p.r;
|
|
225
|
+
if (p.isDot) {
|
|
226
|
+
nodeR = p.r + Math.sin(frame * 0.04) * 0.8;
|
|
227
|
+
// Pulsing glow ring for tittle
|
|
228
|
+
ctx.beginPath();
|
|
229
|
+
ctx.arc(p.x, p.y, nodeR + 4 + Math.sin(frame * 0.04) * 2, 0, Math.PI * 2);
|
|
230
|
+
ctx.strokeStyle = `rgba(${nr},${ng},${nb},${(0.18 * m).toFixed(3)})`;
|
|
231
|
+
ctx.lineWidth = 1;
|
|
232
|
+
ctx.stroke();
|
|
233
|
+
}
|
|
234
|
+
ctx.beginPath();
|
|
235
|
+
ctx.arc(p.x, p.y, nodeR * m, 0, Math.PI * 2);
|
|
236
|
+
ctx.fillStyle = `rgba(${nr},${ng},${nb},${(alpha * m).toFixed(3)})`;
|
|
237
|
+
ctx.fill();
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
frame++;
|
|
242
|
+
raf = requestAnimationFrame(tick);
|
|
243
|
+
};
|
|
244
|
+
tick();
|
|
245
|
+
|
|
246
|
+
// ⚠️ prefers-reduced-motion: do NOT early-return before drawing (leaves canvas blank).
|
|
247
|
+
// To support reduced-motion: snap pts to targets, call tick() once, then cancelRAF.
|
|
248
|
+
|
|
249
|
+
return () => {
|
|
250
|
+
cancelAnimationFrame(raf);
|
|
251
|
+
el.removeEventListener("mousemove", onMove);
|
|
252
|
+
el.removeEventListener("mouseleave", onLeave);
|
|
253
|
+
};
|
|
254
|
+
}, [size]);
|
|
255
|
+
|
|
256
|
+
return (
|
|
257
|
+
<canvas
|
|
258
|
+
ref={ref}
|
|
259
|
+
style={{ width: size, height: size, cursor: "crosshair" }}
|
|
260
|
+
aria-hidden="true"
|
|
261
|
+
/>
|
|
262
|
+
);
|
|
263
|
+
}
|