opencode-skills-collection 3.1.0 → 3.1.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/bundled-skills/.antigravity-install-manifest.json +84 -1
- package/bundled-skills/android-ui-journey-testing/SKILL.md +191 -0
- package/bundled-skills/ask-matt/SKILL.md +92 -0
- package/bundled-skills/bugs-are-annoying/SKILL.md +137 -0
- package/bundled-skills/codebase-design/DEEPENING.md +37 -0
- package/bundled-skills/codebase-design/DESIGN-IT-TWICE.md +44 -0
- package/bundled-skills/codebase-design/SKILL.md +145 -0
- package/bundled-skills/competitor-analysis/LICENSE.txt +21 -0
- package/bundled-skills/competitor-analysis/SKILL.md +434 -0
- package/bundled-skills/competitor-analysis/references/battle-card-subagent.md +127 -0
- package/bundled-skills/competitor-analysis/references/battle-card.md +91 -0
- package/bundled-skills/competitor-analysis/references/example-research.md +130 -0
- package/bundled-skills/competitor-analysis/references/report-template.html +127 -0
- package/bundled-skills/competitor-analysis/references/research-patterns.md +217 -0
- package/bundled-skills/competitor-analysis/references/workflow.md +434 -0
- package/bundled-skills/competitor-analysis/scripts/capture_screenshots.mjs +142 -0
- package/bundled-skills/competitor-analysis/scripts/compile_report.mjs +929 -0
- package/bundled-skills/competitor-analysis/scripts/extract_vs_names.mjs +140 -0
- package/bundled-skills/competitor-analysis/scripts/gate_candidates.mjs +194 -0
- package/bundled-skills/competitor-analysis/scripts/list_urls.mjs +90 -0
- package/bundled-skills/competitor-analysis/scripts/md_utils.mjs +50 -0
- package/bundled-skills/competitor-analysis/scripts/merge_partials.mjs +291 -0
- package/bundled-skills/competitor-analysis/scripts/package.json +6 -0
- package/bundled-skills/design-it/3d-ui/SKILL.md +259 -0
- package/bundled-skills/design-it/SKILL.md +170 -0
- package/bundled-skills/design-it/ai-native-ui/SKILL.md +295 -0
- package/bundled-skills/design-it/aurora-ui/SKILL.md +307 -0
- package/bundled-skills/design-it/bento-ui/SKILL.md +314 -0
- package/bundled-skills/design-it/brutalism/SKILL.md +270 -0
- package/bundled-skills/design-it/brutalist-typography/SKILL.md +287 -0
- package/bundled-skills/design-it/card-based-design/SKILL.md +262 -0
- package/bundled-skills/design-it/claymorphism/SKILL.md +287 -0
- package/bundled-skills/design-it/color-blocking/SKILL.md +278 -0
- package/bundled-skills/design-it/command-center-ui/SKILL.md +345 -0
- package/bundled-skills/design-it/cyber-y2k/SKILL.md +312 -0
- package/bundled-skills/design-it/cyberpunk-ui/SKILL.md +262 -0
- package/bundled-skills/design-it/dark-mode/SKILL.md +289 -0
- package/bundled-skills/design-it/dashboard-design/SKILL.md +331 -0
- package/bundled-skills/design-it/data-dense-design/SKILL.md +322 -0
- package/bundled-skills/design-it/duotone-design/SKILL.md +248 -0
- package/bundled-skills/design-it/editorial-design/SKILL.md +328 -0
- package/bundled-skills/design-it/flat-design/SKILL.md +221 -0
- package/bundled-skills/design-it/flat-design-2/SKILL.md +240 -0
- package/bundled-skills/design-it/floating-ui/SKILL.md +299 -0
- package/bundled-skills/design-it/frutiger-aero/SKILL.md +274 -0
- package/bundled-skills/design-it/glassmorphism/SKILL.md +272 -0
- package/bundled-skills/design-it/gradient-design/SKILL.md +309 -0
- package/bundled-skills/design-it/high-contrast/SKILL.md +288 -0
- package/bundled-skills/design-it/holographic-ui/SKILL.md +310 -0
- package/bundled-skills/design-it/isometric-design/SKILL.md +228 -0
- package/bundled-skills/design-it/layered-design/SKILL.md +247 -0
- package/bundled-skills/design-it/material-design/SKILL.md +275 -0
- package/bundled-skills/design-it/maximalism/SKILL.md +297 -0
- package/bundled-skills/design-it/minimalism/SKILL.md +267 -0
- package/bundled-skills/design-it/monochromatic-ui/SKILL.md +296 -0
- package/bundled-skills/design-it/neo-brutalism/SKILL.md +270 -0
- package/bundled-skills/design-it/neumorphism/SKILL.md +248 -0
- package/bundled-skills/design-it/retro-design/SKILL.md +283 -0
- package/bundled-skills/design-it/retro-futurism/SKILL.md +259 -0
- package/bundled-skills/design-it/sci-fi-interface/SKILL.md +309 -0
- package/bundled-skills/design-it/skeuomorphism/SKILL.md +280 -0
- package/bundled-skills/design-it/soft-pastel/SKILL.md +307 -0
- package/bundled-skills/design-it/spatial-computing-ui/SKILL.md +300 -0
- package/bundled-skills/design-it/spatial-design/SKILL.md +268 -0
- package/bundled-skills/design-it/swiss-design/SKILL.md +293 -0
- package/bundled-skills/design-it/synthwave/SKILL.md +257 -0
- package/bundled-skills/design-it/tile-design/SKILL.md +297 -0
- package/bundled-skills/design-it/typography-first/SKILL.md +247 -0
- package/bundled-skills/design-it/vaporwave/SKILL.md +331 -0
- package/bundled-skills/design-it/vibrant-maximalism/SKILL.md +291 -0
- package/bundled-skills/design-it/widget-based-design/SKILL.md +274 -0
- package/bundled-skills/design-it/y2k-design/SKILL.md +268 -0
- package/bundled-skills/diagnosing-bugs/SKILL.md +165 -0
- package/bundled-skills/diagnosing-bugs/scripts/hitl-loop.template.sh +41 -0
- package/bundled-skills/docs/contributors/skill-scoring.md +235 -0
- package/bundled-skills/docs/integrations/jetski-cortex.md +3 -3
- package/bundled-skills/docs/integrations/jetski-gemini-loader/README.md +1 -1
- package/bundled-skills/docs/maintainers/repo-growth-seo.md +3 -3
- package/bundled-skills/docs/maintainers/skills-update-guide.md +1 -1
- package/bundled-skills/docs/users/bundles.md +1 -1
- package/bundled-skills/docs/users/claude-code-skills.md +1 -1
- package/bundled-skills/docs/users/gemini-cli-skills.md +1 -1
- package/bundled-skills/docs/users/getting-started.md +1 -1
- package/bundled-skills/docs/users/kiro-integration.md +1 -1
- package/bundled-skills/docs/users/usage.md +4 -4
- package/bundled-skills/docs/users/visual-guide.md +4 -4
- package/bundled-skills/domain-modeling/ADR-FORMAT.md +47 -0
- package/bundled-skills/domain-modeling/CONTEXT-FORMAT.md +60 -0
- package/bundled-skills/domain-modeling/SKILL.md +105 -0
- package/bundled-skills/grill-me/SKILL.md +36 -0
- package/bundled-skills/grill-with-docs/SKILL.md +36 -0
- package/bundled-skills/grilling/SKILL.md +39 -0
- package/bundled-skills/handoff/SKILL.md +45 -0
- package/bundled-skills/image-generator/.env.example +7 -0
- package/bundled-skills/image-generator/SKILL.md +509 -0
- package/bundled-skills/improve-codebase-architecture/HTML-REPORT.md +123 -0
- package/bundled-skills/improve-codebase-architecture/SKILL.md +97 -0
- package/bundled-skills/learn/SKILL.md +156 -0
- package/bundled-skills/lesson-generator/SKILL.md +90 -0
- package/bundled-skills/llm-council/.env.example +7 -0
- package/bundled-skills/llm-council/SKILL.md +602 -0
- package/bundled-skills/loop-library/SKILL.md +205 -0
- package/bundled-skills/loop-library/agents/openai.yaml +4 -0
- package/bundled-skills/loop-library/references/catalog.md +270 -0
- package/bundled-skills/mailtrap-managing-contacts/SKILL.md +112 -0
- package/bundled-skills/mailtrap-sending-emails/SKILL.md +167 -0
- package/bundled-skills/mailtrap-setting-up-sending-domain/SKILL.md +77 -0
- package/bundled-skills/mailtrap-testing-with-sandbox/SKILL.md +110 -0
- package/bundled-skills/prototype/LOGIC.md +79 -0
- package/bundled-skills/prototype/SKILL.md +62 -0
- package/bundled-skills/prototype/UI.md +112 -0
- package/bundled-skills/setup-matt-pocock-skills/SKILL.md +158 -0
- package/bundled-skills/setup-matt-pocock-skills/domain.md +51 -0
- package/bundled-skills/setup-matt-pocock-skills/issue-tracker-github.md +34 -0
- package/bundled-skills/setup-matt-pocock-skills/issue-tracker-gitlab.md +35 -0
- package/bundled-skills/setup-matt-pocock-skills/issue-tracker-local.md +19 -0
- package/bundled-skills/setup-matt-pocock-skills/triage-labels.md +15 -0
- package/bundled-skills/survey-generator/LICENSE +21 -0
- package/bundled-skills/survey-generator/SKILL.md +143 -0
- package/bundled-skills/survey-generator/build_artifact.py +208 -0
- package/bundled-skills/survey-generator/examples/agentic-engineering/research_bundle.json +1196 -0
- package/bundled-skills/survey-generator/examples/agentic-engineering/survey.html +706 -0
- package/bundled-skills/survey-generator/style_spec.json +85 -0
- package/bundled-skills/survey-generator/templates/research_bundle_template.json +69 -0
- package/bundled-skills/tdd/SKILL.md +139 -0
- package/bundled-skills/tdd/mocking.md +59 -0
- package/bundled-skills/tdd/refactoring.md +10 -0
- package/bundled-skills/tdd/tests.md +61 -0
- package/bundled-skills/teach/GLOSSARY-FORMAT.md +35 -0
- package/bundled-skills/teach/LEARNING-RECORD-FORMAT.md +46 -0
- package/bundled-skills/teach/MISSION-FORMAT.md +31 -0
- package/bundled-skills/teach/RESOURCES-FORMAT.md +32 -0
- package/bundled-skills/teach/SKILL.md +169 -0
- package/bundled-skills/to-issues/SKILL.md +115 -0
- package/bundled-skills/to-prd/SKILL.md +104 -0
- package/bundled-skills/tools-page-seo-optimizer/SKILL.md +616 -0
- package/bundled-skills/triage/AGENT-BRIEF.md +207 -0
- package/bundled-skills/triage/OUT-OF-SCOPE.md +105 -0
- package/bundled-skills/triage/SKILL.md +143 -0
- package/bundled-skills/vibecode-production-qa-validator/SKILL.md +371 -141
- package/bundled-skills/wiki-builder/SKILL.md +157 -0
- package/bundled-skills/wiki-builder/agents/openai.yaml +5 -0
- package/bundled-skills/wiki-builder/references/wiki-flavors.md +98 -0
- package/bundled-skills/wiki-builder/scripts/init_wiki.sh +105 -0
- package/bundled-skills/wiki-builder/templates/index.md +20 -0
- package/bundled-skills/wiki-builder/templates/maintenance-log.md +7 -0
- package/bundled-skills/wiki-builder/templates/prompts/compile-concept-page.md +12 -0
- package/bundled-skills/wiki-builder/templates/prompts/compile-index.md +11 -0
- package/bundled-skills/wiki-builder/templates/prompts/compile-source-page.md +12 -0
- package/bundled-skills/wiki-builder/templates/prompts/lint-wiki.md +10 -0
- package/bundled-skills/wiki-builder/templates/prompts/query-and-file.md +11 -0
- package/bundled-skills/wiki-builder/templates/sources.md +9 -0
- package/bundled-skills/wiki-builder/templates/wiki.config.md +53 -0
- package/bundled-skills/writing-great-skills/GLOSSARY.md +181 -0
- package/bundled-skills/writing-great-skills/SKILL.md +111 -0
- package/bundled-skills/yao-meta-skill/SKILL.md +86 -0
- package/bundled-skills/yao-meta-skill/agents/interface.yaml +26 -0
- package/bundled-skills/yao-meta-skill/manifest.json +24 -0
- package/bundled-skills/yao-meta-skill/references/artifact-design-doctrine.md +49 -0
- package/bundled-skills/yao-meta-skill/references/authoring-discipline.md +78 -0
- package/bundled-skills/yao-meta-skill/references/autonomous-adaptation.md +65 -0
- package/bundled-skills/yao-meta-skill/references/distribution-registry-method.md +60 -0
- package/bundled-skills/yao-meta-skill/references/eval-playbook.md +69 -0
- package/bundled-skills/yao-meta-skill/references/gate-selection.md +68 -0
- package/bundled-skills/yao-meta-skill/references/governance.md +134 -0
- package/bundled-skills/yao-meta-skill/references/human-review-template.md +54 -0
- package/bundled-skills/yao-meta-skill/references/intent-dialogue.md +138 -0
- package/bundled-skills/yao-meta-skill/references/iteration-philosophy.md +30 -0
- package/bundled-skills/yao-meta-skill/references/non-skill-decision-tree.md +39 -0
- package/bundled-skills/yao-meta-skill/references/operating-modes.md +107 -0
- package/bundled-skills/yao-meta-skill/references/output-eval-method.md +113 -0
- package/bundled-skills/yao-meta-skill/references/output-quality-risk.md +41 -0
- package/bundled-skills/yao-meta-skill/references/output-visual-quality.md +53 -0
- package/bundled-skills/yao-meta-skill/references/packaging-contracts.md +70 -0
- package/bundled-skills/yao-meta-skill/references/pattern-extraction-doctrine.md +76 -0
- package/bundled-skills/yao-meta-skill/references/platform-capability-matrix.md +49 -0
- package/bundled-skills/yao-meta-skill/references/prompt-engineering-doctrine.md +76 -0
- package/bundled-skills/yao-meta-skill/references/qa-ladder.md +57 -0
- package/bundled-skills/yao-meta-skill/references/reference-scan.md +126 -0
- package/bundled-skills/yao-meta-skill/references/regression-cause-taxonomy.md +80 -0
- package/bundled-skills/yao-meta-skill/references/resource-boundaries.md +120 -0
- package/bundled-skills/yao-meta-skill/references/review-studio-method.md +87 -0
- package/bundled-skills/yao-meta-skill/references/review-waiver-method.md +76 -0
- package/bundled-skills/yao-meta-skill/references/runtime-conformance-method.md +21 -0
- package/bundled-skills/yao-meta-skill/references/skill-archetypes.md +86 -0
- package/bundled-skills/yao-meta-skill/references/skill-atlas-method.md +35 -0
- package/bundled-skills/yao-meta-skill/references/skill-engineering-method.md +210 -0
- package/bundled-skills/yao-meta-skill/references/skill-ir-method.md +41 -0
- package/bundled-skills/yao-meta-skill/references/skillops-decision-policy.md +53 -0
- package/bundled-skills/yao-meta-skill/references/systems-thinking-doctrine.md +75 -0
- package/bundled-skills/yao-meta-skill/references/telemetry-drift-method.md +182 -0
- package/bundled-skills/yao-meta-skill/references/trust-security-method.md +79 -0
- package/bundled-skills/yao-meta-skill/references/user-memory-policy.md +35 -0
- package/bundled-skills/youtube-notetaker/SKILL.md +209 -0
- package/bundled-skills/youtube-notetaker/reference/artifact.html +269 -0
- package/bundled-skills/youtube-notetaker/scripts/contact_sheet.py +53 -0
- package/bundled-skills/youtube-notetaker/scripts/detect_slides.sh +19 -0
- package/bundled-skills/youtube-notetaker/scripts/download.sh +24 -0
- package/bundled-skills/youtube-notetaker/scripts/extract_slides.py +43 -0
- package/bundled-skills/youtube-notetaker/scripts/serve.py +173 -0
- package/bundled-skills/youtube-notetaker/scripts/setup.sh +27 -0
- package/bundled-skills/youtube-notetaker/scripts/verify.sh +31 -0
- package/bundled-skills/youtube-notetaker/scripts/vtt_to_transcript.py +59 -0
- package/bundled-skills/youtube-notetaker/scripts/write_library_item.py +69 -0
- package/package.json +1 -1
- package/skills_index.json +1956 -286
- package/bundled-skills/ai-md/SKILL.md +0 -523
- package/bundled-skills/atlas-contract/SKILL.md +0 -650
- package/bundled-skills/busybox-on-windows/SKILL.md +0 -40
- package/bundled-skills/monte-carlo-prevent/SKILL.md +0 -257
- package/bundled-skills/monte-carlo-prevent/references/TROUBLESHOOTING.md +0 -23
- package/bundled-skills/monte-carlo-prevent/references/parameters.md +0 -32
- package/bundled-skills/monte-carlo-prevent/references/workflows.md +0 -478
- package/bundled-skills/monte-carlo-push-ingestion/SKILL.md +0 -372
- package/bundled-skills/monte-carlo-push-ingestion/references/anomaly-detection.md +0 -87
- package/bundled-skills/monte-carlo-push-ingestion/references/custom-lineage.md +0 -203
- package/bundled-skills/monte-carlo-push-ingestion/references/direct-http-api.md +0 -207
- package/bundled-skills/monte-carlo-push-ingestion/references/prerequisites.md +0 -150
- package/bundled-skills/monte-carlo-push-ingestion/references/push-lineage.md +0 -160
- package/bundled-skills/monte-carlo-push-ingestion/references/push-metadata.md +0 -158
- package/bundled-skills/monte-carlo-push-ingestion/references/push-query-logs.md +0 -219
- package/bundled-skills/monte-carlo-push-ingestion/references/validation.md +0 -257
- package/bundled-skills/monte-carlo-push-ingestion/scripts/sample_verify.py +0 -357
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery/collect_and_push_lineage.py +0 -70
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery/collect_and_push_metadata.py +0 -65
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery/collect_and_push_query_logs.py +0 -70
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery/collect_lineage.py +0 -214
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery/collect_metadata.py +0 -160
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery/collect_query_logs.py +0 -164
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery/push_lineage.py +0 -198
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery/push_metadata.py +0 -193
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery/push_query_logs.py +0 -207
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery-iceberg/collect_and_push_metadata.py +0 -71
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery-iceberg/collect_and_push_query_logs.py +0 -64
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery-iceberg/collect_metadata.py +0 -253
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery-iceberg/collect_query_logs.py +0 -149
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery-iceberg/push_metadata.py +0 -190
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery-iceberg/push_query_logs.py +0 -208
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/databricks/collect_and_push_lineage.py +0 -83
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/databricks/collect_and_push_metadata.py +0 -77
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/databricks/collect_and_push_query_logs.py +0 -83
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/databricks/collect_lineage.py +0 -240
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/databricks/collect_metadata.py +0 -212
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/databricks/collect_query_logs.py +0 -204
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/databricks/push_lineage.py +0 -192
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/databricks/push_metadata.py +0 -178
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/databricks/push_query_logs.py +0 -200
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/hive/collect_and_push_lineage.py +0 -119
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/hive/collect_and_push_metadata.py +0 -119
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/hive/collect_and_push_query_logs.py +0 -117
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/hive/collect_lineage.py +0 -265
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/hive/collect_metadata.py +0 -313
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/hive/collect_query_logs.py +0 -284
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/hive/push_lineage.py +0 -309
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/hive/push_metadata.py +0 -245
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/hive/push_query_logs.py +0 -255
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/redshift/collect_and_push_lineage.py +0 -78
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/redshift/collect_and_push_metadata.py +0 -80
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/redshift/collect_and_push_query_logs.py +0 -88
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/redshift/collect_lineage.py +0 -235
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/redshift/collect_metadata.py +0 -219
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/redshift/collect_query_logs.py +0 -239
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/redshift/push_lineage.py +0 -178
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/redshift/push_metadata.py +0 -178
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/redshift/push_query_logs.py +0 -196
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/snowflake/collect_and_push_lineage.py +0 -154
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/snowflake/collect_and_push_metadata.py +0 -137
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/snowflake/collect_and_push_query_logs.py +0 -137
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/snowflake/collect_lineage.py +0 -349
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/snowflake/collect_metadata.py +0 -329
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/snowflake/collect_query_logs.py +0 -254
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/snowflake/push_lineage.py +0 -307
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/snowflake/push_metadata.py +0 -228
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/snowflake/push_query_logs.py +0 -248
- package/bundled-skills/monte-carlo-push-ingestion/scripts/test_template_sdk_usage.py +0 -340
- package/bundled-skills/skill-optimizer/SKILL.md +0 -271
- package/bundled-skills/using-superpowers/SKILL.md +0 -98
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// Merges per-lane partial markdown files into one consolidated file per competitor.
|
|
4
|
+
//
|
|
5
|
+
// The 5-lane subagent fan-out writes partials to: {OUTPUT_DIR}/partials/{slug}.{lane}.md
|
|
6
|
+
// lane ∈ { marketing, discussion, social, news, technical }
|
|
7
|
+
//
|
|
8
|
+
// Each partial has its own YAML frontmatter + sections. The marketing partial owns
|
|
9
|
+
// the canonical frontmatter (pricing, features, etc.); other lanes contribute only
|
|
10
|
+
// Mentions / Benchmarks / Findings bullets. The merge:
|
|
11
|
+
// 1. Starts from marketing.md's frontmatter as the canonical header
|
|
12
|
+
// 2. Appends body sections in the canonical order (Product, Pricing, Features,
|
|
13
|
+
// Positioning, Comparison, Mentions, Benchmarks, Research Findings)
|
|
14
|
+
// 3. Unions all Mentions bullets across lanes, dedups by URL, sorts by date desc
|
|
15
|
+
// 4. Unions all Research Findings bullets across lanes
|
|
16
|
+
// 5. Unions all Benchmarks bullets
|
|
17
|
+
// 6. Writes the consolidated file to {OUTPUT_DIR}/{slug}.md
|
|
18
|
+
//
|
|
19
|
+
// Usage: node merge_partials.mjs <research-dir>
|
|
20
|
+
|
|
21
|
+
import { readdirSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
22
|
+
import { join } from 'path';
|
|
23
|
+
import { parseFrontmatter, parseBody, parseSections } from './md_utils.mjs';
|
|
24
|
+
|
|
25
|
+
const args = process.argv.slice(2);
|
|
26
|
+
if (args.includes('--help') || args.includes('-h') || args.length === 0) {
|
|
27
|
+
console.error(`Usage: node merge_partials.mjs <research-dir>
|
|
28
|
+
|
|
29
|
+
Reads {dir}/partials/{slug}.{lane}.md files and writes consolidated
|
|
30
|
+
{dir}/{slug}.md per competitor. Lanes: marketing, discussion, social, news, technical.`);
|
|
31
|
+
process.exit(args.includes('--help') || args.includes('-h') ? 0 : 1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const dir = args[0];
|
|
35
|
+
const partialsDir = join(dir, 'partials');
|
|
36
|
+
|
|
37
|
+
const LANES = ['marketing', 'discussion', 'social', 'news', 'technical', 'battle'];
|
|
38
|
+
|
|
39
|
+
function extractBullets(sectionText) {
|
|
40
|
+
if (!sectionText) return [];
|
|
41
|
+
const out = [];
|
|
42
|
+
for (const raw of sectionText.split('\n')) {
|
|
43
|
+
const line = raw.trim();
|
|
44
|
+
// Accept either "- ..." or numbered-list "1. ..." — normalize both to "- ...".
|
|
45
|
+
if (line.startsWith('- ')) out.push(line);
|
|
46
|
+
else {
|
|
47
|
+
const m = line.match(/^\d+\.\s+(.*)$/);
|
|
48
|
+
if (m) out.push('- ' + m[1]);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Normalize Mentions bullet lines to the canonical format that `compile_report.mjs`
|
|
55
|
+
// parses: `- **[SourceType]** Title | Snippet (source: URL, YYYY-MM-DD)`.
|
|
56
|
+
//
|
|
57
|
+
// Lane subagents deviate in practice — we've observed at least three variants:
|
|
58
|
+
// A) discussion-style: `- **HN** — [Title](url) — snippet`
|
|
59
|
+
// B) news-style: `- **2025-08-06** — [News] Outlet — "title" — url`
|
|
60
|
+
// C) canonical: `- **[SourceType]** Title | Snippet (source: URL, YYYY-MM-DD)`
|
|
61
|
+
// Rather than fighting prompt drift, normalize at merge time so downstream stays clean.
|
|
62
|
+
function normalizeMentionBullet(line) {
|
|
63
|
+
// Already canonical — nothing to do.
|
|
64
|
+
if (/^-\s*\*\*\[\w+\]\*\*/.test(line)) return line;
|
|
65
|
+
|
|
66
|
+
const urlMatch = line.match(/https?:\/\/\S+/);
|
|
67
|
+
const url = urlMatch ? urlMatch[0].replace(/[).,\]\s]+$/, '') : '';
|
|
68
|
+
const dateMatch = line.match(/\b(\d{4}-\d{2}-\d{2})\b/);
|
|
69
|
+
const date = dateMatch ? dateMatch[1] : '';
|
|
70
|
+
|
|
71
|
+
// Pattern A — `- **SourceType** — [Title](url) — snippet` (e.g. discussion lane)
|
|
72
|
+
// **SourceType** is bold but without the brackets we want in canonical form.
|
|
73
|
+
let m = line.match(/^-\s*\*\*([^*]+)\*\*\s*[—\-]\s*\[([^\]]+)\]\(([^)]+)\)\s*(?:[—\-]\s*(.*))?$/);
|
|
74
|
+
if (m) {
|
|
75
|
+
const [, rawType, title, linkUrl, snippet] = m;
|
|
76
|
+
const sourceType = rawType.trim().replace(/^\[|\]$/g, '');
|
|
77
|
+
const snippetStr = snippet && snippet.trim() ? ` | ${snippet.trim()}` : '';
|
|
78
|
+
const dateStr = date ? `, ${date}` : '';
|
|
79
|
+
return `- **[${sourceType}]** ${title.trim()}${snippetStr} (source: ${linkUrl}${dateStr})`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Pattern B — `- **YYYY-MM-DD** — [SourceType] Outlet — "title" — url` (e.g. news lane)
|
|
83
|
+
m = line.match(/^-\s*\*\*(\d{4}-\d{2}-\d{2})\*\*\s*[—\-]\s*\[(\w+)\]\s+([^—]+?)\s*[—\-]\s*"?([^"]+?)"?\s*(?:[—\-]\s*(\S+))?\s*$/);
|
|
84
|
+
if (m) {
|
|
85
|
+
const [, dateStr, sourceType, outlet, title, trailingUrl] = m;
|
|
86
|
+
const finalUrl = trailingUrl && trailingUrl.startsWith('http') ? trailingUrl : url;
|
|
87
|
+
const snippet = outlet.trim();
|
|
88
|
+
return `- **[${sourceType}]** ${title.trim()}${snippet ? ` | ${snippet}` : ''} (source: ${finalUrl || ''}, ${dateStr})`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Pattern C — generic fallback: find any `**X**` tag + URL and format canonically.
|
|
92
|
+
m = line.match(/^-\s*\*\*([^*]+)\*\*\s*(.*)/);
|
|
93
|
+
if (m && url) {
|
|
94
|
+
const rawType = m[1].trim().replace(/^\[|\]$/g, '');
|
|
95
|
+
// If the leading token is a date, try to pull a later **type** off the rest.
|
|
96
|
+
let sourceType = rawType;
|
|
97
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(rawType)) {
|
|
98
|
+
const innerType = m[2].match(/\[(\w+)\]/);
|
|
99
|
+
if (innerType) sourceType = innerType[1];
|
|
100
|
+
}
|
|
101
|
+
const linkTextM = m[2].match(/\[([^\]]+)\]/);
|
|
102
|
+
const title = linkTextM ? linkTextM[1] : m[2].replace(url, '').replace(/[—"]+/g, '').replace(/^\W+|\W+$/g, '').slice(0, 100);
|
|
103
|
+
const dateStr = date ? `, ${date}` : '';
|
|
104
|
+
return `- **[${sourceType}]** ${title.trim()} (source: ${url}${dateStr})`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Last resort — leave line untouched (preserves data even if un-parseable).
|
|
108
|
+
return line;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function urlOf(bullet) {
|
|
112
|
+
const m = bullet.match(/\(source:\s*([^,)]+)/);
|
|
113
|
+
return m ? m[1].trim() : null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function dateOf(bullet) {
|
|
117
|
+
const m = bullet.match(/\(source:\s*[^,)]+,\s*(\d{4}-\d{2}-\d{2})/);
|
|
118
|
+
return m ? m[1] : '';
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
let files;
|
|
122
|
+
try { files = readdirSync(partialsDir); } catch {
|
|
123
|
+
console.error(`No partials directory at ${partialsDir} — nothing to merge.`);
|
|
124
|
+
process.exit(0);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Group partials by slug
|
|
128
|
+
const bySlug = new Map();
|
|
129
|
+
for (const f of files) {
|
|
130
|
+
if (!f.endsWith('.md')) continue;
|
|
131
|
+
const m = f.match(/^(.+)\.([a-z]+)\.md$/);
|
|
132
|
+
if (!m) continue;
|
|
133
|
+
const slug = m[1];
|
|
134
|
+
const lane = m[2];
|
|
135
|
+
if (!LANES.includes(lane)) continue;
|
|
136
|
+
if (!bySlug.has(slug)) bySlug.set(slug, {});
|
|
137
|
+
const content = readFileSync(join(partialsDir, f), 'utf-8');
|
|
138
|
+
bySlug.get(slug)[lane] = { fm: parseFrontmatter(content), body: parseBody(content) };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
let merged = 0;
|
|
142
|
+
for (const [slug, lanes] of bySlug.entries()) {
|
|
143
|
+
const marketing = lanes.marketing;
|
|
144
|
+
if (!marketing || !marketing.fm) {
|
|
145
|
+
console.error(`[skip] ${slug}: no marketing partial — cannot form canonical frontmatter`);
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Union body sections
|
|
150
|
+
const allSections = {};
|
|
151
|
+
for (const lane of LANES) {
|
|
152
|
+
if (!lanes[lane]) continue;
|
|
153
|
+
const secs = parseSections(lanes[lane].body);
|
|
154
|
+
for (const [k, v] of Object.entries(secs)) {
|
|
155
|
+
if (!allSections[k]) allSections[k] = [];
|
|
156
|
+
allSections[k].push(v);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Normalize → dedup Mentions by URL, sort by date desc
|
|
161
|
+
const rawBullets = (allSections['Mentions'] || []).flatMap(s => extractBullets(s));
|
|
162
|
+
const mentionBullets = rawBullets.map(normalizeMentionBullet);
|
|
163
|
+
const seenUrls = new Set();
|
|
164
|
+
const dedupedMentions = [];
|
|
165
|
+
for (const b of mentionBullets) {
|
|
166
|
+
const u = urlOf(b);
|
|
167
|
+
const key = u || b; // fallback to bullet text if no URL
|
|
168
|
+
if (seenUrls.has(key)) continue;
|
|
169
|
+
seenUrls.add(key);
|
|
170
|
+
dedupedMentions.push(b);
|
|
171
|
+
}
|
|
172
|
+
dedupedMentions.sort((a, b) => {
|
|
173
|
+
const da = dateOf(a), db = dateOf(b);
|
|
174
|
+
if (da && db) return db.localeCompare(da);
|
|
175
|
+
if (da) return -1;
|
|
176
|
+
if (db) return 1;
|
|
177
|
+
return 0;
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// Dedup Benchmarks by URL
|
|
181
|
+
const benchmarkBullets = (allSections['Benchmarks'] || []).flatMap(s => extractBullets(s));
|
|
182
|
+
const seenBench = new Set();
|
|
183
|
+
const dedupedBench = [];
|
|
184
|
+
for (const b of benchmarkBullets) {
|
|
185
|
+
const m = b.match(/https?:\/\/\S+/);
|
|
186
|
+
const key = m ? m[0] : b;
|
|
187
|
+
if (seenBench.has(key)) continue;
|
|
188
|
+
seenBench.add(key);
|
|
189
|
+
dedupedBench.push(b);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Dedup Findings loosely (by exact text)
|
|
193
|
+
const findingBullets = (allSections['Research Findings'] || []).flatMap(s => extractBullets(s));
|
|
194
|
+
const dedupedFindings = [...new Set(findingBullets)];
|
|
195
|
+
|
|
196
|
+
// Merge/prefer marketing for Product/Pricing/Features/Positioning/Comparison
|
|
197
|
+
function first(key) {
|
|
198
|
+
const arr = allSections[key] || [];
|
|
199
|
+
return arr.length ? arr[0] : '';
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Rebuild frontmatter — whitelist canonical fields only. Non-marketing lane subagents
|
|
203
|
+
// sometimes leak ad-hoc meta fields (notes, searches_run, lane, etc.) into their partial's
|
|
204
|
+
// frontmatter; those are debug/summary fields, not canonical data. Drop them here.
|
|
205
|
+
const CANONICAL_FIELDS = [
|
|
206
|
+
'competitor_name', 'website', 'pricing_url',
|
|
207
|
+
'tagline', 'positioning', 'product_description', 'target_customer',
|
|
208
|
+
'pricing_model', 'pricing_tiers', 'key_features', 'integrations',
|
|
209
|
+
'headquarters', 'founded', 'employee_estimate', 'funding_info',
|
|
210
|
+
'strategic_diff',
|
|
211
|
+
];
|
|
212
|
+
// Subagents drift on canonical field names too. Common aliases observed in real runs:
|
|
213
|
+
// `competitor` → `competitor_name` (browsaur marketing subagent), `homepage` → `website`,
|
|
214
|
+
// `price_tiers` → `pricing_tiers`. Accept aliases silently.
|
|
215
|
+
//
|
|
216
|
+
// NOTE: a bare `pricing` key is mapped to `pricing_model`, NOT `pricing_tiers`. In practice
|
|
217
|
+
// subagents use `pricing` for a pricing *model* or prose summary ("usage-based", "$0.005/req")
|
|
218
|
+
// far more often than for an enumerated tier list, so routing it to `pricing_tiers` corrupted
|
|
219
|
+
// the structured tier data the overview/matrix render from. Use `price_tiers`/`pricing_tiers`
|
|
220
|
+
// explicitly for tiers.
|
|
221
|
+
const FIELD_ALIASES = {
|
|
222
|
+
'competitor': 'competitor_name',
|
|
223
|
+
'name': 'competitor_name',
|
|
224
|
+
'company': 'competitor_name',
|
|
225
|
+
'homepage': 'website',
|
|
226
|
+
'url': 'website',
|
|
227
|
+
'price_tiers': 'pricing_tiers',
|
|
228
|
+
'pricing': 'pricing_model',
|
|
229
|
+
};
|
|
230
|
+
function canonicalValue(fm, key) {
|
|
231
|
+
if (fm[key]) return fm[key];
|
|
232
|
+
for (const [alias, canonical] of Object.entries(FIELD_ALIASES)) {
|
|
233
|
+
if (canonical === key && fm[alias]) return fm[alias];
|
|
234
|
+
}
|
|
235
|
+
return undefined;
|
|
236
|
+
}
|
|
237
|
+
const mergedFm = {};
|
|
238
|
+
for (const k of CANONICAL_FIELDS) {
|
|
239
|
+
const v = canonicalValue(marketing.fm, k);
|
|
240
|
+
if (v) mergedFm[k] = v;
|
|
241
|
+
}
|
|
242
|
+
// Other lanes may fill in canonical gaps (e.g. funding_info from news, strategic_diff from technical).
|
|
243
|
+
for (const lane of LANES) {
|
|
244
|
+
if (lane === 'marketing' || !lanes[lane] || !lanes[lane].fm) continue;
|
|
245
|
+
for (const k of CANONICAL_FIELDS) {
|
|
246
|
+
if (!mergedFm[k]) {
|
|
247
|
+
const v = canonicalValue(lanes[lane].fm, k);
|
|
248
|
+
if (v) mergedFm[k] = v;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const fmLines = Object.entries(mergedFm).map(([k, v]) => `${k}: ${v}`).join('\n');
|
|
254
|
+
|
|
255
|
+
// Comparison heading may be "Comparison vs Exa" etc — find any key starting with "Comparison"
|
|
256
|
+
const comparisonKey = Object.keys(allSections).find(k => k.startsWith('Comparison'));
|
|
257
|
+
// Battle lane is format-drifty: subagents emit `## Battle Card`, `# Battle Card: X vs Y`
|
|
258
|
+
// (h1 — not picked up by parseSections), or skip the wrapper and lead with `## Landmines`.
|
|
259
|
+
// Treat the ENTIRE battle partial body as the Battle Card section regardless of heading style,
|
|
260
|
+
// so sales enablement content always lands in the merged file.
|
|
261
|
+
let battleCardBody = '';
|
|
262
|
+
if (lanes.battle && lanes.battle.body) {
|
|
263
|
+
const body = lanes.battle.body.trim();
|
|
264
|
+
// Strip the FIRST heading line if it mentions "Battle Card" — handles h1/h2/h3 and any
|
|
265
|
+
// suffix (e.g. `## Battle Card — Serper`, `# Battle Card: Tavily`). Otherwise the
|
|
266
|
+
// canonical `## Battle Card` wrapper added below produces duplicate headings.
|
|
267
|
+
battleCardBody = body.replace(/^#{1,3}\s+Battle\s*Card\b[^\n]*\n+/m, '').trim();
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const out = [
|
|
271
|
+
'---',
|
|
272
|
+
fmLines,
|
|
273
|
+
'---',
|
|
274
|
+
'',
|
|
275
|
+
first('Product') ? `## Product\n${first('Product')}\n` : '',
|
|
276
|
+
first('Pricing') ? `## Pricing\n${first('Pricing')}\n` : '',
|
|
277
|
+
first('Features') ? `## Features\n${first('Features')}\n` : '',
|
|
278
|
+
first('Positioning') ? `## Positioning\n${first('Positioning')}\n` : '',
|
|
279
|
+
comparisonKey && allSections[comparisonKey].length ? `## ${comparisonKey}\n${allSections[comparisonKey][0]}\n` : '',
|
|
280
|
+
battleCardBody ? `## Battle Card\n${battleCardBody}\n` : '',
|
|
281
|
+
dedupedMentions.length ? `## Mentions\n${dedupedMentions.join('\n')}\n` : '',
|
|
282
|
+
dedupedBench.length ? `## Benchmarks\n${dedupedBench.join('\n')}\n` : '',
|
|
283
|
+
dedupedFindings.length ? `## Research Findings\n${dedupedFindings.join('\n')}\n` : '',
|
|
284
|
+
].filter(Boolean).join('\n');
|
|
285
|
+
|
|
286
|
+
writeFileSync(join(dir, `${slug}.md`), out);
|
|
287
|
+
merged += 1;
|
|
288
|
+
console.error(`[ok] ${slug}: ${dedupedMentions.length} mentions, ${dedupedBench.length} benchmarks, ${dedupedFindings.length} findings`);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
console.log(JSON.stringify({ merged, competitors: bySlug.size }));
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: 3d-ui
|
|
3
|
+
description: Web and App implementation guide for 3D UI. Trigger when user wants actual 3D objects, perspective effects, and spatial depth.
|
|
4
|
+
date_added: "2026-06-17"
|
|
5
|
+
risk: safe
|
|
6
|
+
source: self
|
|
7
|
+
source_type: self
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# 3D UI
|
|
11
|
+
|
|
12
|
+
> "Breaking the plane. Interfaces that exist in a three-dimensional, rotatable space."
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
## When to Use
|
|
16
|
+
Use this sub-style when the user's request matches the aesthetic described above. This is a child reference of the `design-it` skill and is not meant to be triggered directly.
|
|
17
|
+
|
|
18
|
+
## Core Principles
|
|
19
|
+
1. **True Depth (Z-Axis Translation)**: Elements don't just have shadows; they physically move closer to or further from the camera.
|
|
20
|
+
2. **Perspective**: Use CSS perspective or WebGL to create realistic vanishing points.
|
|
21
|
+
3. **Interactive Rotation**: Elements should respond to mouse movement or device gyroscope by tilting or rotating in 3D space.
|
|
22
|
+
|
|
23
|
+
## Visual DNA
|
|
24
|
+
- **Colors**: Bold, striking palettes like **Midnight Luxury** or **Industrial Chic**. 3D elements need high contrast to show their geometry.
|
|
25
|
+
- **Typography**: Bold, blocky, or extruded text.
|
|
26
|
+
- **Graphics**: Instead of flat icons, use rendered 3D assets (.glb, .gltf, or high-res PNGs of 3D objects).
|
|
27
|
+
|
|
28
|
+
## Web Implementation
|
|
29
|
+
- Rely heavily on `perspective`, `transform-style: preserve-3d`, and `rotateX`/`rotateY`.
|
|
30
|
+
- **CSS Example**:
|
|
31
|
+
```css
|
|
32
|
+
.perspective-container {
|
|
33
|
+
perspective: 1000px;
|
|
34
|
+
display: flex;
|
|
35
|
+
justify-content: center;
|
|
36
|
+
align-items: center;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.card-3d {
|
|
40
|
+
width: 300px;
|
|
41
|
+
height: 400px;
|
|
42
|
+
transform-style: preserve-3d;
|
|
43
|
+
transition: transform 0.5s ease;
|
|
44
|
+
|
|
45
|
+
/* Initial slight rotation */
|
|
46
|
+
transform: rotateX(15deg) rotateY(-15deg);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.card-3d:hover {
|
|
50
|
+
/* Straighten out on hover */
|
|
51
|
+
transform: rotateX(0) rotateY(0) translateZ(50px);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* Inner elements popping out */
|
|
55
|
+
.card-content {
|
|
56
|
+
transform: translateZ(30px); /* Pushes content 30px closer to viewer */
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## App Implementation
|
|
61
|
+
|
|
62
|
+
### SwiftUI
|
|
63
|
+
```swift
|
|
64
|
+
struct Card3D: View {
|
|
65
|
+
@State private var dragOffset = CGSize.zero
|
|
66
|
+
|
|
67
|
+
var body: some View {
|
|
68
|
+
VStack {
|
|
69
|
+
Text("3D Card")
|
|
70
|
+
.font(.largeTitle.bold())
|
|
71
|
+
.foregroundColor(.white)
|
|
72
|
+
}
|
|
73
|
+
.frame(width: 300, height: 400)
|
|
74
|
+
.background(
|
|
75
|
+
LinearGradient(colors: [.blue, .purple], startPoint: .topLeading, endPoint: .bottomTrailing)
|
|
76
|
+
)
|
|
77
|
+
.cornerRadius(24)
|
|
78
|
+
.shadow(radius: 20)
|
|
79
|
+
// Magic 3D effect based on drag gesture
|
|
80
|
+
.rotation3DEffect(
|
|
81
|
+
.degrees(Double(dragOffset.width / 10)),
|
|
82
|
+
axis: (x: 0, y: 1, z: 0),
|
|
83
|
+
perspective: 0.5
|
|
84
|
+
)
|
|
85
|
+
.rotation3DEffect(
|
|
86
|
+
.degrees(Double(-dragOffset.height / 10)),
|
|
87
|
+
axis: (x: 1, y: 0, z: 0),
|
|
88
|
+
perspective: 0.5
|
|
89
|
+
)
|
|
90
|
+
.gesture(
|
|
91
|
+
DragGesture()
|
|
92
|
+
.onChanged { value in
|
|
93
|
+
withAnimation(.interactiveSpring()) {
|
|
94
|
+
dragOffset = value.translation
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
.onEnded { _ in
|
|
98
|
+
withAnimation(.spring()) {
|
|
99
|
+
dragOffset = .zero
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
- SwiftUI makes this incredibly easy with `.rotation3DEffect()`.
|
|
107
|
+
- Use the `perspective` parameter (default 1/6, higher = more distorted) to control the camera distance.
|
|
108
|
+
- Link the rotation axes (`x`, `y`) to drag gestures or CoreMotion (gyroscope) for interactive 3D UI.
|
|
109
|
+
|
|
110
|
+
### Flutter
|
|
111
|
+
```dart
|
|
112
|
+
class Card3D extends StatefulWidget {
|
|
113
|
+
@override
|
|
114
|
+
State<Card3D> createState() => _Card3DState();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
class _Card3DState extends State<Card3D> {
|
|
118
|
+
Offset _offset = Offset.zero;
|
|
119
|
+
|
|
120
|
+
@override
|
|
121
|
+
Widget build(BuildContext context) {
|
|
122
|
+
return GestureDetector(
|
|
123
|
+
onPanUpdate: (details) {
|
|
124
|
+
setState(() => _offset += details.delta);
|
|
125
|
+
},
|
|
126
|
+
onPanEnd: (_) {
|
|
127
|
+
setState(() => _offset = Offset.zero); // Snap back
|
|
128
|
+
},
|
|
129
|
+
child: TweenAnimationBuilder(
|
|
130
|
+
tween: Tween<Offset>(begin: Offset.zero, end: _offset),
|
|
131
|
+
duration: const Duration(milliseconds: 200),
|
|
132
|
+
curve: Curves.easeOut,
|
|
133
|
+
builder: (context, Offset offset, child) {
|
|
134
|
+
// Perspective Matrix
|
|
135
|
+
final transform = Matrix4.identity()
|
|
136
|
+
..setEntry(3, 2, 0.001) // perspective
|
|
137
|
+
..rotateX(-offset.dy * 0.01)
|
|
138
|
+
..rotateY(offset.dx * 0.01);
|
|
139
|
+
|
|
140
|
+
return Transform(
|
|
141
|
+
transform: transform,
|
|
142
|
+
alignment: FractionalOffset.center,
|
|
143
|
+
child: Container(
|
|
144
|
+
width: 300,
|
|
145
|
+
height: 400,
|
|
146
|
+
decoration: BoxDecoration(
|
|
147
|
+
gradient: const LinearGradient(colors: [Colors.blue, Colors.purple]),
|
|
148
|
+
borderRadius: BorderRadius.circular(24),
|
|
149
|
+
boxShadow: const [BoxShadow(color: Colors.black45, blurRadius: 20)],
|
|
150
|
+
),
|
|
151
|
+
alignment: Alignment.center,
|
|
152
|
+
child: const Text('3D Card',
|
|
153
|
+
style: TextStyle(color: Colors.white, fontSize: 32, fontWeight: FontWeight.bold)),
|
|
154
|
+
),
|
|
155
|
+
);
|
|
156
|
+
},
|
|
157
|
+
),
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
- The secret to perspective in Flutter is `Matrix4.identity()..setEntry(3, 2, 0.001)`.
|
|
163
|
+
- Wrap the target container in a `Transform` widget and apply rotations on the X and Y axes.
|
|
164
|
+
- Use `TweenAnimationBuilder` to smooth out the return-to-center physics.
|
|
165
|
+
|
|
166
|
+
### React Native
|
|
167
|
+
```jsx
|
|
168
|
+
const Card3D = () => {
|
|
169
|
+
const pan = useRef(new Animated.ValueXY()).current;
|
|
170
|
+
|
|
171
|
+
const panResponder = useRef(
|
|
172
|
+
PanResponder.create({
|
|
173
|
+
onStartShouldSetPanResponder: () => true,
|
|
174
|
+
onPanResponderMove: Animated.event([null, { dx: pan.x, dy: pan.y }], { useNativeDriver: false }),
|
|
175
|
+
onPanResponderRelease: () => {
|
|
176
|
+
Animated.spring(pan, { toValue: { x: 0, y: 0 }, useNativeDriver: false }).start();
|
|
177
|
+
},
|
|
178
|
+
})
|
|
179
|
+
).current;
|
|
180
|
+
|
|
181
|
+
// Map drag distance to degrees
|
|
182
|
+
const rotateX = pan.y.interpolate({ inputRange: [-200, 200], outputRange: ['20deg', '-20deg'] });
|
|
183
|
+
const rotateY = pan.x.interpolate({ inputRange: [-200, 200], outputRange: ['-20deg', '20deg'] });
|
|
184
|
+
|
|
185
|
+
return (
|
|
186
|
+
<Animated.View
|
|
187
|
+
{...panResponder.panHandlers}
|
|
188
|
+
style={{
|
|
189
|
+
width: 300, height: 400,
|
|
190
|
+
backgroundColor: '#6b21a8',
|
|
191
|
+
borderRadius: 24,
|
|
192
|
+
justifyContent: 'center', alignItems: 'center',
|
|
193
|
+
// Pseudo-3D transforms
|
|
194
|
+
transform: [
|
|
195
|
+
{ perspective: 1000 },
|
|
196
|
+
{ rotateX },
|
|
197
|
+
{ rotateY }
|
|
198
|
+
]
|
|
199
|
+
}}
|
|
200
|
+
>
|
|
201
|
+
<Text style={{ color: '#fff', fontSize: 32, fontWeight: '700' }}>3D Card</Text>
|
|
202
|
+
</Animated.View>
|
|
203
|
+
);
|
|
204
|
+
};
|
|
205
|
+
```
|
|
206
|
+
- True 3D models require `react-three-fiber`.
|
|
207
|
+
- For UI perspective transforms, use the `transform` array: `[{ perspective: 1000 }, { rotateX: '...' }, { rotateY: '...' }]`.
|
|
208
|
+
- `perspective` MUST be the first item in the transform array for the effect to render correctly.
|
|
209
|
+
|
|
210
|
+
### Jetpack Compose
|
|
211
|
+
```kotlin
|
|
212
|
+
@Composable
|
|
213
|
+
fun Card3D() {
|
|
214
|
+
var offset by remember { mutableStateOf(Offset.Zero) }
|
|
215
|
+
val animatedOffset by animateOffsetAsState(
|
|
216
|
+
targetValue = offset,
|
|
217
|
+
animationSpec = spring(dampingRatio = Spring.DampingRatioMediumBouncy)
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
Box(
|
|
221
|
+
modifier = Modifier
|
|
222
|
+
.size(300.dp, 400.dp)
|
|
223
|
+
.pointerInput(Unit) {
|
|
224
|
+
detectDragGestures(
|
|
225
|
+
onDrag = { change, dragAmount ->
|
|
226
|
+
change.consume()
|
|
227
|
+
offset += dragAmount
|
|
228
|
+
},
|
|
229
|
+
onDragEnd = { offset = Offset.Zero },
|
|
230
|
+
onDragCancel = { offset = Offset.Zero }
|
|
231
|
+
)
|
|
232
|
+
}
|
|
233
|
+
.graphicsLayer {
|
|
234
|
+
// Apply 3D rotation based on drag offset
|
|
235
|
+
rotationX = -animatedOffset.y * 0.1f
|
|
236
|
+
rotationY = animatedOffset.x * 0.1f
|
|
237
|
+
cameraDistance = 8f * density // Sets the perspective
|
|
238
|
+
}
|
|
239
|
+
.shadow(20.dp, RoundedCornerShape(24.dp))
|
|
240
|
+
.clip(RoundedCornerShape(24.dp))
|
|
241
|
+
.background(Brush.linearGradient(listOf(Color.Blue, Color.Magenta)))
|
|
242
|
+
) {
|
|
243
|
+
Text("3D Card",
|
|
244
|
+
color = Color.White, fontSize = 32.sp, fontWeight = FontWeight.Bold,
|
|
245
|
+
modifier = Modifier.align(Alignment.Center))
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
- Apply 3D transformations inside `Modifier.graphicsLayer { }`.
|
|
250
|
+
- Set `rotationX` and `rotationY` for the tilt.
|
|
251
|
+
- **Critical**: Set `cameraDistance` to establish the Z-axis perspective vanishing point. Usually `8f * density` is a good starting point.
|
|
252
|
+
|
|
253
|
+
## Do's and Don'ts
|
|
254
|
+
- **DO**: Tie 3D rotation to user input (mouse move on web, device tilt on mobile) for maximum impact.
|
|
255
|
+
- **DON'T**: Make text itself 3D unless it's a massive headline. 3D text is generally unreadable at small sizes.
|
|
256
|
+
|
|
257
|
+
## Limitations
|
|
258
|
+
- This is a styling reference and does not replace environment-specific validation, accessibility testing, or expert review.
|
|
259
|
+
- Ensure appropriate contrast ratios and responsive behaviors are verified separately.
|