From 6de73abf1b5231cc8b5404efcf088a63872c9cc5 Mon Sep 17 00:00:00 2001 From: Paul Bakaus Date: Tue, 24 Mar 2026 09:48:42 -0700 Subject: [PATCH 1/3] Fix invalid YAML frontmatter and consolidate build transformers Fixes #67: argument-hint values starting with [ were parsed as YAML flow sequences. Replace structured args arrays in source files with pre-formatted argument-hint strings, and quote values starting with [ or { in generateYamlFrontmatter(). Also consolidates 8 nearly-identical transformer files into a single config-driven createTransformer() factory. Adding a new provider now requires only a config object in providers.js instead of a full file. - Replace args source frontmatter with argument-hint strings - Add YAML quoting for values starting with [ or { - Add quote stripping to parseFrontmatter() for round-trip support - Create factory.js + providers.js, delete 8 individual transformers - Replace 16 explicit build.js calls with a loop over PROVIDERS - Consolidate 8 test files into 2 (factory + providers) Co-Authored-By: Claude Opus 4.6 (1M context) --- .agents/skills/adapt/SKILL.md | 2 +- .agents/skills/animate/SKILL.md | 2 +- .agents/skills/arrange/SKILL.md | 2 +- .agents/skills/audit/SKILL.md | 2 +- .agents/skills/bolder/SKILL.md | 2 +- .agents/skills/clarify/SKILL.md | 2 +- .agents/skills/colorize/SKILL.md | 2 +- .agents/skills/critique/SKILL.md | 2 +- .agents/skills/delight/SKILL.md | 2 +- .agents/skills/distill/SKILL.md | 2 +- .agents/skills/extract/SKILL.md | 2 +- .agents/skills/harden/SKILL.md | 2 +- .agents/skills/normalize/SKILL.md | 2 +- .agents/skills/onboard/SKILL.md | 2 +- .agents/skills/optimize/SKILL.md | 2 +- .agents/skills/overdrive/SKILL.md | 2 +- .agents/skills/polish/SKILL.md | 2 +- .agents/skills/quieter/SKILL.md | 2 +- .agents/skills/typeset/SKILL.md | 2 +- .claude/skills/adapt/SKILL.md | 8 +- .claude/skills/animate/SKILL.md | 5 +- .claude/skills/arrange/SKILL.md | 5 +- .claude/skills/audit/SKILL.md | 5 +- .claude/skills/bolder/SKILL.md | 5 +- .claude/skills/clarify/SKILL.md | 5 +- .claude/skills/colorize/SKILL.md | 5 +- .claude/skills/critique/SKILL.md | 5 +- .claude/skills/delight/SKILL.md | 5 +- .claude/skills/distill/SKILL.md | 5 +- .claude/skills/extract/SKILL.md | 5 +- .claude/skills/harden/SKILL.md | 5 +- .claude/skills/normalize/SKILL.md | 5 +- .claude/skills/onboard/SKILL.md | 5 +- .claude/skills/optimize/SKILL.md | 5 +- .claude/skills/overdrive/SKILL.md | 5 +- .claude/skills/polish/SKILL.md | 5 +- .claude/skills/quieter/SKILL.md | 5 +- .claude/skills/typeset/SKILL.md | 5 +- .codex/skills/adapt/SKILL.md | 2 +- .codex/skills/animate/SKILL.md | 2 +- .codex/skills/arrange/SKILL.md | 2 +- .codex/skills/audit/SKILL.md | 2 +- .codex/skills/bolder/SKILL.md | 2 +- .codex/skills/clarify/SKILL.md | 2 +- .codex/skills/colorize/SKILL.md | 2 +- .codex/skills/critique/SKILL.md | 2 +- .codex/skills/delight/SKILL.md | 2 +- .codex/skills/distill/SKILL.md | 2 +- .codex/skills/extract/SKILL.md | 2 +- .codex/skills/harden/SKILL.md | 2 +- .codex/skills/normalize/SKILL.md | 2 +- .codex/skills/onboard/SKILL.md | 2 +- .codex/skills/optimize/SKILL.md | 2 +- .codex/skills/overdrive/SKILL.md | 2 +- .codex/skills/polish/SKILL.md | 2 +- .codex/skills/quieter/SKILL.md | 2 +- .codex/skills/typeset/SKILL.md | 2 +- .opencode/skills/adapt/SKILL.md | 8 +- .opencode/skills/animate/SKILL.md | 5 +- .opencode/skills/arrange/SKILL.md | 5 +- .opencode/skills/audit/SKILL.md | 5 +- .opencode/skills/bolder/SKILL.md | 5 +- .opencode/skills/clarify/SKILL.md | 5 +- .opencode/skills/colorize/SKILL.md | 5 +- .opencode/skills/critique/SKILL.md | 5 +- .opencode/skills/delight/SKILL.md | 5 +- .opencode/skills/distill/SKILL.md | 5 +- .opencode/skills/extract/SKILL.md | 5 +- .opencode/skills/harden/SKILL.md | 5 +- .opencode/skills/normalize/SKILL.md | 5 +- .opencode/skills/onboard/SKILL.md | 5 +- .opencode/skills/optimize/SKILL.md | 5 +- .opencode/skills/overdrive/SKILL.md | 5 +- .opencode/skills/polish/SKILL.md | 5 +- .opencode/skills/quieter/SKILL.md | 5 +- .opencode/skills/typeset/SKILL.md | 5 +- public/css/styles.css | 3963 +------------------- scripts/build.js | 52 +- scripts/lib/transformers/agents.js | 69 - scripts/lib/transformers/claude-code.js | 68 - scripts/lib/transformers/codex.js | 79 - scripts/lib/transformers/cursor.js | 62 - scripts/lib/transformers/factory.js | 113 + scripts/lib/transformers/gemini.js | 66 - scripts/lib/transformers/index.js | 21 +- scripts/lib/transformers/kiro.js | 65 - scripts/lib/transformers/opencode.js | 84 - scripts/lib/transformers/pi.js | 65 - scripts/lib/transformers/providers.js | 72 + scripts/lib/utils.js | 11 +- source/skills/adapt/SKILL.md | 8 +- source/skills/animate/SKILL.md | 5 +- source/skills/arrange/SKILL.md | 5 +- source/skills/audit/SKILL.md | 5 +- source/skills/bolder/SKILL.md | 5 +- source/skills/clarify/SKILL.md | 5 +- source/skills/colorize/SKILL.md | 5 +- source/skills/critique/SKILL.md | 5 +- source/skills/delight/SKILL.md | 5 +- source/skills/distill/SKILL.md | 5 +- source/skills/extract/SKILL.md | 5 +- source/skills/harden/SKILL.md | 5 +- source/skills/normalize/SKILL.md | 5 +- source/skills/onboard/SKILL.md | 5 +- source/skills/optimize/SKILL.md | 5 +- source/skills/overdrive/SKILL.md | 5 +- source/skills/polish/SKILL.md | 5 +- source/skills/quieter/SKILL.md | 5 +- source/skills/typeset/SKILL.md | 5 +- tests/build.test.js | 7 +- tests/lib/transformers/agents.test.js | 330 -- tests/lib/transformers/claude-code.test.js | 356 -- tests/lib/transformers/codex.test.js | 308 -- tests/lib/transformers/cursor.test.js | 245 -- tests/lib/transformers/factory.test.js | 276 ++ tests/lib/transformers/gemini.test.js | 210 -- tests/lib/transformers/kiro.test.js | 302 -- tests/lib/transformers/opencode.test.js | 395 -- tests/lib/transformers/pi.test.js | 302 -- tests/lib/transformers/providers.test.js | 304 ++ tests/lib/utils.test.js | 41 +- 121 files changed, 904 insertions(+), 7332 deletions(-) delete mode 100644 scripts/lib/transformers/agents.js delete mode 100644 scripts/lib/transformers/claude-code.js delete mode 100644 scripts/lib/transformers/codex.js delete mode 100644 scripts/lib/transformers/cursor.js create mode 100644 scripts/lib/transformers/factory.js delete mode 100644 scripts/lib/transformers/gemini.js delete mode 100644 scripts/lib/transformers/kiro.js delete mode 100644 scripts/lib/transformers/opencode.js delete mode 100644 scripts/lib/transformers/pi.js create mode 100644 scripts/lib/transformers/providers.js delete mode 100644 tests/lib/transformers/agents.test.js delete mode 100644 tests/lib/transformers/claude-code.test.js delete mode 100644 tests/lib/transformers/codex.test.js delete mode 100644 tests/lib/transformers/cursor.test.js create mode 100644 tests/lib/transformers/factory.test.js delete mode 100644 tests/lib/transformers/gemini.test.js delete mode 100644 tests/lib/transformers/kiro.test.js delete mode 100644 tests/lib/transformers/opencode.test.js delete mode 100644 tests/lib/transformers/pi.test.js create mode 100644 tests/lib/transformers/providers.test.js diff --git a/.agents/skills/adapt/SKILL.md b/.agents/skills/adapt/SKILL.md index 0bf65f30a..9a0703af0 100644 --- a/.agents/skills/adapt/SKILL.md +++ b/.agents/skills/adapt/SKILL.md @@ -2,7 +2,7 @@ name: adapt description: Adapt designs to work across different screen sizes, devices, contexts, or platforms. Ensures consistent experience across varied environments. user-invocable: true -argument-hint: [TARGET=] [CONTEXT=] +argument-hint: "[target] [context (mobile, tablet, print...)]" --- Adapt existing designs to work effectively across different contexts - different screen sizes, devices, platforms, or use cases. diff --git a/.agents/skills/animate/SKILL.md b/.agents/skills/animate/SKILL.md index 3a8b2380b..420f9863e 100644 --- a/.agents/skills/animate/SKILL.md +++ b/.agents/skills/animate/SKILL.md @@ -2,7 +2,7 @@ name: animate description: Review a feature and enhance it with purposeful animations, micro-interactions, and motion effects that improve usability and delight. user-invocable: true -argument-hint: [TARGET=] +argument-hint: "[target]" --- Analyze a feature and strategically add animations and micro-interactions that enhance understanding, provide feedback, and create delight. diff --git a/.agents/skills/arrange/SKILL.md b/.agents/skills/arrange/SKILL.md index 255055c01..2f22f3faa 100644 --- a/.agents/skills/arrange/SKILL.md +++ b/.agents/skills/arrange/SKILL.md @@ -2,7 +2,7 @@ name: arrange description: Improve layout, spacing, and visual rhythm. Fixes monotonous grids, inconsistent spacing, and weak visual hierarchy to create intentional compositions. user-invocable: true -argument-hint: [TARGET=] +argument-hint: "[target]" --- Assess and improve layout and spacing that feels monotonous, crowded, or structurally weak — turning generic arrangements into intentional, rhythmic compositions. diff --git a/.agents/skills/audit/SKILL.md b/.agents/skills/audit/SKILL.md index 74cbb7475..f11131ae0 100644 --- a/.agents/skills/audit/SKILL.md +++ b/.agents/skills/audit/SKILL.md @@ -2,7 +2,7 @@ name: audit description: Perform comprehensive audit of interface quality across accessibility, performance, theming, and responsive design. Generates detailed report of issues with severity ratings and recommendations. user-invocable: true -argument-hint: [AREA=] +argument-hint: "[area (feature, page, component...)]" --- Run systematic quality checks and generate a comprehensive audit report with prioritized issues and actionable recommendations. Don't fix issues - document them for other commands to address. diff --git a/.agents/skills/bolder/SKILL.md b/.agents/skills/bolder/SKILL.md index 77e78e703..d375ad1f6 100644 --- a/.agents/skills/bolder/SKILL.md +++ b/.agents/skills/bolder/SKILL.md @@ -2,7 +2,7 @@ name: bolder description: Amplify safe or boring designs to make them more visually interesting and stimulating. Increases impact while maintaining usability. user-invocable: true -argument-hint: [TARGET=] +argument-hint: "[target]" --- Increase visual impact and personality in designs that are too safe, generic, or visually underwhelming, creating more engaging and memorable experiences. diff --git a/.agents/skills/clarify/SKILL.md b/.agents/skills/clarify/SKILL.md index 86814d2c2..f0f602fa4 100644 --- a/.agents/skills/clarify/SKILL.md +++ b/.agents/skills/clarify/SKILL.md @@ -2,7 +2,7 @@ name: clarify description: Improve unclear UX copy, error messages, microcopy, labels, and instructions. Makes interfaces easier to understand and use. user-invocable: true -argument-hint: [TARGET=] +argument-hint: "[target]" --- Identify and improve unclear, confusing, or poorly written interface text to make the product easier to understand and use. diff --git a/.agents/skills/colorize/SKILL.md b/.agents/skills/colorize/SKILL.md index beb8afd0d..7a72c3e57 100644 --- a/.agents/skills/colorize/SKILL.md +++ b/.agents/skills/colorize/SKILL.md @@ -2,7 +2,7 @@ name: colorize description: Add strategic color to features that are too monochromatic or lack visual interest. Makes interfaces more engaging and expressive. user-invocable: true -argument-hint: [TARGET=] +argument-hint: "[target]" --- Strategically introduce color to designs that are too monochromatic, gray, or lacking in visual warmth and personality. diff --git a/.agents/skills/critique/SKILL.md b/.agents/skills/critique/SKILL.md index f25717cab..62e2552fd 100644 --- a/.agents/skills/critique/SKILL.md +++ b/.agents/skills/critique/SKILL.md @@ -2,7 +2,7 @@ name: critique description: Evaluate design effectiveness from a UX perspective. Assesses visual hierarchy, information architecture, emotional resonance, and overall design quality with actionable feedback. user-invocable: true -argument-hint: [AREA=] +argument-hint: "[area (feature, page, component...)]" --- ## MANDATORY PREPARATION diff --git a/.agents/skills/delight/SKILL.md b/.agents/skills/delight/SKILL.md index 2d92c285c..9eb1edaf1 100644 --- a/.agents/skills/delight/SKILL.md +++ b/.agents/skills/delight/SKILL.md @@ -2,7 +2,7 @@ name: delight description: Add moments of joy, personality, and unexpected touches that make interfaces memorable and enjoyable to use. Elevates functional to delightful. user-invocable: true -argument-hint: [TARGET=] +argument-hint: "[target]" --- Identify opportunities to add moments of joy, personality, and unexpected polish that transform functional interfaces into delightful experiences. diff --git a/.agents/skills/distill/SKILL.md b/.agents/skills/distill/SKILL.md index 521bddb87..8d7a50faf 100644 --- a/.agents/skills/distill/SKILL.md +++ b/.agents/skills/distill/SKILL.md @@ -2,7 +2,7 @@ name: distill description: Strip designs to their essence by removing unnecessary complexity. Great design is simple, powerful, and clean. user-invocable: true -argument-hint: [TARGET=] +argument-hint: "[target]" --- Remove unnecessary complexity from designs, revealing the essential elements and creating clarity through ruthless simplification. diff --git a/.agents/skills/extract/SKILL.md b/.agents/skills/extract/SKILL.md index 57019a9d2..98f62a04c 100644 --- a/.agents/skills/extract/SKILL.md +++ b/.agents/skills/extract/SKILL.md @@ -2,7 +2,7 @@ name: extract description: Extract and consolidate reusable components, design tokens, and patterns into your design system. Identifies opportunities for systematic reuse and enriches your component library. user-invocable: true -argument-hint: [TARGET=] +argument-hint: "[target]" --- Identify reusable patterns, components, and design tokens, then extract and consolidate them into the design system for systematic reuse. diff --git a/.agents/skills/harden/SKILL.md b/.agents/skills/harden/SKILL.md index 36dc972ab..f901773a6 100644 --- a/.agents/skills/harden/SKILL.md +++ b/.agents/skills/harden/SKILL.md @@ -2,7 +2,7 @@ name: harden description: Improve interface resilience through better error handling, i18n support, text overflow handling, and edge case management. Makes interfaces robust and production-ready. user-invocable: true -argument-hint: [TARGET=] +argument-hint: "[target]" --- Strengthen interfaces against edge cases, errors, internationalization issues, and real-world usage scenarios that break idealized designs. diff --git a/.agents/skills/normalize/SKILL.md b/.agents/skills/normalize/SKILL.md index 5106b9724..f8af99752 100644 --- a/.agents/skills/normalize/SKILL.md +++ b/.agents/skills/normalize/SKILL.md @@ -2,7 +2,7 @@ name: normalize description: Normalize design to match your design system and ensure consistency user-invocable: true -argument-hint: [FEATURE=] +argument-hint: "[feature (page, route, component...)]" --- Analyze and redesign the feature to perfectly match our design system standards, aesthetics, and established patterns. diff --git a/.agents/skills/onboard/SKILL.md b/.agents/skills/onboard/SKILL.md index 966a1cda9..10f3618b1 100644 --- a/.agents/skills/onboard/SKILL.md +++ b/.agents/skills/onboard/SKILL.md @@ -2,7 +2,7 @@ name: onboard description: Design or improve onboarding flows, empty states, and first-time user experiences. Helps users get started successfully and understand value quickly. user-invocable: true -argument-hint: [TARGET=] +argument-hint: "[target]" --- ## MANDATORY PREPARATION diff --git a/.agents/skills/optimize/SKILL.md b/.agents/skills/optimize/SKILL.md index 661ab952b..3f76667f6 100644 --- a/.agents/skills/optimize/SKILL.md +++ b/.agents/skills/optimize/SKILL.md @@ -2,7 +2,7 @@ name: optimize description: Improve interface performance across loading speed, rendering, animations, images, and bundle size. Makes experiences faster and smoother. user-invocable: true -argument-hint: [TARGET=] +argument-hint: "[target]" --- Identify and fix performance issues to create faster, smoother user experiences. diff --git a/.agents/skills/overdrive/SKILL.md b/.agents/skills/overdrive/SKILL.md index 843c4ce45..36dfa8b87 100644 --- a/.agents/skills/overdrive/SKILL.md +++ b/.agents/skills/overdrive/SKILL.md @@ -2,7 +2,7 @@ name: overdrive description: Push interfaces past conventional limits with technically ambitious implementations. Whether that's a shader, a 60fps virtual table, spring physics on a dialog, or scroll-driven reveals — make users ask "how did they do that?" user-invocable: true -argument-hint: [TARGET=] +argument-hint: "[target]" --- Start your response with: diff --git a/.agents/skills/polish/SKILL.md b/.agents/skills/polish/SKILL.md index b6e75ac54..fe9e27687 100644 --- a/.agents/skills/polish/SKILL.md +++ b/.agents/skills/polish/SKILL.md @@ -2,7 +2,7 @@ name: polish description: Final quality pass before shipping. Fixes alignment, spacing, consistency, and detail issues that separate good from great. user-invocable: true -argument-hint: [TARGET=] +argument-hint: "[target]" --- ## MANDATORY PREPARATION diff --git a/.agents/skills/quieter/SKILL.md b/.agents/skills/quieter/SKILL.md index 3fa7819a4..a542c3ee9 100644 --- a/.agents/skills/quieter/SKILL.md +++ b/.agents/skills/quieter/SKILL.md @@ -2,7 +2,7 @@ name: quieter description: Tone down overly bold or visually aggressive designs. Reduces intensity while maintaining design quality and impact. user-invocable: true -argument-hint: [TARGET=] +argument-hint: "[target]" --- Reduce visual intensity in designs that are too bold, aggressive, or overstimulating, creating a more refined and approachable aesthetic without losing effectiveness. diff --git a/.agents/skills/typeset/SKILL.md b/.agents/skills/typeset/SKILL.md index 50c2e3998..d331e2745 100644 --- a/.agents/skills/typeset/SKILL.md +++ b/.agents/skills/typeset/SKILL.md @@ -2,7 +2,7 @@ name: typeset description: Improve typography by fixing font choices, hierarchy, sizing, weight consistency, and readability. Makes text feel intentional and polished. user-invocable: true -argument-hint: [TARGET=] +argument-hint: "[target]" --- Assess and improve typography that feels generic, inconsistent, or poorly structured — turning default-looking text into intentional, well-crafted type. diff --git a/.claude/skills/adapt/SKILL.md b/.claude/skills/adapt/SKILL.md index 9d46f289a..9a0703af0 100644 --- a/.claude/skills/adapt/SKILL.md +++ b/.claude/skills/adapt/SKILL.md @@ -2,13 +2,7 @@ name: adapt description: Adapt designs to work across different screen sizes, devices, contexts, or platforms. Ensures consistent experience across varied environments. user-invocable: true -args: - - name: target - description: The feature or component to adapt (optional) - required: false - - name: context - description: What to adapt for (mobile, tablet, desktop, print, email, etc.) - required: false +argument-hint: "[target] [context (mobile, tablet, print...)]" --- Adapt existing designs to work effectively across different contexts - different screen sizes, devices, platforms, or use cases. diff --git a/.claude/skills/animate/SKILL.md b/.claude/skills/animate/SKILL.md index 182b93e90..f1d74000d 100644 --- a/.claude/skills/animate/SKILL.md +++ b/.claude/skills/animate/SKILL.md @@ -2,10 +2,7 @@ name: animate description: Review a feature and enhance it with purposeful animations, micro-interactions, and motion effects that improve usability and delight. user-invocable: true -args: - - name: target - description: The feature or component to animate (optional) - required: false +argument-hint: "[target]" --- Analyze a feature and strategically add animations and micro-interactions that enhance understanding, provide feedback, and create delight. diff --git a/.claude/skills/arrange/SKILL.md b/.claude/skills/arrange/SKILL.md index 93474d0f6..2f22f3faa 100644 --- a/.claude/skills/arrange/SKILL.md +++ b/.claude/skills/arrange/SKILL.md @@ -2,10 +2,7 @@ name: arrange description: Improve layout, spacing, and visual rhythm. Fixes monotonous grids, inconsistent spacing, and weak visual hierarchy to create intentional compositions. user-invocable: true -args: - - name: target - description: The feature or component to improve layout for (optional) - required: false +argument-hint: "[target]" --- Assess and improve layout and spacing that feels monotonous, crowded, or structurally weak — turning generic arrangements into intentional, rhythmic compositions. diff --git a/.claude/skills/audit/SKILL.md b/.claude/skills/audit/SKILL.md index 9f2d8b9d7..f11131ae0 100644 --- a/.claude/skills/audit/SKILL.md +++ b/.claude/skills/audit/SKILL.md @@ -2,10 +2,7 @@ name: audit description: Perform comprehensive audit of interface quality across accessibility, performance, theming, and responsive design. Generates detailed report of issues with severity ratings and recommendations. user-invocable: true -args: - - name: area - description: The feature or area to audit (optional) - required: false +argument-hint: "[area (feature, page, component...)]" --- Run systematic quality checks and generate a comprehensive audit report with prioritized issues and actionable recommendations. Don't fix issues - document them for other commands to address. diff --git a/.claude/skills/bolder/SKILL.md b/.claude/skills/bolder/SKILL.md index adcba4f71..19c37abc1 100644 --- a/.claude/skills/bolder/SKILL.md +++ b/.claude/skills/bolder/SKILL.md @@ -2,10 +2,7 @@ name: bolder description: Amplify safe or boring designs to make them more visually interesting and stimulating. Increases impact while maintaining usability. user-invocable: true -args: - - name: target - description: The feature or component to make bolder (optional) - required: false +argument-hint: "[target]" --- Increase visual impact and personality in designs that are too safe, generic, or visually underwhelming, creating more engaging and memorable experiences. diff --git a/.claude/skills/clarify/SKILL.md b/.claude/skills/clarify/SKILL.md index e6a859286..f0f602fa4 100644 --- a/.claude/skills/clarify/SKILL.md +++ b/.claude/skills/clarify/SKILL.md @@ -2,10 +2,7 @@ name: clarify description: Improve unclear UX copy, error messages, microcopy, labels, and instructions. Makes interfaces easier to understand and use. user-invocable: true -args: - - name: target - description: The feature or component with unclear copy (optional) - required: false +argument-hint: "[target]" --- Identify and improve unclear, confusing, or poorly written interface text to make the product easier to understand and use. diff --git a/.claude/skills/colorize/SKILL.md b/.claude/skills/colorize/SKILL.md index 0bfc3731a..e7424d47a 100644 --- a/.claude/skills/colorize/SKILL.md +++ b/.claude/skills/colorize/SKILL.md @@ -2,10 +2,7 @@ name: colorize description: Add strategic color to features that are too monochromatic or lack visual interest. Makes interfaces more engaging and expressive. user-invocable: true -args: - - name: target - description: The feature or component to colorize (optional) - required: false +argument-hint: "[target]" --- Strategically introduce color to designs that are too monochromatic, gray, or lacking in visual warmth and personality. diff --git a/.claude/skills/critique/SKILL.md b/.claude/skills/critique/SKILL.md index bf735c842..62e2552fd 100644 --- a/.claude/skills/critique/SKILL.md +++ b/.claude/skills/critique/SKILL.md @@ -2,10 +2,7 @@ name: critique description: Evaluate design effectiveness from a UX perspective. Assesses visual hierarchy, information architecture, emotional resonance, and overall design quality with actionable feedback. user-invocable: true -args: - - name: area - description: The feature or area to critique (optional) - required: false +argument-hint: "[area (feature, page, component...)]" --- ## MANDATORY PREPARATION diff --git a/.claude/skills/delight/SKILL.md b/.claude/skills/delight/SKILL.md index 644bacb55..0b10c822d 100644 --- a/.claude/skills/delight/SKILL.md +++ b/.claude/skills/delight/SKILL.md @@ -2,10 +2,7 @@ name: delight description: Add moments of joy, personality, and unexpected touches that make interfaces memorable and enjoyable to use. Elevates functional to delightful. user-invocable: true -args: - - name: target - description: The feature or area to add delight to (optional) - required: false +argument-hint: "[target]" --- Identify opportunities to add moments of joy, personality, and unexpected polish that transform functional interfaces into delightful experiences. diff --git a/.claude/skills/distill/SKILL.md b/.claude/skills/distill/SKILL.md index 2c168904f..b8d2b86f6 100644 --- a/.claude/skills/distill/SKILL.md +++ b/.claude/skills/distill/SKILL.md @@ -2,10 +2,7 @@ name: distill description: Strip designs to their essence by removing unnecessary complexity. Great design is simple, powerful, and clean. user-invocable: true -args: - - name: target - description: The feature or component to distill (optional) - required: false +argument-hint: "[target]" --- Remove unnecessary complexity from designs, revealing the essential elements and creating clarity through ruthless simplification. diff --git a/.claude/skills/extract/SKILL.md b/.claude/skills/extract/SKILL.md index ca1b8cd81..98f62a04c 100644 --- a/.claude/skills/extract/SKILL.md +++ b/.claude/skills/extract/SKILL.md @@ -2,10 +2,7 @@ name: extract description: Extract and consolidate reusable components, design tokens, and patterns into your design system. Identifies opportunities for systematic reuse and enriches your component library. user-invocable: true -args: - - name: target - description: The feature, component, or area to extract from (optional) - required: false +argument-hint: "[target]" --- Identify reusable patterns, components, and design tokens, then extract and consolidate them into the design system for systematic reuse. diff --git a/.claude/skills/harden/SKILL.md b/.claude/skills/harden/SKILL.md index 867842183..f901773a6 100644 --- a/.claude/skills/harden/SKILL.md +++ b/.claude/skills/harden/SKILL.md @@ -2,10 +2,7 @@ name: harden description: Improve interface resilience through better error handling, i18n support, text overflow handling, and edge case management. Makes interfaces robust and production-ready. user-invocable: true -args: - - name: target - description: The feature or area to harden (optional) - required: false +argument-hint: "[target]" --- Strengthen interfaces against edge cases, errors, internationalization issues, and real-world usage scenarios that break idealized designs. diff --git a/.claude/skills/normalize/SKILL.md b/.claude/skills/normalize/SKILL.md index 8a8f92356..f8af99752 100644 --- a/.claude/skills/normalize/SKILL.md +++ b/.claude/skills/normalize/SKILL.md @@ -2,10 +2,7 @@ name: normalize description: Normalize design to match your design system and ensure consistency user-invocable: true -args: - - name: feature - description: The page, route, or feature to normalize (optional) - required: false +argument-hint: "[feature (page, route, component...)]" --- Analyze and redesign the feature to perfectly match our design system standards, aesthetics, and established patterns. diff --git a/.claude/skills/onboard/SKILL.md b/.claude/skills/onboard/SKILL.md index 12d246dd8..10f3618b1 100644 --- a/.claude/skills/onboard/SKILL.md +++ b/.claude/skills/onboard/SKILL.md @@ -2,10 +2,7 @@ name: onboard description: Design or improve onboarding flows, empty states, and first-time user experiences. Helps users get started successfully and understand value quickly. user-invocable: true -args: - - name: target - description: The feature or area needing onboarding (optional) - required: false +argument-hint: "[target]" --- ## MANDATORY PREPARATION diff --git a/.claude/skills/optimize/SKILL.md b/.claude/skills/optimize/SKILL.md index 7240fa9aa..3f76667f6 100644 --- a/.claude/skills/optimize/SKILL.md +++ b/.claude/skills/optimize/SKILL.md @@ -2,10 +2,7 @@ name: optimize description: Improve interface performance across loading speed, rendering, animations, images, and bundle size. Makes experiences faster and smoother. user-invocable: true -args: - - name: target - description: The feature or area to optimize (optional) - required: false +argument-hint: "[target]" --- Identify and fix performance issues to create faster, smoother user experiences. diff --git a/.claude/skills/overdrive/SKILL.md b/.claude/skills/overdrive/SKILL.md index 61817077d..3011b3c21 100644 --- a/.claude/skills/overdrive/SKILL.md +++ b/.claude/skills/overdrive/SKILL.md @@ -2,10 +2,7 @@ name: overdrive description: Push interfaces past conventional limits with technically ambitious implementations. Whether that's a shader, a 60fps virtual table, spring physics on a dialog, or scroll-driven reveals — make users ask "how did they do that?" user-invocable: true -args: - - name: target - description: The feature or area to push into overdrive (optional) - required: false +argument-hint: "[target]" --- Start your response with: diff --git a/.claude/skills/polish/SKILL.md b/.claude/skills/polish/SKILL.md index f8b8efbad..fe9e27687 100644 --- a/.claude/skills/polish/SKILL.md +++ b/.claude/skills/polish/SKILL.md @@ -2,10 +2,7 @@ name: polish description: Final quality pass before shipping. Fixes alignment, spacing, consistency, and detail issues that separate good from great. user-invocable: true -args: - - name: target - description: The feature or area to polish (optional) - required: false +argument-hint: "[target]" --- ## MANDATORY PREPARATION diff --git a/.claude/skills/quieter/SKILL.md b/.claude/skills/quieter/SKILL.md index b99ed5372..8cc092848 100644 --- a/.claude/skills/quieter/SKILL.md +++ b/.claude/skills/quieter/SKILL.md @@ -2,10 +2,7 @@ name: quieter description: Tone down overly bold or visually aggressive designs. Reduces intensity while maintaining design quality and impact. user-invocable: true -args: - - name: target - description: The feature or component to make quieter (optional) - required: false +argument-hint: "[target]" --- Reduce visual intensity in designs that are too bold, aggressive, or overstimulating, creating a more refined and approachable aesthetic without losing effectiveness. diff --git a/.claude/skills/typeset/SKILL.md b/.claude/skills/typeset/SKILL.md index 246b99ef0..d331e2745 100644 --- a/.claude/skills/typeset/SKILL.md +++ b/.claude/skills/typeset/SKILL.md @@ -2,10 +2,7 @@ name: typeset description: Improve typography by fixing font choices, hierarchy, sizing, weight consistency, and readability. Makes text feel intentional and polished. user-invocable: true -args: - - name: target - description: The feature or component to improve typography for (optional) - required: false +argument-hint: "[target]" --- Assess and improve typography that feels generic, inconsistent, or poorly structured — turning default-looking text into intentional, well-crafted type. diff --git a/.codex/skills/adapt/SKILL.md b/.codex/skills/adapt/SKILL.md index d6fa63430..0a5403f8b 100644 --- a/.codex/skills/adapt/SKILL.md +++ b/.codex/skills/adapt/SKILL.md @@ -1,7 +1,7 @@ --- name: adapt description: Adapt designs to work across different screen sizes, devices, contexts, or platforms. Ensures consistent experience across varied environments. -argument-hint: [TARGET=] [CONTEXT=] +argument-hint: "[target] [context (mobile, tablet, print...)]" --- Adapt existing designs to work effectively across different contexts - different screen sizes, devices, platforms, or use cases. diff --git a/.codex/skills/animate/SKILL.md b/.codex/skills/animate/SKILL.md index 4d7a9d83e..52eb0bf5d 100644 --- a/.codex/skills/animate/SKILL.md +++ b/.codex/skills/animate/SKILL.md @@ -1,7 +1,7 @@ --- name: animate description: Review a feature and enhance it with purposeful animations, micro-interactions, and motion effects that improve usability and delight. -argument-hint: [TARGET=] +argument-hint: "[target]" --- Analyze a feature and strategically add animations and micro-interactions that enhance understanding, provide feedback, and create delight. diff --git a/.codex/skills/arrange/SKILL.md b/.codex/skills/arrange/SKILL.md index a862918fc..27cad8e59 100644 --- a/.codex/skills/arrange/SKILL.md +++ b/.codex/skills/arrange/SKILL.md @@ -1,7 +1,7 @@ --- name: arrange description: Improve layout, spacing, and visual rhythm. Fixes monotonous grids, inconsistent spacing, and weak visual hierarchy to create intentional compositions. -argument-hint: [TARGET=] +argument-hint: "[target]" --- Assess and improve layout and spacing that feels monotonous, crowded, or structurally weak — turning generic arrangements into intentional, rhythmic compositions. diff --git a/.codex/skills/audit/SKILL.md b/.codex/skills/audit/SKILL.md index ac09ebc59..48ab66d54 100644 --- a/.codex/skills/audit/SKILL.md +++ b/.codex/skills/audit/SKILL.md @@ -1,7 +1,7 @@ --- name: audit description: Perform comprehensive audit of interface quality across accessibility, performance, theming, and responsive design. Generates detailed report of issues with severity ratings and recommendations. -argument-hint: [AREA=] +argument-hint: "[area (feature, page, component...)]" --- Run systematic quality checks and generate a comprehensive audit report with prioritized issues and actionable recommendations. Don't fix issues - document them for other commands to address. diff --git a/.codex/skills/bolder/SKILL.md b/.codex/skills/bolder/SKILL.md index 79b09ba99..2680f00cb 100644 --- a/.codex/skills/bolder/SKILL.md +++ b/.codex/skills/bolder/SKILL.md @@ -1,7 +1,7 @@ --- name: bolder description: Amplify safe or boring designs to make them more visually interesting and stimulating. Increases impact while maintaining usability. -argument-hint: [TARGET=] +argument-hint: "[target]" --- Increase visual impact and personality in designs that are too safe, generic, or visually underwhelming, creating more engaging and memorable experiences. diff --git a/.codex/skills/clarify/SKILL.md b/.codex/skills/clarify/SKILL.md index 5578658f2..88941b26e 100644 --- a/.codex/skills/clarify/SKILL.md +++ b/.codex/skills/clarify/SKILL.md @@ -1,7 +1,7 @@ --- name: clarify description: Improve unclear UX copy, error messages, microcopy, labels, and instructions. Makes interfaces easier to understand and use. -argument-hint: [TARGET=] +argument-hint: "[target]" --- Identify and improve unclear, confusing, or poorly written interface text to make the product easier to understand and use. diff --git a/.codex/skills/colorize/SKILL.md b/.codex/skills/colorize/SKILL.md index 801bb4a4e..0e511001d 100644 --- a/.codex/skills/colorize/SKILL.md +++ b/.codex/skills/colorize/SKILL.md @@ -1,7 +1,7 @@ --- name: colorize description: Add strategic color to features that are too monochromatic or lack visual interest. Makes interfaces more engaging and expressive. -argument-hint: [TARGET=] +argument-hint: "[target]" --- Strategically introduce color to designs that are too monochromatic, gray, or lacking in visual warmth and personality. diff --git a/.codex/skills/critique/SKILL.md b/.codex/skills/critique/SKILL.md index 615b3586b..6077c439a 100644 --- a/.codex/skills/critique/SKILL.md +++ b/.codex/skills/critique/SKILL.md @@ -1,7 +1,7 @@ --- name: critique description: Evaluate design effectiveness from a UX perspective. Assesses visual hierarchy, information architecture, emotional resonance, and overall design quality with actionable feedback. -argument-hint: [AREA=] +argument-hint: "[area (feature, page, component...)]" --- ## MANDATORY PREPARATION diff --git a/.codex/skills/delight/SKILL.md b/.codex/skills/delight/SKILL.md index 4f837f4fd..42ea11367 100644 --- a/.codex/skills/delight/SKILL.md +++ b/.codex/skills/delight/SKILL.md @@ -1,7 +1,7 @@ --- name: delight description: Add moments of joy, personality, and unexpected touches that make interfaces memorable and enjoyable to use. Elevates functional to delightful. -argument-hint: [TARGET=] +argument-hint: "[target]" --- Identify opportunities to add moments of joy, personality, and unexpected polish that transform functional interfaces into delightful experiences. diff --git a/.codex/skills/distill/SKILL.md b/.codex/skills/distill/SKILL.md index fcfb05e64..7f3d9ff49 100644 --- a/.codex/skills/distill/SKILL.md +++ b/.codex/skills/distill/SKILL.md @@ -1,7 +1,7 @@ --- name: distill description: Strip designs to their essence by removing unnecessary complexity. Great design is simple, powerful, and clean. -argument-hint: [TARGET=] +argument-hint: "[target]" --- Remove unnecessary complexity from designs, revealing the essential elements and creating clarity through ruthless simplification. diff --git a/.codex/skills/extract/SKILL.md b/.codex/skills/extract/SKILL.md index 42d77aa6e..ced714904 100644 --- a/.codex/skills/extract/SKILL.md +++ b/.codex/skills/extract/SKILL.md @@ -1,7 +1,7 @@ --- name: extract description: Extract and consolidate reusable components, design tokens, and patterns into your design system. Identifies opportunities for systematic reuse and enriches your component library. -argument-hint: [TARGET=] +argument-hint: "[target]" --- Identify reusable patterns, components, and design tokens, then extract and consolidate them into the design system for systematic reuse. diff --git a/.codex/skills/harden/SKILL.md b/.codex/skills/harden/SKILL.md index 154aa7356..e74bc150f 100644 --- a/.codex/skills/harden/SKILL.md +++ b/.codex/skills/harden/SKILL.md @@ -1,7 +1,7 @@ --- name: harden description: Improve interface resilience through better error handling, i18n support, text overflow handling, and edge case management. Makes interfaces robust and production-ready. -argument-hint: [TARGET=] +argument-hint: "[target]" --- Strengthen interfaces against edge cases, errors, internationalization issues, and real-world usage scenarios that break idealized designs. diff --git a/.codex/skills/normalize/SKILL.md b/.codex/skills/normalize/SKILL.md index 014fa93c3..08ff9286a 100644 --- a/.codex/skills/normalize/SKILL.md +++ b/.codex/skills/normalize/SKILL.md @@ -1,7 +1,7 @@ --- name: normalize description: Normalize design to match your design system and ensure consistency -argument-hint: [FEATURE=] +argument-hint: "[feature (page, route, component...)]" --- Analyze and redesign the feature to perfectly match our design system standards, aesthetics, and established patterns. diff --git a/.codex/skills/onboard/SKILL.md b/.codex/skills/onboard/SKILL.md index 132d2ecaf..82ddb5874 100644 --- a/.codex/skills/onboard/SKILL.md +++ b/.codex/skills/onboard/SKILL.md @@ -1,7 +1,7 @@ --- name: onboard description: Design or improve onboarding flows, empty states, and first-time user experiences. Helps users get started successfully and understand value quickly. -argument-hint: [TARGET=] +argument-hint: "[target]" --- ## MANDATORY PREPARATION diff --git a/.codex/skills/optimize/SKILL.md b/.codex/skills/optimize/SKILL.md index 6b7d8a588..35417e9f6 100644 --- a/.codex/skills/optimize/SKILL.md +++ b/.codex/skills/optimize/SKILL.md @@ -1,7 +1,7 @@ --- name: optimize description: Improve interface performance across loading speed, rendering, animations, images, and bundle size. Makes experiences faster and smoother. -argument-hint: [TARGET=] +argument-hint: "[target]" --- Identify and fix performance issues to create faster, smoother user experiences. diff --git a/.codex/skills/overdrive/SKILL.md b/.codex/skills/overdrive/SKILL.md index 9047fea9a..0b990230f 100644 --- a/.codex/skills/overdrive/SKILL.md +++ b/.codex/skills/overdrive/SKILL.md @@ -1,7 +1,7 @@ --- name: overdrive description: Push interfaces past conventional limits with technically ambitious implementations. Whether that's a shader, a 60fps virtual table, spring physics on a dialog, or scroll-driven reveals — make users ask "how did they do that?" -argument-hint: [TARGET=] +argument-hint: "[target]" --- Start your response with: diff --git a/.codex/skills/polish/SKILL.md b/.codex/skills/polish/SKILL.md index 0e229290b..4f8dea20e 100644 --- a/.codex/skills/polish/SKILL.md +++ b/.codex/skills/polish/SKILL.md @@ -1,7 +1,7 @@ --- name: polish description: Final quality pass before shipping. Fixes alignment, spacing, consistency, and detail issues that separate good from great. -argument-hint: [TARGET=] +argument-hint: "[target]" --- ## MANDATORY PREPARATION diff --git a/.codex/skills/quieter/SKILL.md b/.codex/skills/quieter/SKILL.md index a5d5a5fb1..e4ba957f8 100644 --- a/.codex/skills/quieter/SKILL.md +++ b/.codex/skills/quieter/SKILL.md @@ -1,7 +1,7 @@ --- name: quieter description: Tone down overly bold or visually aggressive designs. Reduces intensity while maintaining design quality and impact. -argument-hint: [TARGET=] +argument-hint: "[target]" --- Reduce visual intensity in designs that are too bold, aggressive, or overstimulating, creating a more refined and approachable aesthetic without losing effectiveness. diff --git a/.codex/skills/typeset/SKILL.md b/.codex/skills/typeset/SKILL.md index 899f7ad51..57a1d9dfc 100644 --- a/.codex/skills/typeset/SKILL.md +++ b/.codex/skills/typeset/SKILL.md @@ -1,7 +1,7 @@ --- name: typeset description: Improve typography by fixing font choices, hierarchy, sizing, weight consistency, and readability. Makes text feel intentional and polished. -argument-hint: [TARGET=] +argument-hint: "[target]" --- Assess and improve typography that feels generic, inconsistent, or poorly structured — turning default-looking text into intentional, well-crafted type. diff --git a/.opencode/skills/adapt/SKILL.md b/.opencode/skills/adapt/SKILL.md index 9d46f289a..9a0703af0 100644 --- a/.opencode/skills/adapt/SKILL.md +++ b/.opencode/skills/adapt/SKILL.md @@ -2,13 +2,7 @@ name: adapt description: Adapt designs to work across different screen sizes, devices, contexts, or platforms. Ensures consistent experience across varied environments. user-invocable: true -args: - - name: target - description: The feature or component to adapt (optional) - required: false - - name: context - description: What to adapt for (mobile, tablet, desktop, print, email, etc.) - required: false +argument-hint: "[target] [context (mobile, tablet, print...)]" --- Adapt existing designs to work effectively across different contexts - different screen sizes, devices, platforms, or use cases. diff --git a/.opencode/skills/animate/SKILL.md b/.opencode/skills/animate/SKILL.md index dd5c7bfe9..ef9497c3f 100644 --- a/.opencode/skills/animate/SKILL.md +++ b/.opencode/skills/animate/SKILL.md @@ -2,10 +2,7 @@ name: animate description: Review a feature and enhance it with purposeful animations, micro-interactions, and motion effects that improve usability and delight. user-invocable: true -args: - - name: target - description: The feature or component to animate (optional) - required: false +argument-hint: "[target]" --- Analyze a feature and strategically add animations and micro-interactions that enhance understanding, provide feedback, and create delight. diff --git a/.opencode/skills/arrange/SKILL.md b/.opencode/skills/arrange/SKILL.md index 93474d0f6..2f22f3faa 100644 --- a/.opencode/skills/arrange/SKILL.md +++ b/.opencode/skills/arrange/SKILL.md @@ -2,10 +2,7 @@ name: arrange description: Improve layout, spacing, and visual rhythm. Fixes monotonous grids, inconsistent spacing, and weak visual hierarchy to create intentional compositions. user-invocable: true -args: - - name: target - description: The feature or component to improve layout for (optional) - required: false +argument-hint: "[target]" --- Assess and improve layout and spacing that feels monotonous, crowded, or structurally weak — turning generic arrangements into intentional, rhythmic compositions. diff --git a/.opencode/skills/audit/SKILL.md b/.opencode/skills/audit/SKILL.md index 9f2d8b9d7..f11131ae0 100644 --- a/.opencode/skills/audit/SKILL.md +++ b/.opencode/skills/audit/SKILL.md @@ -2,10 +2,7 @@ name: audit description: Perform comprehensive audit of interface quality across accessibility, performance, theming, and responsive design. Generates detailed report of issues with severity ratings and recommendations. user-invocable: true -args: - - name: area - description: The feature or area to audit (optional) - required: false +argument-hint: "[area (feature, page, component...)]" --- Run systematic quality checks and generate a comprehensive audit report with prioritized issues and actionable recommendations. Don't fix issues - document them for other commands to address. diff --git a/.opencode/skills/bolder/SKILL.md b/.opencode/skills/bolder/SKILL.md index 192e3b97e..75500e712 100644 --- a/.opencode/skills/bolder/SKILL.md +++ b/.opencode/skills/bolder/SKILL.md @@ -2,10 +2,7 @@ name: bolder description: Amplify safe or boring designs to make them more visually interesting and stimulating. Increases impact while maintaining usability. user-invocable: true -args: - - name: target - description: The feature or component to make bolder (optional) - required: false +argument-hint: "[target]" --- Increase visual impact and personality in designs that are too safe, generic, or visually underwhelming, creating more engaging and memorable experiences. diff --git a/.opencode/skills/clarify/SKILL.md b/.opencode/skills/clarify/SKILL.md index e6a859286..f0f602fa4 100644 --- a/.opencode/skills/clarify/SKILL.md +++ b/.opencode/skills/clarify/SKILL.md @@ -2,10 +2,7 @@ name: clarify description: Improve unclear UX copy, error messages, microcopy, labels, and instructions. Makes interfaces easier to understand and use. user-invocable: true -args: - - name: target - description: The feature or component with unclear copy (optional) - required: false +argument-hint: "[target]" --- Identify and improve unclear, confusing, or poorly written interface text to make the product easier to understand and use. diff --git a/.opencode/skills/colorize/SKILL.md b/.opencode/skills/colorize/SKILL.md index b7c4aab90..c503c4b6c 100644 --- a/.opencode/skills/colorize/SKILL.md +++ b/.opencode/skills/colorize/SKILL.md @@ -2,10 +2,7 @@ name: colorize description: Add strategic color to features that are too monochromatic or lack visual interest. Makes interfaces more engaging and expressive. user-invocable: true -args: - - name: target - description: The feature or component to colorize (optional) - required: false +argument-hint: "[target]" --- Strategically introduce color to designs that are too monochromatic, gray, or lacking in visual warmth and personality. diff --git a/.opencode/skills/critique/SKILL.md b/.opencode/skills/critique/SKILL.md index bf735c842..62e2552fd 100644 --- a/.opencode/skills/critique/SKILL.md +++ b/.opencode/skills/critique/SKILL.md @@ -2,10 +2,7 @@ name: critique description: Evaluate design effectiveness from a UX perspective. Assesses visual hierarchy, information architecture, emotional resonance, and overall design quality with actionable feedback. user-invocable: true -args: - - name: area - description: The feature or area to critique (optional) - required: false +argument-hint: "[area (feature, page, component...)]" --- ## MANDATORY PREPARATION diff --git a/.opencode/skills/delight/SKILL.md b/.opencode/skills/delight/SKILL.md index 17af7899f..5aa6d85e5 100644 --- a/.opencode/skills/delight/SKILL.md +++ b/.opencode/skills/delight/SKILL.md @@ -2,10 +2,7 @@ name: delight description: Add moments of joy, personality, and unexpected touches that make interfaces memorable and enjoyable to use. Elevates functional to delightful. user-invocable: true -args: - - name: target - description: The feature or area to add delight to (optional) - required: false +argument-hint: "[target]" --- Identify opportunities to add moments of joy, personality, and unexpected polish that transform functional interfaces into delightful experiences. diff --git a/.opencode/skills/distill/SKILL.md b/.opencode/skills/distill/SKILL.md index 086b74345..0c476f598 100644 --- a/.opencode/skills/distill/SKILL.md +++ b/.opencode/skills/distill/SKILL.md @@ -2,10 +2,7 @@ name: distill description: Strip designs to their essence by removing unnecessary complexity. Great design is simple, powerful, and clean. user-invocable: true -args: - - name: target - description: The feature or component to distill (optional) - required: false +argument-hint: "[target]" --- Remove unnecessary complexity from designs, revealing the essential elements and creating clarity through ruthless simplification. diff --git a/.opencode/skills/extract/SKILL.md b/.opencode/skills/extract/SKILL.md index ca1b8cd81..98f62a04c 100644 --- a/.opencode/skills/extract/SKILL.md +++ b/.opencode/skills/extract/SKILL.md @@ -2,10 +2,7 @@ name: extract description: Extract and consolidate reusable components, design tokens, and patterns into your design system. Identifies opportunities for systematic reuse and enriches your component library. user-invocable: true -args: - - name: target - description: The feature, component, or area to extract from (optional) - required: false +argument-hint: "[target]" --- Identify reusable patterns, components, and design tokens, then extract and consolidate them into the design system for systematic reuse. diff --git a/.opencode/skills/harden/SKILL.md b/.opencode/skills/harden/SKILL.md index 867842183..f901773a6 100644 --- a/.opencode/skills/harden/SKILL.md +++ b/.opencode/skills/harden/SKILL.md @@ -2,10 +2,7 @@ name: harden description: Improve interface resilience through better error handling, i18n support, text overflow handling, and edge case management. Makes interfaces robust and production-ready. user-invocable: true -args: - - name: target - description: The feature or area to harden (optional) - required: false +argument-hint: "[target]" --- Strengthen interfaces against edge cases, errors, internationalization issues, and real-world usage scenarios that break idealized designs. diff --git a/.opencode/skills/normalize/SKILL.md b/.opencode/skills/normalize/SKILL.md index 8a8f92356..f8af99752 100644 --- a/.opencode/skills/normalize/SKILL.md +++ b/.opencode/skills/normalize/SKILL.md @@ -2,10 +2,7 @@ name: normalize description: Normalize design to match your design system and ensure consistency user-invocable: true -args: - - name: feature - description: The page, route, or feature to normalize (optional) - required: false +argument-hint: "[feature (page, route, component...)]" --- Analyze and redesign the feature to perfectly match our design system standards, aesthetics, and established patterns. diff --git a/.opencode/skills/onboard/SKILL.md b/.opencode/skills/onboard/SKILL.md index 12d246dd8..10f3618b1 100644 --- a/.opencode/skills/onboard/SKILL.md +++ b/.opencode/skills/onboard/SKILL.md @@ -2,10 +2,7 @@ name: onboard description: Design or improve onboarding flows, empty states, and first-time user experiences. Helps users get started successfully and understand value quickly. user-invocable: true -args: - - name: target - description: The feature or area needing onboarding (optional) - required: false +argument-hint: "[target]" --- ## MANDATORY PREPARATION diff --git a/.opencode/skills/optimize/SKILL.md b/.opencode/skills/optimize/SKILL.md index 7240fa9aa..3f76667f6 100644 --- a/.opencode/skills/optimize/SKILL.md +++ b/.opencode/skills/optimize/SKILL.md @@ -2,10 +2,7 @@ name: optimize description: Improve interface performance across loading speed, rendering, animations, images, and bundle size. Makes experiences faster and smoother. user-invocable: true -args: - - name: target - description: The feature or area to optimize (optional) - required: false +argument-hint: "[target]" --- Identify and fix performance issues to create faster, smoother user experiences. diff --git a/.opencode/skills/overdrive/SKILL.md b/.opencode/skills/overdrive/SKILL.md index 96c945a1e..57a60bda3 100644 --- a/.opencode/skills/overdrive/SKILL.md +++ b/.opencode/skills/overdrive/SKILL.md @@ -2,10 +2,7 @@ name: overdrive description: Push interfaces past conventional limits with technically ambitious implementations. Whether that's a shader, a 60fps virtual table, spring physics on a dialog, or scroll-driven reveals — make users ask "how did they do that?" user-invocable: true -args: - - name: target - description: The feature or area to push into overdrive (optional) - required: false +argument-hint: "[target]" --- Start your response with: diff --git a/.opencode/skills/polish/SKILL.md b/.opencode/skills/polish/SKILL.md index f8b8efbad..fe9e27687 100644 --- a/.opencode/skills/polish/SKILL.md +++ b/.opencode/skills/polish/SKILL.md @@ -2,10 +2,7 @@ name: polish description: Final quality pass before shipping. Fixes alignment, spacing, consistency, and detail issues that separate good from great. user-invocable: true -args: - - name: target - description: The feature or area to polish (optional) - required: false +argument-hint: "[target]" --- ## MANDATORY PREPARATION diff --git a/.opencode/skills/quieter/SKILL.md b/.opencode/skills/quieter/SKILL.md index 64566b1af..09bcdcbe5 100644 --- a/.opencode/skills/quieter/SKILL.md +++ b/.opencode/skills/quieter/SKILL.md @@ -2,10 +2,7 @@ name: quieter description: Tone down overly bold or visually aggressive designs. Reduces intensity while maintaining design quality and impact. user-invocable: true -args: - - name: target - description: The feature or component to make quieter (optional) - required: false +argument-hint: "[target]" --- Reduce visual intensity in designs that are too bold, aggressive, or overstimulating, creating a more refined and approachable aesthetic without losing effectiveness. diff --git a/.opencode/skills/typeset/SKILL.md b/.opencode/skills/typeset/SKILL.md index 246b99ef0..d331e2745 100644 --- a/.opencode/skills/typeset/SKILL.md +++ b/.opencode/skills/typeset/SKILL.md @@ -2,10 +2,7 @@ name: typeset description: Improve typography by fixing font choices, hierarchy, sizing, weight consistency, and readability. Makes text feel intentional and polished. user-invocable: true -args: - - name: target - description: The feature or component to improve typography for (optional) - required: false +argument-hint: "[target]" --- Assess and improve typography that feels generic, inconsistent, or poorly structured — turning default-looking text into intentional, well-crafted type. diff --git a/public/css/styles.css b/public/css/styles.css index 5e3aef6cb..6555d93b0 100644 --- a/public/css/styles.css +++ b/public/css/styles.css @@ -1,3963 +1,2 @@ /*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */ -.split-container::before { - content: ''; - position: absolute; - inset: 0; - background-image: linear-gradient(var(--color-mist) 1px, transparent 1px), linear-gradient(90deg, var(--color-mist) 1px, transparent 1px); - background-size: 20px 20px; - opacity: 0.3; - pointer-events: none; -} -.split-container::after { - content: '← Drag →'; - position: absolute; - bottom: 12px; - left: 50%; - transform: translateX(-50%); - font-size: 0.625rem; - font-weight: 600; - letter-spacing: 0.1em; - text-transform: uppercase; - color: var(--color-ash); - background: var(--color-paper); - padding: 4px 12px; - border-radius: 4px; - opacity: 0.8; - transition: opacity 0.3s ease; - z-index: 10; -} -.split-container:hover::after { - opacity: 0; -} -.split-after .impeccable-card { - box-shadow: 0 10px 40px rgba(0,0,0,0.08); -} -@keyframes splitEntry { - from { - opacity: 0; - transform: translateX(-50%) skewX(-10deg) scaleY(0.8); - } - to { - opacity: 1; - transform: translateX(-50%) skewX(-10deg) scaleY(1); - } -} -.split-divider { - animation: splitEntry 0.6s var(--ease-out) 0.3s backwards; -} -.split-label-item { - transition: color var(--duration-fast) var(--ease-out); - cursor: default; -} -.split-label-item:hover { - color: var(--color-text); -} -.split-label-item[data-point="after"]:hover .split-label-dot--accent { - transform: scale(1.3); -} -.split-label-dot { - transition: transform var(--duration-fast) var(--ease-spring); -} -.split-badge { - position: absolute; - top: 10px; - font-size: 0.625rem; - font-weight: 600; - letter-spacing: 0.08em; - text-transform: uppercase; - padding: 3px 8px; - border-radius: 3px; - z-index: 5; - pointer-events: none; -} -.split-badge--before { - left: 10px; - color: var(--color-ash); - background: var(--color-paper); - border: 1px solid var(--color-mist); -} -.split-badge--after { - right: 10px; - color: var(--color-paper); - background: var(--color-accent); -} -@media (hover: none) { - .split-container::after { - content: '← Swipe →'; - } -} -@media (max-width: 600px) { - .split-label { - font-size: 0.5625rem; - padding: 4px 10px; - } -} -.commands-section { - position: relative; - padding: var(--spacing-xl) 0; - background: var(--color-paper); -} -.commands-gallery { - display: block; -} -.commands-container { - display: grid; - grid-template-columns: 1fr 1.2fr; - gap: var(--spacing-2xl); - align-items: start; -} -@media (max-width: 900px) { - .commands-container { - grid-template-columns: 1fr; - } -} -.command-manual { - display: flex; - flex-direction: column; - gap: var(--spacing-sm); - padding-bottom: 20vh; -} -.command-category-header { - font-family: var(--font-display); - font-size: 0.875rem; - font-weight: 600; - color: var(--color-accent); - text-transform: uppercase; - letter-spacing: 0.1em; - padding: var(--spacing-lg) var(--spacing-lg) var(--spacing-sm); - margin-top: var(--spacing-md); - border-bottom: 1px solid var(--color-mist); -} -.command-category-header:first-child { - margin-top: 0; -} -.manual-entry { - position: relative; - padding: var(--spacing-lg); - padding-left: calc(var(--spacing-lg) + 16px); - border-left: 2px solid var(--color-mist); - transition: border-color 0.4s var(--ease-out), opacity 0.4s var(--ease-out), background 0.4s var(--ease-out), transform 0.4s var(--ease-out); - opacity: 0.4; - cursor: pointer; - transform: translateX(-16px); -} -.manual-entry:hover { - opacity: 0.7; -} -.manual-entry.active { - border-left-color: var(--color-accent); - opacity: 1; - transform: translateX(0); - background: linear-gradient(to right, var(--color-bg), transparent); -} -.manual-cmd-name { - font-family: var(--font-mono); - font-size: 1.5rem; - margin: 0 0 var(--spacing-sm); - color: var(--color-ink); - font-weight: 500; -} -.beta-badge { - font-family: var(--font-body); - font-size: 0.55rem; - font-weight: 600; - letter-spacing: 0.08em; - text-transform: uppercase; - color: var(--color-accent); - border: 1px solid var(--color-accent); - border-radius: 3px; - padding: 1px 5px; - vertical-align: middle; - margin-left: 6px; -} -.manual-cmd-desc { - font-size: 1rem; - line-height: 1.6; - color: var(--color-charcoal); - margin: 0; -} -.manual-cmd-rel { - font-size: 0.8125rem; - color: var(--color-ash); - margin-top: var(--spacing-sm); - display: flex; - align-items: center; - gap: 0.5ch; - flex-wrap: wrap; -} -.manual-cmd-rel .rel-icon { - color: var(--color-accent); - font-weight: 600; -} -.manual-cmd-rel code { - font-family: var(--font-mono); - font-size: 0.75rem; - background: var(--color-mist); - padding: 2px 6px; - border-radius: 3px; - color: var(--color-ink); -} -.glass-terminal-wrapper { - position: sticky; - top: var(--spacing-xl); - height: calc(100vh - var(--spacing-xl) * 2); - max-height: 800px; - min-height: 500px; -} -.terminal-stack { - position: relative; - height: 100%; - perspective: 1200px; -} -.terminal-stack-tabs { - position: absolute; - top: -31px; - right: 8px; - display: flex; - gap: 4px; - z-index: 10; -} -.terminal-stack-tab { - font-family: var(--font-mono); - font-size: 0.75rem; - padding: 5px 12px; - background: var(--color-cream); - border: 1px solid var(--color-mist); - border-bottom: none; - border-radius: 6px 6px 0 0; - color: var(--color-ash); - cursor: pointer; - transition: all 0.2s ease; -} -.terminal-stack-tab:hover { - background: var(--color-paper); - color: var(--color-charcoal); -} -.terminal-stack-tab.active { - background: var(--color-paper); - color: var(--color-ink); - border-color: var(--color-mist); -} -.terminal-window { - position: absolute; - inset: 0; - transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s ease, filter 0.3s ease; - transform-origin: center bottom; -} -.terminal-window--demo { - z-index: 2; -} -.terminal-window--demo.is-back { - transform: translateY(16px) translateX(12px) scale(0.96); - opacity: 0.6; - filter: brightness(0.92); - pointer-events: none; - z-index: 1; -} -.terminal-window--source { - z-index: 1; - transform: translateY(16px) translateX(12px) scale(0.96); - opacity: 0.6; - filter: brightness(0.92); - pointer-events: none; -} -.terminal-window--source.is-front { - transform: translateY(0) translateX(0) scale(1); - opacity: 1; - filter: brightness(1); - pointer-events: auto; - z-index: 2; -} -.source-window { - background: var(--color-paper); - backdrop-filter: blur(12px); - -webkit-backdrop-filter: blur(12px); - border: 1px solid var(--color-mist); - border-radius: 8px; - box-shadow: 0 20px 60px -10px rgba(0,0,0,0.15); - height: 100%; - display: flex; - flex-direction: column; - overflow: hidden; -} -.source-header { - background: var(--color-cream); - padding: 12px 16px; - display: flex; - align-items: center; - gap: 8px; - border-bottom: 1px solid var(--color-mist); - flex-shrink: 0; -} -.source-title { - font-family: var(--font-mono); - font-size: 0.875rem; - color: var(--color-ink); - font-weight: 500; -} -.source-body { - flex: 1; - padding: var(--spacing-md); - font-family: var(--font-mono); - font-size: 0.75rem; - line-height: 1.5; - color: var(--color-charcoal); - overflow-y: auto; - overscroll-behavior: contain; - white-space: pre-wrap; - word-break: break-word; - background: var(--color-cream); -} -.source-loading { - color: var(--color-ash); - font-style: italic; -} -@media (max-width: 900px) { - .glass-terminal-wrapper { - display: none; - } -} -.glass-terminal { - background: var(--color-paper); - backdrop-filter: blur(12px); - -webkit-backdrop-filter: blur(12px); - border: 1px solid var(--color-mist); - border-radius: 8px; - box-shadow: 0 20px 60px -10px rgba(0,0,0,0.15); - height: 100%; - display: flex; - flex-direction: column; - overflow: hidden; -} -.terminal-header { - background: var(--color-cream); - padding: 12px 16px; - display: flex; - align-items: center; - gap: 8px; - border-bottom: 1px solid var(--color-mist); -} -.terminal-dot { - width: 10px; - height: 10px; - border-radius: 50%; -} -.terminal-dot.red { - background: #ff5f56; -} -.terminal-dot.yellow { - background: #ffbd2e; -} -.terminal-dot.green { - background: #27c93f; -} -.terminal-title { - margin-left: auto; - font-family: var(--font-mono); - font-size: 0.75rem; - color: var(--color-ash); -} -.terminal-body { - flex: 1; - padding: var(--spacing-md); - font-family: var(--font-mono); - font-size: 0.9375rem; - color: var(--color-ink); - overflow-y: auto; - display: flex; - flex-direction: column; - min-height: 0; -} -.terminal-line { - margin-bottom: var(--spacing-sm); - display: flex; - gap: var(--spacing-sm); - line-height: 1.5; -} -.terminal-prompt { - color: var(--color-accent); - user-select: none; - font-weight: bold; -} -.terminal-cursor { - display: inline-block; - width: 8px; - height: 1.2em; - background: var(--color-accent); - vertical-align: middle; - animation: blink 1s step-end infinite; -} -.terminal-output { - color: var(--color-ash); - margin-bottom: var(--spacing-md); - white-space: pre-wrap; -} -@media (max-height: 800px) { - .terminal-output { - display: none; - } -} -.terminal-cmd { - color: var(--color-accent); - font-weight: 600; -} -.terminal-step { - color: var(--color-charcoal); -} -.terminal-done { - color: var(--color-success, #22c55e); - font-weight: 500; -} -.terminal-preview { - background: var(--color-paper); - margin: var(--spacing-sm) 0; - flex: 1; - min-height: 0; - overflow: hidden; - border-radius: 12px; -} -.terminal-cursor-line { - flex-shrink: 0; - margin-top: var(--spacing-sm) !important; -} -.terminal-preview .demo-split-comparison { - display: flex; - flex-direction: column; - height: 100%; -} -.terminal-preview .demo-split-comparison .split-container { - position: relative; - flex: 1; - min-height: 0; - overflow: hidden; - cursor: ew-resize; - user-select: none; - background: var(--color-cream); -} -.terminal-preview .demo-split-comparison .split-before, .terminal-preview .demo-split-comparison .split-after { - position: absolute; - inset: 0; - display: flex; - align-items: center; - justify-content: center; - padding: var(--spacing-md); -} -.terminal-preview .demo-split-comparison .split-before { - z-index: 1; - background: var(--color-cream); -} -.terminal-preview .demo-split-comparison .split-after { - z-index: 2; - background: var(--color-paper); - clip-path: polygon(58% 0%, 100% 0%, 100% 100%, 42% 100%); -} -.terminal-preview .demo-split-comparison .split-content { - width: 100%; - max-width: 280px; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; -} -.terminal-preview .demo-split-comparison .split-divider { - position: absolute; - top: 0; - bottom: 0; - left: 50%; - width: 2px; - background: var(--color-accent); - transform: translateX(-50%) skewX(-10deg); - pointer-events: none; - z-index: 3; - box-shadow: 0 0 12px rgba(0,0,0,0.1); -} -.terminal-preview .demo-split-comparison .split-label { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%) skewX(10deg); - font-size: 0.5625rem; - font-weight: 600; - letter-spacing: 0.08em; - text-transform: uppercase; - color: var(--color-paper); - background: var(--color-accent); - padding: 4px 10px; - border-radius: 3px; - white-space: nowrap; -} -.terminal-preview .demo-split-comparison .demo-caption { - flex-shrink: 0; - font-size: 0.75rem; - color: var(--color-ash); - text-align: center; - padding: var(--spacing-sm) var(--spacing-md); -} -@keyframes blink { - 50% { - opacity: 0; - } -} -.casestudies-section { - position: relative; - padding: var(--spacing-2xl) 0; - border-top: 1px solid var(--color-mist); -} -.transformations-tabbed { - margin-top: var(--spacing-xl); -} -.transformation-tabs { - display: flex; - gap: var(--spacing-xs); - border-bottom: 1px solid var(--color-mist); - margin-bottom: var(--spacing-lg); -} -.transformation-tab { - font-family: var(--font-display); - font-size: 0.9375rem; - font-weight: 500; - color: var(--color-ash); - background: none; - border: none; - padding: var(--spacing-sm) var(--spacing-md); - cursor: pointer; - position: relative; - transition: color 0.2s ease; -} -.transformation-tab:hover { - color: var(--color-charcoal); -} -.transformation-tab.active { - color: var(--color-ink); -} -.transformation-tab.active::after { - content: ''; - position: absolute; - bottom: -1px; - left: 0; - right: 0; - height: 2px; - background: var(--color-accent); -} -.transformation-panels { - position: relative; -} -.transformation-panel { - display: none; - flex-direction: column; - gap: var(--spacing-lg); - animation: fadeInPanel 0.3s ease; -} -.transformation-panel.active { - display: flex; -} -@keyframes fadeInPanel { - from { - opacity: 0; - transform: translateY(8px); - } - to { - opacity: 1; - transform: translateY(0); - } -} -.transformation-images { - display: flex; - align-items: center; - gap: var(--spacing-md); -} -.transformation-before, .transformation-after { - flex: 1; - margin: 0; -} -.transformation-before img, .transformation-after img, .transformation-placeholder { - width: 100%; - aspect-ratio: 16 / 10; - object-fit: cover; - border-radius: 8px; - border: 1px solid var(--color-mist); - cursor: pointer; - transition: transform 0.2s ease, box-shadow 0.2s ease; -} -.transformation-before img:hover, .transformation-after img:hover, .transformation-placeholder:hover { - transform: scale(1.02); - box-shadow: 0 8px 24px -4px rgba(0,0,0,0.15); -} -.transformation-placeholder { - background: linear-gradient(135deg, var(--color-mist) 0%, var(--color-cream) 100%); - display: flex; - align-items: center; - justify-content: center; - color: var(--color-ash); - font-size: 0.8125rem; - font-style: italic; -} -.transformation-before figcaption, .transformation-after figcaption { - font-size: 0.75rem; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.05em; - color: var(--color-ash); - margin-top: var(--spacing-xs); - text-align: center; -} -.transformation-arrow { - font-size: 1.5rem; - color: var(--color-accent); - font-weight: 300; - flex-shrink: 0; -} -.transformation-info { - max-width: 600px; -} -.transformation-title { - font-family: var(--font-display); - font-size: 1.25rem; - font-weight: 600; - color: var(--color-ink); - margin: 0 0 var(--spacing-xs); -} -.transformation-desc { - font-size: 0.9375rem; - color: var(--color-charcoal); - line-height: 1.6; - margin: 0 0 var(--spacing-sm); -} -.transformation-commands { - display: flex; - flex-wrap: wrap; - gap: 6px; -} -.transformation-command { - font-family: var(--font-mono); - font-size: 0.75rem; - background: var(--color-mist); - color: var(--color-charcoal); - padding: 4px 10px; - border-radius: 4px; -} -.lightbox { - position: fixed; - inset: 0; - background: rgba(0, 0, 0, 0.9); - display: flex; - align-items: center; - justify-content: center; - z-index: 1000; - opacity: 0; - visibility: hidden; - transition: opacity 0.3s ease, visibility 0.3s ease; -} -.lightbox.active { - opacity: 1; - visibility: visible; -} -.lightbox-close { - position: absolute; - top: 20px; - right: 24px; - background: none; - border: none; - color: white; - font-size: 2.5rem; - cursor: pointer; - opacity: 0.7; - transition: opacity 0.2s ease; - line-height: 1; -} -.lightbox-close:hover { - opacity: 1; -} -.lightbox-image { - max-width: 90vw; - max-height: 85vh; - object-fit: contain; - border-radius: 8px; - box-shadow: 0 20px 60px rgba(0,0,0,0.5); -} -@media (max-width: 768px) { - .transformation-images { - flex-direction: column; - } - .transformation-arrow { - transform: rotate(90deg); - } - .transformation-before, .transformation-after { - width: 100%; - } -} -.hero-version-link { - font-size: 0.8125rem; - color: var(--color-ash); - margin-top: var(--spacing-sm); -} -.hero-version-link a { - color: var(--color-ash); - text-decoration: none; - border-bottom: 1px solid transparent; - transition: color 0.2s ease, border-color 0.2s ease; -} -.hero-version-link a:hover { - color: var(--color-accent); - border-bottom-color: var(--color-accent); -} -.changelog-section { - position: relative; - padding: var(--spacing-xl) 0; - border-top: 1px solid var(--color-mist); -} -.changelog-list { - display: flex; - flex-direction: column; - gap: 0; -} -.changelog-entry { - padding: var(--spacing-md) 0; - border-bottom: 1px solid var(--color-mist); -} -.changelog-entry:first-child { - border-top: 1px solid var(--color-mist); -} -.changelog-version-header { - display: flex; - align-items: baseline; - gap: var(--spacing-sm); - margin-bottom: var(--spacing-sm); -} -.changelog-version { - font-family: var(--font-mono); - font-size: 1.125rem; - font-weight: 600; - color: var(--color-ink); -} -.changelog-date { - font-size: 0.8125rem; - color: var(--color-ash); -} -.changelog-items { - margin: 0; - padding-left: var(--spacing-md); - color: var(--color-charcoal); - line-height: 1.7; -} -.changelog-items li { - margin-bottom: var(--spacing-xs); -} -.changelog-items code { - font-family: var(--font-mono); - font-size: 0.875em; - background: var(--color-mist); - padding: 2px 6px; - border-radius: 3px; - color: var(--color-ink); -} -.faq-section { - position: relative; - padding: var(--spacing-xl) 0; - border-top: 1px solid var(--color-mist); -} -.faq-list { - display: flex; - flex-direction: column; - gap: 0; -} -.faq-item { - border-bottom: 1px solid var(--color-mist); -} -.faq-item:first-child { - border-top: 1px solid var(--color-mist); -} -.faq-question { - font-family: var(--font-display); - font-size: 1.125rem; - font-weight: 500; - color: var(--color-ink); - padding: var(--spacing-md) 0; - cursor: pointer; - list-style: none; - display: flex; - align-items: center; - justify-content: space-between; - transition: color 0.2s ease; -} -.faq-question::-webkit-details-marker { - display: none; -} -.faq-question::after { - content: '+'; - font-family: var(--font-body); - font-size: 1.5rem; - font-weight: 300; - color: var(--color-accent); - transition: transform 0.3s var(--ease-out); -} -.faq-item[open] .faq-question::after { - transform: rotate(45deg); -} -.faq-question:hover { - color: var(--color-accent); -} -.faq-answer { - padding: 0 0 var(--spacing-md); - color: var(--color-charcoal); - line-height: 1.7; - animation: faqFadeIn 0.3s var(--ease-out); -} -.faq-answer p { - margin: 0 0 var(--spacing-sm); -} -.faq-answer p:last-child { - margin-bottom: 0; -} -.faq-answer ul { - margin: var(--spacing-sm) 0; - padding-left: var(--spacing-md); -} -.faq-answer li { - margin-bottom: var(--spacing-xs); -} -.faq-answer code { - font-family: var(--font-mono); - font-size: 0.875em; - background: var(--color-mist); - padding: 2px 6px; - border-radius: 3px; - color: var(--color-ink); -} -.faq-answer a { - color: var(--color-accent); - text-decoration: none; - border-bottom: 1px solid transparent; - transition: border-color 0.2s ease; -} -.faq-answer a:hover { - border-bottom-color: var(--color-accent); -} -@keyframes faqFadeIn { - from { - opacity: 0; - transform: translateY(-8px); - } - to { - opacity: 1; - transform: translateY(0); - } -} -.skills-section { - position: relative; - padding: var(--spacing-xl) 0; - overflow: hidden; - background: var(--color-bg); -} -.skills-gallery { - display: block; - position: relative; -} -.gallery-track { - display: flex; - gap: var(--spacing-lg); - overflow-x: auto; - scroll-snap-type: x mandatory; - padding: var(--spacing-md) var(--spacing-lg) var(--spacing-xl); - -webkit-overflow-scrolling: touch; - scrollbar-width: none; - cursor: grab; -} -.gallery-track:active { - cursor: grabbing; -} -.gallery-track::-webkit-scrollbar { - display: none; -} -.gallery-frame { - flex: 0 0 80vw; - max-width: 1100px; - scroll-snap-align: center; - position: relative; - background: var(--color-paper); - border: 1px solid var(--color-mist); - box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05), 0 20px 50px -10px rgba(0, 0, 0, 0.1); - border-radius: 2px; - overflow: hidden; - opacity: 0.4; - transform: scale(0.95); - transition: opacity 0.6s var(--ease-out), transform 0.6s var(--ease-out), box-shadow 0.6s var(--ease-out); -} -.gallery-frame.active { - opacity: 1; - transform: scale(1); - box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 40px 100px -20px rgba(0, 0, 0, 0.2); - border-color: var(--color-charcoal); - border-width: 1px; -} -.gallery-content { - display: grid; - grid-template-columns: 1.2fr 1fr; - height: 600px; -} -@media (max-width: 900px) { - .gallery-frame { - flex: 0 0 90vw; - } - .gallery-content { - grid-template-columns: 1fr; - height: auto; - min-height: 600px; - } -} -.gallery-visual { - background: var(--color-cream); - border-right: 1px solid var(--color-mist); - position: relative; - overflow: hidden; - display: flex; - align-items: center; - justify-content: center; - padding: var(--spacing-lg); -} -.gallery-info { - padding: var(--spacing-xl); - display: flex; - flex-direction: column; - overflow-y: auto; -} -.gallery-header { - margin-bottom: var(--spacing-lg); -} -.gallery-title { - font-family: var(--font-display); - font-size: 2.5rem; - font-style: italic; - margin: 0 0 var(--spacing-xs); - color: var(--color-ink); -} -.gallery-meta { - font-family: var(--font-mono); - font-size: 0.75rem; - text-transform: uppercase; - letter-spacing: 0.1em; - color: var(--color-ash); -} -.gallery-desc { - font-size: 1.125rem; - line-height: 1.6; - color: var(--color-charcoal); - margin-bottom: var(--spacing-xl); - max-width: 45ch; -} -.gallery-tags { - display: flex; - flex-wrap: wrap; - gap: var(--spacing-xs); - margin-top: auto; -} -.gallery-tag { - padding: 6px 12px; - border: 1px solid var(--color-mist); - border-radius: 4px; - font-size: 0.8125rem; - color: var(--color-ash); -} -.gallery-map { - display: flex; - justify-content: center; - gap: 8px; - margin-top: var(--spacing-lg); -} -.gallery-dot { - width: 40px; - height: 2px; - background: var(--color-mist); - cursor: pointer; - transition: all 0.3s ease; - position: relative; - border: none; - padding: 0; - font: inherit; -} -.gallery-dot::after { - content: ''; - position: absolute; - top: -10px; - bottom: -10px; - left: 0; - right: 0; -} -.gallery-dot:focus-visible { - outline: 2px solid var(--color-accent); - outline-offset: 4px; - border-radius: 1px; -} -.gallery-dot.active { - background: var(--color-accent); - height: 4px; -} -.demo-tabbed-container { - display: flex; - flex-direction: column; -} -.demo-tabs { - display: flex; - gap: 0; - margin-bottom: 0; - justify-content: center; - background: var(--color-paper); - border-bottom: 1px solid var(--color-mist); -} -.demo-tab { - padding: var(--spacing-sm) var(--spacing-lg); - background: transparent; - border: none; - border-bottom: 2px solid transparent; - font-family: var(--font-mono); - font-size: 0.75rem; - font-weight: 500; - letter-spacing: 0.05em; - text-transform: uppercase; - color: var(--color-ash); - cursor: pointer; - transition: all var(--duration-fast) var(--ease-out); -} -.demo-tab:hover { - color: var(--color-text); - background: var(--color-cream); -} -.demo-tab.active { - color: var(--color-accent); - border-bottom-color: var(--color-accent); - background: var(--color-accent-dim); -} -.demo-panels { - flex: 1; -} -.demo-panel { - display: none; -} -.demo-panel.active { - display: block; - animation: fadeSlideIn 0.3s var(--ease-out); -} -@keyframes fadeSlideIn { - from { - opacity: 0; - transform: translateY(10px); - } - to { - opacity: 1; - transform: translateY(0); - } -} -.demo-container { - background: var(--color-paper); - border: none; - border-radius: 0; - overflow: hidden; -} -.demo-header { - display: flex; - align-items: center; - justify-content: center; - padding: var(--spacing-sm) var(--spacing-md); - background: var(--color-paper); - border-bottom: 1px solid var(--color-mist); - min-height: 48px; -} -.demo-toggle { - display: flex; - align-items: center; - gap: var(--spacing-md); -} -.demo-toggle-label { - font-family: var(--font-mono); - font-size: 0.6875rem; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.08em; - color: var(--color-ash); - transition: color var(--duration-fast) var(--ease-out); - cursor: pointer; -} -.demo-toggle-label:hover { - color: var(--color-text); -} -.demo-toggle-label.active { - color: var(--color-accent); -} -.demo-toggle-switch { - position: relative; - width: 44px; - height: 24px; - background: var(--color-mist); - border-radius: 12px; - cursor: pointer; - transition: background var(--duration-fast) var(--ease-out); - border: 1px solid transparent; - padding: 0; - font: inherit; -} -.demo-toggle-switch:focus-visible { - outline: 2px solid var(--color-accent); - outline-offset: 2px; -} -.demo-toggle-switch:hover { - border-color: var(--color-ash); -} -.demo-toggle-switch::after { - content: ''; - position: absolute; - top: 3px; - left: 3px; - width: 16px; - height: 16px; - background: var(--color-paper); - border-radius: 50%; - box-shadow: 0 1px 4px rgba(0,0,0,0.15); - transition: transform var(--duration-base) var(--ease-spring); -} -.demo-toggle-switch.active { - background: var(--color-accent); -} -.demo-toggle-switch.active::after { - transform: translateX(20px); -} -.demo-viewport { - padding: var(--spacing-xl); - display: flex; - align-items: center; - justify-content: center; - min-height: 280px; - background: var(--color-cream); - transition: background var(--duration-base) var(--ease-out); -} -.demo-viewport[data-state="after"] { - background: var(--color-paper); -} -.demo-caption { - padding: var(--spacing-sm) var(--spacing-md); - font-family: var(--font-mono); - font-size: 0.6875rem; - letter-spacing: 0.03em; - color: var(--color-ash); - background: var(--color-paper); - text-align: center; -} -.uxw-demo { - width: 100%; - max-width: 320px; - padding: var(--spacing-lg); - background: var(--color-paper); - border: 1px solid var(--color-mist); - border-radius: 6px; - text-align: center; -} -.uxw-error-icon { - font-size: 2rem; - margin-bottom: var(--spacing-sm); -} -.uxw-error-title { - font-weight: 600; - color: #c00; - margin-bottom: var(--spacing-xs); -} -.uxw-error-text { - font-size: 0.875rem; - color: var(--color-ash); -} -.uxw-error-action { - margin-top: var(--spacing-sm); - font-size: 0.875rem; - color: var(--color-accent); - cursor: pointer; - text-decoration: underline; -} -.uxw-error-after .uxw-error-icon { - color: var(--color-accent); -} -.uxw-error-after .uxw-error-title { - color: var(--color-text); -} -.uxw-error-after .uxw-error-text { - color: var(--color-charcoal); -} -.uxw-button-context { - font-size: 0.875rem; - color: var(--color-charcoal); - margin-bottom: var(--spacing-md); - font-weight: 500; -} -.uxw-button-row { - display: flex; - gap: var(--spacing-sm); - justify-content: center; -} -.uxw-btn { - padding: var(--spacing-xs) var(--spacing-md); - border-radius: 4px; - font-size: 0.875rem; - font-weight: 500; - cursor: pointer; - border: none; -} -.uxw-btn-primary { - background: var(--color-text); - color: var(--color-paper); -} -.uxw-btn-secondary { - background: transparent; - color: var(--color-ash); - border: 1px solid var(--color-mist); -} -.uxw-btn-danger { - background: #c00; - color: white; -} -.uxw-empty-icon { - font-size: 2.5rem; - margin-bottom: var(--spacing-sm); - opacity: 0.4; -} -.uxw-empty-title { - font-weight: 500; - color: var(--color-ash); -} -.uxw-empty-text { - font-size: 0.875rem; - color: var(--color-charcoal); - margin-top: var(--spacing-xs); -} -.uxw-empty-action { - margin-top: var(--spacing-md); -} -.uxw-empty-after .uxw-empty-icon { - opacity: 1; -} -.uxw-empty-after .uxw-empty-title { - color: var(--color-text); -} -.spatial-demo { - width: 100%; - max-width: 340px; - padding: var(--spacing-md); - background: var(--color-paper); - border: 1px solid var(--color-mist); - border-radius: 6px; -} -.spatial-grid-before { - display: flex; - flex-wrap: wrap; - gap: 6px; -} -.spatial-grid-after { - display: grid; - grid-template-columns: 1fr 1fr; - gap: var(--spacing-sm); -} -.spatial-card-item { - padding: var(--spacing-sm); - background: var(--color-bg); - border: 1px solid var(--color-mist); - border-radius: 4px; - font-size: 0.8125rem; - color: var(--color-charcoal); - text-align: center; -} -.spatial-grid-after .spatial-card-item { - width: auto !important; -} -.spatial-hierarchy-before .spatial-h-title, .spatial-hierarchy-before .spatial-h-subtitle, .spatial-hierarchy-before .spatial-h-cta, .spatial-hierarchy-before .spatial-h-link { - font-size: 0.9375rem; - margin-bottom: var(--spacing-xs); - color: var(--color-charcoal); -} -.spatial-hierarchy-after .spatial-h-title { - font-family: var(--font-display); - font-size: 1.75rem; - font-weight: 300; - font-style: italic; - margin-bottom: var(--spacing-xs); - color: var(--color-text); -} -.spatial-hierarchy-after .spatial-h-subtitle { - font-size: 0.6875rem; - text-transform: uppercase; - letter-spacing: 0.1em; - color: var(--color-ash); - margin-bottom: var(--spacing-md); -} -.spatial-hierarchy-after .spatial-h-cta { - display: inline-block; - padding: var(--spacing-sm) var(--spacing-lg); - background: var(--color-text); - color: var(--color-paper); - font-size: 0.875rem; - font-weight: 500; - border-radius: 4px; - margin-bottom: var(--spacing-sm); -} -.spatial-hierarchy-after .spatial-h-link { - font-size: 0.75rem; - color: var(--color-ash); -} -.spatial-whitespace-before { - padding: var(--spacing-xs) !important; -} -.spatial-whitespace-before .spatial-ws-title { - font-size: 1rem; - font-weight: 600; - margin-bottom: 2px; -} -.spatial-whitespace-before .spatial-ws-price { - font-size: 0.875rem; - color: var(--color-ash); - margin-bottom: 4px; -} -.spatial-whitespace-before .spatial-ws-features { - font-size: 0.75rem; - color: var(--color-ash); - margin-bottom: 6px; -} -.spatial-whitespace-before .spatial-ws-btn { - width: 100%; - padding: 6px; - font-size: 0.75rem; - background: var(--color-text); - color: var(--color-paper); - border: none; - border-radius: 3px; - cursor: pointer; -} -.spatial-whitespace-after { - padding: var(--spacing-lg) !important; -} -.spatial-whitespace-after .spatial-ws-title { - font-family: var(--font-display); - font-size: 1.5rem; - font-weight: 400; - margin-bottom: var(--spacing-sm); -} -.spatial-whitespace-after .spatial-ws-price { - font-size: 1.25rem; - font-weight: 600; - color: var(--color-text); - margin-bottom: var(--spacing-sm); -} -.spatial-whitespace-after .spatial-ws-features { - font-size: 0.8125rem; - color: var(--color-ash); - margin-bottom: var(--spacing-lg); - line-height: 1.6; -} -.spatial-whitespace-after .spatial-ws-btn { - width: 100%; - padding: var(--spacing-sm); - font-size: 0.875rem; - background: var(--color-text); - color: var(--color-paper); - border: none; - border-radius: 4px; - cursor: pointer; - font-weight: 500; -} -.motion-demo { - display: flex; - flex-direction: column; - align-items: center; - gap: var(--spacing-sm); - width: 100%; - max-width: 280px; -} -.motion-stagger-demo { - align-items: stretch; -} -.motion-list-item { - display: flex; - align-items: center; - gap: var(--spacing-sm); - padding: var(--spacing-sm) var(--spacing-md); - background: var(--color-bg); - border: 1px solid var(--color-mist); - border-radius: 4px; - font-size: 0.875rem; - color: var(--color-charcoal); -} -.motion-dot { - width: 8px; - height: 8px; - background: var(--color-accent); - border-radius: 50%; -} -.demo-viewport[data-state="after"] .motion-list-item { - opacity: 0; - transform: translateY(12px); - animation: staggerIn 0.35s cubic-bezier(0.16, 1, 0.3, 1) forwards; -} -.demo-viewport[data-state="after"] .motion-list-item:nth-child(1) { - animation-delay: 0s; -} -.demo-viewport[data-state="after"] .motion-list-item:nth-child(2) { - animation-delay: 0.05s; -} -.demo-viewport[data-state="after"] .motion-list-item:nth-child(3) { - animation-delay: 0.1s; -} -.demo-viewport[data-state="after"] .motion-list-item:nth-child(4) { - animation-delay: 0.15s; -} -@keyframes staggerIn { - to { - opacity: 1; - transform: translateY(0); - } -} -.motion-btn { - padding: 12px 24px; - font-size: 0.9375rem; - font-weight: 500; - border: none; - border-radius: 4px; - cursor: pointer; -} -.motion-btn-before { - background: var(--color-charcoal); - color: var(--color-paper); -} -.motion-btn-after { - background: var(--color-text); - color: var(--color-paper); - transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.2s ease; -} -.motion-btn-after:hover { - transform: translateY(-2px); - box-shadow: 0 4px 12px rgba(0,0,0,0.15); -} -.motion-btn-after:active { - transform: translateY(0) scale(0.98); -} -.motion-card { - padding: var(--spacing-md); - background: var(--color-bg); - border: 1px solid var(--color-mist); - border-radius: 6px; - text-align: center; - min-width: 140px; -} -.motion-card-icon { - font-size: 1.5rem; - margin-bottom: var(--spacing-xs); -} -.motion-card-text { - font-size: 0.8125rem; - color: var(--color-charcoal); -} -.motion-card-after { - transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); -} -.demo-viewport[data-state="after"] .motion-card-after { - background: var(--color-accent); - @supports (color: color-mix(in lab, red, red)) { - background: color-mix(in oklch, var(--color-accent) 10%, var(--color-paper)); - } - border-color: var(--color-accent); -} -.demo-viewport[data-state="after"] .motion-card-after .motion-card-icon { - animation: checkPop 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); -} -@keyframes checkPop { - 50% { - transform: scale(1.3); - } -} -.typo-demo { - width: 100%; - max-width: 320px; - text-align: left; -} -.typo-pairing-before { - font-family: 'Inter', system-ui, sans-serif; -} -.typo-pairing-before .typo-heading { - font-size: 1.5rem; - font-weight: 600; - margin-bottom: var(--spacing-xs); -} -.typo-pairing-before .typo-body { - font-size: 0.9375rem; - line-height: 1.5; - color: var(--color-ash); -} -.typo-pairing-after .typo-heading { - font-family: var(--font-display); - font-size: 2rem; - font-weight: 300; - font-style: italic; - letter-spacing: -0.02em; - margin-bottom: var(--spacing-sm); - color: var(--color-text); -} -.typo-pairing-after .typo-body { - font-family: var(--font-body); - font-size: 0.9375rem; - line-height: 1.7; - color: var(--color-charcoal); -} -.typo-hierarchy-before .typo-h1 { - font-size: 1.125rem; - font-weight: 600; - margin-bottom: 4px; -} -.typo-hierarchy-before .typo-meta { - font-size: 0.9375rem; - color: var(--color-ash); - margin-bottom: var(--spacing-xs); -} -.typo-hierarchy-before .typo-p { - font-size: 0.875rem; - line-height: 1.5; - color: var(--color-charcoal); -} -.typo-hierarchy-after .typo-h1 { - font-family: var(--font-display); - font-size: 2.25rem; - font-weight: 300; - letter-spacing: -0.03em; - margin-bottom: 2px; - line-height: 1.1; -} -.typo-hierarchy-after .typo-meta { - font-size: 0.6875rem; - text-transform: uppercase; - letter-spacing: 0.12em; - color: var(--color-accent); - margin-bottom: var(--spacing-md); -} -.typo-hierarchy-after .typo-p { - font-size: 0.9375rem; - line-height: 1.7; - color: var(--color-ash); -} -.int-demo { - display: flex; - flex-direction: column; - gap: var(--spacing-md); - width: 100%; - max-width: 280px; -} -.int-states-demo { - gap: var(--spacing-lg); -} -.int-state-row { - display: flex; - align-items: center; - gap: var(--spacing-md); -} -.int-state-label { - font-size: 0.6875rem; - text-transform: uppercase; - letter-spacing: 0.08em; - color: var(--color-ash); - width: 40px; -} -.int-btn { - flex: 1; - padding: var(--spacing-sm) var(--spacing-md); - font-size: 0.875rem; - font-weight: 500; - border-radius: 4px; - cursor: pointer; -} -.int-btn-poor { - background: var(--color-charcoal); - color: var(--color-paper); - border: none; -} -.int-btn-good { - background: var(--color-text); - color: var(--color-paper); - border: 2px solid transparent; - transition: all 0.15s ease; -} -.int-btn-good:hover { - background: var(--color-charcoal); -} -.int-btn-good:focus { - outline: none; - border-color: var(--color-accent); - box-shadow: 0 0 0 3px var(--color-accent); - @supports (color: color-mix(in lab, red, red)) { - box-shadow: 0 0 0 3px color-mix(in oklch, var(--color-accent) 25%, transparent); - } -} -.int-btn-good:active { - transform: scale(0.98); -} -.int-aff-item { - padding: var(--spacing-sm) var(--spacing-md); - border-radius: 4px; - font-size: 0.875rem; - cursor: pointer; -} -.int-aff-poor { - color: var(--color-charcoal); -} -.int-aff-good { - color: var(--color-accent); - text-decoration: underline; - text-underline-offset: 2px; -} -.int-aff-good::after { - content: ' →'; -} -.int-affordance-after .int-aff-item { - background: var(--color-bg); - border: 1px solid var(--color-mist); - color: var(--color-accent); - text-decoration: underline; - text-underline-offset: 2px; - transition: background 0.15s ease; -} -.int-affordance-after .int-aff-item:hover { - background: var(--color-accent); - @supports (color: color-mix(in lab, red, red)) { - background: color-mix(in oklch, var(--color-accent) 5%, var(--color-paper)); - } -} -.int-affordance-after .int-aff-item::after { - content: ' →'; -} -.int-feedback-before, .int-feedback-after { - display: flex; - align-items: center; - gap: var(--spacing-md); - flex-direction: row; -} -.int-fb-btn { - width: 48px; - height: 48px; - border-radius: 50%; - border: none; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; -} -.int-fb-btn svg { - width: 22px; - height: 22px; -} -.int-fb-silent { - background: var(--color-mist); - color: var(--color-ash); -} -.int-fb-active { - background: var(--color-charcoal); - color: var(--color-paper); - transition: all 0.15s cubic-bezier(0.34, 1.56, 0.64, 1); -} -.int-fb-active:hover { - transform: scale(1.1); -} -.int-fb-active:active { - transform: scale(0.95); -} -.int-fb-active.liked { - background: var(--color-accent); - animation: heartPop 0.35s cubic-bezier(0.34, 1.56, 0.64, 1); -} -@keyframes heartPop { - 50% { - transform: scale(1.25); - } -} -.int-fb-label { - font-size: 0.875rem; - color: var(--color-charcoal); -} -.color-demo { - width: 100%; - max-width: 300px; -} -.color-palette-before, .color-palette-after { - display: flex; - flex-wrap: wrap; - gap: var(--spacing-xs); - padding: var(--spacing-md); - background: var(--color-paper); - border: 1px solid var(--color-mist); - border-radius: 6px; -} -.color-swatch { - width: 40px; - height: 40px; - border-radius: 4px; - transition: background 0.2s ease; -} -.color-card { - width: 100%; - margin-top: var(--spacing-sm); - padding: var(--spacing-sm); - background: var(--color-paper); - border: 1px solid var(--color-mist); - border-radius: 4px; - display: flex; - flex-direction: column; - gap: 4px; -} -.color-card span { - font-size: 0.8125rem; - font-weight: 500; - transition: color 0.2s ease; -} -.color-card button { - padding: 6px; - border: none; - border-radius: 3px; - font-size: 0.75rem; - font-weight: 500; - cursor: pointer; - transition: all 0.2s ease; -} -.color-palette-before .swatch-1 { - background: #ff6b6b; -} -.color-palette-before .swatch-2 { - background: #4ecdc4; -} -.color-palette-before .swatch-3 { - background: #ffe66d; -} -.color-palette-before .swatch-4 { - background: #95e1d3; -} -.color-palette-before .swatch-5 { - background: #f38181; -} -.color-palette-before .card-title { - color: #ff6b6b; -} -.color-palette-before .card-subtitle { - color: #4ecdc4; -} -.color-palette-before .card-btn { - background: #ffe66d; - color: #333; -} -.color-palette-after .swatch-1 { - background: var(--color-text); -} -.color-palette-after .swatch-2 { - background: var(--color-charcoal); -} -.color-palette-after .swatch-3 { - background: var(--color-ash); -} -.color-palette-after .swatch-4 { - background: var(--color-mist); -} -.color-palette-after .swatch-5 { - background: var(--color-accent); -} -.color-palette-after .card-title { - color: var(--color-text); -} -.color-palette-after .card-subtitle { - color: var(--color-ash); -} -.color-palette-after .card-btn { - background: var(--color-accent); - color: var(--color-paper); -} -.color-accent-card { - padding: var(--spacing-md); - border-radius: 6px; -} -.color-accent-before .color-accent-card { - background: #f5f5f5; - border: 1px solid #e0e0e0; -} -.color-accent-before .color-accent-title { - font-weight: 600; - color: #333; - margin-bottom: 4px; -} -.color-accent-before .color-accent-text { - font-size: 0.8125rem; - color: #666; - margin-bottom: var(--spacing-sm); -} -.color-accent-before .color-accent-btn { - width: 100%; - padding: var(--spacing-xs); - background: #333; - color: white; - border: none; - border-radius: 4px; - font-size: 0.8125rem; - cursor: pointer; -} -.color-accent-after .color-accent-card { - background: var(--color-accent); - @supports (color: color-mix(in lab, red, red)) { - background: color-mix(in oklch, var(--color-accent) 8%, var(--color-paper)); - } - border: 1px solid var(--color-accent); - @supports (color: color-mix(in lab, red, red)) { - border: 1px solid color-mix(in oklch, var(--color-accent) 20%, var(--color-paper)); - } -} -.color-accent-after .color-accent-title { - font-weight: 600; - color: var(--color-text); - margin-bottom: 4px; -} -.color-accent-after .color-accent-text { - font-size: 0.8125rem; - color: var(--color-ash); - margin-bottom: var(--spacing-sm); -} -.color-accent-after .color-accent-btn { - width: 100%; - padding: var(--spacing-xs); - background: var(--color-accent); - color: var(--color-paper); - border: none; - border-radius: 4px; - font-size: 0.8125rem; - font-weight: 500; - cursor: pointer; -} -.color-contrast-static { - display: flex; - flex-direction: column; - gap: var(--spacing-sm); -} -.contrast-example { - padding: var(--spacing-md); - border-radius: 6px; - text-align: center; -} -.contrast-fail { - background: #f0f0f0; - color: #a0a0a0; -} -.contrast-pass { - background: var(--color-charcoal); - color: var(--color-paper); -} -.contrast-badge { - display: inline-block; - font-size: 0.5625rem; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.1em; - padding: 2px 6px; - border-radius: 2px; - margin-bottom: 4px; -} -.contrast-fail .contrast-badge { - background: #ddd; -} -.contrast-pass .contrast-badge { - background: var(--color-accent); - color: var(--color-paper); -} -.contrast-text { - font-size: 1rem; - font-weight: 500; - margin-bottom: 2px; -} -.contrast-ratio { - font-size: 0.6875rem; - opacity: 0.7; -} -.resp-demo { - width: 100%; - max-width: 340px; -} -.resp-touch-demo { - display: flex; - flex-direction: column; - gap: var(--spacing-lg); -} -.resp-touch-row { - display: flex; - align-items: center; - gap: var(--spacing-md); -} -.resp-label { - font-size: 0.6875rem; - text-transform: uppercase; - letter-spacing: 0.08em; - color: var(--color-ash); - width: 70px; -} -.resp-touch-targets { - display: flex; - gap: 4px; -} -.resp-touch-targets button { - border: none; - border-radius: 4px; - cursor: pointer; - font-weight: 500; -} -.resp-touch-bad button { - width: 24px; - height: 24px; - font-size: 0.75rem; - background: var(--color-mist); - color: var(--color-ash); -} -.resp-touch-good button { - width: 44px; - height: 44px; - font-size: 1rem; - background: var(--color-text); - color: var(--color-paper); -} -.resp-fluid-demo { - padding: var(--spacing-md); - background: var(--color-bg); - border: 1px solid var(--color-mist); - border-radius: 6px; -} -.resp-fluid-container { - display: flex; - flex-direction: column; - gap: var(--spacing-md); -} -.resp-fluid-fixed, .resp-fluid-adaptive { - font-size: 0.75rem; - color: var(--color-ash); -} -.resp-fluid-fixed span, .resp-fluid-adaptive span { - display: block; - margin-bottom: 4px; -} -.resp-fluid-bar { - height: 24px; - background: var(--color-mist); - border-radius: 4px; -} -.resp-fluid-adaptive .resp-fluid-bar { - background: var(--color-accent); -} -.resp-adapt-demo { - display: flex; - gap: var(--spacing-sm); - align-items: flex-end; -} -.resp-device { - text-align: center; -} -.resp-device > span { - display: block; - margin-top: 4px; - font-size: 0.625rem; - color: var(--color-ash); - text-transform: uppercase; - letter-spacing: 0.08em; -} -.resp-device-screen { - background: var(--color-paper); - border: 2px solid var(--color-mist); - border-radius: 4px; - padding: 4px; - display: flex; - flex-direction: column; - gap: 3px; -} -.resp-device-mobile .resp-device-screen { - width: 50px; - height: 80px; -} -.resp-device-tablet .resp-device-screen { - width: 80px; - height: 60px; -} -.resp-device-desktop .resp-device-screen { - width: 120px; - height: 70px; -} -.resp-block { - background: var(--color-mist); - border-radius: 2px; -} -.resp-block-row { - display: flex; - gap: 3px; - flex: 1; -} -.resp-header { - height: 16px; - background: var(--color-charcoal); -} -.resp-sidebar { - width: 30%; - background: var(--color-charcoal); -} -.resp-content { - flex: 1; -} -@keyframes fadeIn { - from { - opacity: 0; - } - to { - opacity: 1; - } -} -@media (prefers-reduced-motion: reduce) { - *, *::before, *::after { - animation-duration: 0.01ms !important; - animation-iteration-count: 1 !important; - transition-duration: 0.01ms !important; - } -} -*, *::before, *::after { - box-sizing: border-box; -} -* { - margin: 0; -} -img, picture, video, canvas, svg { - display: block; - max-width: 100%; -} -button, input, textarea, select { - font: inherit; -} -:root { - --font-display: 'Cormorant Garamond', Georgia, serif; - --font-body: 'Instrument Sans', system-ui, sans-serif; - --font-mono: 'Space Grotesk', monospace; - --spacing-xs: 8px; - --spacing-sm: 16px; - --spacing-md: 24px; - --spacing-lg: 32px; - --spacing-xl: 48px; - --spacing-2xl: 80px; - --spacing-3xl: 120px; - --width-max: 1400px; - --width-content: 900px; - --ease-out: cubic-bezier(0.16, 1, 0.3, 1); - --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1); - --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1); - --duration-fast: 0.15s; - --duration-base: 0.3s; - --duration-slow: 0.6s; - --duration-slower: 0.8s; - --duration-slowest: 1.2s; - --color-ink: oklch(10% 0 0); - --color-text: oklch(10% 0 0); - --color-paper: oklch(98% 0 0); - --color-cream: oklch(96% 0.005 350); - --color-charcoal: oklch(25% 0 0); - --color-ash: oklch(55% 0 0); - --color-mist: oklch(92% 0 0); - --color-bg: oklch(96% 0.005 350); - --color-accent: oklch(60% 0.25 350); - --color-accent-hover: oklch(52% 0.25 350); - --color-accent-dim: oklch(60% 0.25 350 / 0.15); - --color-accent-soft: oklch(60% 0.25 350 / 0.25); - --cat-diagnostic-bg: #fdf4ff; - --cat-diagnostic-border: #d946ef; - --cat-diagnostic-text: #a21caf; - --cat-quality-bg: #f0fdf4; - --cat-quality-border: #22c55e; - --cat-quality-text: #15803d; - --cat-intensity-bg: #fffbeb; - --cat-intensity-border: #f59e0b; - --cat-intensity-text: #b45309; - --cat-adaptation-bg: #eff6ff; - --cat-adaptation-border: #3b82f6; - --cat-adaptation-text: #1d4ed8; - --cat-enhancement-bg: #fdf2f8; - --cat-enhancement-border: #ec4899; - --cat-enhancement-text: #be185d; - --cat-system-bg: #f5f5f4; - --cat-system-border: #78716c; - --cat-system-text: #44403c; -} -.skip-link { - position: absolute; - top: -100%; - left: 50%; - transform: translateX(-50%); - z-index: 10000; - padding: var(--spacing-sm) var(--spacing-lg); - background: var(--color-ink); - color: var(--color-paper); - font-weight: 600; - text-decoration: none; - border-radius: 0 0 8px 8px; - transition: top 0.2s ease; -} -.skip-link:focus { - top: 0; - outline: 2px solid var(--color-accent); - outline-offset: 2px; -} -html { - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - text-rendering: optimizeLegibility; - overflow-x: clip; -} -body { - font-family: var(--font-body); - font-size: 16px; - line-height: 1.625; - color: var(--color-text); - background: var(--color-paper); - overflow-x: clip; - min-height: 100vh; - min-height: 100dvh; -} -h1, h2, h3, h4, h5, h6 { - font-family: var(--font-display); - font-weight: 400; - line-height: 1.1; - letter-spacing: -0.02em; - color: var(--color-ink); -} -a { - color: var(--color-accent); - text-decoration: underline; - text-decoration-thickness: 1px; - text-underline-offset: 2px; - transition: color var(--duration-fast) var(--ease-out), text-decoration-color var(--duration-fast) var(--ease-out); -} -a:hover { - color: var(--color-accent-hover); - text-decoration-thickness: 2px; -} -.btn, .footer-logo, [class*="nav-item"] { - text-decoration: none; -} -strong { - font-weight: 600; - color: var(--color-ink); -} -code { - font-family: var(--font-mono); - font-size: 0.9em; - padding: 0.15em 0.4em; - background: var(--color-accent-dim); - color: var(--color-accent); - border-radius: 4px; -} -::selection { - background: var(--color-accent-soft); - color: var(--color-ink); -} -.grain-overlay { - position: fixed; - inset: 0; - pointer-events: none; - z-index: 9999; - opacity: 0.03; - background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E"); - background-repeat: repeat; -} -.site-content { - max-width: var(--width-max); - margin: 0 auto; - padding: 0 var(--spacing-lg); -} -@media (max-width: 768px) { - .site-content { - padding: 0 var(--spacing-md); - } -} -.section-header { - margin-bottom: var(--spacing-lg); - position: relative; -} -.section-number { - display: block; - font-family: var(--font-mono); - font-size: 0.625rem; - font-weight: 500; - letter-spacing: 0.05em; - color: var(--color-ash); - margin-bottom: var(--spacing-xs); - text-transform: uppercase; -} -.section-title { - font-size: clamp(1.75rem, 4vw, 2.5rem); - font-weight: 400; - line-height: 1.2; - margin: 0; -} -.section-subtitle { - font-size: 1rem; - line-height: 1.6; - color: var(--color-charcoal); - margin-top: var(--spacing-sm); - max-width: 55ch; -} -.cheatsheet-link { - color: var(--color-accent); - text-decoration: none; - font-size: 0.875rem; - margin-left: 0.5em; -} -.cheatsheet-link:hover { - text-decoration: underline; -} -.section-lead { - font-size: 1rem; - line-height: 1.6; - color: var(--color-charcoal); - max-width: 55ch; - margin-bottom: var(--spacing-lg); -} -.hero-combined { - position: relative; - display: flex; - flex-direction: column; - justify-content: center; - min-height: 100vh; - min-height: 100dvh; - padding: var(--spacing-2xl) 0; - background: var(--color-paper); -} -.github-link { - position: absolute; - top: var(--spacing-md); - right: var(--spacing-lg); - z-index: 10; - color: var(--color-ash); - transition: color 0.2s ease; -} -.github-link:hover { - color: var(--color-ink); -} -.github-link svg { - width: 28px; - height: 28px; -} -.hero-combined-container { - max-width: var(--width-max); - margin: 0 auto; - padding: 0 var(--spacing-lg); - display: grid; - grid-template-columns: 1fr 1fr; - gap: var(--spacing-xl); - align-items: center; - width: 100%; -} -@media (max-width: 1024px) { - .hero-combined-container { - grid-template-columns: 1fr; - gap: var(--spacing-lg); - text-align: center; - } -} -.hero-combined-left { - display: flex; - flex-direction: column; - gap: var(--spacing-md); -} -@media (max-width: 1024px) { - .hero-combined-left { - align-items: center; - } -} -.hero-title-combined { - font-family: var(--font-display); - font-size: clamp(2.5rem, 7vw, 4.5rem); - font-weight: 300; - font-style: italic; - line-height: 1; - letter-spacing: -0.02em; - margin: 0; - color: var(--color-ink); -} -.hero-tagline-combined { - font-family: var(--font-display); - font-size: clamp(1.125rem, 2.5vw, 1.75rem); - font-weight: 400; - font-style: italic; - line-height: 1.3; - margin: 0; - color: var(--color-charcoal); -} -.hero-hook-text { - font-size: 1rem; - line-height: 1.6; - color: var(--color-charcoal); - max-width: 45ch; - margin: 0; -} -.hero-included-box { - display: flex; - flex-direction: column; - gap: 6px; - padding: 10px 14px; - border: 1px solid var(--color-mist); - background: transparent; - max-width: 45ch; -} -.hero-included-title { - font-family: var(--font-body); - font-size: 0.5625rem; - font-weight: 500; - text-transform: uppercase; - letter-spacing: 0.1em; - color: var(--color-ash); -} -.hero-included-items { - display: flex; - flex-wrap: wrap; - align-items: center; - gap: 6px; - font-size: 0.8125rem; - color: var(--color-charcoal); - line-height: 1.5; -} -.hero-included-items em { - font-style: normal; - font-family: var(--font-mono); - font-size: 0.75rem; -} -.hero-included-sep { - color: var(--color-mist); -} -@media (max-width: 500px) { - .hero-included-items { - flex-direction: column; - align-items: flex-start; - gap: 4px; - } - .hero-included-sep { - display: none; - } -} -.hero-cta-group { - display: flex; - align-items: center; - gap: var(--spacing-lg); - margin-top: var(--spacing-sm); -} -@media (max-width: 600px) { - .hero-cta-group { - flex-direction: column; - gap: var(--spacing-md); - } -} -.hero-cta-combined { - display: inline-block; - padding: var(--spacing-sm) var(--spacing-xl); - font-family: var(--font-body); - font-size: 0.9rem; - font-weight: 500; - letter-spacing: 0.05em; - text-transform: uppercase; - text-decoration: none; - color: var(--color-paper); - background: var(--color-ink); - border: none; - transition: transform 0.2s ease, background 0.2s ease; -} -.hero-cta-combined:hover { - transform: translateY(-2px); - background: var(--color-accent); - color: var(--color-paper); -} -.hero-logos-inline { - display: flex; - align-items: center; - gap: var(--spacing-sm); -} -.hero-logos-inline .hero-logos-label { - font-size: 0.6875rem; - color: var(--color-ash); - letter-spacing: 0.03em; -} -.hero-logos-inline .hero-logos-row { - display: flex; - align-items: center; - gap: 8px; -} -.hero-logos-inline .hero-logos-row img { - border-radius: 4px; - opacity: 0.7; - transition: opacity 0.2s ease; -} -.hero-logos-inline .hero-logos-row img:hover { - opacity: 1; -} -.hero-combined-right { - display: flex; - justify-content: center; -} -.hero-combined-right .split-comparison { - max-width: 520px; - width: 100%; -} -.hero-combined-right .split-container { - max-width: 100%; -} -.hero-bias-tags { - display: flex; - flex-direction: column; - align-items: center; - gap: var(--spacing-xs); - margin-top: var(--spacing-lg); - padding-top: var(--spacing-md); - border-top: 1px solid var(--color-mist); - max-width: var(--width-max); - margin-left: auto; - margin-right: auto; - width: 100%; - padding-bottom: var(--spacing-md); -} -.problem-section { - padding: var(--spacing-2xl) 0; - border-top: 1px solid var(--color-mist); -} -.problem-content { - display: grid; - gap: var(--spacing-xl); -} -.split-comparison { - position: relative; - width: 100%; - max-width: 600px; - margin: 0 auto; - padding: 20px; - margin-top: -20px; - margin-bottom: -20px; -} -.split-container { - position: relative; - width: 100%; - max-width: 500px; - height: 380px; - margin: 0 auto; - border-radius: 12px; - overflow: hidden; - background: var(--color-cream); - border: 1px solid var(--color-mist); - cursor: ew-resize; - user-select: none; -} -.split-before, .split-after { - position: absolute; - inset: 0; - display: flex; - align-items: center; - justify-content: center; -} -.split-before { - z-index: 1; -} -.split-content { - width: 100%; - height: 100%; - display: flex; - align-items: center; - justify-content: center; -} -.split-after { - clip-path: polygon(78% 0%, 100% 0%, 100% 100%, 62% 100%); - z-index: 2; - background: var(--color-paper); -} -.split-divider { - position: absolute; - top: 0; - bottom: 0; - left: 70%; - width: 3px; - background: var(--color-accent); - transform: translateX(-50%) skewX(-10deg); - pointer-events: none; - z-index: 3; - box-shadow: 0 0 20px rgba(0,0,0,0.15); -} -.split-label { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%) skewX(10deg); - font-size: 0.6875rem; - font-weight: 600; - letter-spacing: 0.08em; - text-transform: uppercase; - color: var(--color-paper); - background: var(--color-accent); - padding: 6px 14px; - border-radius: 4px; - white-space: nowrap; - box-shadow: 0 2px 8px rgba(0,0,0,0.2); -} -.slop-card { - width: 280px; - height: 280px; - background: linear-gradient(135deg, #f5f3ff 0%, #ede9fe 50%, #ddd6fe 100%); - border-radius: 16px; - padding: 24px; - box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); - font-family: 'Inter', system-ui, sans-serif; - display: flex; - flex-direction: column; -} -.slop-header { - display: flex; - align-items: center; - gap: 12px; - margin-bottom: 16px; -} -.slop-avatar { - width: 40px; - height: 40px; - border-radius: 50%; - background: linear-gradient(135deg, #8b5cf6, #7c3aed); - flex-shrink: 0; -} -.slop-text { - flex: 1; -} -.slop-title { - font-size: 14px; - font-weight: 600; - color: #1f2937; - margin-bottom: 2px; -} -.slop-subtitle { - font-size: 12px; - color: #6b7280; -} -.slop-body { - font-size: 13px; - line-height: 1.5; - color: #4b5563; - margin-bottom: auto; - flex: 1; -} -.slop-button { - width: 100%; - padding: 10px 20px; - background: linear-gradient(135deg, #8b5cf6, #7c3aed); - color: white; - border: none; - border-radius: 8px; - font-family: 'Inter', system-ui, sans-serif; - font-size: 13px; - font-weight: 500; - cursor: pointer; - margin-top: auto; -} -.slop-callouts { - position: absolute; - inset: 0; - pointer-events: none; -} -.slop-callout { - position: absolute; - font-size: 0.625rem; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.06em; - color: var(--color-accent); - background: var(--color-paper); - padding: 4px 8px; - border: 1px solid var(--color-accent); - border-radius: 3px; - white-space: nowrap; - opacity: 0; - animation: calloutFadeIn 0.4s var(--ease-out) forwards; - box-shadow: 0 2px 8px rgba(0,0,0,0.1); -} -.slop-callout[data-point="font"] { - top: 15%; - right: 5%; - animation-delay: 0.1s; -} -.slop-callout[data-point="gradient"] { - top: 40%; - left: 5%; - animation-delay: 0.25s; -} -.slop-callout[data-point="copy"] { - bottom: 35%; - right: 8%; - animation-delay: 0.4s; -} -.slop-callout[data-point="rounded"] { - bottom: 12%; - left: 10%; - animation-delay: 0.55s; -} -@keyframes calloutFadeIn { - from { - opacity: 0; - transform: scale(0.9); - } - to { - opacity: 1; - transform: scale(1); - } -} -.impeccable-card { - width: 280px; - height: 300px; - background: var(--color-paper); - border: 1px solid var(--color-mist); - padding: var(--spacing-lg); - text-align: left; - display: flex; - flex-direction: column; -} -.impeccable-eyebrow { - font-family: var(--font-mono); - font-size: 0.625rem; - font-weight: 500; - letter-spacing: 0.15em; - text-transform: uppercase; - color: var(--color-accent); - margin-bottom: var(--spacing-xs); -} -.impeccable-title { - font-family: var(--font-display); - font-size: 1.75rem; - font-weight: 300; - font-style: italic; - color: var(--color-ink); - margin-bottom: var(--spacing-sm); - line-height: 1.1; -} -.impeccable-body { - font-size: 0.875rem; - line-height: 1.6; - color: var(--color-ash); - margin-bottom: auto; - flex: 1; -} -.impeccable-button { - display: inline-flex; - margin-top: var(--spacing-sm); - padding: 0.625rem 1.5rem; - background: var(--color-ink); - color: var(--color-paper); - border: none; - font-family: var(--font-body); - font-size: 0.8125rem; - font-weight: 500; - letter-spacing: 0.03em; - cursor: pointer; - transition: all var(--duration-base) var(--ease-out); - align-self: flex-start; -} -.impeccable-button:hover { - background: var(--color-accent); -} -.split-labels { - display: flex; - justify-content: center; - gap: var(--spacing-xl); - margin-top: var(--spacing-md); -} -.split-label-item { - display: flex; - align-items: center; - gap: var(--spacing-xs); - font-size: 0.8125rem; - color: var(--color-ash); -} -.split-label-dot { - width: 8px; - height: 8px; - border-radius: 50%; - background: var(--color-mist); -} -.split-label-dot--accent { - background: var(--color-accent); -} -.solution-section { - padding: var(--spacing-2xl) 0; - border-top: 1px solid var(--color-mist); -} -.solution-content { - display: grid; - gap: var(--spacing-lg); -} -.solution-content .section-lead { - margin-bottom: 0; -} -.solution-visual { - display: grid; - grid-template-columns: 1fr auto 1fr; - gap: var(--spacing-lg); - align-items: stretch; -} -@media (max-width: 900px) { - .solution-visual { - grid-template-columns: 1fr; - gap: var(--spacing-md); - } -} -.solution-visual-interactive { - width: 100%; - min-height: 380px; - background: var(--color-paper); - border: 1px solid var(--color-mist); - border-radius: 8px; - position: relative; - overflow: hidden; -} -.solution-pillar { - background: var(--color-cream); - border: 1px solid var(--color-mist); - padding: var(--spacing-lg); - transition: all var(--duration-base) var(--ease-out); -} -.solution-pillar:hover { - border-color: var(--color-accent); - transform: translateY(-4px); - box-shadow: 0 20px 60px var(--color-accent-dim); -} -.pillar-header { - text-align: center; - margin-bottom: var(--spacing-lg); - padding-bottom: var(--spacing-md); - border-bottom: 1px solid var(--color-mist); -} -.pillar-icon { - display: inline-flex; - align-items: center; - justify-content: center; - width: 56px; - height: 56px; - border-radius: 50%; - background: var(--color-accent-dim); - color: var(--color-accent); - margin-bottom: var(--spacing-sm); -} -.pillar-title { - font-family: var(--font-display); - font-size: 1.75rem; - font-weight: 400; - margin: 0 0 var(--spacing-xs); -} -.pillar-subtitle { - font-size: 0.875rem; - color: var(--color-ash); - margin: 0; -} -.pillar-content { - display: flex; - flex-direction: column; - gap: var(--spacing-sm); -} -.pillar-item { - display: flex; - justify-content: space-between; - align-items: center; - padding: var(--spacing-sm); - background: var(--color-paper); - border-radius: 4px; - transition: all var(--duration-fast) var(--ease-out); -} -.pillar-item:hover { - background: var(--color-accent-dim); -} -.pillar-item-name { - font-weight: 500; - color: var(--color-ink); - font-size: 0.9375rem; -} -.pillar-item-code { - font-family: var(--font-mono); - font-size: 0.875rem; - font-weight: 500; - color: var(--color-accent); - background: transparent; - padding: 0; -} -.pillar-item-desc { - font-size: 0.75rem; - color: var(--color-ash); -} -.pillar-item--more { - justify-content: center; - font-size: 0.8125rem; - font-weight: 500; - color: var(--color-accent); - background: transparent; - border: 1px dashed var(--color-mist); -} -.solution-connector { - display: flex; - align-items: center; - justify-content: center; -} -.connector-plus { - font-family: var(--font-display); - font-size: 3rem; - font-weight: 300; - color: var(--color-accent); - opacity: 0.5; -} -@media (max-width: 900px) { - .solution-connector { - padding: var(--spacing-sm) 0; - } - .connector-plus { - font-size: 2rem; - } -} -.skills-section { - padding: var(--spacing-2xl) 0; - border-top: 1px solid var(--color-mist); -} -.skills-gallery { - display: grid; - grid-template-columns: 200px 1fr; - gap: var(--spacing-xl); - align-items: start; -} -@media (max-width: 968px) { - .skills-gallery { - grid-template-columns: 1fr; - gap: var(--spacing-lg); - } -} -.skills-nav { - display: flex; - flex-direction: column; - gap: 2px; - position: sticky; - top: var(--spacing-lg); -} -@media (max-width: 968px) { - .skills-nav { - flex-direction: row; - flex-wrap: wrap; - gap: var(--spacing-xs); - position: static; - } -} -.skill-nav-item { - padding: var(--spacing-sm) var(--spacing-md); - background: transparent; - border: none; - border-left: 2px solid transparent; - color: var(--color-ash); - font-family: var(--font-body); - font-size: 0.9375rem; - font-weight: 400; - cursor: pointer; - transition: all 0.2s ease; - text-align: left; - text-decoration: none; - display: block; -} -.skill-nav-item:hover { - color: var(--color-text); - background: var(--color-cream); -} -.skill-nav-item.active { - color: var(--color-accent); - border-left-color: var(--color-accent); - background: var(--color-accent-dim); - font-weight: 500; -} -@media (max-width: 968px) { - .skill-nav-item { - border-left: none; - border-bottom: 2px solid transparent; - padding: var(--spacing-xs) var(--spacing-md); - } - .skill-nav-item.active { - border-bottom-color: var(--color-accent); - } -} -.skills-showcase { - display: grid; - grid-template-columns: 1.2fr 1fr; - gap: var(--spacing-lg); - align-items: start; -} -@media (max-width: 1100px) { - .skills-showcase { - grid-template-columns: 1fr; - } -} -.loading-state { - padding: var(--spacing-xl); - text-align: center; - color: var(--color-ash); - font-style: italic; -} -.mobile-commands-layout { - display: none; -} -@media (max-width: 900px) { - .mobile-commands-layout { - display: flex; - flex-direction: column; - gap: var(--spacing-md); - } - .commands-container { - display: none; - } -} -.mobile-carousel-wrapper { - overflow-x: auto; - -webkit-overflow-scrolling: touch; - scrollbar-width: none; - padding: var(--spacing-xs) 0; -} -.mobile-carousel-wrapper::-webkit-scrollbar { - display: none; -} -.mobile-carousel { - display: flex; - gap: var(--spacing-xs); - padding-right: var(--spacing-md); -} -.mobile-cmd-pill { - flex-shrink: 0; - padding: var(--spacing-sm) var(--spacing-md); - min-height: 44px; - font-family: var(--font-mono); - font-size: 0.8125rem; - font-weight: 500; - color: var(--color-ash); - background: var(--color-cream); - border: 1px solid var(--color-mist); - border-radius: 100px; - cursor: pointer; - transition: all 0.2s ease; - white-space: nowrap; -} -.mobile-cmd-pill:hover { - color: var(--color-charcoal); - border-color: var(--color-charcoal); -} -.mobile-cmd-pill.active { - color: var(--color-paper); - background: var(--color-ink); - border-color: var(--color-ink); -} -.mobile-demo-area { - background: var(--color-cream); - border: 1px solid var(--color-mist); - border-radius: 8px; - padding: var(--spacing-sm); -} -.mobile-demo-area .demo-split-comparison { - width: 100%; -} -.mobile-demo-area .split-container { - width: 100%; - max-width: 100%; - height: 320px; -} -.mobile-demo-area .demo-caption { - font-size: 0.75rem; - margin-top: var(--spacing-sm); -} -.mobile-info-area { - padding-top: var(--spacing-sm); -} -.mobile-cmd-info { - display: none; - padding: var(--spacing-sm) 0; -} -.mobile-cmd-info.active { - display: block; -} -.mobile-cmd-name { - font-family: var(--font-mono); - font-size: 1.125rem; - font-weight: 600; - color: var(--color-ink); - margin: 0 0 var(--spacing-xs) 0; -} -.mobile-cmd-desc { - font-size: 0.875rem; - color: var(--color-charcoal); - line-height: 1.5; - margin: 0; -} -.mobile-cmd-rel { - margin-top: var(--spacing-xs); - font-size: 0.75rem; - color: var(--color-ash); -} -.mobile-cmd-rel code { - font-family: var(--font-mono); - color: var(--color-ink); -} -.downloads-section { - padding: var(--spacing-2xl) 0; - border-top: 1px solid var(--color-mist); -} -.downloads-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); - gap: var(--spacing-lg); -} -.download-card { - display: flex; - flex-direction: column; - align-items: center; - text-align: center; - padding: var(--spacing-lg); - background: var(--color-cream); - border: 1px solid var(--color-mist); - transition: all var(--duration-base) var(--ease-out); -} -.download-card:hover { - border-color: var(--color-accent); - transform: translateY(-4px); - box-shadow: 0 20px 60px var(--color-accent-dim); -} -.download-card-icon { - margin-bottom: var(--spacing-sm); -} -.download-card-icon img { - width: 40px; - height: 40px; - object-fit: contain; - border-radius: 8px; -} -.download-card-title { - font-family: var(--font-display); - font-size: 1.25rem; - font-weight: 400; - margin: 0 0 var(--spacing-sm) 0; -} -.download-card-note { - font-size: 0.75rem; - color: var(--color-ash); - margin-bottom: var(--spacing-xs); -} -.download-card .btn { - margin-top: var(--spacing-xs); -} -.install-command { - display: flex; - align-items: center; - gap: var(--spacing-xs); - background: var(--color-paper); - border: 1px solid var(--color-mist); - border-radius: 6px; - padding: var(--spacing-sm); - margin-top: var(--spacing-sm); - width: 100%; -} -.install-command code { - flex: 1; - font-family: var(--font-mono); - font-size: 0.75rem; - color: var(--color-ink); - background: transparent; - padding: 0; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} -.copy-btn { - display: flex; - align-items: center; - justify-content: center; - width: 28px; - height: 28px; - background: transparent; - border: 1px solid var(--color-mist); - border-radius: 4px; - color: var(--color-ash); - cursor: pointer; - flex-shrink: 0; - transition: all var(--duration-fast) var(--ease-out); -} -.copy-btn:hover { - background: var(--color-accent-dim); - border-color: var(--color-accent); - color: var(--color-accent); -} -.copy-btn.copied { - background: var(--color-accent); - border-color: var(--color-accent); - color: var(--color-paper); -} -.install-hint { - font-size: 0.75rem; - color: var(--color-ash); - margin: var(--spacing-xs) 0 0 0; -} -.install-hint code { - font-family: var(--font-mono); - font-size: 0.6875rem; - background: var(--color-mist); - padding: 2px 5px; - border-radius: 3px; -} -.download-card-details { - width: 100%; - margin-top: var(--spacing-sm); - font-size: 0.8125rem; - text-align: left; -} -.download-card-details summary { - cursor: pointer; - color: var(--color-ash); - font-size: 0.75rem; - padding: var(--spacing-xs) 0; - list-style: none; - display: flex; - align-items: center; - justify-content: center; - gap: 4px; -} -.download-card-details summary::before { - content: '▶'; - font-size: 0.5rem; - transition: transform var(--duration-fast) var(--ease-out); -} -.download-card-details[open] summary::before { - transform: rotate(90deg); -} -.download-card-details summary::-webkit-details-marker { - display: none; -} -.download-card-details ol { - margin: var(--spacing-sm) 0; - padding-left: var(--spacing-md); - color: var(--color-charcoal); - line-height: 1.6; -} -.download-card-details li { - margin-bottom: 4px; -} -.download-card-details code { - font-family: var(--font-mono); - font-size: 0.6875rem; - background: var(--color-mist); - padding: 2px 5px; - border-radius: 3px; -} -.download-card-details a { - color: var(--color-accent); - text-decoration: none; - font-size: 0.75rem; -} -.download-card-details a:hover { - text-decoration: underline; -} -.opensource-section { - padding: var(--spacing-2xl) 0; - border-top: 1px solid var(--color-mist); - text-align: center; -} -.opensource-content { - max-width: 500px; - margin: 0 auto; - display: flex; - flex-direction: column; - align-items: center; - gap: var(--spacing-md); -} -.opensource-title { - font-size: clamp(1.5rem, 4vw, 2rem); - font-weight: 300; -} -.opensource-desc { - font-size: 1.125rem; - color: var(--color-ash); - line-height: 1.6; -} -.site-footer { - border-top: 1px solid var(--color-mist); - padding: var(--spacing-xl) var(--spacing-lg); - background: var(--color-cream); -} -.footer-content { - max-width: var(--width-max); - margin: 0 auto; - display: flex; - justify-content: space-between; - align-items: center; - flex-wrap: wrap; - gap: var(--spacing-lg); -} -@media (max-width: 768px) { - .footer-content { - flex-direction: column; - text-align: center; - } -} -.footer-brand { - display: flex; - flex-direction: column; - gap: var(--spacing-xs); -} -.footer-logo { - font-family: var(--font-display); - font-size: 1.25rem; - font-weight: 400; - color: var(--color-ink); -} -.footer-tagline { - font-size: 0.875rem; - color: var(--color-ash); -} -.footer-links { - display: flex; - gap: var(--spacing-lg); - flex-wrap: wrap; -} -.footer-links a { - font-size: 0.875rem; - color: var(--color-ash); - transition: color var(--duration-fast) var(--ease-out); -} -.footer-links a:hover { - color: var(--color-accent); -} -.footer-author { - display: flex; - align-items: center; - justify-content: center; - gap: var(--spacing-md); - flex-wrap: wrap; - width: 100%; - padding-top: var(--spacing-lg); - margin-top: var(--spacing-md); - border-top: 1px solid var(--color-mist); -} -.footer-author-label { - font-size: 0.875rem; - color: var(--color-ash); -} -.footer-author-label a { - color: var(--color-text); - transition: color var(--duration-fast) var(--ease-out); -} -.footer-author-label a:hover { - color: var(--color-accent); -} -.footer-author-links { - display: flex; - align-items: center; - gap: var(--spacing-sm); -} -.footer-social-link { - display: flex; - align-items: center; - justify-content: center; - width: 36px; - height: 36px; - color: var(--color-ash); - background: transparent; - border-radius: 50%; - transition: all var(--duration-fast) var(--ease-out); - text-decoration: none; -} -.footer-social-link:hover { - color: var(--color-accent); - background: var(--color-accent-dim); -} -.footer-author-divider { - width: 1px; - height: 20px; - background: var(--color-mist); - margin: 0 var(--spacing-xs); -} -.footer-newsletter { - display: inline-flex; - align-items: center; - gap: 6px; - font-size: 0.875rem; - font-weight: 500; - color: var(--color-text); - text-decoration: none; - padding: 8px 14px; - background: var(--color-paper); - border: 1px solid var(--color-mist); - border-radius: 100px; - transition: all var(--duration-fast) var(--ease-out); -} -.footer-newsletter:hover { - border-color: var(--color-accent); - color: var(--color-accent); -} -.footer-newsletter svg { - transition: transform var(--duration-fast) var(--ease-out); -} -.footer-newsletter:hover svg { - transform: translateX(3px); -} -@media (max-width: 768px) { - .footer-author { - flex-direction: column; - gap: var(--spacing-sm); - } - .footer-author-divider { - display: none; - } -} -.btn { - display: inline-flex; - align-items: center; - justify-content: center; - gap: var(--spacing-xs); - padding: 1rem 2rem; - font-family: var(--font-body); - font-size: 0.9375rem; - font-weight: 600; - letter-spacing: 0.03em; - border: none; - cursor: pointer; - transition: all var(--duration-base) var(--ease-out); - position: relative; - overflow: hidden; - text-decoration: none; -} -.btn-primary { - background: var(--color-ink); - color: var(--color-paper); -} -.btn-primary::before { - content: ''; - position: absolute; - inset: 0; - background: var(--color-accent); - transform: translateY(100%); - transition: transform var(--duration-base) var(--ease-out); - z-index: 0; -} -.btn-primary:hover::before { - transform: translateY(0); -} -.btn-primary:hover { - color: var(--color-paper); -} -.btn-primary span, .btn-primary svg { - position: relative; - z-index: 1; -} -.btn-primary:not(:has(span)) { - position: relative; - z-index: 1; -} -.btn-secondary { - background: transparent; - color: var(--color-ink); - border: 1px solid var(--color-ink); -} -.btn-secondary:hover { - background: var(--color-ink); - color: var(--color-paper); -} -.btn:focus-visible { - outline: 2px solid var(--color-accent); - outline-offset: 2px; -} -.btn-primary:focus-visible { - outline-color: var(--color-paper); - box-shadow: 0 0 0 4px var(--color-accent); -} -.btn-secondary:focus-visible { - outline-color: var(--color-accent); -} -@keyframes revealUp { - to { - opacity: 1; - transform: translateY(0); - } -} -@keyframes fadeIn { - to { - opacity: 1; - } -} -@keyframes float { - 0%, 100% { - transform: translateX(-50%) translateY(0); - } - 50% { - transform: translateX(-50%) translateY(-8px); - } -} -@keyframes bounce { - 0%, 100% { - transform: translateY(0); - } - 50% { - transform: translateY(4px); - } -} -[data-reveal] { - opacity: 0; - transform: translateY(30px); - transition: opacity 0.8s var(--ease-out), transform 0.8s var(--ease-out); -} -[data-reveal].revealed { - opacity: 1; - transform: translateY(0); -} -[data-reveal]:nth-child(1) { - transition-delay: 0s; -} -[data-reveal]:nth-child(2) { - transition-delay: 0.1s; -} -[data-reveal]:nth-child(3) { - transition-delay: 0.2s; -} -[data-reveal]:nth-child(4) { - transition-delay: 0.3s; -} -@media (prefers-reduced-motion: reduce) { - *, *::before, *::after { - animation-duration: 0.01ms !important; - animation-iteration-count: 1 !important; - transition-duration: 0.01ms !important; - scroll-behavior: auto !important; - } - html { - scroll-behavior: auto; - } - .hero-canvas { - display: none; - } - .hero-scroll-indicator { - animation: none; - opacity: 1; - } - [data-reveal] { - opacity: 1; - transform: none; - } - .gallery-frame { - opacity: 1; - transform: none; - } -} -.load-error { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - text-align: center; - padding: var(--spacing-2xl) var(--spacing-lg); - gap: var(--spacing-md); - background: var(--color-cream); - border: 1px solid var(--color-mist); - border-radius: 8px; -} -.load-error-icon { - font-size: 2.5rem; - color: var(--color-accent); -} -.load-error-title { - font-family: var(--font-display); - font-size: 1.5rem; - font-weight: 400; - color: var(--color-ink); - margin: 0; -} -.load-error-text { - font-size: 1rem; - color: var(--color-ash); - max-width: 40ch; - line-height: 1.5; -} -.load-error-retry { - margin-top: var(--spacing-sm); -} -.bias-tags { - display: flex; - flex-direction: column; - align-items: center; - gap: var(--spacing-sm); - margin-top: var(--spacing-lg); -} -.bias-tags-label { - font-family: var(--font-mono); - font-size: 0.6875rem; - font-weight: 500; - letter-spacing: 0.1em; - text-transform: uppercase; - color: var(--color-ash); -} -.bias-tags-list { - display: flex; - flex-wrap: wrap; - justify-content: center; - gap: var(--spacing-xs); -} -.bias-tag { - font-size: 0.75rem; - font-weight: 500; - padding: 6px 12px; - background: var(--color-cream); - border: 1px solid var(--color-mist); - color: var(--color-charcoal); - transition: all var(--duration-fast) var(--ease-out); -} -.bias-tag:hover { - border-color: var(--color-accent); - color: var(--color-accent); -} -.antidote-section { - padding: var(--spacing-2xl) 0; - border-top: 1px solid var(--color-mist); -} -.patterns-categories { - margin-bottom: var(--spacing-xl); -} -.pattern-tabs { - display: flex; - flex-wrap: wrap; - gap: var(--spacing-xs); - margin-bottom: var(--spacing-lg); -} -.pattern-tab { - background: none; - border: none; - border-bottom: 2px solid transparent; - font-family: var(--font-body); - font-size: 0.875rem; - color: var(--color-ash); - padding: var(--spacing-xs) var(--spacing-sm); - cursor: pointer; - transition: color var(--duration-fast) var(--ease-out), border-color var(--duration-fast) var(--ease-out); -} -.pattern-tab:hover { - color: var(--color-charcoal); -} -.pattern-tab.active { - color: var(--color-ink); - font-weight: 500; - border-bottom-color: var(--color-accent); -} -.pattern-tab:focus-visible { - outline: 2px solid var(--color-accent); - outline-offset: 2px; - border-radius: 4px; -} -.pattern-panel { - display: none; -} -.pattern-panel.active { - display: block; -} -.pattern-columns { - display: grid; - grid-template-columns: 1fr 1fr; - gap: var(--spacing-xl); -} -@media (max-width: 768px) { - .pattern-columns { - grid-template-columns: 1fr; - gap: var(--spacing-md); - } -} -.pattern-column-label { - display: block; - font-size: 0.6875rem; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.1em; - margin-bottom: var(--spacing-sm); - color: var(--color-ash); -} -.pattern-column--anti .pattern-column-label { - color: var(--color-accent); -} -.pattern-column--do .pattern-column-label { - color: var(--color-success, #22c55e); -} -.pattern-list { - list-style: none; - padding: 0; - margin: 0; - display: flex; - flex-direction: column; - gap: var(--spacing-xs); -} -.pattern-item { - font-size: 0.8125rem; - padding-left: var(--spacing-md); - position: relative; - line-height: 1.5; -} -.pattern-item--anti { - color: var(--color-ash); -} -.pattern-item--anti::before { - content: '×'; - position: absolute; - left: 0; - color: var(--color-accent); - font-weight: 600; -} -.pattern-item--do { - color: var(--color-charcoal); -} -.pattern-item--do::before { - content: '✓'; - position: absolute; - left: 0; - color: var(--color-success, #22c55e); - font-weight: 600; -} -.contribute-inline { - font-size: 0.875rem; - color: var(--color-ash); - margin-top: var(--spacing-md); -} -.contribute-inline a { - color: var(--color-accent); - text-decoration: none; -} -.contribute-inline a:hover { - text-decoration: underline; -} -.pillar-item--main { - background: var(--color-accent-dim); - border: 1px solid var(--color-accent); -} -.pillar-item--main .pillar-item-name { - font-size: 1.125rem; - font-weight: 600; - color: var(--color-accent); -} -.pillar-item--ref { - background: transparent; - padding: var(--spacing-xs) var(--spacing-sm); -} -.pillar-item-label { - font-size: 0.75rem; - font-weight: 500; - text-transform: uppercase; - letter-spacing: 0.05em; - color: var(--color-ash); -} -.pillar-refs { - display: flex; - flex-wrap: wrap; - gap: var(--spacing-xs); - padding: 0 var(--spacing-sm); -} -.pillar-ref { - font-size: 0.6875rem; - font-weight: 500; - text-transform: uppercase; - letter-spacing: 0.03em; - padding: 4px 10px; - background: var(--color-paper); - color: var(--color-ash); - border: 1px solid var(--color-mist); - border-radius: 3px; - transition: all var(--duration-fast) var(--ease-out); -} -.pillar-ref:hover { - border-color: var(--color-accent); - color: var(--color-accent); -} -.pillar-command-group { - display: flex; - flex-wrap: wrap; - align-items: center; - gap: var(--spacing-xs); - padding: var(--spacing-sm); - background: var(--color-paper); - border-radius: 4px; -} -.pillar-group-label { - font-size: 0.6875rem; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.05em; - color: var(--color-ash); - width: 100%; - margin-bottom: 4px; -} -.pillar-command-group .pillar-item-code { - font-size: 0.8125rem; - padding: 4px 8px; - background: var(--color-accent-dim); - border-radius: 3px; -} -.platforms-section { - padding: var(--spacing-2xl) 0; - border-top: 1px solid var(--color-mist); -} -.platforms-section .section-subtitle { - max-width: 60ch; -} -.install-terminal { - max-width: 640px; - margin: 0 auto var(--spacing-xl); -} -.install-terminal .glass-terminal { - height: auto; -} -.install-terminal .terminal-body { - padding: 0; - display: flex; - flex-direction: column; -} -.install-terminal-row { - display: flex; - flex-direction: column; - gap: 6px; - padding: var(--spacing-md) var(--spacing-lg); -} -.install-terminal-label { - font-family: var(--font-mono); - font-size: 0.625rem; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.1em; - color: var(--color-ash); -} -.install-terminal-cmd { - display: flex; - align-items: center; - gap: var(--spacing-sm); -} -.install-terminal-cmd .terminal-prompt { - flex-shrink: 0; -} -.install-terminal-cmd code { - flex: 1; - font-family: var(--font-mono); - font-size: 0.9375rem; - color: var(--color-ink); - background: transparent; - padding: 0; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} -.install-terminal-cmd .copy-btn { - flex-shrink: 0; -} -.install-terminal-cmd .btn { - padding: 0.5rem 1rem; - font-size: 0.8125rem; -} -.install-terminal-note { - font-size: 0.75rem; - color: var(--color-ash); - padding-left: calc(0.75rem + var(--spacing-sm)); -} -.install-terminal-note code { - font-family: var(--font-mono); - font-size: 0.6875rem; - background: var(--color-mist); - padding: 2px 5px; - border-radius: 3px; - color: var(--color-ink); -} -.install-terminal-divider { - height: 1px; - background: var(--color-mist); - margin: 0; -} -@media (max-width: 600px) { - .install-terminal-row { - padding: var(--spacing-sm) var(--spacing-md); - } - .install-terminal-cmd code { - font-size: 0.75rem; - } -} -.install-providers { - display: flex; - flex-direction: column; - align-items: center; - gap: var(--spacing-sm); - margin-top: var(--spacing-md); -} -.install-providers-label { - font-size: 0.75rem; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.1em; - color: var(--color-ash); -} -.install-providers-row { - display: flex; - flex-wrap: wrap; - justify-content: center; - gap: var(--spacing-sm); -} -.install-provider-badge { - display: flex; - align-items: center; - gap: 6px; - font-size: 0.8125rem; - color: var(--color-charcoal); -} -.install-provider-badge img { - border-radius: 4px; -} -.install-terminal-cmd--download { - display: flex; - align-items: center; - gap: var(--spacing-sm); - flex-wrap: wrap; -} -.prefix-toggle { - display: inline-flex; - align-items: center; - gap: 8px; - cursor: pointer; - padding: 6px 12px; - background: rgba(0, 0, 0, 0.06); - border: 1px solid rgba(0, 0, 0, 0.08); - border-radius: 6px; - transition: border-color var(--duration-fast) var(--ease-out); -} -.prefix-toggle:hover { - border-color: rgba(0, 0, 0, 0.2); -} -.prefix-toggle input { - position: absolute; - opacity: 0; - width: 0; - height: 0; -} -.prefix-toggle-slider { - position: relative; - width: 32px; - height: 18px; - background: var(--color-mist); - border-radius: 18px; - transition: background var(--duration-fast) var(--ease-out); - flex-shrink: 0; -} -.prefix-toggle-slider::after { - content: ''; - position: absolute; - top: 2px; - left: 2px; - width: 14px; - height: 14px; - background: var(--color-paper); - border-radius: 50%; - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15); - transition: transform var(--duration-fast) var(--ease-out); -} -.prefix-toggle input:checked + .prefix-toggle-slider { - background: var(--color-accent); -} -.prefix-toggle input:checked + .prefix-toggle-slider::after { - transform: translateX(14px); -} -.prefix-toggle input:focus-visible + .prefix-toggle-slider { - outline: 2px solid var(--color-accent); - outline-offset: 2px; -} -.prefix-toggle-label { - font-size: 0.75rem; - color: var(--color-charcoal); - white-space: nowrap; -} -.prefix-toggle-label code { - font-family: var(--font-mono); - font-size: 0.6875rem; - background: var(--color-accent-dim); - color: var(--color-accent); - padding: 1px 4px; - border-radius: 3px; -} -.has-tooltip { - position: relative; - cursor: default; -} -.has-tooltip::after { - content: attr(data-tooltip); - position: absolute; - bottom: calc(100% + 8px); - left: 50%; - transform: translateX(-50%); - padding: 6px 10px; - background: var(--color-ink); - color: var(--color-paper); - font-size: 0.6875rem; - line-height: 1.4; - border-radius: 6px; - white-space: nowrap; - pointer-events: none; - opacity: 0; - transition: opacity 0.15s ease; - z-index: 100; -} -.has-tooltip:hover::after { - opacity: 1; -} -.hero-logo-icon { - display: inline-flex; - align-items: center; -} -.download-tip { - font-size: 0.8125rem; - color: var(--color-ash); - margin-top: var(--spacing-sm); - text-align: center; -} -.download-tip a { - color: var(--color-accent); - text-decoration: none; -} -.download-tip a:hover { - text-decoration: underline; -} -.consulting-section { - padding: var(--spacing-xl) 0; - border-top: 1px solid var(--color-mist); -} -.consulting-content { - display: flex; - align-items: center; - justify-content: space-between; - gap: var(--spacing-lg); - flex-wrap: wrap; -} -.consulting-actions { - display: flex; - gap: var(--spacing-sm); - flex-shrink: 0; -} -.consulting-text { - flex: 1; - min-width: 280px; -} -.consulting-title { - font-size: clamp(1.5rem, 4vw, 2rem); - font-weight: 300; - font-style: italic; - margin: 0 0 var(--spacing-sm) 0; -} -.consulting-desc { - font-size: 1rem; - color: var(--color-charcoal); - line-height: 1.6; - margin: 0; - max-width: 45ch; -} -@media (max-width: 600px) { - .consulting-content { - flex-direction: column; - align-items: flex-start; - } - .consulting-actions { - flex-direction: column; - width: 100%; - } - .consulting-actions .btn { - width: 100%; - justify-content: center; - } -} +.split-container:before{content:"";background-image:linear-gradient(var(--color-mist) 1px, transparent 1px), linear-gradient(90deg, var(--color-mist) 1px, transparent 1px);opacity:.3;pointer-events:none;background-size:20px 20px;position:absolute;inset:0}.split-container:after{content:"← Drag →";letter-spacing:.1em;text-transform:uppercase;color:var(--color-ash);background:var(--color-paper);opacity:.8;z-index:10;border-radius:4px;padding:4px 12px;font-size:.625rem;font-weight:600;transition:opacity .3s;position:absolute;bottom:12px;left:50%;transform:translate(-50%)}.split-container:hover:after{opacity:0}.split-after .impeccable-card{box-shadow:0 10px 40px #00000014}@keyframes splitEntry{0%{opacity:0;transform:translate(-50%)skew(-10deg)scaleY(.8)}to{opacity:1;transform:translate(-50%)skew(-10deg)scaleY(1)}}.split-divider{animation:splitEntry .6s var(--ease-out) .3s backwards}.split-label-item{transition:color var(--duration-fast) var(--ease-out);cursor:default}.split-label-item:hover{color:var(--color-text)}.split-label-item[data-point=after]:hover .split-label-dot--accent{transform:scale(1.3)}.split-label-dot{transition:transform var(--duration-fast) var(--ease-spring)}.split-badge{letter-spacing:.08em;text-transform:uppercase;z-index:5;pointer-events:none;border-radius:3px;padding:3px 8px;font-size:.625rem;font-weight:600;position:absolute;top:10px}.split-badge--before{color:var(--color-ash);background:var(--color-paper);border:1px solid var(--color-mist);left:10px}.split-badge--after{color:var(--color-paper);background:var(--color-accent);right:10px}@media (hover:none){.split-container:after{content:"← Swipe →"}}@media (max-width:600px){.split-label{padding:4px 10px;font-size:.5625rem}}.commands-section{padding:var(--spacing-xl) 0;background:var(--color-paper);position:relative}.commands-gallery{display:block}.commands-container{gap:var(--spacing-2xl);grid-template-columns:1fr 1.2fr;align-items:start;display:grid}@media (max-width:900px){.commands-container{grid-template-columns:1fr}}.command-manual{gap:var(--spacing-sm);flex-direction:column;padding-bottom:20vh;display:flex}.command-category-header{font-family:var(--font-display);color:var(--color-accent);text-transform:uppercase;letter-spacing:.1em;padding:var(--spacing-lg) var(--spacing-lg) var(--spacing-sm);margin-top:var(--spacing-md);border-bottom:1px solid var(--color-mist);font-size:.875rem;font-weight:600}.command-category-header:first-child{margin-top:0}.manual-entry{padding:var(--spacing-lg);padding-left:calc(var(--spacing-lg) + 16px);border-left:2px solid var(--color-mist);transition:border-color .4s var(--ease-out), opacity .4s var(--ease-out), background .4s var(--ease-out), transform .4s var(--ease-out);opacity:.4;cursor:pointer;position:relative;transform:translate(-16px)}.manual-entry:hover{opacity:.7}.manual-entry.active{border-left-color:var(--color-accent);opacity:1;background:linear-gradient(to right, var(--color-bg), transparent);transform:translate(0)}.manual-cmd-name{font-family:var(--font-mono);margin:0 0 var(--spacing-sm);color:var(--color-ink);font-size:1.5rem;font-weight:500}.beta-badge{font-family:var(--font-body);letter-spacing:.08em;text-transform:uppercase;color:var(--color-accent);border:1px solid var(--color-accent);vertical-align:middle;border-radius:3px;margin-left:6px;padding:1px 5px;font-size:.55rem;font-weight:600}.manual-cmd-desc{color:var(--color-charcoal);margin:0;font-size:1rem;line-height:1.6}.manual-cmd-rel{color:var(--color-ash);margin-top:var(--spacing-sm);flex-wrap:wrap;align-items:center;gap:.5ch;font-size:.8125rem;display:flex}.manual-cmd-rel .rel-icon{color:var(--color-accent);font-weight:600}.manual-cmd-rel code{font-family:var(--font-mono);background:var(--color-mist);color:var(--color-ink);border-radius:3px;padding:2px 6px;font-size:.75rem}.glass-terminal-wrapper{top:var(--spacing-xl);height:calc(100vh - var(--spacing-xl) * 2);min-height:500px;max-height:800px;position:sticky}.terminal-stack{perspective:1200px;height:100%;position:relative}.terminal-stack-tabs{z-index:10;gap:4px;display:flex;position:absolute;top:-31px;right:8px}.terminal-stack-tab{font-family:var(--font-mono);background:var(--color-cream);border:1px solid var(--color-mist);color:var(--color-ash);cursor:pointer;border-bottom:none;border-radius:6px 6px 0 0;padding:5px 12px;font-size:.75rem;transition:all .2s}.terminal-stack-tab:hover{background:var(--color-paper);color:var(--color-charcoal)}.terminal-stack-tab.active{background:var(--color-paper);color:var(--color-ink);border-color:var(--color-mist)}.terminal-window{transform-origin:bottom;transition:transform .4s cubic-bezier(.4,0,.2,1),opacity .3s,filter .3s;position:absolute;inset:0}.terminal-window--demo{z-index:2}.terminal-window--demo.is-back{opacity:.6;filter:brightness(.92);pointer-events:none;z-index:1;transform:translateY(16px)translate(12px)scale(.96)}.terminal-window--source{z-index:1;opacity:.6;filter:brightness(.92);pointer-events:none;transform:translateY(16px)translate(12px)scale(.96)}.terminal-window--source.is-front{opacity:1;filter:brightness();pointer-events:auto;z-index:2;transform:translateY(0)translate(0)scale(1)}.source-window{background:var(--color-paper);-webkit-backdrop-filter:blur(12px);border:1px solid var(--color-mist);border-radius:8px;flex-direction:column;height:100%;display:flex;overflow:hidden;box-shadow:0 20px 60px -10px #00000026}.source-header{background:var(--color-cream);border-bottom:1px solid var(--color-mist);flex-shrink:0;align-items:center;gap:8px;padding:12px 16px;display:flex}.source-title{font-family:var(--font-mono);color:var(--color-ink);font-size:.875rem;font-weight:500}.source-body{padding:var(--spacing-md);font-family:var(--font-mono);color:var(--color-charcoal);overscroll-behavior:contain;white-space:pre-wrap;word-break:break-word;background:var(--color-cream);flex:1;font-size:.75rem;line-height:1.5;overflow-y:auto}.source-loading{color:var(--color-ash);font-style:italic}@media (max-width:900px){.glass-terminal-wrapper{display:none}}.glass-terminal{background:var(--color-paper);-webkit-backdrop-filter:blur(12px);border:1px solid var(--color-mist);border-radius:8px;flex-direction:column;height:100%;display:flex;overflow:hidden;box-shadow:0 20px 60px -10px #00000026}.terminal-header{background:var(--color-cream);border-bottom:1px solid var(--color-mist);align-items:center;gap:8px;padding:12px 16px;display:flex}.terminal-dot{border-radius:50%;width:10px;height:10px}.terminal-dot.red{background:#ff5f56}.terminal-dot.yellow{background:#ffbd2e}.terminal-dot.green{background:#27c93f}.terminal-title{font-family:var(--font-mono);color:var(--color-ash);margin-left:auto;font-size:.75rem}.terminal-body{padding:var(--spacing-md);font-family:var(--font-mono);color:var(--color-ink);flex-direction:column;flex:1;min-height:0;font-size:.9375rem;display:flex;overflow-y:auto}.terminal-line{margin-bottom:var(--spacing-sm);gap:var(--spacing-sm);line-height:1.5;display:flex}.terminal-prompt{color:var(--color-accent);-webkit-user-select:none;user-select:none;font-weight:700}.terminal-cursor{background:var(--color-accent);vertical-align:middle;width:8px;height:1.2em;animation:1s step-end infinite blink;display:inline-block}.terminal-output{color:var(--color-ash);margin-bottom:var(--spacing-md);white-space:pre-wrap}@media (max-height:800px){.terminal-output{display:none}}.terminal-cmd{color:var(--color-accent);font-weight:600}.terminal-step{color:var(--color-charcoal)}.terminal-done{color:var(--color-success,#22c55e);font-weight:500}.terminal-preview{background:var(--color-paper);margin:var(--spacing-sm) 0;border-radius:12px;flex:1;min-height:0;overflow:hidden}.terminal-cursor-line{flex-shrink:0;margin-top:var(--spacing-sm)!important}.terminal-preview .demo-split-comparison{flex-direction:column;height:100%;display:flex}.terminal-preview .demo-split-comparison .split-container{cursor:ew-resize;-webkit-user-select:none;user-select:none;background:var(--color-cream);flex:1;min-height:0;position:relative;overflow:hidden}.terminal-preview .demo-split-comparison .split-before,.terminal-preview .demo-split-comparison .split-after{padding:var(--spacing-md);justify-content:center;align-items:center;display:flex;position:absolute;inset:0}.terminal-preview .demo-split-comparison .split-before{z-index:1;background:var(--color-cream)}.terminal-preview .demo-split-comparison .split-after{z-index:2;background:var(--color-paper);clip-path:polygon(58% 0%,100% 0%,100% 100%,42% 100%)}.terminal-preview .demo-split-comparison .split-content{flex-direction:column;justify-content:center;align-items:center;width:100%;max-width:280px;display:flex}.terminal-preview .demo-split-comparison .split-divider{background:var(--color-accent);pointer-events:none;z-index:3;width:2px;position:absolute;top:0;bottom:0;left:50%;transform:translate(-50%)skew(-10deg);box-shadow:0 0 12px #0000001a}.terminal-preview .demo-split-comparison .split-label{letter-spacing:.08em;text-transform:uppercase;color:var(--color-paper);background:var(--color-accent);white-space:nowrap;border-radius:3px;padding:4px 10px;font-size:.5625rem;font-weight:600;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)skew(10deg)}.terminal-preview .demo-split-comparison .demo-caption{color:var(--color-ash);text-align:center;padding:var(--spacing-sm) var(--spacing-md);flex-shrink:0;font-size:.75rem}@keyframes blink{50%{opacity:0}}.casestudies-section{padding:var(--spacing-2xl) 0;border-top:1px solid var(--color-mist);position:relative}.transformations-tabbed{margin-top:var(--spacing-xl)}.transformation-tabs{gap:var(--spacing-xs);border-bottom:1px solid var(--color-mist);margin-bottom:var(--spacing-lg);display:flex}.transformation-tab{font-family:var(--font-display);color:var(--color-ash);padding:var(--spacing-sm) var(--spacing-md);cursor:pointer;background:0 0;border:none;font-size:.9375rem;font-weight:500;transition:color .2s;position:relative}.transformation-tab:hover{color:var(--color-charcoal)}.transformation-tab.active{color:var(--color-ink)}.transformation-tab.active:after{content:"";background:var(--color-accent);height:2px;position:absolute;bottom:-1px;left:0;right:0}.transformation-panels{position:relative}.transformation-panel{gap:var(--spacing-lg);flex-direction:column;animation:.3s fadeInPanel;display:none}.transformation-panel.active{display:flex}@keyframes fadeInPanel{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.transformation-images{align-items:center;gap:var(--spacing-md);display:flex}.transformation-before,.transformation-after{flex:1;margin:0}.transformation-before img,.transformation-after img,.transformation-placeholder{aspect-ratio:16/10;object-fit:cover;border:1px solid var(--color-mist);cursor:pointer;border-radius:8px;width:100%;transition:transform .2s,box-shadow .2s}.transformation-before img:hover,.transformation-after img:hover,.transformation-placeholder:hover{transform:scale(1.02);box-shadow:0 8px 24px -4px #00000026}.transformation-placeholder{background:linear-gradient(135deg, var(--color-mist) 0%, var(--color-cream) 100%);color:var(--color-ash);justify-content:center;align-items:center;font-size:.8125rem;font-style:italic;display:flex}.transformation-before figcaption,.transformation-after figcaption{text-transform:uppercase;letter-spacing:.05em;color:var(--color-ash);margin-top:var(--spacing-xs);text-align:center;font-size:.75rem;font-weight:600}.transformation-arrow{color:var(--color-accent);flex-shrink:0;font-size:1.5rem;font-weight:300}.transformation-info{max-width:600px}.transformation-title{font-family:var(--font-display);color:var(--color-ink);margin:0 0 var(--spacing-xs);font-size:1.25rem;font-weight:600}.transformation-desc{color:var(--color-charcoal);margin:0 0 var(--spacing-sm);font-size:.9375rem;line-height:1.6}.transformation-commands{flex-wrap:wrap;gap:6px;display:flex}.transformation-command{font-family:var(--font-mono);background:var(--color-mist);color:var(--color-charcoal);border-radius:4px;padding:4px 10px;font-size:.75rem}.lightbox{z-index:1000;opacity:0;visibility:hidden;background:#000000e6;justify-content:center;align-items:center;transition:opacity .3s,visibility .3s;display:flex;position:fixed;inset:0}.lightbox.active{opacity:1;visibility:visible}.lightbox-close{color:#fff;cursor:pointer;opacity:.7;background:0 0;border:none;font-size:2.5rem;line-height:1;transition:opacity .2s;position:absolute;top:20px;right:24px}.lightbox-close:hover{opacity:1}.lightbox-image{object-fit:contain;border-radius:8px;max-width:90vw;max-height:85vh;box-shadow:0 20px 60px #00000080}@media (max-width:768px){.transformation-images{flex-direction:column}.transformation-arrow{transform:rotate(90deg)}.transformation-before,.transformation-after{width:100%}}.hero-version-link{color:var(--color-ash);margin-top:var(--spacing-sm);font-size:.8125rem}.hero-version-link a{color:var(--color-ash);border-bottom:1px solid #0000;text-decoration:none;transition:color .2s,border-color .2s}.hero-version-link a:hover{color:var(--color-accent);border-bottom-color:var(--color-accent)}.changelog-section{padding:var(--spacing-xl) 0;border-top:1px solid var(--color-mist);position:relative}.changelog-list{flex-direction:column;gap:0;display:flex}.changelog-entry{padding:var(--spacing-md) 0;border-bottom:1px solid var(--color-mist)}.changelog-entry:first-child{border-top:1px solid var(--color-mist)}.changelog-version-header{align-items:baseline;gap:var(--spacing-sm);margin-bottom:var(--spacing-sm);display:flex}.changelog-version{font-family:var(--font-mono);color:var(--color-ink);font-size:1.125rem;font-weight:600}.changelog-date{color:var(--color-ash);font-size:.8125rem}.changelog-items{padding-left:var(--spacing-md);color:var(--color-charcoal);margin:0;line-height:1.7}.changelog-items li{margin-bottom:var(--spacing-xs)}.changelog-items code{font-family:var(--font-mono);background:var(--color-mist);color:var(--color-ink);border-radius:3px;padding:2px 6px;font-size:.875em}.faq-section{padding:var(--spacing-xl) 0;border-top:1px solid var(--color-mist);position:relative}.faq-list{flex-direction:column;gap:0;display:flex}.faq-item{border-bottom:1px solid var(--color-mist)}.faq-item:first-child{border-top:1px solid var(--color-mist)}.faq-question{font-family:var(--font-display);color:var(--color-ink);padding:var(--spacing-md) 0;cursor:pointer;justify-content:space-between;align-items:center;font-size:1.125rem;font-weight:500;list-style:none;transition:color .2s;display:flex}.faq-question::-webkit-details-marker{display:none}.faq-question:after{content:"+";font-family:var(--font-body);color:var(--color-accent);transition:transform .3s var(--ease-out);font-size:1.5rem;font-weight:300}.faq-item[open] .faq-question:after{transform:rotate(45deg)}.faq-question:hover{color:var(--color-accent)}.faq-answer{padding:0 0 var(--spacing-md);color:var(--color-charcoal);animation:faqFadeIn .3s var(--ease-out);line-height:1.7}.faq-answer p{margin:0 0 var(--spacing-sm)}.faq-answer p:last-child{margin-bottom:0}.faq-answer ul{margin:var(--spacing-sm) 0;padding-left:var(--spacing-md)}.faq-answer li{margin-bottom:var(--spacing-xs)}.faq-answer code{font-family:var(--font-mono);background:var(--color-mist);color:var(--color-ink);border-radius:3px;padding:2px 6px;font-size:.875em}.faq-answer a{color:var(--color-accent);border-bottom:1px solid #0000;text-decoration:none;transition:border-color .2s}.faq-answer a:hover{border-bottom-color:var(--color-accent)}@keyframes faqFadeIn{0%{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:translateY(0)}}.skills-section{padding:var(--spacing-xl) 0;background:var(--color-bg);position:relative;overflow:hidden}.skills-gallery{display:block;position:relative}.gallery-track{gap:var(--spacing-lg);scroll-snap-type:x mandatory;padding:var(--spacing-md) var(--spacing-lg) var(--spacing-xl);-webkit-overflow-scrolling:touch;scrollbar-width:none;cursor:grab;display:flex;overflow-x:auto}.gallery-track:active{cursor:grabbing}.gallery-track::-webkit-scrollbar{display:none}.gallery-frame{scroll-snap-align:center;background:var(--color-paper);border:1px solid var(--color-mist);opacity:.4;max-width:1100px;transition:opacity .6s var(--ease-out), transform .6s var(--ease-out), box-shadow .6s var(--ease-out);border-radius:2px;flex:0 0 80vw;position:relative;overflow:hidden;transform:scale(.95);box-shadow:0 4px 6px -1px #0000000d,0 20px 50px -10px #0000001a}.gallery-frame.active{opacity:1;border-color:var(--color-charcoal);border-width:1px;transform:scale(1);box-shadow:0 20px 25px -5px #0000001a,0 40px 100px -20px #0003}.gallery-content{grid-template-columns:1.2fr 1fr;height:600px;display:grid}@media (max-width:900px){.gallery-frame{flex:0 0 90vw}.gallery-content{grid-template-columns:1fr;height:auto;min-height:600px}}.gallery-visual{background:var(--color-cream);border-right:1px solid var(--color-mist);padding:var(--spacing-lg);justify-content:center;align-items:center;display:flex;position:relative;overflow:hidden}.gallery-info{padding:var(--spacing-xl);flex-direction:column;display:flex;overflow-y:auto}.gallery-header{margin-bottom:var(--spacing-lg)}.gallery-title{font-family:var(--font-display);margin:0 0 var(--spacing-xs);color:var(--color-ink);font-size:2.5rem;font-style:italic}.gallery-meta{font-family:var(--font-mono);text-transform:uppercase;letter-spacing:.1em;color:var(--color-ash);font-size:.75rem}.gallery-desc{color:var(--color-charcoal);margin-bottom:var(--spacing-xl);max-width:45ch;font-size:1.125rem;line-height:1.6}.gallery-tags{gap:var(--spacing-xs);flex-wrap:wrap;margin-top:auto;display:flex}.gallery-tag{border:1px solid var(--color-mist);color:var(--color-ash);border-radius:4px;padding:6px 12px;font-size:.8125rem}.gallery-map{margin-top:var(--spacing-lg);justify-content:center;gap:8px;display:flex}.gallery-dot{background:var(--color-mist);cursor:pointer;width:40px;height:2px;font:inherit;border:none;padding:0;transition:all .3s;position:relative}.gallery-dot:after{content:"";position:absolute;inset:-10px 0}.gallery-dot:focus-visible{outline:2px solid var(--color-accent);outline-offset:4px;border-radius:1px}.gallery-dot.active{background:var(--color-accent);height:4px}.demo-tabbed-container{flex-direction:column;display:flex}.demo-tabs{background:var(--color-paper);border-bottom:1px solid var(--color-mist);justify-content:center;gap:0;margin-bottom:0;display:flex}.demo-tab{padding:var(--spacing-sm) var(--spacing-lg);font-family:var(--font-mono);letter-spacing:.05em;text-transform:uppercase;color:var(--color-ash);cursor:pointer;transition:all var(--duration-fast) var(--ease-out);background:0 0;border:none;border-bottom:2px solid #0000;font-size:.75rem;font-weight:500}.demo-tab:hover{color:var(--color-text);background:var(--color-cream)}.demo-tab.active{color:var(--color-accent);border-bottom-color:var(--color-accent);background:var(--color-accent-dim)}.demo-panels{flex:1}.demo-panel{display:none}.demo-panel.active{animation:fadeSlideIn .3s var(--ease-out);display:block}@keyframes fadeSlideIn{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.demo-container{background:var(--color-paper);border:none;border-radius:0;overflow:hidden}.demo-header{padding:var(--spacing-sm) var(--spacing-md);background:var(--color-paper);border-bottom:1px solid var(--color-mist);justify-content:center;align-items:center;min-height:48px;display:flex}.demo-toggle{align-items:center;gap:var(--spacing-md);display:flex}.demo-toggle-label{font-family:var(--font-mono);text-transform:uppercase;letter-spacing:.08em;color:var(--color-ash);transition:color var(--duration-fast) var(--ease-out);cursor:pointer;font-size:.6875rem;font-weight:600}.demo-toggle-label:hover{color:var(--color-text)}.demo-toggle-label.active{color:var(--color-accent)}.demo-toggle-switch{background:var(--color-mist);cursor:pointer;width:44px;height:24px;transition:background var(--duration-fast) var(--ease-out);font:inherit;border:1px solid #0000;border-radius:12px;padding:0;position:relative}.demo-toggle-switch:focus-visible{outline:2px solid var(--color-accent);outline-offset:2px}.demo-toggle-switch:hover{border-color:var(--color-ash)}.demo-toggle-switch:after{content:"";background:var(--color-paper);width:16px;height:16px;transition:transform var(--duration-base) var(--ease-spring);border-radius:50%;position:absolute;top:3px;left:3px;box-shadow:0 1px 4px #00000026}.demo-toggle-switch.active{background:var(--color-accent)}.demo-toggle-switch.active:after{transform:translate(20px)}.demo-viewport{padding:var(--spacing-xl);background:var(--color-cream);min-height:280px;transition:background var(--duration-base) var(--ease-out);justify-content:center;align-items:center;display:flex}.demo-viewport[data-state=after]{background:var(--color-paper)}.demo-caption{padding:var(--spacing-sm) var(--spacing-md);font-family:var(--font-mono);letter-spacing:.03em;color:var(--color-ash);background:var(--color-paper);text-align:center;font-size:.6875rem}.uxw-demo{width:100%;max-width:320px;padding:var(--spacing-lg);background:var(--color-paper);border:1px solid var(--color-mist);text-align:center;border-radius:6px}.uxw-error-icon{margin-bottom:var(--spacing-sm);font-size:2rem}.uxw-error-title{color:#c00;margin-bottom:var(--spacing-xs);font-weight:600}.uxw-error-text{color:var(--color-ash);font-size:.875rem}.uxw-error-action{margin-top:var(--spacing-sm);color:var(--color-accent);cursor:pointer;font-size:.875rem;text-decoration:underline}.uxw-error-after .uxw-error-icon{color:var(--color-accent)}.uxw-error-after .uxw-error-title{color:var(--color-text)}.uxw-error-after .uxw-error-text{color:var(--color-charcoal)}.uxw-button-context{color:var(--color-charcoal);margin-bottom:var(--spacing-md);font-size:.875rem;font-weight:500}.uxw-button-row{gap:var(--spacing-sm);justify-content:center;display:flex}.uxw-btn{padding:var(--spacing-xs) var(--spacing-md);cursor:pointer;border:none;border-radius:4px;font-size:.875rem;font-weight:500}.uxw-btn-primary{background:var(--color-text);color:var(--color-paper)}.uxw-btn-secondary{color:var(--color-ash);border:1px solid var(--color-mist);background:0 0}.uxw-btn-danger{color:#fff;background:#c00}.uxw-empty-icon{margin-bottom:var(--spacing-sm);opacity:.4;font-size:2.5rem}.uxw-empty-title{color:var(--color-ash);font-weight:500}.uxw-empty-text{color:var(--color-charcoal);margin-top:var(--spacing-xs);font-size:.875rem}.uxw-empty-action{margin-top:var(--spacing-md)}.uxw-empty-after .uxw-empty-icon{opacity:1}.uxw-empty-after .uxw-empty-title{color:var(--color-text)}.spatial-demo{width:100%;max-width:340px;padding:var(--spacing-md);background:var(--color-paper);border:1px solid var(--color-mist);border-radius:6px}.spatial-grid-before{flex-wrap:wrap;gap:6px;display:flex}.spatial-grid-after{gap:var(--spacing-sm);grid-template-columns:1fr 1fr;display:grid}.spatial-card-item{padding:var(--spacing-sm);background:var(--color-bg);border:1px solid var(--color-mist);color:var(--color-charcoal);text-align:center;border-radius:4px;font-size:.8125rem}.spatial-grid-after .spatial-card-item{width:auto!important}.spatial-hierarchy-before .spatial-h-title,.spatial-hierarchy-before .spatial-h-subtitle,.spatial-hierarchy-before .spatial-h-cta,.spatial-hierarchy-before .spatial-h-link{margin-bottom:var(--spacing-xs);color:var(--color-charcoal);font-size:.9375rem}.spatial-hierarchy-after .spatial-h-title{font-family:var(--font-display);margin-bottom:var(--spacing-xs);color:var(--color-text);font-size:1.75rem;font-style:italic;font-weight:300}.spatial-hierarchy-after .spatial-h-subtitle{text-transform:uppercase;letter-spacing:.1em;color:var(--color-ash);margin-bottom:var(--spacing-md);font-size:.6875rem}.spatial-hierarchy-after .spatial-h-cta{padding:var(--spacing-sm) var(--spacing-lg);background:var(--color-text);color:var(--color-paper);margin-bottom:var(--spacing-sm);border-radius:4px;font-size:.875rem;font-weight:500;display:inline-block}.spatial-hierarchy-after .spatial-h-link{color:var(--color-ash);font-size:.75rem}.spatial-whitespace-before{padding:var(--spacing-xs)!important}.spatial-whitespace-before .spatial-ws-title{margin-bottom:2px;font-size:1rem;font-weight:600}.spatial-whitespace-before .spatial-ws-price{color:var(--color-ash);margin-bottom:4px;font-size:.875rem}.spatial-whitespace-before .spatial-ws-features{color:var(--color-ash);margin-bottom:6px;font-size:.75rem}.spatial-whitespace-before .spatial-ws-btn{background:var(--color-text);width:100%;color:var(--color-paper);cursor:pointer;border:none;border-radius:3px;padding:6px;font-size:.75rem}.spatial-whitespace-after{padding:var(--spacing-lg)!important}.spatial-whitespace-after .spatial-ws-title{font-family:var(--font-display);margin-bottom:var(--spacing-sm);font-size:1.5rem;font-weight:400}.spatial-whitespace-after .spatial-ws-price{color:var(--color-text);margin-bottom:var(--spacing-sm);font-size:1.25rem;font-weight:600}.spatial-whitespace-after .spatial-ws-features{color:var(--color-ash);margin-bottom:var(--spacing-lg);font-size:.8125rem;line-height:1.6}.spatial-whitespace-after .spatial-ws-btn{width:100%;padding:var(--spacing-sm);background:var(--color-text);color:var(--color-paper);cursor:pointer;border:none;border-radius:4px;font-size:.875rem;font-weight:500}.motion-demo{align-items:center;gap:var(--spacing-sm);flex-direction:column;width:100%;max-width:280px;display:flex}.motion-stagger-demo{align-items:stretch}.motion-list-item{align-items:center;gap:var(--spacing-sm);padding:var(--spacing-sm) var(--spacing-md);background:var(--color-bg);border:1px solid var(--color-mist);color:var(--color-charcoal);border-radius:4px;font-size:.875rem;display:flex}.motion-dot{background:var(--color-accent);border-radius:50%;width:8px;height:8px}.demo-viewport[data-state=after] .motion-list-item{opacity:0;animation:.35s cubic-bezier(.16,1,.3,1) forwards staggerIn;transform:translateY(12px)}.demo-viewport[data-state=after] .motion-list-item:first-child{animation-delay:0s}.demo-viewport[data-state=after] .motion-list-item:nth-child(2){animation-delay:50ms}.demo-viewport[data-state=after] .motion-list-item:nth-child(3){animation-delay:.1s}.demo-viewport[data-state=after] .motion-list-item:nth-child(4){animation-delay:.15s}@keyframes staggerIn{to{opacity:1;transform:translateY(0)}}.motion-btn{cursor:pointer;border:none;border-radius:4px;padding:12px 24px;font-size:.9375rem;font-weight:500}.motion-btn-before{background:var(--color-charcoal);color:var(--color-paper)}.motion-btn-after{background:var(--color-text);color:var(--color-paper);transition:transform .2s cubic-bezier(.34,1.56,.64,1),box-shadow .2s}.motion-btn-after:hover{transform:translateY(-2px);box-shadow:0 4px 12px #00000026}.motion-btn-after:active{transform:translateY(0)scale(.98)}.motion-card{padding:var(--spacing-md);background:var(--color-bg);border:1px solid var(--color-mist);text-align:center;border-radius:6px;min-width:140px}.motion-card-icon{margin-bottom:var(--spacing-xs);font-size:1.5rem}.motion-card-text{color:var(--color-charcoal);font-size:.8125rem}.motion-card-after{transition:all .3s cubic-bezier(.34,1.56,.64,1)}.demo-viewport[data-state=after] .motion-card-after{background:var(--color-accent)}@supports (color:color-mix(in lab, red, red)){.demo-viewport[data-state=after] .motion-card-after{background:color-mix(in oklch, var(--color-accent) 10%, var(--color-paper))}}.demo-viewport[data-state=after] .motion-card-after{border-color:var(--color-accent)}.demo-viewport[data-state=after] .motion-card-after .motion-card-icon{animation:.4s cubic-bezier(.34,1.56,.64,1) checkPop}@keyframes checkPop{50%{transform:scale(1.3)}}.typo-demo{text-align:left;width:100%;max-width:320px}.typo-pairing-before{font-family:Inter,system-ui,sans-serif}.typo-pairing-before .typo-heading{margin-bottom:var(--spacing-xs);font-size:1.5rem;font-weight:600}.typo-pairing-before .typo-body{color:var(--color-ash);font-size:.9375rem;line-height:1.5}.typo-pairing-after .typo-heading{font-family:var(--font-display);letter-spacing:-.02em;margin-bottom:var(--spacing-sm);color:var(--color-text);font-size:2rem;font-style:italic;font-weight:300}.typo-pairing-after .typo-body{font-family:var(--font-body);color:var(--color-charcoal);font-size:.9375rem;line-height:1.7}.typo-hierarchy-before .typo-h1{margin-bottom:4px;font-size:1.125rem;font-weight:600}.typo-hierarchy-before .typo-meta{color:var(--color-ash);margin-bottom:var(--spacing-xs);font-size:.9375rem}.typo-hierarchy-before .typo-p{color:var(--color-charcoal);font-size:.875rem;line-height:1.5}.typo-hierarchy-after .typo-h1{font-family:var(--font-display);letter-spacing:-.03em;margin-bottom:2px;font-size:2.25rem;font-weight:300;line-height:1.1}.typo-hierarchy-after .typo-meta{text-transform:uppercase;letter-spacing:.12em;color:var(--color-accent);margin-bottom:var(--spacing-md);font-size:.6875rem}.typo-hierarchy-after .typo-p{color:var(--color-ash);font-size:.9375rem;line-height:1.7}.int-demo{gap:var(--spacing-md);flex-direction:column;width:100%;max-width:280px;display:flex}.int-states-demo{gap:var(--spacing-lg)}.int-state-row{align-items:center;gap:var(--spacing-md);display:flex}.int-state-label{text-transform:uppercase;letter-spacing:.08em;color:var(--color-ash);width:40px;font-size:.6875rem}.int-btn{padding:var(--spacing-sm) var(--spacing-md);cursor:pointer;border-radius:4px;flex:1;font-size:.875rem;font-weight:500}.int-btn-poor{background:var(--color-charcoal);color:var(--color-paper);border:none}.int-btn-good{background:var(--color-text);color:var(--color-paper);border:2px solid #0000;transition:all .15s}.int-btn-good:hover{background:var(--color-charcoal)}.int-btn-good:focus{border-color:var(--color-accent);box-shadow:0 0 0 3px var(--color-accent);outline:none}@supports (color:color-mix(in lab, red, red)){.int-btn-good:focus{box-shadow:0 0 0 3px color-mix(in oklch, var(--color-accent) 25%, transparent)}}.int-btn-good:active{transform:scale(.98)}.int-aff-item{padding:var(--spacing-sm) var(--spacing-md);cursor:pointer;border-radius:4px;font-size:.875rem}.int-aff-poor{color:var(--color-charcoal)}.int-aff-good{color:var(--color-accent);text-underline-offset:2px;text-decoration:underline}.int-aff-good:after{content:" →"}.int-affordance-after .int-aff-item{background:var(--color-bg);border:1px solid var(--color-mist);color:var(--color-accent);text-underline-offset:2px;text-decoration:underline;transition:background .15s}.int-affordance-after .int-aff-item:hover{background:var(--color-accent)}@supports (color:color-mix(in lab, red, red)){.int-affordance-after .int-aff-item:hover{background:color-mix(in oklch, var(--color-accent) 5%, var(--color-paper))}}.int-affordance-after .int-aff-item:after{content:" →"}.int-feedback-before,.int-feedback-after{align-items:center;gap:var(--spacing-md);flex-direction:row;display:flex}.int-fb-btn{cursor:pointer;border:none;border-radius:50%;justify-content:center;align-items:center;width:48px;height:48px;display:flex}.int-fb-btn svg{width:22px;height:22px}.int-fb-silent{background:var(--color-mist);color:var(--color-ash)}.int-fb-active{background:var(--color-charcoal);color:var(--color-paper);transition:all .15s cubic-bezier(.34,1.56,.64,1)}.int-fb-active:hover{transform:scale(1.1)}.int-fb-active:active{transform:scale(.95)}.int-fb-active.liked{background:var(--color-accent);animation:.35s cubic-bezier(.34,1.56,.64,1) heartPop}@keyframes heartPop{50%{transform:scale(1.25)}}.int-fb-label{color:var(--color-charcoal);font-size:.875rem}.color-demo{width:100%;max-width:300px}.color-palette-before,.color-palette-after{gap:var(--spacing-xs);padding:var(--spacing-md);background:var(--color-paper);border:1px solid var(--color-mist);border-radius:6px;flex-wrap:wrap;display:flex}.color-swatch{border-radius:4px;width:40px;height:40px;transition:background .2s}.color-card{width:100%;margin-top:var(--spacing-sm);padding:var(--spacing-sm);background:var(--color-paper);border:1px solid var(--color-mist);border-radius:4px;flex-direction:column;gap:4px;display:flex}.color-card span{font-size:.8125rem;font-weight:500;transition:color .2s}.color-card button{cursor:pointer;border:none;border-radius:3px;padding:6px;font-size:.75rem;font-weight:500;transition:all .2s}.color-palette-before .swatch-1{background:#ff6b6b}.color-palette-before .swatch-2{background:#4ecdc4}.color-palette-before .swatch-3{background:#ffe66d}.color-palette-before .swatch-4{background:#95e1d3}.color-palette-before .swatch-5{background:#f38181}.color-palette-before .card-title{color:#ff6b6b}.color-palette-before .card-subtitle{color:#4ecdc4}.color-palette-before .card-btn{color:#333;background:#ffe66d}.color-palette-after .swatch-1{background:var(--color-text)}.color-palette-after .swatch-2{background:var(--color-charcoal)}.color-palette-after .swatch-3{background:var(--color-ash)}.color-palette-after .swatch-4{background:var(--color-mist)}.color-palette-after .swatch-5{background:var(--color-accent)}.color-palette-after .card-title{color:var(--color-text)}.color-palette-after .card-subtitle{color:var(--color-ash)}.color-palette-after .card-btn{background:var(--color-accent);color:var(--color-paper)}.color-accent-card{padding:var(--spacing-md);border-radius:6px}.color-accent-before .color-accent-card{background:#f5f5f5;border:1px solid #e0e0e0}.color-accent-before .color-accent-title{color:#333;margin-bottom:4px;font-weight:600}.color-accent-before .color-accent-text{color:#666;margin-bottom:var(--spacing-sm);font-size:.8125rem}.color-accent-before .color-accent-btn{width:100%;padding:var(--spacing-xs);color:#fff;cursor:pointer;background:#333;border:none;border-radius:4px;font-size:.8125rem}.color-accent-after .color-accent-card{background:var(--color-accent)}@supports (color:color-mix(in lab, red, red)){.color-accent-after .color-accent-card{background:color-mix(in oklch, var(--color-accent) 8%, var(--color-paper))}}.color-accent-after .color-accent-card{border:1px solid var(--color-accent)}@supports (color:color-mix(in lab, red, red)){.color-accent-after .color-accent-card{border:1px solid color-mix(in oklch, var(--color-accent) 20%, var(--color-paper))}}.color-accent-after .color-accent-title{color:var(--color-text);margin-bottom:4px;font-weight:600}.color-accent-after .color-accent-text{color:var(--color-ash);margin-bottom:var(--spacing-sm);font-size:.8125rem}.color-accent-after .color-accent-btn{width:100%;padding:var(--spacing-xs);background:var(--color-accent);color:var(--color-paper);cursor:pointer;border:none;border-radius:4px;font-size:.8125rem;font-weight:500}.color-contrast-static{gap:var(--spacing-sm);flex-direction:column;display:flex}.contrast-example{padding:var(--spacing-md);text-align:center;border-radius:6px}.contrast-fail{color:#a0a0a0;background:#f0f0f0}.contrast-pass{background:var(--color-charcoal);color:var(--color-paper)}.contrast-badge{text-transform:uppercase;letter-spacing:.1em;border-radius:2px;margin-bottom:4px;padding:2px 6px;font-size:.5625rem;font-weight:600;display:inline-block}.contrast-fail .contrast-badge{background:#ddd}.contrast-pass .contrast-badge{background:var(--color-accent);color:var(--color-paper)}.contrast-text{margin-bottom:2px;font-size:1rem;font-weight:500}.contrast-ratio{opacity:.7;font-size:.6875rem}.resp-demo{width:100%;max-width:340px}.resp-touch-demo{gap:var(--spacing-lg);flex-direction:column;display:flex}.resp-touch-row{align-items:center;gap:var(--spacing-md);display:flex}.resp-label{text-transform:uppercase;letter-spacing:.08em;color:var(--color-ash);width:70px;font-size:.6875rem}.resp-touch-targets{gap:4px;display:flex}.resp-touch-targets button{cursor:pointer;border:none;border-radius:4px;font-weight:500}.resp-touch-bad button{background:var(--color-mist);width:24px;height:24px;color:var(--color-ash);font-size:.75rem}.resp-touch-good button{background:var(--color-text);width:44px;height:44px;color:var(--color-paper);font-size:1rem}.resp-fluid-demo{padding:var(--spacing-md);background:var(--color-bg);border:1px solid var(--color-mist);border-radius:6px}.resp-fluid-container{gap:var(--spacing-md);flex-direction:column;display:flex}.resp-fluid-fixed,.resp-fluid-adaptive{color:var(--color-ash);font-size:.75rem}.resp-fluid-fixed span,.resp-fluid-adaptive span{margin-bottom:4px;display:block}.resp-fluid-bar{background:var(--color-mist);border-radius:4px;height:24px}.resp-fluid-adaptive .resp-fluid-bar{background:var(--color-accent)}.resp-adapt-demo{gap:var(--spacing-sm);align-items:flex-end;display:flex}.resp-device{text-align:center}.resp-device>span{color:var(--color-ash);text-transform:uppercase;letter-spacing:.08em;margin-top:4px;font-size:.625rem;display:block}.resp-device-screen{background:var(--color-paper);border:2px solid var(--color-mist);border-radius:4px;flex-direction:column;gap:3px;padding:4px;display:flex}.resp-device-mobile .resp-device-screen{width:50px;height:80px}.resp-device-tablet .resp-device-screen{width:80px;height:60px}.resp-device-desktop .resp-device-screen{width:120px;height:70px}.resp-block{background:var(--color-mist);border-radius:2px}.resp-block-row{flex:1;gap:3px;display:flex}.resp-header{background:var(--color-charcoal);height:16px}.resp-sidebar{background:var(--color-charcoal);width:30%}.resp-content{flex:1}@keyframes fadeIn{to{opacity:1}}@media (prefers-reduced-motion:reduce){*,:before,:after{transition-duration:.01ms!important;animation-duration:.01ms!important;animation-iteration-count:1!important}}*,:before,:after{box-sizing:border-box}*{margin:0}img,picture,video,canvas,svg{max-width:100%;display:block}button,input,textarea,select{font:inherit}:root{--font-display:"Cormorant Garamond", Georgia, serif;--font-body:"Instrument Sans", system-ui, sans-serif;--font-mono:"Space Grotesk", monospace;--spacing-xs:8px;--spacing-sm:16px;--spacing-md:24px;--spacing-lg:32px;--spacing-xl:48px;--spacing-2xl:80px;--spacing-3xl:120px;--width-max:1400px;--width-content:900px;--ease-out:cubic-bezier(.16, 1, .3, 1);--ease-in-out:cubic-bezier(.65, 0, .35, 1);--ease-spring:cubic-bezier(.34, 1.56, .64, 1);--duration-fast:.15s;--duration-base:.3s;--duration-slow:.6s;--duration-slower:.8s;--duration-slowest:1.2s;--color-ink:oklch(10% 0 0);--color-text:oklch(10% 0 0);--color-paper:oklch(98% 0 0);--color-cream:oklch(96% .005 350);--color-charcoal:oklch(25% 0 0);--color-ash:oklch(55% 0 0);--color-mist:oklch(92% 0 0);--color-bg:oklch(96% .005 350);--color-accent:oklch(60% .25 350);--color-accent-hover:oklch(52% .25 350);--color-accent-dim:oklch(60% .25 350/.15);--color-accent-soft:oklch(60% .25 350/.25);--cat-diagnostic-bg:#fdf4ff;--cat-diagnostic-border:#d946ef;--cat-diagnostic-text:#a21caf;--cat-quality-bg:#f0fdf4;--cat-quality-border:#22c55e;--cat-quality-text:#15803d;--cat-intensity-bg:#fffbeb;--cat-intensity-border:#f59e0b;--cat-intensity-text:#b45309;--cat-adaptation-bg:#eff6ff;--cat-adaptation-border:#3b82f6;--cat-adaptation-text:#1d4ed8;--cat-enhancement-bg:#fdf2f8;--cat-enhancement-border:#ec4899;--cat-enhancement-text:#be185d;--cat-system-bg:#f5f5f4;--cat-system-border:#78716c;--cat-system-text:#44403c}.skip-link{z-index:10000;padding:var(--spacing-sm) var(--spacing-lg);background:var(--color-ink);color:var(--color-paper);border-radius:0 0 8px 8px;font-weight:600;text-decoration:none;transition:top .2s;position:absolute;top:-100%;left:50%;transform:translate(-50%)}.skip-link:focus{outline:2px solid var(--color-accent);outline-offset:2px;top:0}html{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-rendering:optimizelegibility;overflow-x:clip}body{font-family:var(--font-body);color:var(--color-text);background:var(--color-paper);min-height:100dvh;font-size:16px;line-height:1.625;overflow-x:clip}h1,h2,h3,h4,h5,h6{font-family:var(--font-display);letter-spacing:-.02em;color:var(--color-ink);font-weight:400;line-height:1.1}a{color:var(--color-accent);text-underline-offset:2px;transition:color var(--duration-fast) var(--ease-out), text-decoration-color var(--duration-fast) var(--ease-out);text-decoration:underline;text-decoration-thickness:1px}a:hover{color:var(--color-accent-hover);text-decoration-thickness:2px}.btn,.footer-logo,[class*=nav-item]{text-decoration:none}strong{color:var(--color-ink);font-weight:600}code{font-family:var(--font-mono);background:var(--color-accent-dim);color:var(--color-accent);border-radius:4px;padding:.15em .4em;font-size:.9em}::selection{background:var(--color-accent-soft);color:var(--color-ink)}.grain-overlay{pointer-events:none;z-index:9999;opacity:.03;background-image:url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E");background-repeat:repeat;position:fixed;inset:0}.site-content{max-width:var(--width-max);padding:0 var(--spacing-lg);margin:0 auto}@media (max-width:768px){.site-content{padding:0 var(--spacing-md)}}.section-header{margin-bottom:var(--spacing-lg);position:relative}.section-number{font-family:var(--font-mono);letter-spacing:.05em;color:var(--color-ash);margin-bottom:var(--spacing-xs);text-transform:uppercase;font-size:.625rem;font-weight:500;display:block}.section-title{margin:0;font-size:clamp(1.75rem,4vw,2.5rem);font-weight:400;line-height:1.2}.section-subtitle{color:var(--color-charcoal);margin-top:var(--spacing-sm);max-width:55ch;font-size:1rem;line-height:1.6}.cheatsheet-link{color:var(--color-accent);margin-left:.5em;font-size:.875rem;text-decoration:none}.cheatsheet-link:hover{text-decoration:underline}.section-lead{color:var(--color-charcoal);max-width:55ch;margin-bottom:var(--spacing-lg);font-size:1rem;line-height:1.6}.hero-combined{min-height:100dvh;padding:var(--spacing-2xl) 0;background:var(--color-paper);flex-direction:column;justify-content:center;display:flex;position:relative}.github-link{top:var(--spacing-md);right:var(--spacing-lg);z-index:10;color:var(--color-ash);transition:color .2s;position:absolute}.github-link:hover{color:var(--color-ink)}.github-link svg{width:28px;height:28px}.hero-combined-container{max-width:var(--width-max);padding:0 var(--spacing-lg);gap:var(--spacing-xl);grid-template-columns:1fr 1fr;align-items:center;width:100%;margin:0 auto;display:grid}@media (max-width:1024px){.hero-combined-container{gap:var(--spacing-lg);text-align:center;grid-template-columns:1fr}}.hero-combined-left{gap:var(--spacing-md);flex-direction:column;display:flex}@media (max-width:1024px){.hero-combined-left{align-items:center}}.hero-title-combined{font-family:var(--font-display);letter-spacing:-.02em;color:var(--color-ink);margin:0;font-size:clamp(2.5rem,7vw,4.5rem);font-style:italic;font-weight:300;line-height:1}.hero-tagline-combined{font-family:var(--font-display);color:var(--color-charcoal);margin:0;font-size:clamp(1.125rem,2.5vw,1.75rem);font-style:italic;font-weight:400;line-height:1.3}.hero-hook-text{color:var(--color-charcoal);max-width:45ch;margin:0;font-size:1rem;line-height:1.6}.hero-included-box{border:1px solid var(--color-mist);background:0 0;flex-direction:column;gap:6px;max-width:45ch;padding:10px 14px;display:flex}.hero-included-title{font-family:var(--font-body);text-transform:uppercase;letter-spacing:.1em;color:var(--color-ash);font-size:.5625rem;font-weight:500}.hero-included-items{color:var(--color-charcoal);flex-wrap:wrap;align-items:center;gap:6px;font-size:.8125rem;line-height:1.5;display:flex}.hero-included-items em{font-style:normal;font-family:var(--font-mono);font-size:.75rem}.hero-included-sep{color:var(--color-mist)}@media (max-width:500px){.hero-included-items{flex-direction:column;align-items:flex-start;gap:4px}.hero-included-sep{display:none}}.hero-cta-group{align-items:center;gap:var(--spacing-lg);margin-top:var(--spacing-sm);display:flex}@media (max-width:600px){.hero-cta-group{gap:var(--spacing-md);flex-direction:column}}.hero-cta-combined{padding:var(--spacing-sm) var(--spacing-xl);font-family:var(--font-body);letter-spacing:.05em;text-transform:uppercase;color:var(--color-paper);background:var(--color-ink);border:none;font-size:.9rem;font-weight:500;text-decoration:none;transition:transform .2s,background .2s;display:inline-block}.hero-cta-combined:hover{background:var(--color-accent);color:var(--color-paper);transform:translateY(-2px)}.hero-logos-inline{align-items:center;gap:var(--spacing-sm);display:flex}.hero-logos-inline .hero-logos-label{color:var(--color-ash);letter-spacing:.03em;font-size:.6875rem}.hero-logos-inline .hero-logos-row{align-items:center;gap:8px;display:flex}.hero-logos-inline .hero-logos-row img{opacity:.7;border-radius:4px;transition:opacity .2s}.hero-logos-inline .hero-logos-row img:hover{opacity:1}.hero-combined-right{justify-content:center;display:flex}.hero-combined-right .split-comparison{width:100%;max-width:520px}.hero-combined-right .split-container{max-width:100%}.hero-bias-tags{align-items:center;gap:var(--spacing-xs);margin-top:var(--spacing-lg);padding-top:var(--spacing-md);border-top:1px solid var(--color-mist);max-width:var(--width-max);width:100%;padding-bottom:var(--spacing-md);flex-direction:column;margin-left:auto;margin-right:auto;display:flex}.problem-section{padding:var(--spacing-2xl) 0;border-top:1px solid var(--color-mist)}.problem-content{gap:var(--spacing-xl);display:grid}.split-comparison{width:100%;max-width:600px;margin:-20px auto;padding:20px;position:relative}.split-container{background:var(--color-cream);border:1px solid var(--color-mist);cursor:ew-resize;-webkit-user-select:none;user-select:none;border-radius:12px;width:100%;max-width:500px;height:380px;margin:0 auto;position:relative;overflow:hidden}.split-before,.split-after{justify-content:center;align-items:center;display:flex;position:absolute;inset:0}.split-before{z-index:1}.split-content{justify-content:center;align-items:center;width:100%;height:100%;display:flex}.split-after{clip-path:polygon(78% 0%,100% 0%,100% 100%,62% 100%);z-index:2;background:var(--color-paper)}.split-divider{background:var(--color-accent);pointer-events:none;z-index:3;width:3px;position:absolute;top:0;bottom:0;left:70%;transform:translate(-50%)skew(-10deg);box-shadow:0 0 20px #00000026}.split-label{letter-spacing:.08em;text-transform:uppercase;color:var(--color-paper);background:var(--color-accent);white-space:nowrap;border-radius:4px;padding:6px 14px;font-size:.6875rem;font-weight:600;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)skew(10deg);box-shadow:0 2px 8px #0003}.slop-card{background:linear-gradient(135deg,#f5f3ff 0%,#ede9fe 50%,#ddd6fe 100%);border-radius:16px;flex-direction:column;width:280px;height:280px;padding:24px;font-family:Inter,system-ui,sans-serif;display:flex;box-shadow:0 4px 6px -1px #0000001a}.slop-header{align-items:center;gap:12px;margin-bottom:16px;display:flex}.slop-avatar{background:linear-gradient(135deg,#8b5cf6,#7c3aed);border-radius:50%;flex-shrink:0;width:40px;height:40px}.slop-text{flex:1}.slop-title{color:#1f2937;margin-bottom:2px;font-size:14px;font-weight:600}.slop-subtitle{color:#6b7280;font-size:12px}.slop-body{color:#4b5563;flex:1;margin-bottom:auto;font-size:13px;line-height:1.5}.slop-button{color:#fff;cursor:pointer;background:linear-gradient(135deg,#8b5cf6,#7c3aed);border:none;border-radius:8px;width:100%;margin-top:auto;padding:10px 20px;font-family:Inter,system-ui,sans-serif;font-size:13px;font-weight:500}.slop-callouts{pointer-events:none;position:absolute;inset:0}.slop-callout{text-transform:uppercase;letter-spacing:.06em;color:var(--color-accent);background:var(--color-paper);border:1px solid var(--color-accent);white-space:nowrap;opacity:0;animation:calloutFadeIn .4s var(--ease-out) forwards;border-radius:3px;padding:4px 8px;font-size:.625rem;font-weight:600;position:absolute;box-shadow:0 2px 8px #0000001a}.slop-callout[data-point=font]{animation-delay:.1s;top:15%;right:5%}.slop-callout[data-point=gradient]{animation-delay:.25s;top:40%;left:5%}.slop-callout[data-point=copy]{animation-delay:.4s;bottom:35%;right:8%}.slop-callout[data-point=rounded]{animation-delay:.55s;bottom:12%;left:10%}@keyframes calloutFadeIn{0%{opacity:0;transform:scale(.9)}to{opacity:1;transform:scale(1)}}.impeccable-card{background:var(--color-paper);border:1px solid var(--color-mist);width:280px;height:300px;padding:var(--spacing-lg);text-align:left;flex-direction:column;display:flex}.impeccable-eyebrow{font-family:var(--font-mono);letter-spacing:.15em;text-transform:uppercase;color:var(--color-accent);margin-bottom:var(--spacing-xs);font-size:.625rem;font-weight:500}.impeccable-title{font-family:var(--font-display);color:var(--color-ink);margin-bottom:var(--spacing-sm);font-size:1.75rem;font-style:italic;font-weight:300;line-height:1.1}.impeccable-body{color:var(--color-ash);flex:1;margin-bottom:auto;font-size:.875rem;line-height:1.6}.impeccable-button{margin-top:var(--spacing-sm);background:var(--color-ink);color:var(--color-paper);font-family:var(--font-body);letter-spacing:.03em;cursor:pointer;transition:all var(--duration-base) var(--ease-out);border:none;align-self:flex-start;padding:.625rem 1.5rem;font-size:.8125rem;font-weight:500;display:inline-flex}.impeccable-button:hover{background:var(--color-accent)}.split-labels{justify-content:center;gap:var(--spacing-xl);margin-top:var(--spacing-md);display:flex}.split-label-item{align-items:center;gap:var(--spacing-xs);color:var(--color-ash);font-size:.8125rem;display:flex}.split-label-dot{background:var(--color-mist);border-radius:50%;width:8px;height:8px}.split-label-dot--accent{background:var(--color-accent)}.solution-section{padding:var(--spacing-2xl) 0;border-top:1px solid var(--color-mist)}.solution-content{gap:var(--spacing-lg);display:grid}.solution-content .section-lead{margin-bottom:0}.solution-visual{gap:var(--spacing-lg);grid-template-columns:1fr auto 1fr;align-items:stretch;display:grid}@media (max-width:900px){.solution-visual{gap:var(--spacing-md);grid-template-columns:1fr}}.solution-visual-interactive{background:var(--color-paper);border:1px solid var(--color-mist);border-radius:8px;width:100%;min-height:380px;position:relative;overflow:hidden}.solution-pillar{background:var(--color-cream);border:1px solid var(--color-mist);padding:var(--spacing-lg);transition:all var(--duration-base) var(--ease-out)}.solution-pillar:hover{border-color:var(--color-accent);box-shadow:0 20px 60px var(--color-accent-dim);transform:translateY(-4px)}.pillar-header{text-align:center;margin-bottom:var(--spacing-lg);padding-bottom:var(--spacing-md);border-bottom:1px solid var(--color-mist)}.pillar-icon{background:var(--color-accent-dim);width:56px;height:56px;color:var(--color-accent);margin-bottom:var(--spacing-sm);border-radius:50%;justify-content:center;align-items:center;display:inline-flex}.pillar-title{font-family:var(--font-display);margin:0 0 var(--spacing-xs);font-size:1.75rem;font-weight:400}.pillar-subtitle{color:var(--color-ash);margin:0;font-size:.875rem}.pillar-content{gap:var(--spacing-sm);flex-direction:column;display:flex}.pillar-item{padding:var(--spacing-sm);background:var(--color-paper);transition:all var(--duration-fast) var(--ease-out);border-radius:4px;justify-content:space-between;align-items:center;display:flex}.pillar-item:hover{background:var(--color-accent-dim)}.pillar-item-name{color:var(--color-ink);font-size:.9375rem;font-weight:500}.pillar-item-code{font-family:var(--font-mono);color:var(--color-accent);background:0 0;padding:0;font-size:.875rem;font-weight:500}.pillar-item-desc{color:var(--color-ash);font-size:.75rem}.pillar-item--more{color:var(--color-accent);border:1px dashed var(--color-mist);background:0 0;justify-content:center;font-size:.8125rem;font-weight:500}.solution-connector{justify-content:center;align-items:center;display:flex}.connector-plus{font-family:var(--font-display);color:var(--color-accent);opacity:.5;font-size:3rem;font-weight:300}@media (max-width:900px){.solution-connector{padding:var(--spacing-sm) 0}.connector-plus{font-size:2rem}}.skills-section{padding:var(--spacing-2xl) 0;border-top:1px solid var(--color-mist)}.skills-gallery{gap:var(--spacing-xl);grid-template-columns:200px 1fr;align-items:start;display:grid}@media (max-width:968px){.skills-gallery{gap:var(--spacing-lg);grid-template-columns:1fr}}.skills-nav{top:var(--spacing-lg);flex-direction:column;gap:2px;display:flex;position:sticky}@media (max-width:968px){.skills-nav{gap:var(--spacing-xs);flex-flow:wrap;position:static}}.skill-nav-item{padding:var(--spacing-sm) var(--spacing-md);color:var(--color-ash);font-family:var(--font-body);cursor:pointer;text-align:left;background:0 0;border:none;border-left:2px solid #0000;font-size:.9375rem;font-weight:400;text-decoration:none;transition:all .2s;display:block}.skill-nav-item:hover{color:var(--color-text);background:var(--color-cream)}.skill-nav-item.active{color:var(--color-accent);border-left-color:var(--color-accent);background:var(--color-accent-dim);font-weight:500}@media (max-width:968px){.skill-nav-item{padding:var(--spacing-xs) var(--spacing-md);border-bottom:2px solid #0000;border-left:none}.skill-nav-item.active{border-bottom-color:var(--color-accent)}}.skills-showcase{gap:var(--spacing-lg);grid-template-columns:1.2fr 1fr;align-items:start;display:grid}@media (max-width:1100px){.skills-showcase{grid-template-columns:1fr}}.loading-state{padding:var(--spacing-xl);text-align:center;color:var(--color-ash);font-style:italic}.mobile-commands-layout{display:none}@media (max-width:900px){.mobile-commands-layout{gap:var(--spacing-md);flex-direction:column;display:flex}.commands-container{display:none}}.mobile-carousel-wrapper{-webkit-overflow-scrolling:touch;scrollbar-width:none;padding:var(--spacing-xs) 0;overflow-x:auto}.mobile-carousel-wrapper::-webkit-scrollbar{display:none}.mobile-carousel{gap:var(--spacing-xs);padding-right:var(--spacing-md);display:flex}.mobile-cmd-pill{padding:var(--spacing-sm) var(--spacing-md);min-height:44px;font-family:var(--font-mono);color:var(--color-ash);background:var(--color-cream);border:1px solid var(--color-mist);cursor:pointer;white-space:nowrap;border-radius:100px;flex-shrink:0;font-size:.8125rem;font-weight:500;transition:all .2s}.mobile-cmd-pill:hover{color:var(--color-charcoal);border-color:var(--color-charcoal)}.mobile-cmd-pill.active{color:var(--color-paper);background:var(--color-ink);border-color:var(--color-ink)}.mobile-demo-area{background:var(--color-cream);border:1px solid var(--color-mist);padding:var(--spacing-sm);border-radius:8px}.mobile-demo-area .demo-split-comparison{width:100%}.mobile-demo-area .split-container{width:100%;max-width:100%;height:320px}.mobile-demo-area .demo-caption{margin-top:var(--spacing-sm);font-size:.75rem}.mobile-info-area{padding-top:var(--spacing-sm)}.mobile-cmd-info{padding:var(--spacing-sm) 0;display:none}.mobile-cmd-info.active{display:block}.mobile-cmd-name{font-family:var(--font-mono);color:var(--color-ink);margin:0 0 var(--spacing-xs) 0;font-size:1.125rem;font-weight:600}.mobile-cmd-desc{color:var(--color-charcoal);margin:0;font-size:.875rem;line-height:1.5}.mobile-cmd-rel{margin-top:var(--spacing-xs);color:var(--color-ash);font-size:.75rem}.mobile-cmd-rel code{font-family:var(--font-mono);color:var(--color-ink)}.downloads-section{padding:var(--spacing-2xl) 0;border-top:1px solid var(--color-mist)}.downloads-grid{gap:var(--spacing-lg);grid-template-columns:repeat(auto-fit,minmax(280px,1fr));display:grid}.download-card{text-align:center;padding:var(--spacing-lg);background:var(--color-cream);border:1px solid var(--color-mist);transition:all var(--duration-base) var(--ease-out);flex-direction:column;align-items:center;display:flex}.download-card:hover{border-color:var(--color-accent);box-shadow:0 20px 60px var(--color-accent-dim);transform:translateY(-4px)}.download-card-icon{margin-bottom:var(--spacing-sm)}.download-card-icon img{object-fit:contain;border-radius:8px;width:40px;height:40px}.download-card-title{font-family:var(--font-display);margin:0 0 var(--spacing-sm) 0;font-size:1.25rem;font-weight:400}.download-card-note{color:var(--color-ash);margin-bottom:var(--spacing-xs);font-size:.75rem}.download-card .btn{margin-top:var(--spacing-xs)}.install-command{align-items:center;gap:var(--spacing-xs);background:var(--color-paper);border:1px solid var(--color-mist);padding:var(--spacing-sm);margin-top:var(--spacing-sm);border-radius:6px;width:100%;display:flex}.install-command code{font-family:var(--font-mono);color:var(--color-ink);white-space:nowrap;text-overflow:ellipsis;background:0 0;flex:1;padding:0;font-size:.75rem;overflow:hidden}.copy-btn{border:1px solid var(--color-mist);width:28px;height:28px;color:var(--color-ash);cursor:pointer;transition:all var(--duration-fast) var(--ease-out);background:0 0;border-radius:4px;flex-shrink:0;justify-content:center;align-items:center;display:flex}.copy-btn:hover{background:var(--color-accent-dim);border-color:var(--color-accent);color:var(--color-accent)}.copy-btn.copied{background:var(--color-accent);border-color:var(--color-accent);color:var(--color-paper)}.install-hint{color:var(--color-ash);margin:var(--spacing-xs) 0 0 0;font-size:.75rem}.install-hint code{font-family:var(--font-mono);background:var(--color-mist);border-radius:3px;padding:2px 5px;font-size:.6875rem}.download-card-details{width:100%;margin-top:var(--spacing-sm);text-align:left;font-size:.8125rem}.download-card-details summary{cursor:pointer;color:var(--color-ash);padding:var(--spacing-xs) 0;justify-content:center;align-items:center;gap:4px;font-size:.75rem;list-style:none;display:flex}.download-card-details summary:before{content:"▶";transition:transform var(--duration-fast) var(--ease-out);font-size:.5rem}.download-card-details[open] summary:before{transform:rotate(90deg)}.download-card-details summary::-webkit-details-marker{display:none}.download-card-details ol{margin:var(--spacing-sm) 0;padding-left:var(--spacing-md);color:var(--color-charcoal);line-height:1.6}.download-card-details li{margin-bottom:4px}.download-card-details code{font-family:var(--font-mono);background:var(--color-mist);border-radius:3px;padding:2px 5px;font-size:.6875rem}.download-card-details a{color:var(--color-accent);font-size:.75rem;text-decoration:none}.download-card-details a:hover{text-decoration:underline}.opensource-section{padding:var(--spacing-2xl) 0;border-top:1px solid var(--color-mist);text-align:center}.opensource-content{align-items:center;gap:var(--spacing-md);flex-direction:column;max-width:500px;margin:0 auto;display:flex}.opensource-title{font-size:clamp(1.5rem,4vw,2rem);font-weight:300}.opensource-desc{color:var(--color-ash);font-size:1.125rem;line-height:1.6}.site-footer{border-top:1px solid var(--color-mist);padding:var(--spacing-xl) var(--spacing-lg);background:var(--color-cream)}.footer-content{max-width:var(--width-max);justify-content:space-between;align-items:center;gap:var(--spacing-lg);flex-wrap:wrap;margin:0 auto;display:flex}@media (max-width:768px){.footer-content{text-align:center;flex-direction:column}}.footer-brand{gap:var(--spacing-xs);flex-direction:column;display:flex}.footer-logo{font-family:var(--font-display);color:var(--color-ink);font-size:1.25rem;font-weight:400}.footer-tagline{color:var(--color-ash);font-size:.875rem}.footer-links{gap:var(--spacing-lg);flex-wrap:wrap;display:flex}.footer-links a{color:var(--color-ash);transition:color var(--duration-fast) var(--ease-out);font-size:.875rem}.footer-links a:hover{color:var(--color-accent)}.footer-author{justify-content:center;align-items:center;gap:var(--spacing-md);width:100%;padding-top:var(--spacing-lg);margin-top:var(--spacing-md);border-top:1px solid var(--color-mist);flex-wrap:wrap;display:flex}.footer-author-label{color:var(--color-ash);font-size:.875rem}.footer-author-label a{color:var(--color-text);transition:color var(--duration-fast) var(--ease-out)}.footer-author-label a:hover{color:var(--color-accent)}.footer-author-links{align-items:center;gap:var(--spacing-sm);display:flex}.footer-social-link{width:36px;height:36px;color:var(--color-ash);transition:all var(--duration-fast) var(--ease-out);background:0 0;border-radius:50%;justify-content:center;align-items:center;text-decoration:none;display:flex}.footer-social-link:hover{color:var(--color-accent);background:var(--color-accent-dim)}.footer-author-divider{background:var(--color-mist);width:1px;height:20px;margin:0 var(--spacing-xs)}.footer-newsletter{color:var(--color-text);background:var(--color-paper);border:1px solid var(--color-mist);transition:all var(--duration-fast) var(--ease-out);border-radius:100px;align-items:center;gap:6px;padding:8px 14px;font-size:.875rem;font-weight:500;text-decoration:none;display:inline-flex}.footer-newsletter:hover{border-color:var(--color-accent);color:var(--color-accent)}.footer-newsletter svg{transition:transform var(--duration-fast) var(--ease-out)}.footer-newsletter:hover svg{transform:translate(3px)}@media (max-width:768px){.footer-author{gap:var(--spacing-sm);flex-direction:column}.footer-author-divider{display:none}}.btn{justify-content:center;align-items:center;gap:var(--spacing-xs);font-family:var(--font-body);letter-spacing:.03em;cursor:pointer;transition:all var(--duration-base) var(--ease-out);border:none;padding:1rem 2rem;font-size:.9375rem;font-weight:600;text-decoration:none;display:inline-flex;position:relative;overflow:hidden}.btn-primary{background:var(--color-ink);color:var(--color-paper)}.btn-primary:before{content:"";background:var(--color-accent);transition:transform var(--duration-base) var(--ease-out);z-index:0;position:absolute;inset:0;transform:translateY(100%)}.btn-primary:hover:before{transform:translateY(0)}.btn-primary:hover{color:var(--color-paper)}.btn-primary span,.btn-primary svg,.btn-primary:not(:has(span)){z-index:1;position:relative}.btn-secondary{color:var(--color-ink);border:1px solid var(--color-ink);background:0 0}.btn-secondary:hover{background:var(--color-ink);color:var(--color-paper)}.btn:focus-visible{outline:2px solid var(--color-accent);outline-offset:2px}.btn-primary:focus-visible{outline-color:var(--color-paper);box-shadow:0 0 0 4px var(--color-accent)}.btn-secondary:focus-visible{outline-color:var(--color-accent)}@keyframes revealUp{to{opacity:1;transform:translateY(0)}}@keyframes float{0%,to{transform:translate(-50%)translateY(0)}50%{transform:translate(-50%)translateY(-8px)}}@keyframes bounce{0%,to{transform:translateY(0)}50%{transform:translateY(4px)}}[data-reveal]{opacity:0;transition:opacity .8s var(--ease-out), transform .8s var(--ease-out);transform:translateY(30px)}[data-reveal].revealed{opacity:1;transform:translateY(0)}[data-reveal]:first-child{transition-delay:0s}[data-reveal]:nth-child(2){transition-delay:.1s}[data-reveal]:nth-child(3){transition-delay:.2s}[data-reveal]:nth-child(4){transition-delay:.3s}@media (prefers-reduced-motion:reduce){*,:before,:after{scroll-behavior:auto!important;transition-duration:.01ms!important;animation-duration:.01ms!important;animation-iteration-count:1!important}html{scroll-behavior:auto}.hero-canvas{display:none}.hero-scroll-indicator{opacity:1;animation:none}[data-reveal],.gallery-frame{opacity:1;transform:none}}.load-error{text-align:center;padding:var(--spacing-2xl) var(--spacing-lg);justify-content:center;align-items:center;gap:var(--spacing-md);background:var(--color-cream);border:1px solid var(--color-mist);border-radius:8px;flex-direction:column;display:flex}.load-error-icon{color:var(--color-accent);font-size:2.5rem}.load-error-title{font-family:var(--font-display);color:var(--color-ink);margin:0;font-size:1.5rem;font-weight:400}.load-error-text{color:var(--color-ash);max-width:40ch;font-size:1rem;line-height:1.5}.load-error-retry{margin-top:var(--spacing-sm)}.bias-tags{align-items:center;gap:var(--spacing-sm);margin-top:var(--spacing-lg);flex-direction:column;display:flex}.bias-tags-label{font-family:var(--font-mono);letter-spacing:.1em;text-transform:uppercase;color:var(--color-ash);font-size:.6875rem;font-weight:500}.bias-tags-list{justify-content:center;gap:var(--spacing-xs);flex-wrap:wrap;display:flex}.bias-tag{background:var(--color-cream);border:1px solid var(--color-mist);color:var(--color-charcoal);transition:all var(--duration-fast) var(--ease-out);padding:6px 12px;font-size:.75rem;font-weight:500}.bias-tag:hover{border-color:var(--color-accent);color:var(--color-accent)}.antidote-section{padding:var(--spacing-2xl) 0;border-top:1px solid var(--color-mist)}.patterns-categories{margin-bottom:var(--spacing-xl)}.pattern-tabs{gap:var(--spacing-xs);margin-bottom:var(--spacing-lg);flex-wrap:wrap;display:flex}.pattern-tab{font-family:var(--font-body);color:var(--color-ash);padding:var(--spacing-xs) var(--spacing-sm);cursor:pointer;transition:color var(--duration-fast) var(--ease-out), border-color var(--duration-fast) var(--ease-out);background:0 0;border:none;border-bottom:2px solid #0000;font-size:.875rem}.pattern-tab:hover{color:var(--color-charcoal)}.pattern-tab.active{color:var(--color-ink);border-bottom-color:var(--color-accent);font-weight:500}.pattern-tab:focus-visible{outline:2px solid var(--color-accent);outline-offset:2px;border-radius:4px}.pattern-panel{display:none}.pattern-panel.active{display:block}.pattern-columns{gap:var(--spacing-xl);grid-template-columns:1fr 1fr;display:grid}@media (max-width:768px){.pattern-columns{gap:var(--spacing-md);grid-template-columns:1fr}}.pattern-column-label{text-transform:uppercase;letter-spacing:.1em;margin-bottom:var(--spacing-sm);color:var(--color-ash);font-size:.6875rem;font-weight:600;display:block}.pattern-column--anti .pattern-column-label{color:var(--color-accent)}.pattern-column--do .pattern-column-label{color:var(--color-success,#22c55e)}.pattern-list{gap:var(--spacing-xs);flex-direction:column;margin:0;padding:0;list-style:none;display:flex}.pattern-item{padding-left:var(--spacing-md);font-size:.8125rem;line-height:1.5;position:relative}.pattern-item--anti{color:var(--color-ash)}.pattern-item--anti:before{content:"×";color:var(--color-accent);font-weight:600;position:absolute;left:0}.pattern-item--do{color:var(--color-charcoal)}.pattern-item--do:before{content:"✓";color:var(--color-success,#22c55e);font-weight:600;position:absolute;left:0}.contribute-inline{color:var(--color-ash);margin-top:var(--spacing-md);font-size:.875rem}.contribute-inline a{color:var(--color-accent);text-decoration:none}.contribute-inline a:hover{text-decoration:underline}.pillar-item--main{background:var(--color-accent-dim);border:1px solid var(--color-accent)}.pillar-item--main .pillar-item-name{color:var(--color-accent);font-size:1.125rem;font-weight:600}.pillar-item--ref{padding:var(--spacing-xs) var(--spacing-sm);background:0 0}.pillar-item-label{text-transform:uppercase;letter-spacing:.05em;color:var(--color-ash);font-size:.75rem;font-weight:500}.pillar-refs{gap:var(--spacing-xs);padding:0 var(--spacing-sm);flex-wrap:wrap;display:flex}.pillar-ref{text-transform:uppercase;letter-spacing:.03em;background:var(--color-paper);color:var(--color-ash);border:1px solid var(--color-mist);transition:all var(--duration-fast) var(--ease-out);border-radius:3px;padding:4px 10px;font-size:.6875rem;font-weight:500}.pillar-ref:hover{border-color:var(--color-accent);color:var(--color-accent)}.pillar-command-group{align-items:center;gap:var(--spacing-xs);padding:var(--spacing-sm);background:var(--color-paper);border-radius:4px;flex-wrap:wrap;display:flex}.pillar-group-label{text-transform:uppercase;letter-spacing:.05em;color:var(--color-ash);width:100%;margin-bottom:4px;font-size:.6875rem;font-weight:600}.pillar-command-group .pillar-item-code{background:var(--color-accent-dim);border-radius:3px;padding:4px 8px;font-size:.8125rem}.platforms-section{padding:var(--spacing-2xl) 0;border-top:1px solid var(--color-mist)}.platforms-section .section-subtitle{max-width:60ch}.install-terminal{max-width:640px;margin:0 auto var(--spacing-xl)}.install-terminal .glass-terminal{height:auto}.install-terminal .terminal-body{flex-direction:column;padding:0;display:flex}.install-terminal-row{padding:var(--spacing-md) var(--spacing-lg);flex-direction:column;gap:6px;display:flex}.install-terminal-label{font-family:var(--font-mono);text-transform:uppercase;letter-spacing:.1em;color:var(--color-ash);font-size:.625rem;font-weight:600}.install-terminal-cmd{align-items:center;gap:var(--spacing-sm);display:flex}.install-terminal-cmd .terminal-prompt{flex-shrink:0}.install-terminal-cmd code{font-family:var(--font-mono);color:var(--color-ink);white-space:nowrap;text-overflow:ellipsis;background:0 0;flex:1;padding:0;font-size:.9375rem;overflow:hidden}.install-terminal-cmd .copy-btn{flex-shrink:0}.install-terminal-cmd .btn{padding:.5rem 1rem;font-size:.8125rem}.install-terminal-note{color:var(--color-ash);padding-left:calc(.75rem + var(--spacing-sm));font-size:.75rem}.install-terminal-note code{font-family:var(--font-mono);background:var(--color-mist);color:var(--color-ink);border-radius:3px;padding:2px 5px;font-size:.6875rem}.install-terminal-divider{background:var(--color-mist);height:1px;margin:0}@media (max-width:600px){.install-terminal-row{padding:var(--spacing-sm) var(--spacing-md)}.install-terminal-cmd code{font-size:.75rem}}.install-providers{align-items:center;gap:var(--spacing-sm);margin-top:var(--spacing-md);flex-direction:column;display:flex}.install-providers-label{text-transform:uppercase;letter-spacing:.1em;color:var(--color-ash);font-size:.75rem;font-weight:600}.install-providers-row{justify-content:center;gap:var(--spacing-sm);flex-wrap:wrap;display:flex}.install-provider-badge{color:var(--color-charcoal);align-items:center;gap:6px;font-size:.8125rem;display:flex}.install-provider-badge img{border-radius:4px}.install-terminal-cmd--download{align-items:center;gap:var(--spacing-sm);flex-wrap:wrap;display:flex}.prefix-toggle{cursor:pointer;transition:border-color var(--duration-fast) var(--ease-out);background:#0000000f;border:1px solid #00000014;border-radius:6px;align-items:center;gap:8px;padding:6px 12px;display:inline-flex}.prefix-toggle:hover{border-color:#0003}.prefix-toggle input{opacity:0;width:0;height:0;position:absolute}.prefix-toggle-slider{background:var(--color-mist);width:32px;height:18px;transition:background var(--duration-fast) var(--ease-out);border-radius:18px;flex-shrink:0;position:relative}.prefix-toggle-slider:after{content:"";background:var(--color-paper);width:14px;height:14px;transition:transform var(--duration-fast) var(--ease-out);border-radius:50%;position:absolute;top:2px;left:2px;box-shadow:0 1px 2px #00000026}.prefix-toggle input:checked+.prefix-toggle-slider{background:var(--color-accent)}.prefix-toggle input:checked+.prefix-toggle-slider:after{transform:translate(14px)}.prefix-toggle input:focus-visible+.prefix-toggle-slider{outline:2px solid var(--color-accent);outline-offset:2px}.prefix-toggle-label{color:var(--color-charcoal);white-space:nowrap;font-size:.75rem}.prefix-toggle-label code{font-family:var(--font-mono);background:var(--color-accent-dim);color:var(--color-accent);border-radius:3px;padding:1px 4px;font-size:.6875rem}.has-tooltip{cursor:default;position:relative}.has-tooltip:after{content:attr(data-tooltip);background:var(--color-ink);color:var(--color-paper);white-space:nowrap;pointer-events:none;opacity:0;z-index:100;border-radius:6px;padding:6px 10px;font-size:.6875rem;line-height:1.4;transition:opacity .15s;position:absolute;bottom:calc(100% + 8px);left:50%;transform:translate(-50%)}.has-tooltip:hover:after{opacity:1}.hero-logo-icon{align-items:center;display:inline-flex}.download-tip{color:var(--color-ash);margin-top:var(--spacing-sm);text-align:center;font-size:.8125rem}.download-tip a{color:var(--color-accent);text-decoration:none}.download-tip a:hover{text-decoration:underline}.consulting-section{padding:var(--spacing-xl) 0;border-top:1px solid var(--color-mist)}.consulting-content{justify-content:space-between;align-items:center;gap:var(--spacing-lg);flex-wrap:wrap;display:flex}.consulting-actions{gap:var(--spacing-sm);flex-shrink:0;display:flex}.consulting-text{flex:1;min-width:280px}.consulting-title{margin:0 0 var(--spacing-sm) 0;font-size:clamp(1.5rem,4vw,2rem);font-style:italic;font-weight:300}.consulting-desc{color:var(--color-charcoal);max-width:45ch;margin:0;font-size:1rem;line-height:1.6}@media (max-width:600px){.consulting-content{flex-direction:column;align-items:flex-start}.consulting-actions{flex-direction:column;width:100%}.consulting-actions .btn{justify-content:center;width:100%}} \ No newline at end of file diff --git a/scripts/build.js b/scripts/build.js index 720f9891d..a0499f1ee 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -18,16 +18,7 @@ import path from 'path'; import fs from 'fs'; import { fileURLToPath } from 'url'; import { readSourceFiles, readPatterns } from './lib/utils.js'; -import { - transformCursor, - transformClaudeCode, - transformGemini, - transformCodex, - transformAgents, - transformKiro, - transformOpenCode, - transformPi -} from './lib/transformers/index.js'; +import { createTransformer, PROVIDERS } from './lib/transformers/index.js'; import { createAllZips } from './lib/zip.js'; import { execSync } from 'child_process'; @@ -141,18 +132,9 @@ function assembleUniversal(distDir, suffix = '') { fs.rmSync(universalDir, { recursive: true, force: true }); } - const providerMappings = [ - { provider: 'cursor', configDir: '.cursor' }, - { provider: 'claude-code', configDir: '.claude' }, - { provider: 'gemini', configDir: '.gemini' }, - { provider: 'codex', configDir: '.codex' }, - { provider: 'agents', configDir: '.agents' }, - { provider: 'kiro', configDir: '.kiro' }, - { provider: 'opencode', configDir: '.opencode' }, - { provider: 'pi', configDir: '.pi' } - ]; + const providerConfigs = Object.values(PROVIDERS); - for (const { provider, configDir } of providerMappings) { + for (const { provider, configDir } of providerConfigs) { const src = path.join(distDir, `${provider}${suffix}`, configDir); const dest = path.join(universalDir, configDir); if (fs.existsSync(src)) { @@ -183,7 +165,7 @@ These are hidden folders (dotfiles) — press Cmd+Shift+. in Finder to see them. `); const label = suffix ? ' (prefixed)' : ''; - console.log(`✓ Assembled universal${label} directory (${providerMappings.length} providers)`); + console.log(`✓ Assembled universal${label} directory (${providerConfigs.length} providers)`); } /** @@ -340,26 +322,12 @@ async function build() { const userInvocableCount = skills.filter(s => s.userInvocable).length; console.log(`📖 Read ${skills.length} skills (${userInvocableCount} user-invocable) and ${patterns.patterns.length + patterns.antipatterns.length} pattern categories\n`); - // Transform for each provider - transformCursor(skills, DIST_DIR, patterns); - transformClaudeCode(skills, DIST_DIR, patterns); - transformGemini(skills, DIST_DIR, patterns); - transformCodex(skills, DIST_DIR, patterns); - transformAgents(skills, DIST_DIR, patterns); - transformKiro(skills, DIST_DIR, patterns); - transformOpenCode(skills, DIST_DIR, patterns); - transformPi(skills, DIST_DIR, patterns); - - // Transform for each provider (prefixed with i-) - const prefixOptions = { prefix: 'i-', outputSuffix: '-prefixed' }; - transformCursor(skills, DIST_DIR, patterns, prefixOptions); - transformClaudeCode(skills, DIST_DIR, patterns, prefixOptions); - transformGemini(skills, DIST_DIR, patterns, prefixOptions); - transformCodex(skills, DIST_DIR, patterns, prefixOptions); - transformAgents(skills, DIST_DIR, patterns, prefixOptions); - transformKiro(skills, DIST_DIR, patterns, prefixOptions); - transformOpenCode(skills, DIST_DIR, patterns, prefixOptions); - transformPi(skills, DIST_DIR, patterns, prefixOptions); + // Transform for each provider (unprefixed + prefixed) + for (const config of Object.values(PROVIDERS)) { + const transform = createTransformer(config); + transform(skills, DIST_DIR); + transform(skills, DIST_DIR, { prefix: 'i-', outputSuffix: '-prefixed' }); + } // Assemble universal directory (unprefixed and prefixed) assembleUniversal(DIST_DIR); diff --git a/scripts/lib/transformers/agents.js b/scripts/lib/transformers/agents.js deleted file mode 100644 index 7e27fbf6b..000000000 --- a/scripts/lib/transformers/agents.js +++ /dev/null @@ -1,69 +0,0 @@ -import path from 'path'; -import { cleanDir, ensureDir, writeFile, generateYamlFrontmatter, replacePlaceholders, prefixSkillReferences } from '../utils.js'; - -/** - * Agents Transformer (VS Code Copilot + Antigravity) - * - * All skills output to .agents/skills/{name}/SKILL.md - * Frontmatter: name, description, user-invocable (if true), argument-hint (from args) - * - * @param {Array} skills - All skills (including user-invocable ones) - * @param {string} distDir - Distribution output directory - * @param {Object} patterns - Design patterns data (unused) - * @param {Object} options - Optional settings - */ -export function transformAgents(skills, distDir, patterns = null, options = {}) { - const { prefix = '', outputSuffix = '' } = options; - const agentsDir = path.join(distDir, `agents${outputSuffix}`); - const skillsDir = path.join(agentsDir, '.agents/skills'); - - cleanDir(agentsDir); - ensureDir(skillsDir); - - const allSkillNames = skills.map(s => s.name); - const commandNames = skills.filter(s => s.userInvocable).map(s => `${prefix}${s.name}`); - let refCount = 0; - for (const skill of skills) { - const skillName = `${prefix}${skill.name}`; - const skillDir = path.join(skillsDir, skillName); - - const frontmatterObj = { - name: skillName, - description: skill.description, - }; - - if (skill.userInvocable) frontmatterObj['user-invocable'] = true; - - // Build argument-hint from args array for user-invocable skills - if (skill.userInvocable && skill.args && skill.args.length > 0) { - const hints = skill.args.map(arg => { - return arg.required ? `<${arg.name}>` : `[${arg.name.toUpperCase()}=]`; - }); - frontmatterObj['argument-hint'] = hints.join(' '); - } - - const frontmatter = generateYamlFrontmatter(frontmatterObj); - let skillBody = replacePlaceholders(skill.body, 'agents', commandNames); - if (prefix) skillBody = prefixSkillReferences(skillBody, prefix, allSkillNames); - const content = `${frontmatter}\n\n${skillBody}`; - const outputPath = path.join(skillDir, 'SKILL.md'); - writeFile(outputPath, content); - - // Copy reference files if they exist - if (skill.references && skill.references.length > 0) { - const refDir = path.join(skillDir, 'reference'); - ensureDir(refDir); - for (const ref of skill.references) { - const refOutputPath = path.join(refDir, `${ref.name}.md`); - const refContent = replacePlaceholders(ref.content, 'agents'); - writeFile(refOutputPath, refContent); - refCount++; - } - } - } - - const userInvocableCount = skills.filter(s => s.userInvocable).length; - const refInfo = refCount > 0 ? ` (${refCount} reference files)` : ''; - const prefixInfo = prefix ? ` [${prefix}prefixed]` : ''; - console.log(`✓ Agents${prefixInfo}: ${skills.length} skills (${userInvocableCount} user-invocable)${refInfo}`); -} diff --git a/scripts/lib/transformers/claude-code.js b/scripts/lib/transformers/claude-code.js deleted file mode 100644 index 365647bda..000000000 --- a/scripts/lib/transformers/claude-code.js +++ /dev/null @@ -1,68 +0,0 @@ -import path from 'path'; -import { cleanDir, ensureDir, writeFile, generateYamlFrontmatter, replacePlaceholders, prefixSkillReferences } from '../utils.js'; - -/** - * Claude Code Transformer (Skills Only) - * - * All skills output to .claude/skills/{name}/SKILL.md - * User-invokable skills get args support in frontmatter. - * - * @param {Array} skills - All skills (including user-invocable ones) - * @param {string} distDir - Distribution output directory - * @param {Object} patterns - Design patterns data (unused, kept for interface consistency) - * @param {Object} options - Optional settings - * @param {string} options.prefix - Prefix to add to user-invocable skill names (e.g., 'i-') - * @param {string} options.outputSuffix - Suffix for output directory (e.g., '-prefixed') - */ -export function transformClaudeCode(skills, distDir, patterns = null, options = {}) { - const { prefix = '', outputSuffix = '' } = options; - const claudeDir = path.join(distDir, `claude-code${outputSuffix}`); - const skillsDir = path.join(claudeDir, '.claude/skills'); - - cleanDir(claudeDir); - ensureDir(skillsDir); - - const allSkillNames = skills.map(s => s.name); - const commandNames = skills.filter(s => s.userInvocable).map(s => `${prefix}${s.name}`); - let refCount = 0; - for (const skill of skills) { - const skillName = `${prefix}${skill.name}`; - const skillDir = path.join(skillsDir, skillName); - - const frontmatterObj = { - name: skillName, - description: skill.description, - }; - - if (skill.userInvocable) frontmatterObj['user-invocable'] = true; - if (skill.args && skill.args.length > 0) frontmatterObj.args = skill.args; - if (skill.license) frontmatterObj.license = skill.license; - if (skill.compatibility) frontmatterObj.compatibility = skill.compatibility; - if (skill.metadata) frontmatterObj.metadata = skill.metadata; - if (skill.allowedTools) frontmatterObj['allowed-tools'] = skill.allowedTools; - - const frontmatter = generateYamlFrontmatter(frontmatterObj); - let skillBody = replacePlaceholders(skill.body, 'claude-code', commandNames); - if (prefix) skillBody = prefixSkillReferences(skillBody, prefix, allSkillNames); - const content = `${frontmatter}\n\n${skillBody}`; - const outputPath = path.join(skillDir, 'SKILL.md'); - writeFile(outputPath, content); - - // Copy reference files if they exist - if (skill.references && skill.references.length > 0) { - const refDir = path.join(skillDir, 'reference'); - ensureDir(refDir); - for (const ref of skill.references) { - const refOutputPath = path.join(refDir, `${ref.name}.md`); - const refContent = replacePlaceholders(ref.content, 'claude-code'); - writeFile(refOutputPath, refContent); - refCount++; - } - } - } - - const userInvocableCount = skills.filter(s => s.userInvocable).length; - const refInfo = refCount > 0 ? ` (${refCount} reference files)` : ''; - const prefixInfo = prefix ? ` [${prefix}prefixed]` : ''; - console.log(`✓ Claude Code${prefixInfo}: ${skills.length} skills (${userInvocableCount} user-invocable)${refInfo}`); -} diff --git a/scripts/lib/transformers/codex.js b/scripts/lib/transformers/codex.js deleted file mode 100644 index 2192e3ee3..000000000 --- a/scripts/lib/transformers/codex.js +++ /dev/null @@ -1,79 +0,0 @@ -import path from 'path'; -import { cleanDir, ensureDir, writeFile, generateYamlFrontmatter, replacePlaceholders, prefixSkillReferences } from '../utils.js'; - -/** - * Codex Transformer (Skills Only) - * - * All skills output to .codex/skills/{name}/SKILL.md - * Frontmatter: name, description, argument-hint (from args for user-invocable) - * For user-invocable skills: {{argname}} becomes $ARGNAME in body - * - * @param {Array} skills - All skills (including user-invocable ones) - * @param {string} distDir - Distribution output directory - * @param {Object} patterns - Design patterns data (unused) - * @param {Object} options - Optional settings - * @param {string} options.prefix - Prefix to add to user-invocable skill names (e.g., 'i-') - * @param {string} options.outputSuffix - Suffix for output directory (e.g., '-prefixed') - */ -export function transformCodex(skills, distDir, patterns = null, options = {}) { - const { prefix = '', outputSuffix = '' } = options; - const codexDir = path.join(distDir, `codex${outputSuffix}`); - const skillsDir = path.join(codexDir, '.codex/skills'); - - cleanDir(codexDir); - ensureDir(skillsDir); - - const allSkillNames = skills.map(s => s.name); - const commandNames = skills.filter(s => s.userInvocable).map(s => `${prefix}${s.name}`); - let refCount = 0; - for (const skill of skills) { - const skillName = `${prefix}${skill.name}`; - const skillDir = path.join(skillsDir, skillName); - - const frontmatterObj = { - name: skillName, - description: skill.description, - }; - - // Build argument-hint from args array for user-invocable skills - if (skill.userInvocable && skill.args && skill.args.length > 0) { - const hints = skill.args.map(arg => { - return arg.required ? `<${arg.name}>` : `[${arg.name.toUpperCase()}=]`; - }); - frontmatterObj['argument-hint'] = hints.join(' '); - } - if (skill.license) frontmatterObj.license = skill.license; - - const frontmatter = generateYamlFrontmatter(frontmatterObj); - - let skillBody = replacePlaceholders(skill.body, 'codex', commandNames); - if (prefix) skillBody = prefixSkillReferences(skillBody, prefix, allSkillNames); - // For user-invocable skills, transform remaining {{argname}} to $ARGNAME - if (skill.userInvocable) { - skillBody = skillBody.replace(/\{\{([^}]+)\}\}/g, (match, argName) => { - return `$${argName.toUpperCase()}`; - }); - } - - const content = `${frontmatter}\n\n${skillBody}`; - const outputPath = path.join(skillDir, 'SKILL.md'); - writeFile(outputPath, content); - - // Copy reference files if they exist - if (skill.references && skill.references.length > 0) { - const refDir = path.join(skillDir, 'reference'); - ensureDir(refDir); - for (const ref of skill.references) { - const refOutputPath = path.join(refDir, `${ref.name}.md`); - const refContent = replacePlaceholders(ref.content, 'codex'); - writeFile(refOutputPath, refContent); - refCount++; - } - } - } - - const userInvocableCount = skills.filter(s => s.userInvocable).length; - const refInfo = refCount > 0 ? ` (${refCount} reference files)` : ''; - const prefixInfo = prefix ? ` [${prefix}prefixed]` : ''; - console.log(`✓ Codex${prefixInfo}: ${skills.length} skills (${userInvocableCount} user-invocable)${refInfo}`); -} diff --git a/scripts/lib/transformers/cursor.js b/scripts/lib/transformers/cursor.js deleted file mode 100644 index 9d91d7d9f..000000000 --- a/scripts/lib/transformers/cursor.js +++ /dev/null @@ -1,62 +0,0 @@ -import path from 'path'; -import { cleanDir, ensureDir, writeFile, generateYamlFrontmatter, replacePlaceholders, prefixSkillReferences } from '../utils.js'; - -/** - * Cursor Transformer (Skills Only) - * - * All skills output to .cursor/skills/{name}/SKILL.md - * Frontmatter: name, description, license - * - * @param {Array} skills - All skills (including user-invocable ones) - * @param {string} distDir - Distribution output directory - * @param {Object} patterns - Design patterns data (unused) - * @param {Object} options - Optional settings - * @param {string} options.prefix - Prefix to add to user-invocable skill names (e.g., 'i-') - * @param {string} options.outputSuffix - Suffix for output directory (e.g., '-prefixed') - */ -export function transformCursor(skills, distDir, patterns = null, options = {}) { - const { prefix = '', outputSuffix = '' } = options; - const cursorDir = path.join(distDir, `cursor${outputSuffix}`); - const skillsDir = path.join(cursorDir, '.cursor/skills'); - - cleanDir(cursorDir); - ensureDir(skillsDir); - - const allSkillNames = skills.map(s => s.name); - const commandNames = skills.filter(s => s.userInvocable).map(s => `${prefix}${s.name}`); - let refCount = 0; - for (const skill of skills) { - const skillName = `${prefix}${skill.name}`; - const skillDir = path.join(skillsDir, skillName); - - const frontmatterObj = { - name: skillName, - description: skill.description, - }; - if (skill.license) frontmatterObj.license = skill.license; - - const frontmatter = generateYamlFrontmatter(frontmatterObj); - let skillBody = replacePlaceholders(skill.body, 'cursor', commandNames); - if (prefix) skillBody = prefixSkillReferences(skillBody, prefix, allSkillNames); - const content = `${frontmatter}\n\n${skillBody}`; - const outputPath = path.join(skillDir, 'SKILL.md'); - writeFile(outputPath, content); - - // Copy reference files if they exist - if (skill.references && skill.references.length > 0) { - const refDir = path.join(skillDir, 'reference'); - ensureDir(refDir); - for (const ref of skill.references) { - const refOutputPath = path.join(refDir, `${ref.name}.md`); - const refContent = replacePlaceholders(ref.content, 'cursor'); - writeFile(refOutputPath, refContent); - refCount++; - } - } - } - - const userInvocableCount = skills.filter(s => s.userInvocable).length; - const refInfo = refCount > 0 ? ` (${refCount} reference files)` : ''; - const prefixInfo = prefix ? ` [${prefix}prefixed]` : ''; - console.log(`✓ Cursor${prefixInfo}: ${skills.length} skills (${userInvocableCount} user-invocable)${refInfo}`); -} diff --git a/scripts/lib/transformers/factory.js b/scripts/lib/transformers/factory.js new file mode 100644 index 000000000..df7e535f7 --- /dev/null +++ b/scripts/lib/transformers/factory.js @@ -0,0 +1,113 @@ +import path from 'path'; +import { cleanDir, ensureDir, writeFile, generateYamlFrontmatter, replacePlaceholders, prefixSkillReferences } from '../utils.js'; + +/** + * Map from frontmatter field name to extraction spec. + * + * - sourceKey: property name on the skill object + * - yamlKey: key name in YAML frontmatter + * - condition: if provided, field is only emitted when this returns true + * - value: if provided, use this instead of skill[sourceKey] + */ +const FIELD_SPECS = { + 'user-invocable': { + sourceKey: 'userInvocable', + yamlKey: 'user-invocable', + condition: (skill) => skill.userInvocable, + value: () => true, + }, + 'argument-hint': { + sourceKey: 'argumentHint', + yamlKey: 'argument-hint', + condition: (skill) => skill.userInvocable && skill.argumentHint, + }, + license: { + sourceKey: 'license', + yamlKey: 'license', + }, + compatibility: { + sourceKey: 'compatibility', + yamlKey: 'compatibility', + }, + metadata: { + sourceKey: 'metadata', + yamlKey: 'metadata', + }, + 'allowed-tools': { + sourceKey: 'allowedTools', + yamlKey: 'allowed-tools', + }, +}; + +/** + * Create a transformer function for a given provider config. + * + * @param {Object} config - Provider configuration from providers.js + * @returns {Function} transform(skills, distDir, options?) + */ +export function createTransformer(config) { + const { provider, configDir, displayName, frontmatterFields = [], bodyTransform } = config; + + const activeFields = frontmatterFields + .map((name) => FIELD_SPECS[name]) + .filter(Boolean); + + return function transform(skills, distDir, options = {}) { + const { prefix = '', outputSuffix = '' } = options; + const providerDir = path.join(distDir, `${provider}${outputSuffix}`); + const skillsDir = path.join(providerDir, `${configDir}/skills`); + + cleanDir(providerDir); + ensureDir(skillsDir); + + const allSkillNames = skills.map((s) => s.name); + const commandNames = skills + .filter((s) => s.userInvocable) + .map((s) => `${prefix}${s.name}`); + + let refCount = 0; + + for (const skill of skills) { + const skillName = `${prefix}${skill.name}`; + const skillDir = path.join(skillsDir, skillName); + + // Build frontmatter + const frontmatterObj = { + name: skillName, + description: skill.description, + }; + + for (const spec of activeFields) { + if (spec.condition && !spec.condition(skill)) continue; + const val = spec.value ? spec.value(skill) : skill[spec.sourceKey]; + if (val) frontmatterObj[spec.yamlKey] = val; + } + + const frontmatter = generateYamlFrontmatter(frontmatterObj); + + // Build body + let skillBody = replacePlaceholders(skill.body, provider, commandNames); + if (prefix) skillBody = prefixSkillReferences(skillBody, prefix, allSkillNames); + if (bodyTransform) skillBody = bodyTransform(skillBody, skill); + + const content = `${frontmatter}\n\n${skillBody}`; + writeFile(path.join(skillDir, 'SKILL.md'), content); + + // Copy reference files + if (skill.references && skill.references.length > 0) { + const refDir = path.join(skillDir, 'reference'); + ensureDir(refDir); + for (const ref of skill.references) { + const refContent = replacePlaceholders(ref.content, provider); + writeFile(path.join(refDir, `${ref.name}.md`), refContent); + refCount++; + } + } + } + + const userInvocableCount = skills.filter((s) => s.userInvocable).length; + const refInfo = refCount > 0 ? ` (${refCount} reference files)` : ''; + const prefixInfo = prefix ? ` [${prefix}prefixed]` : ''; + console.log(`✓ ${displayName}${prefixInfo}: ${skills.length} skills (${userInvocableCount} user-invocable)${refInfo}`); + }; +} diff --git a/scripts/lib/transformers/gemini.js b/scripts/lib/transformers/gemini.js deleted file mode 100644 index 4b1a6b7f0..000000000 --- a/scripts/lib/transformers/gemini.js +++ /dev/null @@ -1,66 +0,0 @@ -import path from 'path'; -import { cleanDir, ensureDir, writeFile, generateYamlFrontmatter, replacePlaceholders, prefixSkillReferences } from '../utils.js'; - -/** - * Gemini Transformer (Skills Only) - * - * All skills output to .gemini/skills/{name}/SKILL.md - * Frontmatter: name, description - * For user-invocable skills: {{arg}} placeholders become {{args}} in body - * - * @param {Array} skills - All skills (including user-invocable ones) - * @param {string} distDir - Distribution output directory - * @param {Object} patterns - Design patterns data (unused) - * @param {Object} options - Optional settings - * @param {string} options.prefix - Prefix to add to user-invocable skill names (e.g., 'i-') - * @param {string} options.outputSuffix - Suffix for output directory (e.g., '-prefixed') - */ -export function transformGemini(skills, distDir, patterns = null, options = {}) { - const { prefix = '', outputSuffix = '' } = options; - const geminiDir = path.join(distDir, `gemini${outputSuffix}`); - const skillsDir = path.join(geminiDir, '.gemini/skills'); - - cleanDir(geminiDir); - ensureDir(skillsDir); - - const allSkillNames = skills.map(s => s.name); - const commandNames = skills.filter(s => s.userInvocable).map(s => `${prefix}${s.name}`); - let refCount = 0; - for (const skill of skills) { - const skillName = `${prefix}${skill.name}`; - const skillDir = path.join(skillsDir, skillName); - - const frontmatter = generateYamlFrontmatter({ - name: skillName, - description: skill.description, - }); - - let skillBody = replacePlaceholders(skill.body, 'gemini', commandNames); - if (prefix) skillBody = prefixSkillReferences(skillBody, prefix, allSkillNames); - // For user-invocable skills, replace remaining {{arg}} placeholders with {{args}} - if (skill.userInvocable) { - skillBody = skillBody.replace(/\{\{[^}]+\}\}/g, '{{args}}'); - } - - const content = `${frontmatter}\n\n${skillBody}`; - const outputPath = path.join(skillDir, 'SKILL.md'); - writeFile(outputPath, content); - - // Copy reference files if they exist - if (skill.references && skill.references.length > 0) { - const refDir = path.join(skillDir, 'reference'); - ensureDir(refDir); - for (const ref of skill.references) { - const refOutputPath = path.join(refDir, `${ref.name}.md`); - const refContent = replacePlaceholders(ref.content, 'gemini'); - writeFile(refOutputPath, refContent); - refCount++; - } - } - } - - const userInvocableCount = skills.filter(s => s.userInvocable).length; - const refInfo = refCount > 0 ? ` (${refCount} reference files)` : ''; - const prefixInfo = prefix ? ` [${prefix}prefixed]` : ''; - console.log(`✓ Gemini${prefixInfo}: ${skills.length} skills (${userInvocableCount} user-invocable)${refInfo}`); -} diff --git a/scripts/lib/transformers/index.js b/scripts/lib/transformers/index.js index da00044f3..c8b8b49ca 100644 --- a/scripts/lib/transformers/index.js +++ b/scripts/lib/transformers/index.js @@ -1,8 +1,13 @@ -export { transformCursor } from './cursor.js'; -export { transformClaudeCode } from './claude-code.js'; -export { transformGemini } from './gemini.js'; -export { transformCodex } from './codex.js'; -export { transformAgents } from './agents.js'; -export { transformKiro } from './kiro.js'; -export { transformOpenCode } from "./opencode.js"; -export { transformPi } from './pi.js'; +import { createTransformer } from './factory.js'; +import { PROVIDERS } from './providers.js'; + +export const transformCursor = createTransformer(PROVIDERS.cursor); +export const transformClaudeCode = createTransformer(PROVIDERS['claude-code']); +export const transformGemini = createTransformer(PROVIDERS.gemini); +export const transformCodex = createTransformer(PROVIDERS.codex); +export const transformAgents = createTransformer(PROVIDERS.agents); +export const transformKiro = createTransformer(PROVIDERS.kiro); +export const transformOpenCode = createTransformer(PROVIDERS.opencode); +export const transformPi = createTransformer(PROVIDERS.pi); + +export { createTransformer, PROVIDERS }; diff --git a/scripts/lib/transformers/kiro.js b/scripts/lib/transformers/kiro.js deleted file mode 100644 index 84a334b1e..000000000 --- a/scripts/lib/transformers/kiro.js +++ /dev/null @@ -1,65 +0,0 @@ -import path from 'path'; -import { cleanDir, ensureDir, writeFile, generateYamlFrontmatter, replacePlaceholders, prefixSkillReferences } from '../utils.js'; - -/** - * Kiro Transformer (Skills Only) - * - * All skills output to .kiro/skills/{name}/SKILL.md - * Frontmatter: name, description, license, compatibility, metadata - * - * @param {Array} skills - All skills (including user-invocable ones) - * @param {string} distDir - Distribution output directory - * @param {Object} patterns - Design patterns data (unused) - * @param {Object} options - Optional settings - * @param {string} options.prefix - Prefix to add to skill names (e.g., 'i-') - * @param {string} options.outputSuffix - Suffix for output directory (e.g., '-prefixed') - */ -export function transformKiro(skills, distDir, patterns = null, options = {}) { - const { prefix = '', outputSuffix = '' } = options; - const kiroDir = path.join(distDir, `kiro${outputSuffix}`); - const skillsDir = path.join(kiroDir, '.kiro/skills'); - - cleanDir(kiroDir); - ensureDir(skillsDir); - - const allSkillNames = skills.map(s => s.name); - const commandNames = skills.filter(s => s.userInvocable).map(s => `${prefix}${s.name}`); - let refCount = 0; - for (const skill of skills) { - const skillName = `${prefix}${skill.name}`; - const skillDir = path.join(skillsDir, skillName); - - const frontmatterObj = { - name: skillName, - description: skill.description, - }; - - if (skill.license) frontmatterObj.license = skill.license; - if (skill.compatibility) frontmatterObj.compatibility = skill.compatibility; - if (skill.metadata) frontmatterObj.metadata = skill.metadata; - - const frontmatter = generateYamlFrontmatter(frontmatterObj); - let skillBody = replacePlaceholders(skill.body, 'kiro', commandNames); - if (prefix) skillBody = prefixSkillReferences(skillBody, prefix, allSkillNames); - const content = `${frontmatter}\n\n${skillBody}`; - const outputPath = path.join(skillDir, 'SKILL.md'); - writeFile(outputPath, content); - - // Copy reference files if they exist - if (skill.references && skill.references.length > 0) { - const refDir = path.join(skillDir, 'reference'); - ensureDir(refDir); - for (const ref of skill.references) { - const refOutputPath = path.join(refDir, `${ref.name}.md`); - const refContent = replacePlaceholders(ref.content, 'kiro'); - writeFile(refOutputPath, refContent); - refCount++; - } - } - } - - const userInvocableCount = skills.filter(s => s.userInvocable).length; - const refInfo = refCount > 0 ? ` (${refCount} reference files)` : ''; - const prefixInfo = prefix ? ` [${prefix}prefixed]` : ''; - console.log(`✓ Kiro${prefixInfo}: ${skills.length} skills (${userInvocableCount} user-invocable)${refInfo}`); -} diff --git a/scripts/lib/transformers/opencode.js b/scripts/lib/transformers/opencode.js deleted file mode 100644 index 05181d964..000000000 --- a/scripts/lib/transformers/opencode.js +++ /dev/null @@ -1,84 +0,0 @@ -import path from 'path'; -import { - cleanDir, - ensureDir, - generateYamlFrontmatter, - prefixSkillReferences, - replacePlaceholders, - writeFile, -} from '../utils.js'; - -/** - * OpenCode Transformer (Skills Only) - * - * All skills output to .opencode/skills/{name}/SKILL.md - * User-invokable skills get args support in frontmatter. - * - * @param {Array} skills - All skills (including user-invocable ones) - * @param {string} distDir - Distribution output directory - * @param {Object} patterns - Design patterns data (unused, kept for interface consistency) - * @param {Object} options - Optional settings - * @param {string} options.prefix - Prefix to add to user-invocable skill names (e.g., 'i-') - * @param {string} options.outputSuffix - Suffix for output directory (e.g., '-prefixed') - */ -export function transformOpenCode( - skills, - distDir, - patterns = null, - options = {}, -) { - const { prefix = '', outputSuffix = '' } = options; - const opencodeDir = path.join(distDir, `opencode${outputSuffix}`); - const skillsDir = path.join(opencodeDir, '.opencode/skills'); - - cleanDir(opencodeDir); - ensureDir(skillsDir); - - const allSkillNames = skills.map((s) => s.name); - const commandNames = skills - .filter((s) => s.userInvocable) - .map((s) => `${prefix}${s.name}`); - let refCount = 0; - for (const skill of skills) { - const skillName = `${prefix}${skill.name}`; - const skillDir = path.join(skillsDir, skillName); - - const frontmatterObj = { - name: skillName, - description: skill.description, - }; - - if (skill.userInvocable) frontmatterObj['user-invocable'] = true; - if (skill.args && skill.args.length > 0) frontmatterObj.args = skill.args; - if (skill.license) frontmatterObj.license = skill.license; - if (skill.compatibility) frontmatterObj.compatibility = skill.compatibility; - if (skill.metadata) frontmatterObj.metadata = skill.metadata; - if (skill.allowedTools) - frontmatterObj['allowed-tools'] = skill.allowedTools; - - const frontmatter = generateYamlFrontmatter(frontmatterObj); - let skillBody = replacePlaceholders(skill.body, 'opencode', commandNames); - if (prefix) - skillBody = prefixSkillReferences(skillBody, prefix, allSkillNames); - const content = `${frontmatter}\n\n${skillBody}`; - const outputPath = path.join(skillDir, 'SKILL.md'); - writeFile(outputPath, content); - - // Copy reference files if they exist - if (skill.references && skill.references.length > 0) { - const refDir = path.join(skillDir, 'reference'); - ensureDir(refDir); - for (const ref of skill.references) { - const refOutputPath = path.join(refDir, `${ref.name}.md`); - const refContent = replacePlaceholders(ref.content, 'opencode'); - writeFile(refOutputPath, refContent); - refCount++; - } - } - } - - const userInvocableCount = skills.filter((s) => s.userInvocable).length; - const refInfo = refCount > 0 ? ` (${refCount} reference files)` : ''; - const prefixInfo = prefix ? ` [${prefix}prefixed]` : ''; - console.log(`✓ OpenCode${prefixInfo}: ${skills.length} skills (${userInvocableCount} user-invocable)${refInfo}`); -} diff --git a/scripts/lib/transformers/pi.js b/scripts/lib/transformers/pi.js deleted file mode 100644 index 88cf97aea..000000000 --- a/scripts/lib/transformers/pi.js +++ /dev/null @@ -1,65 +0,0 @@ -import path from 'path'; -import { cleanDir, ensureDir, writeFile, generateYamlFrontmatter, replacePlaceholders, prefixSkillReferences } from '../utils.js'; - -/** - * Pi Transformer (Skills Only) - * - * All skills output to .pi/skills/{name}/SKILL.md - * Frontmatter: name, description, license, compatibility, metadata - * - * @param {Array} skills - All skills (including user-invocable ones) - * @param {string} distDir - Distribution output directory - * @param {Object} patterns - Design patterns data (unused) - * @param {Object} options - Optional settings - * @param {string} options.prefix - Prefix to add to skill names (e.g., 'i-') - * @param {string} options.outputSuffix - Suffix for output directory (e.g., '-prefixed') - */ -export function transformPi(skills, distDir, patterns = null, options = {}) { - const { prefix = '', outputSuffix = '' } = options; - const piDir = path.join(distDir, `pi${outputSuffix}`); - const skillsDir = path.join(piDir, '.pi/skills'); - - cleanDir(piDir); - ensureDir(skillsDir); - - const allSkillNames = skills.map(s => s.name); - const commandNames = skills.filter(s => s.userInvocable).map(s => `${prefix}${s.name}`); - let refCount = 0; - for (const skill of skills) { - const skillName = `${prefix}${skill.name}`; - const skillDir = path.join(skillsDir, skillName); - - const frontmatterObj = { - name: skillName, - description: skill.description, - }; - - if (skill.license) frontmatterObj.license = skill.license; - if (skill.compatibility) frontmatterObj.compatibility = skill.compatibility; - if (skill.metadata) frontmatterObj.metadata = skill.metadata; - - const frontmatter = generateYamlFrontmatter(frontmatterObj); - let skillBody = replacePlaceholders(skill.body, 'pi', commandNames); - if (prefix) skillBody = prefixSkillReferences(skillBody, prefix, allSkillNames); - const content = `${frontmatter}\n\n${skillBody}`; - const outputPath = path.join(skillDir, 'SKILL.md'); - writeFile(outputPath, content); - - // Copy reference files if they exist - if (skill.references && skill.references.length > 0) { - const refDir = path.join(skillDir, 'reference'); - ensureDir(refDir); - for (const ref of skill.references) { - const refOutputPath = path.join(refDir, `${ref.name}.md`); - const refContent = replacePlaceholders(ref.content, 'pi'); - writeFile(refOutputPath, refContent); - refCount++; - } - } - } - - const userInvocableCount = skills.filter(s => s.userInvocable).length; - const refInfo = refCount > 0 ? ` (${refCount} reference files)` : ''; - const prefixInfo = prefix ? ` [${prefix}prefixed]` : ''; - console.log(`✓ Pi${prefixInfo}: ${skills.length} skills (${userInvocableCount} user-invocable)${refInfo}`); -} diff --git a/scripts/lib/transformers/providers.js b/scripts/lib/transformers/providers.js new file mode 100644 index 000000000..d8afc7c07 --- /dev/null +++ b/scripts/lib/transformers/providers.js @@ -0,0 +1,72 @@ +/** + * Provider configurations for the transformer factory. + * + * Each config specifies: + * - provider: key into PROVIDER_PLACEHOLDERS (e.g. 'claude-code') + * - configDir: dot-directory name (e.g. '.claude') + * - displayName: human-readable name for log output (e.g. 'Claude Code') + * - frontmatterFields: which optional fields to emit beyond name + description + * - bodyTransform: optional function (body, skill) => transformed body + */ +export const PROVIDERS = { + cursor: { + provider: 'cursor', + configDir: '.cursor', + displayName: 'Cursor', + frontmatterFields: ['license'], + }, + 'claude-code': { + provider: 'claude-code', + configDir: '.claude', + displayName: 'Claude Code', + frontmatterFields: ['user-invocable', 'argument-hint', 'license', 'compatibility', 'metadata', 'allowed-tools'], + }, + gemini: { + provider: 'gemini', + configDir: '.gemini', + displayName: 'Gemini', + frontmatterFields: [], + bodyTransform: (body, skill) => { + if (skill.userInvocable) { + return body.replace(/\{\{[^}]+\}\}/g, '{{args}}'); + } + return body; + }, + }, + codex: { + provider: 'codex', + configDir: '.codex', + displayName: 'Codex', + frontmatterFields: ['argument-hint', 'license'], + bodyTransform: (body, skill) => { + if (skill.userInvocable) { + return body.replace(/\{\{([^}]+)\}\}/g, (_, argName) => `$${argName.toUpperCase()}`); + } + return body; + }, + }, + agents: { + provider: 'agents', + configDir: '.agents', + displayName: 'Agents', + frontmatterFields: ['user-invocable', 'argument-hint'], + }, + kiro: { + provider: 'kiro', + configDir: '.kiro', + displayName: 'Kiro', + frontmatterFields: ['license', 'compatibility', 'metadata'], + }, + opencode: { + provider: 'opencode', + configDir: '.opencode', + displayName: 'OpenCode', + frontmatterFields: ['user-invocable', 'argument-hint', 'license', 'compatibility', 'metadata', 'allowed-tools'], + }, + pi: { + provider: 'pi', + configDir: '.pi', + displayName: 'Pi', + frontmatterFields: ['license', 'compatibility', 'metadata'], + }, +}; diff --git a/scripts/lib/utils.js b/scripts/lib/utils.js index 50086ece7..86063fd89 100644 --- a/scripts/lib/utils.js +++ b/scripts/lib/utils.js @@ -61,7 +61,11 @@ export function parseFrontmatter(content) { const value = trimmed.slice(colonIndex + 1).trim(); if (value) { - frontmatter[key] = value === 'true' ? true : value === 'false' ? false : value; + // Strip YAML quotes + const unquoted = (value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'")) + ? value.slice(1, -1) + : value; + frontmatter[key] = unquoted === 'true' ? true : unquoted === 'false' ? false : unquoted; currentKey = key; currentArray = null; } else { @@ -148,7 +152,7 @@ export function readSourceFiles(rootDir) { metadata: frontmatter.metadata || null, allowedTools: frontmatter['allowed-tools'] || '', userInvocable: frontmatter['user-invocable'] === true || frontmatter['user-invocable'] === 'true', - args: frontmatter.args || [], + argumentHint: frontmatter['argument-hint'] || '', context: frontmatter.context || null, body, filePath: skillMdPath, @@ -381,7 +385,8 @@ export function generateYamlFrontmatter(data) { } else if (typeof value === 'boolean') { lines.push(`${key}: ${value}`); } else { - lines.push(`${key}: ${value}`); + const needsQuoting = typeof value === 'string' && /^[\[{]/.test(value); + lines.push(`${key}: ${needsQuoting ? `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"` : value}`); } } diff --git a/source/skills/adapt/SKILL.md b/source/skills/adapt/SKILL.md index 0dc9ca859..796eea6d2 100644 --- a/source/skills/adapt/SKILL.md +++ b/source/skills/adapt/SKILL.md @@ -1,13 +1,7 @@ --- name: adapt description: Adapt designs to work across different screen sizes, devices, contexts, or platforms. Ensures consistent experience across varied environments. -args: - - name: target - description: The feature or component to adapt (optional) - required: false - - name: context - description: What to adapt for (mobile, tablet, desktop, print, email, etc.) - required: false +argument-hint: "[target] [context (mobile, tablet, print...)]" user-invocable: true --- diff --git a/source/skills/animate/SKILL.md b/source/skills/animate/SKILL.md index 8789e8e34..458164574 100644 --- a/source/skills/animate/SKILL.md +++ b/source/skills/animate/SKILL.md @@ -1,10 +1,7 @@ --- name: animate description: Review a feature and enhance it with purposeful animations, micro-interactions, and motion effects that improve usability and delight. -args: - - name: target - description: The feature or component to animate (optional) - required: false +argument-hint: "[target]" user-invocable: true --- diff --git a/source/skills/arrange/SKILL.md b/source/skills/arrange/SKILL.md index dc3a889cf..aa9b9d6c5 100644 --- a/source/skills/arrange/SKILL.md +++ b/source/skills/arrange/SKILL.md @@ -1,10 +1,7 @@ --- name: arrange description: Improve layout, spacing, and visual rhythm. Fixes monotonous grids, inconsistent spacing, and weak visual hierarchy to create intentional compositions. -args: - - name: target - description: The feature or component to improve layout for (optional) - required: false +argument-hint: "[target]" user-invocable: true --- diff --git a/source/skills/audit/SKILL.md b/source/skills/audit/SKILL.md index 7e70200e1..072636456 100644 --- a/source/skills/audit/SKILL.md +++ b/source/skills/audit/SKILL.md @@ -1,10 +1,7 @@ --- name: audit description: Perform comprehensive audit of interface quality across accessibility, performance, theming, and responsive design. Generates detailed report of issues with severity ratings and recommendations. -args: - - name: area - description: The feature or area to audit (optional) - required: false +argument-hint: "[area (feature, page, component...)]" user-invocable: true --- diff --git a/source/skills/bolder/SKILL.md b/source/skills/bolder/SKILL.md index 387b174a5..a71a88d39 100644 --- a/source/skills/bolder/SKILL.md +++ b/source/skills/bolder/SKILL.md @@ -1,10 +1,7 @@ --- name: bolder description: Amplify safe or boring designs to make them more visually interesting and stimulating. Increases impact while maintaining usability. -args: - - name: target - description: The feature or component to make bolder (optional) - required: false +argument-hint: "[target]" user-invocable: true --- diff --git a/source/skills/clarify/SKILL.md b/source/skills/clarify/SKILL.md index 69b75eddb..10a1d1422 100644 --- a/source/skills/clarify/SKILL.md +++ b/source/skills/clarify/SKILL.md @@ -1,10 +1,7 @@ --- name: clarify description: Improve unclear UX copy, error messages, microcopy, labels, and instructions. Makes interfaces easier to understand and use. -args: - - name: target - description: The feature or component with unclear copy (optional) - required: false +argument-hint: "[target]" user-invocable: true --- diff --git a/source/skills/colorize/SKILL.md b/source/skills/colorize/SKILL.md index 8ecb974f7..1bc2da5d3 100644 --- a/source/skills/colorize/SKILL.md +++ b/source/skills/colorize/SKILL.md @@ -1,10 +1,7 @@ --- name: colorize description: Add strategic color to features that are too monochromatic or lack visual interest. Makes interfaces more engaging and expressive. -args: - - name: target - description: The feature or component to colorize (optional) - required: false +argument-hint: "[target]" user-invocable: true --- diff --git a/source/skills/critique/SKILL.md b/source/skills/critique/SKILL.md index 618e69d55..dcada50ff 100644 --- a/source/skills/critique/SKILL.md +++ b/source/skills/critique/SKILL.md @@ -1,10 +1,7 @@ --- name: critique description: Evaluate design effectiveness from a UX perspective. Assesses visual hierarchy, information architecture, emotional resonance, and overall design quality with actionable feedback. -args: - - name: area - description: The feature or area to critique (optional) - required: false +argument-hint: "[area (feature, page, component...)]" user-invocable: true --- diff --git a/source/skills/delight/SKILL.md b/source/skills/delight/SKILL.md index fae4b827a..e228375ce 100644 --- a/source/skills/delight/SKILL.md +++ b/source/skills/delight/SKILL.md @@ -1,10 +1,7 @@ --- name: delight description: Add moments of joy, personality, and unexpected touches that make interfaces memorable and enjoyable to use. Elevates functional to delightful. -args: - - name: target - description: The feature or area to add delight to (optional) - required: false +argument-hint: "[target]" user-invocable: true --- diff --git a/source/skills/distill/SKILL.md b/source/skills/distill/SKILL.md index 866c20c70..8a8b2466d 100644 --- a/source/skills/distill/SKILL.md +++ b/source/skills/distill/SKILL.md @@ -1,10 +1,7 @@ --- name: distill description: Strip designs to their essence by removing unnecessary complexity. Great design is simple, powerful, and clean. -args: - - name: target - description: The feature or component to distill (optional) - required: false +argument-hint: "[target]" user-invocable: true --- diff --git a/source/skills/extract/SKILL.md b/source/skills/extract/SKILL.md index 252a31e09..25e73fee8 100644 --- a/source/skills/extract/SKILL.md +++ b/source/skills/extract/SKILL.md @@ -1,10 +1,7 @@ --- name: extract description: Extract and consolidate reusable components, design tokens, and patterns into your design system. Identifies opportunities for systematic reuse and enriches your component library. -args: - - name: target - description: The feature, component, or area to extract from (optional) - required: false +argument-hint: "[target]" user-invocable: true --- diff --git a/source/skills/harden/SKILL.md b/source/skills/harden/SKILL.md index eda956685..814db3f64 100644 --- a/source/skills/harden/SKILL.md +++ b/source/skills/harden/SKILL.md @@ -1,10 +1,7 @@ --- name: harden description: Improve interface resilience through better error handling, i18n support, text overflow handling, and edge case management. Makes interfaces robust and production-ready. -args: - - name: target - description: The feature or area to harden (optional) - required: false +argument-hint: "[target]" user-invocable: true --- diff --git a/source/skills/normalize/SKILL.md b/source/skills/normalize/SKILL.md index cae143ee4..9f5b0af9f 100644 --- a/source/skills/normalize/SKILL.md +++ b/source/skills/normalize/SKILL.md @@ -1,10 +1,7 @@ --- name: normalize description: Normalize design to match your design system and ensure consistency -args: - - name: feature - description: The page, route, or feature to normalize (optional) - required: false +argument-hint: "[feature (page, route, component...)]" user-invocable: true --- diff --git a/source/skills/onboard/SKILL.md b/source/skills/onboard/SKILL.md index 1944d0b1f..6a9e7260c 100644 --- a/source/skills/onboard/SKILL.md +++ b/source/skills/onboard/SKILL.md @@ -1,10 +1,7 @@ --- name: onboard description: Design or improve onboarding flows, empty states, and first-time user experiences. Helps users get started successfully and understand value quickly. -args: - - name: target - description: The feature or area needing onboarding (optional) - required: false +argument-hint: "[target]" user-invocable: true --- diff --git a/source/skills/optimize/SKILL.md b/source/skills/optimize/SKILL.md index 724a1e8c0..de2994bdc 100644 --- a/source/skills/optimize/SKILL.md +++ b/source/skills/optimize/SKILL.md @@ -1,10 +1,7 @@ --- name: optimize description: Improve interface performance across loading speed, rendering, animations, images, and bundle size. Makes experiences faster and smoother. -args: - - name: target - description: The feature or area to optimize (optional) - required: false +argument-hint: "[target]" user-invocable: true --- diff --git a/source/skills/overdrive/SKILL.md b/source/skills/overdrive/SKILL.md index 199632a8b..ba64be3bb 100644 --- a/source/skills/overdrive/SKILL.md +++ b/source/skills/overdrive/SKILL.md @@ -1,10 +1,7 @@ --- name: overdrive description: Push interfaces past conventional limits with technically ambitious implementations. Whether that's a shader, a 60fps virtual table, spring physics on a dialog, or scroll-driven reveals — make users ask "how did they do that?" -args: - - name: target - description: The feature or area to push into overdrive (optional) - required: false +argument-hint: "[target]" user-invocable: true --- diff --git a/source/skills/polish/SKILL.md b/source/skills/polish/SKILL.md index ae348bfa5..927ac3d63 100644 --- a/source/skills/polish/SKILL.md +++ b/source/skills/polish/SKILL.md @@ -1,10 +1,7 @@ --- name: polish description: Final quality pass before shipping. Fixes alignment, spacing, consistency, and detail issues that separate good from great. -args: - - name: target - description: The feature or area to polish (optional) - required: false +argument-hint: "[target]" user-invocable: true --- diff --git a/source/skills/quieter/SKILL.md b/source/skills/quieter/SKILL.md index 190d35241..c05009f10 100644 --- a/source/skills/quieter/SKILL.md +++ b/source/skills/quieter/SKILL.md @@ -1,10 +1,7 @@ --- name: quieter description: Tone down overly bold or visually aggressive designs. Reduces intensity while maintaining design quality and impact. -args: - - name: target - description: The feature or component to make quieter (optional) - required: false +argument-hint: "[target]" user-invocable: true --- diff --git a/source/skills/typeset/SKILL.md b/source/skills/typeset/SKILL.md index e5c792e54..755bba324 100644 --- a/source/skills/typeset/SKILL.md +++ b/source/skills/typeset/SKILL.md @@ -1,10 +1,7 @@ --- name: typeset description: Improve typography by fixing font choices, hierarchy, sizing, weight consistency, and readability. Makes text feel intentional and polished. -args: - - name: target - description: The feature or component to improve typography for (optional) - required: false +argument-hint: "[target]" user-invocable: true --- diff --git a/tests/build.test.js b/tests/build.test.js index cdcd8bac3..78a831f83 100644 --- a/tests/build.test.js +++ b/tests/build.test.js @@ -167,10 +167,7 @@ This is a test skill body.`; name: audit description: Run technical quality checks user-invocable: true -args: - - name: target - description: Target element - required: false +argument-hint: "[TARGET=]" --- Please audit {{target}} for technical quality. Ask {{model}} for help.`; @@ -195,7 +192,7 @@ Please audit {{target}} for technical quality. Ask {{model}} for help.`; expect(cursorContent).toContain('{{target}}'); expect(cursorContent).toContain('the model'); - // Verify Claude Code: full frontmatter with user-invocable and args + // Verify Claude Code: full frontmatter with user-invocable and argument-hint const claudeContent = fs.readFileSync(path.join(DIST_DIR, 'claude-code/.claude/skills/audit/SKILL.md'), 'utf-8'); expect(claudeContent).toContain('---'); expect(claudeContent).toContain('name: audit'); diff --git a/tests/lib/transformers/agents.test.js b/tests/lib/transformers/agents.test.js deleted file mode 100644 index 09667de48..000000000 --- a/tests/lib/transformers/agents.test.js +++ /dev/null @@ -1,330 +0,0 @@ -import { describe, test, expect, beforeEach, afterEach, mock } from 'bun:test'; -import fs from 'fs'; -import path from 'path'; -import { transformAgents } from '../../../scripts/lib/transformers/agents.js'; -import { parseFrontmatter } from '../../../scripts/lib/utils.js'; - -const TEST_DIR = path.join(process.cwd(), 'test-tmp-agents'); - -describe('transformAgents', () => { - beforeEach(() => { - if (fs.existsSync(TEST_DIR)) { - fs.rmSync(TEST_DIR, { recursive: true, force: true }); - } - }); - - afterEach(() => { - if (fs.existsSync(TEST_DIR)) { - fs.rmSync(TEST_DIR, { recursive: true, force: true }); - } - }); - - test('should create correct directory structure', () => { - transformAgents([], TEST_DIR); - expect(fs.existsSync(path.join(TEST_DIR, 'agents/.agents/skills'))).toBe(true); - }); - - test('should create skill with full frontmatter', () => { - const skills = [ - { - name: 'test-skill', - description: 'A test skill', - license: 'MIT', - body: 'Skill instructions.' - } - ]; - - transformAgents(skills, TEST_DIR); - - const outputPath = path.join(TEST_DIR, 'agents/.agents/skills/test-skill/SKILL.md'); - expect(fs.existsSync(outputPath)).toBe(true); - - const content = fs.readFileSync(outputPath, 'utf-8'); - const parsed = parseFrontmatter(content); - - expect(parsed.frontmatter.name).toBe('test-skill'); - expect(parsed.frontmatter.description).toBe('A test skill'); - expect(parsed.body).toBe('Skill instructions.'); - }); - - test('should add user-invocable flag for user-invocable skills', () => { - const skills = [ - { - name: 'audit', - description: 'Audit command', - userInvocable: true, - body: 'Audit the code.' - } - ]; - - transformAgents(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'agents/.agents/skills/audit/SKILL.md'), 'utf-8'); - const parsed = parseFrontmatter(content); - - expect(parsed.frontmatter['user-invocable']).toBe(true); - }); - - test('should not add user-invocable flag for non-user-invocable skills', () => { - const skills = [ - { - name: 'helper', - description: 'Helper skill', - body: 'Helper body.' - } - ]; - - transformAgents(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'agents/.agents/skills/helper/SKILL.md'), 'utf-8'); - const parsed = parseFrontmatter(content); - - expect(parsed.frontmatter['user-invocable']).toBeUndefined(); - }); - - test('should create argument-hint for required args', () => { - const skills = [ - { - name: 'with-args', - description: 'Command with args', - userInvocable: true, - args: [ - { name: 'target', description: 'Target', required: true }, - { name: 'format', description: 'Format', required: false } - ], - body: 'Process {{target}} in {{format}}.' - } - ]; - - transformAgents(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'agents/.agents/skills/with-args/SKILL.md'), 'utf-8'); - const parsed = parseFrontmatter(content); - - expect(parsed.frontmatter['argument-hint']).toBe(' [FORMAT=]'); - }); - - test('should not add argument-hint for skills without args', () => { - const skills = [ - { - name: 'no-args', - description: 'No args', - userInvocable: true, - args: [], - body: 'Simple body.' - } - ]; - - transformAgents(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'agents/.agents/skills/no-args/SKILL.md'), 'utf-8'); - const parsed = parseFrontmatter(content); - - expect(parsed.frontmatter['argument-hint']).toBeUndefined(); - }); - - test('should not add argument-hint for non-user-invocable skills with args', () => { - const skills = [ - { - name: 'internal', - description: 'Internal skill', - userInvocable: false, - args: [{ name: 'target', description: 'Target', required: true }], - body: 'Body.' - } - ]; - - transformAgents(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'agents/.agents/skills/internal/SKILL.md'), 'utf-8'); - const parsed = parseFrontmatter(content); - - expect(parsed.frontmatter['argument-hint']).toBeUndefined(); - }); - - test('should handle multiple skills', () => { - const skills = [ - { name: 'skill1', description: 'Skill 1', body: 'Body 1' }, - { name: 'skill2', description: 'Skill 2', body: 'Body 2' }, - { name: 'skill3', description: 'Skill 3', body: 'Body 3' } - ]; - - transformAgents(skills, TEST_DIR); - - expect(fs.existsSync(path.join(TEST_DIR, 'agents/.agents/skills/skill1/SKILL.md'))).toBe(true); - expect(fs.existsSync(path.join(TEST_DIR, 'agents/.agents/skills/skill2/SKILL.md'))).toBe(true); - expect(fs.existsSync(path.join(TEST_DIR, 'agents/.agents/skills/skill3/SKILL.md'))).toBe(true); - }); - - test('should replace {{model}} placeholder', () => { - const skills = [ - { - name: 'test', - description: 'Test', - body: 'Ask {{model}} for help.' - } - ]; - - transformAgents(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'agents/.agents/skills/test/SKILL.md'), 'utf-8'); - expect(content).toContain('Ask the model for help.'); - }); - - test('should replace {{config_file}} placeholder', () => { - const skills = [ - { - name: 'test', - description: 'Test', - body: 'See {{config_file}} for details.' - } - ]; - - transformAgents(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'agents/.agents/skills/test/SKILL.md'), 'utf-8'); - expect(content).toContain('See .github/copilot-instructions.md for details.'); - }); - - test('should replace {{available_commands}} placeholder', () => { - const skills = [ - { name: 'audit', description: 'Audit', userInvocable: true, body: 'Available: {{available_commands}}' }, - { name: 'polish', description: 'Polish', userInvocable: true, body: 'Polish body.' } - ]; - - transformAgents(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'agents/.agents/skills/audit/SKILL.md'), 'utf-8'); - expect(content).toContain('Available: /audit, /polish'); - }); - - test('should copy reference files', () => { - const skills = [ - { - name: 'frontend-design', - description: 'Design skill', - body: 'Design instructions.', - references: [ - { name: 'typography', content: 'Typography reference', filePath: '/fake/path/typography.md' }, - { name: 'color', content: 'Color reference', filePath: '/fake/path/color.md' } - ] - } - ]; - - transformAgents(skills, TEST_DIR); - - const typoPath = path.join(TEST_DIR, 'agents/.agents/skills/frontend-design/reference/typography.md'); - const colorPath = path.join(TEST_DIR, 'agents/.agents/skills/frontend-design/reference/color.md'); - - expect(fs.existsSync(typoPath)).toBe(true); - expect(fs.existsSync(colorPath)).toBe(true); - expect(fs.readFileSync(typoPath, 'utf-8')).toBe('Typography reference'); - }); - - test('should replace placeholders in reference files', () => { - const skills = [ - { - name: 'test', - description: 'Test', - body: 'Body.', - references: [ - { name: 'ref', content: 'Use {{model}} with {{config_file}}.', filePath: '/fake/ref.md' } - ] - } - ]; - - transformAgents(skills, TEST_DIR); - - const refContent = fs.readFileSync(path.join(TEST_DIR, 'agents/.agents/skills/test/reference/ref.md'), 'utf-8'); - expect(refContent).toContain('Use the model with .github/copilot-instructions.md.'); - }); - - test('should support prefix option', () => { - const skills = [ - { name: 'audit', description: 'Audit', userInvocable: true, body: 'Audit body' } - ]; - - transformAgents(skills, TEST_DIR, null, { prefix: 'i-', outputSuffix: '-prefixed' }); - - const outputPath = path.join(TEST_DIR, 'agents-prefixed/.agents/skills/i-audit/SKILL.md'); - expect(fs.existsSync(outputPath)).toBe(true); - - const content = fs.readFileSync(outputPath, 'utf-8'); - expect(content).toContain('name: i-audit'); - }); - - test('should prefix skill references in body when prefix is set', () => { - const skills = [ - { name: 'audit', description: 'Audit', userInvocable: true, body: 'Run /polish after the audit skill.' }, - { name: 'polish', description: 'Polish', userInvocable: true, body: 'Polish body.' } - ]; - - transformAgents(skills, TEST_DIR, null, { prefix: 'i-', outputSuffix: '-prefixed' }); - - const content = fs.readFileSync(path.join(TEST_DIR, 'agents-prefixed/.agents/skills/i-audit/SKILL.md'), 'utf-8'); - expect(content).toContain('/i-polish'); - expect(content).toContain('the i-audit skill'); - }); - - test('should clean existing directory before writing', () => { - const existingDir = path.join(TEST_DIR, 'agents/.agents/skills/old'); - fs.mkdirSync(existingDir, { recursive: true }); - fs.writeFileSync(path.join(existingDir, 'SKILL.md'), 'old'); - - const skills = [{ name: 'new', description: 'New', body: 'New' }]; - transformAgents(skills, TEST_DIR); - - expect(fs.existsSync(path.join(TEST_DIR, 'agents/.agents/skills/old/SKILL.md'))).toBe(false); - expect(fs.existsSync(path.join(TEST_DIR, 'agents/.agents/skills/new/SKILL.md'))).toBe(true); - }); - - test('should log correct summary', () => { - const consoleMock = mock(() => {}); - const originalLog = console.log; - console.log = consoleMock; - - const skills = [ - { name: 'skill1', description: 'Test', userInvocable: true, body: 'body' }, - { name: 'skill2', description: 'Test', userInvocable: false, body: 'body' } - ]; - - transformAgents(skills, TEST_DIR); - - console.log = originalLog; - - expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('✓ Agents:')); - expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('2 skills')); - expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('1 user-invocable')); - }); - - test('should log reference file count', () => { - const consoleMock = mock(() => {}); - const originalLog = console.log; - console.log = consoleMock; - - const skills = [ - { - name: 'test', - description: 'Test', - body: 'Body.', - references: [ - { name: 'ref1', content: 'Ref 1', filePath: '/fake/ref1.md' }, - { name: 'ref2', content: 'Ref 2', filePath: '/fake/ref2.md' } - ] - } - ]; - - transformAgents(skills, TEST_DIR); - - console.log = originalLog; - - expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('2 reference files')); - }); - - test('should handle empty skills array', () => { - transformAgents([], TEST_DIR); - - const skillDirs = fs.readdirSync(path.join(TEST_DIR, 'agents/.agents/skills')); - expect(skillDirs).toHaveLength(0); - }); -}); diff --git a/tests/lib/transformers/claude-code.test.js b/tests/lib/transformers/claude-code.test.js deleted file mode 100644 index fdaf35909..000000000 --- a/tests/lib/transformers/claude-code.test.js +++ /dev/null @@ -1,356 +0,0 @@ -import { describe, test, expect, beforeEach, afterEach, mock } from 'bun:test'; -import fs from 'fs'; -import path from 'path'; -import { transformClaudeCode } from '../../../scripts/lib/transformers/claude-code.js'; -import { parseFrontmatter } from '../../../scripts/lib/utils.js'; - -const TEST_DIR = path.join(process.cwd(), 'test-tmp-claude'); - -describe('transformClaudeCode', () => { - beforeEach(() => { - if (fs.existsSync(TEST_DIR)) { - fs.rmSync(TEST_DIR, { recursive: true, force: true }); - } - }); - - afterEach(() => { - if (fs.existsSync(TEST_DIR)) { - fs.rmSync(TEST_DIR, { recursive: true, force: true }); - } - }); - - test('should create correct directory structure', () => { - const skills = []; - - transformClaudeCode(skills, TEST_DIR); - - expect(fs.existsSync(path.join(TEST_DIR, 'claude-code/.claude/skills'))).toBe(true); - }); - - test('should create skill with full frontmatter', () => { - const skills = [ - { - name: 'test-skill', - description: 'A test skill', - license: 'MIT', - body: 'Skill instructions.' - } - ]; - - transformClaudeCode(skills, TEST_DIR); - - const outputPath = path.join(TEST_DIR, 'claude-code/.claude/skills/test-skill/SKILL.md'); - expect(fs.existsSync(outputPath)).toBe(true); - - const content = fs.readFileSync(outputPath, 'utf-8'); - const parsed = parseFrontmatter(content); - - expect(parsed.frontmatter.name).toBe('test-skill'); - expect(parsed.frontmatter.description).toBe('A test skill'); - expect(parsed.frontmatter.license).toBe('MIT'); - expect(parsed.body).toBe('Skill instructions.'); - }); - - test('should add user-invocable flag for user-invocable skills', () => { - const skills = [ - { - name: 'audit', - description: 'Audit command', - userInvocable: true, - body: 'Audit the code.' - } - ]; - - transformClaudeCode(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'claude-code/.claude/skills/audit/SKILL.md'), 'utf-8'); - const parsed = parseFrontmatter(content); - - expect(parsed.frontmatter['user-invocable']).toBe(true); - }); - - test('should include args in frontmatter for user-invocable skills', () => { - const skills = [ - { - name: 'test-command', - description: 'A test command', - userInvocable: true, - args: [ - { name: 'target', description: 'The target', required: false }, - { name: 'output', description: 'Output format', required: true } - ], - body: 'Command body here.' - } - ]; - - transformClaudeCode(skills, TEST_DIR); - - const outputPath = path.join(TEST_DIR, 'claude-code/.claude/skills/test-command/SKILL.md'); - const content = fs.readFileSync(outputPath, 'utf-8'); - const parsed = parseFrontmatter(content); - - expect(parsed.frontmatter.args).toBeArray(); - expect(parsed.frontmatter.args).toHaveLength(2); - expect(parsed.frontmatter.args[0].name).toBe('target'); - expect(parsed.frontmatter.args[1].required).toBe(true); - }); - - test('should handle skills without args', () => { - const skills = [ - { - name: 'simple-skill', - description: 'Simple skill', - userInvocable: true, - args: [], - body: 'Simple body.' - } - ]; - - transformClaudeCode(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'claude-code/.claude/skills/simple-skill/SKILL.md'), 'utf-8'); - const parsed = parseFrontmatter(content); - - expect(parsed.frontmatter.args).toBeUndefined(); - }); - - test('should handle skills without license', () => { - const skills = [ - { - name: 'no-license-skill', - description: 'Skill without license', - license: '', - body: 'Body content.' - } - ]; - - transformClaudeCode(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'claude-code/.claude/skills/no-license-skill/SKILL.md'), 'utf-8'); - const parsed = parseFrontmatter(content); - - expect(parsed.frontmatter.license).toBeUndefined(); - }); - - test('should handle multiple skills', () => { - const skills = [ - { name: 'skill1', description: 'Skill 1', license: 'MIT', body: 'Body 1' }, - { name: 'skill2', description: 'Skill 2', license: 'Apache', body: 'Body 2' } - ]; - - transformClaudeCode(skills, TEST_DIR); - - expect(fs.existsSync(path.join(TEST_DIR, 'claude-code/.claude/skills/skill1/SKILL.md'))).toBe(true); - expect(fs.existsSync(path.join(TEST_DIR, 'claude-code/.claude/skills/skill2/SKILL.md'))).toBe(true); - }); - - test('should clean existing directory before writing', () => { - // Create a pre-existing file structure - const existingDir = path.join(TEST_DIR, 'claude-code/.claude/skills/old'); - fs.mkdirSync(existingDir, { recursive: true }); - fs.writeFileSync(path.join(existingDir, 'SKILL.md'), 'old'); - - const skills = [{ name: 'new', description: 'New', license: '', body: 'New' }]; - transformClaudeCode(skills, TEST_DIR); - - expect(fs.existsSync(path.join(TEST_DIR, 'claude-code/.claude/skills/old/SKILL.md'))).toBe(false); - expect(fs.existsSync(path.join(TEST_DIR, 'claude-code/.claude/skills/new/SKILL.md'))).toBe(true); - }); - - test('should preserve {{placeholder}} syntax in body', () => { - const skills = [ - { - name: 'with-placeholder', - description: 'Has placeholder', - userInvocable: true, - args: [{ name: 'target', description: 'Target', required: false }], - body: 'Process {{target}} and generate output.' - } - ]; - - transformClaudeCode(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'claude-code/.claude/skills/with-placeholder/SKILL.md'), 'utf-8'); - expect(content).toContain('{{target}}'); - }); - - test('should copy reference files', () => { - const skills = [ - { - name: 'frontend-design', - description: 'Design skill', - license: 'MIT', - body: 'Design instructions.', - references: [ - { name: 'typography', content: 'Typography reference', filePath: '/fake/path/typography.md' }, - { name: 'color', content: 'Color reference', filePath: '/fake/path/color.md' } - ] - } - ]; - - transformClaudeCode(skills, TEST_DIR); - - expect(fs.existsSync(path.join(TEST_DIR, 'claude-code/.claude/skills/frontend-design/reference/typography.md'))).toBe(true); - expect(fs.existsSync(path.join(TEST_DIR, 'claude-code/.claude/skills/frontend-design/reference/color.md'))).toBe(true); - - const typoContent = fs.readFileSync(path.join(TEST_DIR, 'claude-code/.claude/skills/frontend-design/reference/typography.md'), 'utf-8'); - expect(typoContent).toBe('Typography reference'); - }); - - test('should log correct summary', () => { - const consoleMock = mock(() => {}); - const originalLog = console.log; - console.log = consoleMock; - - const skills = [ - { name: 'skill1', description: 'Test', license: '', userInvocable: true, body: 'body' }, - { name: 'skill2', description: 'Test', license: '', userInvocable: false, body: 'body' } - ]; - - transformClaudeCode(skills, TEST_DIR); - - console.log = originalLog; - - expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('✓ Claude Code:')); - expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('2 skills')); - expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('1 user-invocable')); - }); - - test('should handle empty arrays', () => { - transformClaudeCode([], TEST_DIR); - - const skillDirs = fs.readdirSync(path.join(TEST_DIR, 'claude-code/.claude/skills')); - expect(skillDirs).toHaveLength(0); - }); - - test('should format frontmatter correctly with args', () => { - const skills = [ - { - name: 'test', - description: 'Test command', - userInvocable: true, - args: [ - { name: 'arg1', description: 'First arg', required: true }, - { name: 'arg2', description: 'Second arg', required: false } - ], - body: 'Body' - } - ]; - - transformClaudeCode(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'claude-code/.claude/skills/test/SKILL.md'), 'utf-8'); - - expect(content).toContain('---'); - expect(content).toContain('name: test'); - expect(content).toContain('description: Test command'); - expect(content).toContain('args:'); - expect(content).toContain('- name: arg1'); - expect(content).toContain('description: First arg'); - expect(content).toContain('required: true'); - expect(content).toContain('- name: arg2'); - expect(content).toContain('required: false'); - }); - - test('should preserve multiline body content', () => { - const skills = [ - { - name: 'multiline', - description: 'Test', - license: '', - body: `First paragraph. - -Second paragraph with details. - -- List item 1 -- List item 2` - } - ]; - - transformClaudeCode(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'claude-code/.claude/skills/multiline/SKILL.md'), 'utf-8'); - const parsed = parseFrontmatter(content); - - expect(parsed.body).toContain('First paragraph.'); - expect(parsed.body).toContain('Second paragraph'); - expect(parsed.body).toContain('- List item 1'); - }); - - test('should support prefix option', () => { - const skills = [ - { name: 'audit', description: 'Audit', license: '', userInvocable: true, body: 'Audit body' } - ]; - - transformClaudeCode(skills, TEST_DIR, null, { prefix: 'i-', outputSuffix: '-prefixed' }); - - expect(fs.existsSync(path.join(TEST_DIR, 'claude-code-prefixed/.claude/skills/i-audit/SKILL.md'))).toBe(true); - - const content = fs.readFileSync(path.join(TEST_DIR, 'claude-code-prefixed/.claude/skills/i-audit/SKILL.md'), 'utf-8'); - expect(content).toContain('name: i-audit'); - }); - - test('should include compatibility in frontmatter', () => { - const skills = [ - { - name: 'test', - description: 'Test', - compatibility: 'claude-code', - body: 'Body' - } - ]; - - transformClaudeCode(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'claude-code/.claude/skills/test/SKILL.md'), 'utf-8'); - expect(content).toContain('compatibility: claude-code'); - }); - - test('should include allowed-tools in frontmatter', () => { - const skills = [ - { - name: 'test', - description: 'Test', - allowedTools: 'Bash,Edit', - body: 'Body' - } - ]; - - transformClaudeCode(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'claude-code/.claude/skills/test/SKILL.md'), 'utf-8'); - expect(content).toContain('allowed-tools: Bash,Edit'); - }); - - test('should replace {{model}} placeholder', () => { - const skills = [ - { - name: 'test', - description: 'Test', - license: '', - body: 'Ask {{model}} for help.' - } - ]; - - transformClaudeCode(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'claude-code/.claude/skills/test/SKILL.md'), 'utf-8'); - expect(content).toContain('Ask Claude for help.'); - }); - - test('should replace {{config_file}} placeholder', () => { - const skills = [ - { - name: 'test', - description: 'Test', - license: '', - body: 'See {{config_file}} for more.' - } - ]; - - transformClaudeCode(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'claude-code/.claude/skills/test/SKILL.md'), 'utf-8'); - expect(content).toContain('See CLAUDE.md for more.'); - }); -}); diff --git a/tests/lib/transformers/codex.test.js b/tests/lib/transformers/codex.test.js deleted file mode 100644 index 70e95850f..000000000 --- a/tests/lib/transformers/codex.test.js +++ /dev/null @@ -1,308 +0,0 @@ -import { describe, test, expect, beforeEach, afterEach, mock } from 'bun:test'; -import fs from 'fs'; -import path from 'path'; -import { transformCodex } from '../../../scripts/lib/transformers/codex.js'; -import { parseFrontmatter } from '../../../scripts/lib/utils.js'; - -const TEST_DIR = path.join(process.cwd(), 'test-tmp-codex'); - -describe('transformCodex', () => { - beforeEach(() => { - if (fs.existsSync(TEST_DIR)) { - fs.rmSync(TEST_DIR, { recursive: true, force: true }); - } - }); - - afterEach(() => { - if (fs.existsSync(TEST_DIR)) { - fs.rmSync(TEST_DIR, { recursive: true, force: true }); - } - }); - - test('should create correct directory structure', () => { - const skills = []; - - transformCodex(skills, TEST_DIR); - - expect(fs.existsSync(path.join(TEST_DIR, 'codex/.codex/skills'))).toBe(true); - }); - - test('should create skill files with frontmatter and body in .codex/skills/ directory', () => { - const skills = [ - { - name: 'test-skill', - description: 'A test skill', - license: 'MIT', - body: 'Skill instructions here.' - } - ]; - - transformCodex(skills, TEST_DIR); - - const outputPath = path.join(TEST_DIR, 'codex/.codex/skills/test-skill/SKILL.md'); - expect(fs.existsSync(outputPath)).toBe(true); - - const content = fs.readFileSync(outputPath, 'utf-8'); - const parsed = parseFrontmatter(content); - - expect(parsed.frontmatter.name).toBe('test-skill'); - expect(parsed.frontmatter.description).toBe('A test skill'); - expect(parsed.body).toBe('Skill instructions here.'); - }); - - test('should create argument-hint for required args', () => { - const skills = [ - { - name: 'with-args', - description: 'Command with args', - userInvocable: true, - args: [ - { name: 'target', description: 'Target', required: true }, - { name: 'output', description: 'Output', required: true } - ], - body: 'Body' - } - ]; - - transformCodex(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'codex/.codex/skills/with-args/SKILL.md'), 'utf-8'); - const parsed = parseFrontmatter(content); - - expect(parsed.frontmatter['argument-hint']).toBe(' '); - }); - - test('should create argument-hint for optional args', () => { - const skills = [ - { - name: 'optional-args', - description: 'Command with optional args', - userInvocable: true, - args: [ - { name: 'format', description: 'Format', required: false } - ], - body: 'Body' - } - ]; - - transformCodex(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'codex/.codex/skills/optional-args/SKILL.md'), 'utf-8'); - const parsed = parseFrontmatter(content); - - expect(parsed.frontmatter['argument-hint']).toBe('[FORMAT=]'); - }); - - test('should create argument-hint with mixed required and optional args', () => { - const skills = [ - { - name: 'mixed-args', - description: 'Mixed args', - userInvocable: true, - args: [ - { name: 'input', description: 'Input', required: true }, - { name: 'format', description: 'Format', required: false }, - { name: 'output', description: 'Output', required: true } - ], - body: 'Body' - } - ]; - - transformCodex(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'codex/.codex/skills/mixed-args/SKILL.md'), 'utf-8'); - const parsed = parseFrontmatter(content); - - expect(parsed.frontmatter['argument-hint']).toBe(' [FORMAT=] '); - }); - - test('should transform {{argname}} to $ARGNAME for user-invocable skills', () => { - const skills = [ - { - name: 'normalize', - description: 'Normalize', - userInvocable: true, - args: [{ name: 'target', description: 'Target', required: false }], - body: 'Please normalize {{target}} to match the design system.' - } - ]; - - transformCodex(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'codex/.codex/skills/normalize/SKILL.md'), 'utf-8'); - const parsed = parseFrontmatter(content); - - expect(parsed.body).toContain('$TARGET'); - expect(parsed.body).not.toContain('{{target}}'); - }); - - test('should transform multiple different placeholders', () => { - const skills = [ - { - name: 'multi-arg', - description: 'Multiple args', - userInvocable: true, - args: [], - body: 'Process {{input}} and output to {{output}} with {{format}}.' - } - ]; - - transformCodex(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'codex/.codex/skills/multi-arg/SKILL.md'), 'utf-8'); - const parsed = parseFrontmatter(content); - - expect(parsed.body).toContain('$INPUT'); - expect(parsed.body).toContain('$OUTPUT'); - expect(parsed.body).toContain('$FORMAT'); - }); - - test('should handle multiple skills', () => { - const skills = [ - { name: 'skill1', description: 'Skill 1', license: 'MIT', body: 'Body 1' }, - { name: 'skill2', description: 'Skill 2', license: 'Apache', body: 'Body 2' }, - { name: 'skill3', description: 'Skill 3', license: 'MIT', body: 'Body 3' } - ]; - - transformCodex(skills, TEST_DIR); - - expect(fs.existsSync(path.join(TEST_DIR, 'codex/.codex/skills/skill1/SKILL.md'))).toBe(true); - expect(fs.existsSync(path.join(TEST_DIR, 'codex/.codex/skills/skill2/SKILL.md'))).toBe(true); - expect(fs.existsSync(path.join(TEST_DIR, 'codex/.codex/skills/skill3/SKILL.md'))).toBe(true); - }); - - test('should copy reference files', () => { - const skills = [ - { - name: 'frontend-design', - description: 'Design skill', - license: 'MIT', - body: 'Design instructions.', - references: [ - { name: 'typography', content: 'Typography reference', filePath: '/fake/path/typography.md' } - ] - } - ]; - - transformCodex(skills, TEST_DIR); - - expect(fs.existsSync(path.join(TEST_DIR, 'codex/.codex/skills/frontend-design/reference/typography.md'))).toBe(true); - - const typoContent = fs.readFileSync(path.join(TEST_DIR, 'codex/.codex/skills/frontend-design/reference/typography.md'), 'utf-8'); - expect(typoContent).toBe('Typography reference'); - }); - - test('should log correct summary', () => { - const consoleMock = mock(() => {}); - const originalLog = console.log; - console.log = consoleMock; - - const skills = [ - { name: 'skill1', description: 'Test', license: '', userInvocable: true, body: 'body' }, - { name: 'skill2', description: 'Test', license: '', userInvocable: false, body: 'body' } - ]; - - transformCodex(skills, TEST_DIR); - - console.log = originalLog; - - expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('✓ Codex:')); - expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('2 skills')); - expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('1 user-invocable')); - }); - - test('should handle empty arrays', () => { - transformCodex([], TEST_DIR); - - const skillDirs = fs.readdirSync(path.join(TEST_DIR, 'codex/.codex/skills')); - expect(skillDirs).toHaveLength(0); - }); - - test('should handle user-invocable skills without args', () => { - const skills = [ - { - name: 'no-args', - description: 'No args command', - userInvocable: true, - args: [], - body: 'Body content' - } - ]; - - transformCodex(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'codex/.codex/skills/no-args/SKILL.md'), 'utf-8'); - const parsed = parseFrontmatter(content); - - expect(parsed.frontmatter['argument-hint']).toBeUndefined(); - }); - - test('should preserve multiline body', () => { - const skills = [ - { - name: 'multiline', - description: 'Test', - license: '', - body: `First line. - -Second line after blank. - -- Bullet 1 -- Bullet 2` - } - ]; - - transformCodex(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'codex/.codex/skills/multiline/SKILL.md'), 'utf-8'); - const parsed = parseFrontmatter(content); - - expect(parsed.body).toContain('First line.\n\nSecond line'); - expect(parsed.body).toContain('- Bullet 1\n- Bullet 2'); - }); - - test('should support prefix option', () => { - const skills = [ - { name: 'audit', description: 'Audit', license: '', userInvocable: true, body: 'Audit body' } - ]; - - transformCodex(skills, TEST_DIR, null, { prefix: 'i-', outputSuffix: '-prefixed' }); - - expect(fs.existsSync(path.join(TEST_DIR, 'codex-prefixed/.codex/skills/i-audit/SKILL.md'))).toBe(true); - - const content = fs.readFileSync(path.join(TEST_DIR, 'codex-prefixed/.codex/skills/i-audit/SKILL.md'), 'utf-8'); - expect(content).toContain('name: i-audit'); - }); - - test('should replace {{model}} placeholder', () => { - const skills = [ - { - name: 'test', - description: 'Test', - license: '', - body: 'Ask {{model}} for help.' - } - ]; - - transformCodex(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'codex/.codex/skills/test/SKILL.md'), 'utf-8'); - expect(content).toContain('Ask GPT for help.'); - }); - - test('should replace {{config_file}} placeholder', () => { - const skills = [ - { - name: 'test', - description: 'Test', - license: '', - body: 'See {{config_file}} for more.' - } - ]; - - transformCodex(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'codex/.codex/skills/test/SKILL.md'), 'utf-8'); - expect(content).toContain('See AGENTS.md for more.'); - }); -}); diff --git a/tests/lib/transformers/cursor.test.js b/tests/lib/transformers/cursor.test.js deleted file mode 100644 index 23ba63df2..000000000 --- a/tests/lib/transformers/cursor.test.js +++ /dev/null @@ -1,245 +0,0 @@ -import { describe, test, expect, beforeEach, afterEach, mock } from 'bun:test'; -import fs from 'fs'; -import path from 'path'; -import { transformCursor } from '../../../scripts/lib/transformers/cursor.js'; - -const TEST_DIR = path.join(process.cwd(), 'test-tmp-cursor'); - -describe('transformCursor', () => { - beforeEach(() => { - if (fs.existsSync(TEST_DIR)) { - fs.rmSync(TEST_DIR, { recursive: true, force: true }); - } - }); - - afterEach(() => { - if (fs.existsSync(TEST_DIR)) { - fs.rmSync(TEST_DIR, { recursive: true, force: true }); - } - }); - - test('should create correct directory structure', () => { - const skills = []; - - transformCursor(skills, TEST_DIR); - - expect(fs.existsSync(path.join(TEST_DIR, 'cursor/.cursor/skills'))).toBe(true); - }); - - test('should create skill with frontmatter and body', () => { - const skills = [ - { - name: 'test-skill', - description: 'A test skill', - license: 'MIT', - body: 'Skill instructions here.' - } - ]; - - transformCursor(skills, TEST_DIR); - - const outputPath = path.join(TEST_DIR, 'cursor/.cursor/skills/test-skill/SKILL.md'); - expect(fs.existsSync(outputPath)).toBe(true); - - const content = fs.readFileSync(outputPath, 'utf-8'); - expect(content).toContain('---'); - expect(content).toContain('name: test-skill'); - expect(content).toContain('description: A test skill'); - expect(content).toContain('license: MIT'); - expect(content).toContain('Skill instructions here.'); - }); - - test('should handle skills without license', () => { - const skills = [ - { - name: 'no-license-skill', - description: 'Skill without license', - license: '', - body: 'Body content.' - } - ]; - - transformCursor(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'cursor/.cursor/skills/no-license-skill/SKILL.md'), 'utf-8'); - expect(content).not.toContain('license:'); - }); - - test('should handle multiple skills', () => { - const skills = [ - { name: 'skill1', description: 'Skill 1', license: 'MIT', body: 'Skill body 1' }, - { name: 'skill2', description: 'Skill 2', license: 'Apache', body: 'Skill body 2' } - ]; - - transformCursor(skills, TEST_DIR); - - expect(fs.existsSync(path.join(TEST_DIR, 'cursor/.cursor/skills/skill1/SKILL.md'))).toBe(true); - expect(fs.existsSync(path.join(TEST_DIR, 'cursor/.cursor/skills/skill2/SKILL.md'))).toBe(true); - }); - - test('should copy reference files', () => { - const skills = [ - { - name: 'frontend-design', - description: 'Design skill', - license: 'MIT', - body: 'Design instructions.', - references: [ - { name: 'typography', content: 'Typography reference', filePath: '/fake/path/typography.md' }, - { name: 'color', content: 'Color reference', filePath: '/fake/path/color.md' } - ] - } - ]; - - transformCursor(skills, TEST_DIR); - - expect(fs.existsSync(path.join(TEST_DIR, 'cursor/.cursor/skills/frontend-design/reference/typography.md'))).toBe(true); - expect(fs.existsSync(path.join(TEST_DIR, 'cursor/.cursor/skills/frontend-design/reference/color.md'))).toBe(true); - - const typoContent = fs.readFileSync(path.join(TEST_DIR, 'cursor/.cursor/skills/frontend-design/reference/typography.md'), 'utf-8'); - expect(typoContent).toBe('Typography reference'); - }); - - test('should handle skills without references', () => { - const skills = [ - { - name: 'simple-skill', - description: 'Simple', - license: '', - body: 'Body', - references: [] - } - ]; - - transformCursor(skills, TEST_DIR); - - expect(fs.existsSync(path.join(TEST_DIR, 'cursor/.cursor/skills/simple-skill/SKILL.md'))).toBe(true); - expect(fs.existsSync(path.join(TEST_DIR, 'cursor/.cursor/skills/simple-skill/reference'))).toBe(false); - }); - - test('should log correct summary', () => { - const consoleMock = mock(() => {}); - const originalLog = console.log; - console.log = consoleMock; - - const skills = [ - { name: 'skill1', description: '', license: '', userInvocable: true, body: 'body1' }, - { name: 'skill2', description: '', license: '', userInvocable: false, body: 'body2' } - ]; - - transformCursor(skills, TEST_DIR); - - console.log = originalLog; - - expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('✓ Cursor:')); - expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('2 skills')); - expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('1 user-invocable')); - }); - - test('should handle empty skills array', () => { - transformCursor([], TEST_DIR); - - expect(fs.existsSync(path.join(TEST_DIR, 'cursor/.cursor/skills'))).toBe(true); - }); - - test('should preserve line breaks and formatting in body', () => { - const skills = [ - { - name: 'formatted', - description: 'Test', - license: '', - body: `Line 1 - -Line 3 after blank line - -- Bullet 1 -- Bullet 2 - -End.` - } - ]; - - transformCursor(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'cursor/.cursor/skills/formatted/SKILL.md'), 'utf-8'); - expect(content).toContain('Line 1\n\nLine 3'); - expect(content).toContain('- Bullet 1\n- Bullet 2'); - }); - - test('should support prefix option', () => { - const skills = [ - { name: 'audit', description: 'Audit', license: '', body: 'Audit body' } - ]; - - transformCursor(skills, TEST_DIR, null, { prefix: 'i-', outputSuffix: '-prefixed' }); - - expect(fs.existsSync(path.join(TEST_DIR, 'cursor-prefixed/.cursor/skills/i-audit/SKILL.md'))).toBe(true); - - const content = fs.readFileSync(path.join(TEST_DIR, 'cursor-prefixed/.cursor/skills/i-audit/SKILL.md'), 'utf-8'); - expect(content).toContain('name: i-audit'); - }); - - test('should replace {{model}} placeholder', () => { - const skills = [ - { - name: 'test', - description: 'Test', - license: '', - body: 'Ask {{model}} for help.' - } - ]; - - transformCursor(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'cursor/.cursor/skills/test/SKILL.md'), 'utf-8'); - expect(content).toContain('Ask the model for help.'); - }); - - test('should replace {{config_file}} placeholder', () => { - const skills = [ - { - name: 'test', - description: 'Test', - license: '', - body: 'See {{config_file}} for more.' - } - ]; - - transformCursor(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'cursor/.cursor/skills/test/SKILL.md'), 'utf-8'); - expect(content).toContain('See .cursorrules for more.'); - }); - - test('should replace {{ask_instruction}} placeholder', () => { - const skills = [ - { - name: 'test', - description: 'Test', - license: '', - body: 'When unsure, {{ask_instruction}}' - } - ]; - - transformCursor(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'cursor/.cursor/skills/test/SKILL.md'), 'utf-8'); - expect(content).toContain('When unsure, ask the user directly to clarify what you cannot infer.'); - }); - - test('should clean existing directory before writing', () => { - // Create a pre-existing file structure - const existingDir = path.join(TEST_DIR, 'cursor/.cursor/skills/old-skill'); - fs.mkdirSync(existingDir, { recursive: true }); - fs.writeFileSync(path.join(existingDir, 'SKILL.md'), 'old content'); - - const skills = [ - { name: 'new-skill', description: 'New', license: '', body: 'New body' } - ]; - - transformCursor(skills, TEST_DIR); - - expect(fs.existsSync(path.join(TEST_DIR, 'cursor/.cursor/skills/old-skill/SKILL.md'))).toBe(false); - expect(fs.existsSync(path.join(TEST_DIR, 'cursor/.cursor/skills/new-skill/SKILL.md'))).toBe(true); - }); -}); diff --git a/tests/lib/transformers/factory.test.js b/tests/lib/transformers/factory.test.js new file mode 100644 index 000000000..d6e0f5269 --- /dev/null +++ b/tests/lib/transformers/factory.test.js @@ -0,0 +1,276 @@ +import { describe, test, expect, beforeEach, afterEach, mock } from 'bun:test'; +import fs from 'fs'; +import path from 'path'; +import { createTransformer } from '../../../scripts/lib/transformers/factory.js'; +import { parseFrontmatter } from '../../../scripts/lib/utils.js'; + +const TEST_DIR = path.join(process.cwd(), 'test-tmp-factory'); + +// Minimal config using 'cursor' as provider (has existing PROVIDER_PLACEHOLDERS) +const baseConfig = { + provider: 'cursor', + configDir: '.test', + displayName: 'Test Provider', + frontmatterFields: [], +}; + +describe('createTransformer factory', () => { + beforeEach(() => { + if (fs.existsSync(TEST_DIR)) { + fs.rmSync(TEST_DIR, { recursive: true, force: true }); + } + }); + + afterEach(() => { + if (fs.existsSync(TEST_DIR)) { + fs.rmSync(TEST_DIR, { recursive: true, force: true }); + } + }); + + test('should create correct directory structure', () => { + const transform = createTransformer(baseConfig); + transform([], TEST_DIR); + expect(fs.existsSync(path.join(TEST_DIR, 'cursor/.test/skills'))).toBe(true); + }); + + test('should always emit name and description', () => { + const transform = createTransformer(baseConfig); + const skills = [{ name: 'test', description: 'A test skill', body: 'Body.' }]; + transform(skills, TEST_DIR); + + const content = fs.readFileSync(path.join(TEST_DIR, 'cursor/.test/skills/test/SKILL.md'), 'utf-8'); + const parsed = parseFrontmatter(content); + expect(parsed.frontmatter.name).toBe('test'); + expect(parsed.frontmatter.description).toBe('A test skill'); + expect(parsed.body).toBe('Body.'); + }); + + test('should only emit allowlisted fields', () => { + const config = { ...baseConfig, frontmatterFields: ['license'] }; + const transform = createTransformer(config); + const skills = [{ + name: 'test', + description: 'Test', + license: 'MIT', + compatibility: 'all', + metadata: 'meta', + body: 'Body' + }]; + transform(skills, TEST_DIR); + + const content = fs.readFileSync(path.join(TEST_DIR, 'cursor/.test/skills/test/SKILL.md'), 'utf-8'); + const parsed = parseFrontmatter(content); + expect(parsed.frontmatter.license).toBe('MIT'); + expect(parsed.frontmatter.compatibility).toBeUndefined(); + expect(parsed.frontmatter.metadata).toBeUndefined(); + }); + + test('should skip empty optional fields', () => { + const config = { ...baseConfig, frontmatterFields: ['license'] }; + const transform = createTransformer(config); + const skills = [{ name: 'test', description: 'Test', license: '', body: 'Body' }]; + transform(skills, TEST_DIR); + + const content = fs.readFileSync(path.join(TEST_DIR, 'cursor/.test/skills/test/SKILL.md'), 'utf-8'); + const parsed = parseFrontmatter(content); + expect(parsed.frontmatter.license).toBeUndefined(); + }); + + test('should emit user-invocable as true when skill is user-invocable', () => { + const config = { ...baseConfig, frontmatterFields: ['user-invocable'] }; + const transform = createTransformer(config); + const skills = [{ name: 'test', description: 'Test', userInvocable: true, body: 'Body' }]; + transform(skills, TEST_DIR); + + const content = fs.readFileSync(path.join(TEST_DIR, 'cursor/.test/skills/test/SKILL.md'), 'utf-8'); + const parsed = parseFrontmatter(content); + expect(parsed.frontmatter['user-invocable']).toBe(true); + }); + + test('should not emit user-invocable when skill is not user-invocable', () => { + const config = { ...baseConfig, frontmatterFields: ['user-invocable'] }; + const transform = createTransformer(config); + const skills = [{ name: 'test', description: 'Test', userInvocable: false, body: 'Body' }]; + transform(skills, TEST_DIR); + + const content = fs.readFileSync(path.join(TEST_DIR, 'cursor/.test/skills/test/SKILL.md'), 'utf-8'); + const parsed = parseFrontmatter(content); + expect(parsed.frontmatter['user-invocable']).toBeUndefined(); + }); + + test('should emit argument-hint only when user-invocable', () => { + const config = { ...baseConfig, frontmatterFields: ['argument-hint'] }; + const transform = createTransformer(config); + + // User-invocable with hint + const skills1 = [{ name: 'test', description: 'Test', userInvocable: true, argumentHint: '[target]', body: 'Body' }]; + transform(skills1, TEST_DIR); + let content = fs.readFileSync(path.join(TEST_DIR, 'cursor/.test/skills/test/SKILL.md'), 'utf-8'); + let parsed = parseFrontmatter(content); + expect(parsed.frontmatter['argument-hint']).toBe('[target]'); + + // Non-user-invocable with hint + fs.rmSync(TEST_DIR, { recursive: true, force: true }); + const skills2 = [{ name: 'test', description: 'Test', userInvocable: false, argumentHint: '[target]', body: 'Body' }]; + transform(skills2, TEST_DIR); + content = fs.readFileSync(path.join(TEST_DIR, 'cursor/.test/skills/test/SKILL.md'), 'utf-8'); + parsed = parseFrontmatter(content); + expect(parsed.frontmatter['argument-hint']).toBeUndefined(); + }); + + test('should apply bodyTransform after placeholder replacement', () => { + const config = { + ...baseConfig, + bodyTransform: (body) => body.replace(/PLACEHOLDER/, 'TRANSFORMED'), + }; + const transform = createTransformer(config); + const skills = [{ name: 'test', description: 'Test', body: 'PLACEHOLDER content' }]; + transform(skills, TEST_DIR); + + const content = fs.readFileSync(path.join(TEST_DIR, 'cursor/.test/skills/test/SKILL.md'), 'utf-8'); + expect(content).toContain('TRANSFORMED content'); + }); + + test('should support prefix option', () => { + const transform = createTransformer(baseConfig); + const skills = [{ name: 'audit', description: 'Audit', userInvocable: true, body: 'Body' }]; + transform(skills, TEST_DIR, { prefix: 'i-', outputSuffix: '-prefixed' }); + + const outputPath = path.join(TEST_DIR, 'cursor-prefixed/.test/skills/i-audit/SKILL.md'); + expect(fs.existsSync(outputPath)).toBe(true); + const content = fs.readFileSync(outputPath, 'utf-8'); + expect(content).toContain('name: i-audit'); + }); + + test('should copy reference files', () => { + const transform = createTransformer(baseConfig); + const skills = [{ + name: 'test', + description: 'Test', + body: 'Body', + references: [ + { name: 'ref1', content: 'Reference 1 content', filePath: '/fake/ref1.md' }, + { name: 'ref2', content: 'Reference 2 content', filePath: '/fake/ref2.md' }, + ] + }]; + transform(skills, TEST_DIR); + + expect(fs.existsSync(path.join(TEST_DIR, 'cursor/.test/skills/test/reference/ref1.md'))).toBe(true); + expect(fs.existsSync(path.join(TEST_DIR, 'cursor/.test/skills/test/reference/ref2.md'))).toBe(true); + const ref1 = fs.readFileSync(path.join(TEST_DIR, 'cursor/.test/skills/test/reference/ref1.md'), 'utf-8'); + expect(ref1).toBe('Reference 1 content'); + }); + + test('should clean existing directory before writing', () => { + const transform = createTransformer(baseConfig); + const existingDir = path.join(TEST_DIR, 'cursor/.test/skills/old'); + fs.mkdirSync(existingDir, { recursive: true }); + fs.writeFileSync(path.join(existingDir, 'SKILL.md'), 'old'); + + const skills = [{ name: 'new', description: 'New', body: 'New' }]; + transform(skills, TEST_DIR); + + expect(fs.existsSync(path.join(TEST_DIR, 'cursor/.test/skills/old/SKILL.md'))).toBe(false); + expect(fs.existsSync(path.join(TEST_DIR, 'cursor/.test/skills/new/SKILL.md'))).toBe(true); + }); + + test('should log correct summary', () => { + const consoleMock = mock(() => {}); + const originalLog = console.log; + console.log = consoleMock; + + const transform = createTransformer(baseConfig); + const skills = [ + { name: 's1', description: 'Test', userInvocable: true, body: 'body' }, + { name: 's2', description: 'Test', userInvocable: false, body: 'body' } + ]; + transform(skills, TEST_DIR); + + console.log = originalLog; + expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('✓ Test Provider:')); + expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('2 skills')); + expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('1 user-invocable')); + }); + + test('should handle empty skills array', () => { + const transform = createTransformer(baseConfig); + transform([], TEST_DIR); + + const skillDirs = fs.readdirSync(path.join(TEST_DIR, 'cursor/.test/skills')); + expect(skillDirs).toHaveLength(0); + }); + + test('should replace {{model}} placeholder', () => { + const transform = createTransformer(baseConfig); + const skills = [{ name: 'test', description: 'Test', body: 'Ask {{model}} for help.' }]; + transform(skills, TEST_DIR); + + const content = fs.readFileSync(path.join(TEST_DIR, 'cursor/.test/skills/test/SKILL.md'), 'utf-8'); + expect(content).toContain('Ask the model for help.'); + }); + + test('should replace {{config_file}} placeholder', () => { + const transform = createTransformer(baseConfig); + const skills = [{ name: 'test', description: 'Test', body: 'See {{config_file}}.' }]; + transform(skills, TEST_DIR); + + const content = fs.readFileSync(path.join(TEST_DIR, 'cursor/.test/skills/test/SKILL.md'), 'utf-8'); + expect(content).toContain('See .cursorrules.'); + }); + + test('should handle multiple skills', () => { + const transform = createTransformer(baseConfig); + const skills = [ + { name: 'skill1', description: 'Skill 1', body: 'Body 1' }, + { name: 'skill2', description: 'Skill 2', body: 'Body 2' }, + ]; + transform(skills, TEST_DIR); + + expect(fs.existsSync(path.join(TEST_DIR, 'cursor/.test/skills/skill1/SKILL.md'))).toBe(true); + expect(fs.existsSync(path.join(TEST_DIR, 'cursor/.test/skills/skill2/SKILL.md'))).toBe(true); + }); + + test('should preserve multiline body content', () => { + const transform = createTransformer(baseConfig); + const skills = [{ + name: 'test', + description: 'Test', + body: `First paragraph.\n\nSecond paragraph.\n\n- List item 1\n- List item 2` + }]; + transform(skills, TEST_DIR); + + const content = fs.readFileSync(path.join(TEST_DIR, 'cursor/.test/skills/test/SKILL.md'), 'utf-8'); + const parsed = parseFrontmatter(content); + expect(parsed.body).toContain('First paragraph.'); + expect(parsed.body).toContain('Second paragraph.'); + expect(parsed.body).toContain('- List item 1'); + }); + + test('should emit all spec fields when configured', () => { + const config = { + ...baseConfig, + frontmatterFields: ['user-invocable', 'argument-hint', 'license', 'compatibility', 'metadata', 'allowed-tools'], + }; + const transform = createTransformer(config); + const skills = [{ + name: 'test', + description: 'Test', + userInvocable: true, + argumentHint: '[target]', + license: 'MIT', + compatibility: 'claude-code', + metadata: 'v1', + allowedTools: 'Bash,Edit', + body: 'Body' + }]; + transform(skills, TEST_DIR); + + const content = fs.readFileSync(path.join(TEST_DIR, 'cursor/.test/skills/test/SKILL.md'), 'utf-8'); + expect(content).toContain('user-invocable: true'); + expect(content).toContain('argument-hint:'); + expect(content).toContain('license: MIT'); + expect(content).toContain('compatibility: claude-code'); + expect(content).toContain('metadata: v1'); + expect(content).toContain('allowed-tools: Bash,Edit'); + }); +}); diff --git a/tests/lib/transformers/gemini.test.js b/tests/lib/transformers/gemini.test.js deleted file mode 100644 index 9908bed11..000000000 --- a/tests/lib/transformers/gemini.test.js +++ /dev/null @@ -1,210 +0,0 @@ -import { describe, test, expect, beforeEach, afterEach, mock } from 'bun:test'; -import fs from 'fs'; -import path from 'path'; -import { transformGemini } from '../../../scripts/lib/transformers/gemini.js'; - -const TEST_DIR = path.join(process.cwd(), 'test-tmp-gemini'); - -describe('transformGemini', () => { - beforeEach(() => { - if (fs.existsSync(TEST_DIR)) { - fs.rmSync(TEST_DIR, { recursive: true, force: true }); - } - }); - - afterEach(() => { - if (fs.existsSync(TEST_DIR)) { - fs.rmSync(TEST_DIR, { recursive: true, force: true }); - } - }); - - test('should create correct directory structure', () => { - const skills = []; - - transformGemini(skills, TEST_DIR); - - expect(fs.existsSync(path.join(TEST_DIR, 'gemini/.gemini/skills'))).toBe(true); - }); - - test('should create skill files with frontmatter and body in .gemini/skills/ directory', () => { - const skills = [ - { - name: 'test-skill', - description: 'A test skill', - license: 'MIT', - body: 'Skill instructions here.' - } - ]; - - transformGemini(skills, TEST_DIR); - - const outputPath = path.join(TEST_DIR, 'gemini/.gemini/skills/test-skill/SKILL.md'); - expect(fs.existsSync(outputPath)).toBe(true); - - const content = fs.readFileSync(outputPath, 'utf-8'); - expect(content).toContain('---'); - expect(content).toContain('name: test-skill'); - expect(content).toContain('description: A test skill'); - expect(content).toContain('Skill instructions here.'); - }); - - test('should handle multiple skills', () => { - const skills = [ - { name: 'skill1', description: 'Skill 1', license: 'MIT', body: 'Body 1' }, - { name: 'skill2', description: 'Skill 2', license: 'Apache', body: 'Body 2' }, - { name: 'skill3', description: 'Skill 3', license: 'MIT', body: 'Body 3' } - ]; - - transformGemini(skills, TEST_DIR); - - expect(fs.existsSync(path.join(TEST_DIR, 'gemini/.gemini/skills/skill1/SKILL.md'))).toBe(true); - expect(fs.existsSync(path.join(TEST_DIR, 'gemini/.gemini/skills/skill2/SKILL.md'))).toBe(true); - expect(fs.existsSync(path.join(TEST_DIR, 'gemini/.gemini/skills/skill3/SKILL.md'))).toBe(true); - }); - - test('should handle user-invocable skills with args', () => { - const skills = [ - { - name: 'normalize', - description: 'Normalize design', - userInvocable: true, - args: [{ name: 'target', description: 'Target', required: false }], - body: 'Please normalize {{target}} to match the design system.' - } - ]; - - transformGemini(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'gemini/.gemini/skills/normalize/SKILL.md'), 'utf-8'); - // For user-invocable skills, {{arg}} placeholders become {{args}} - expect(content).toContain('{{args}}'); - expect(content).not.toContain('{{target}}'); - }); - - test('should replace multiple different placeholders with {{args}} for user-invocable skills', () => { - const skills = [ - { - name: 'multi-arg', - description: 'Multiple args', - userInvocable: true, - args: [], - body: 'Process {{input}} and output to {{output}} with {{format}}.' - } - ]; - - transformGemini(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'gemini/.gemini/skills/multi-arg/SKILL.md'), 'utf-8'); - const argsMatches = content.match(/\{\{args\}\}/g); - expect(argsMatches).toHaveLength(3); - }); - - test('should not replace placeholders for non-user-invocable skills', () => { - const skills = [ - { - name: 'passive-skill', - description: 'Passive skill', - userInvocable: false, - body: 'Process {{target}} normally.' - } - ]; - - transformGemini(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'gemini/.gemini/skills/passive-skill/SKILL.md'), 'utf-8'); - expect(content).toContain('{{target}}'); - expect(content).not.toContain('{{args}}'); - }); - - test('should copy reference files', () => { - const skills = [ - { - name: 'frontend-design', - description: 'Design skill', - license: 'MIT', - body: 'Design instructions.', - references: [ - { name: 'typography', content: 'Typography reference', filePath: '/fake/path/typography.md' } - ] - } - ]; - - transformGemini(skills, TEST_DIR); - - expect(fs.existsSync(path.join(TEST_DIR, 'gemini/.gemini/skills/frontend-design/reference/typography.md'))).toBe(true); - - const typoContent = fs.readFileSync(path.join(TEST_DIR, 'gemini/.gemini/skills/frontend-design/reference/typography.md'), 'utf-8'); - expect(typoContent).toBe('Typography reference'); - }); - - test('should log correct summary', () => { - const consoleMock = mock(() => {}); - const originalLog = console.log; - console.log = consoleMock; - - const skills = [ - { name: 'skill1', description: 'Test', license: '', userInvocable: true, body: 'body' }, - { name: 'skill2', description: 'Test', license: '', userInvocable: false, body: 'body' } - ]; - - transformGemini(skills, TEST_DIR); - - console.log = originalLog; - - expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('✓ Gemini:')); - expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('2 skills')); - expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('1 user-invocable')); - }); - - test('should handle empty arrays', () => { - transformGemini([], TEST_DIR); - - const skillDirs = fs.readdirSync(path.join(TEST_DIR, 'gemini/.gemini/skills')); - expect(skillDirs).toHaveLength(0); - }); - - test('should support prefix option', () => { - const skills = [ - { name: 'audit', description: 'Audit', license: '', body: 'Audit body' } - ]; - - transformGemini(skills, TEST_DIR, null, { prefix: 'i-', outputSuffix: '-prefixed' }); - - expect(fs.existsSync(path.join(TEST_DIR, 'gemini-prefixed/.gemini/skills/i-audit/SKILL.md'))).toBe(true); - - const content = fs.readFileSync(path.join(TEST_DIR, 'gemini-prefixed/.gemini/skills/i-audit/SKILL.md'), 'utf-8'); - expect(content).toContain('name: i-audit'); - }); - - test('should replace {{model}} placeholder', () => { - const skills = [ - { - name: 'test', - description: 'Test', - license: '', - body: 'Ask {{model}} for help.' - } - ]; - - transformGemini(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'gemini/.gemini/skills/test/SKILL.md'), 'utf-8'); - expect(content).toContain('Ask Gemini for help.'); - }); - - test('should replace {{config_file}} placeholder', () => { - const skills = [ - { - name: 'test', - description: 'Test', - license: '', - body: 'See {{config_file}} for more.' - } - ]; - - transformGemini(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'gemini/.gemini/skills/test/SKILL.md'), 'utf-8'); - expect(content).toContain('See GEMINI.md for more.'); - }); -}); diff --git a/tests/lib/transformers/kiro.test.js b/tests/lib/transformers/kiro.test.js deleted file mode 100644 index 935e5b0d6..000000000 --- a/tests/lib/transformers/kiro.test.js +++ /dev/null @@ -1,302 +0,0 @@ -import { describe, test, expect, beforeEach, afterEach, mock } from 'bun:test'; -import fs from 'fs'; -import path from 'path'; -import { transformKiro } from '../../../scripts/lib/transformers/kiro.js'; -import { parseFrontmatter } from '../../../scripts/lib/utils.js'; - -const TEST_DIR = path.join(process.cwd(), 'test-tmp-kiro'); - -describe('transformKiro', () => { - beforeEach(() => { - if (fs.existsSync(TEST_DIR)) { - fs.rmSync(TEST_DIR, { recursive: true, force: true }); - } - }); - - afterEach(() => { - if (fs.existsSync(TEST_DIR)) { - fs.rmSync(TEST_DIR, { recursive: true, force: true }); - } - }); - - test('should create correct directory structure', () => { - transformKiro([], TEST_DIR); - expect(fs.existsSync(path.join(TEST_DIR, 'kiro/.kiro/skills'))).toBe(true); - }); - - test('should create skill with full frontmatter', () => { - const skills = [ - { - name: 'test-skill', - description: 'A test skill', - license: 'MIT', - body: 'Skill instructions.' - } - ]; - - transformKiro(skills, TEST_DIR); - - const outputPath = path.join(TEST_DIR, 'kiro/.kiro/skills/test-skill/SKILL.md'); - expect(fs.existsSync(outputPath)).toBe(true); - - const content = fs.readFileSync(outputPath, 'utf-8'); - const parsed = parseFrontmatter(content); - - expect(parsed.frontmatter.name).toBe('test-skill'); - expect(parsed.frontmatter.description).toBe('A test skill'); - expect(parsed.frontmatter.license).toBe('MIT'); - expect(parsed.body).toBe('Skill instructions.'); - }); - - test('should include compatibility in frontmatter', () => { - const skills = [ - { - name: 'test', - description: 'Test', - compatibility: 'kiro', - body: 'Body' - } - ]; - - transformKiro(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'kiro/.kiro/skills/test/SKILL.md'), 'utf-8'); - expect(content).toContain('compatibility: kiro'); - }); - - test('should include metadata in frontmatter', () => { - const skills = [ - { - name: 'test', - description: 'Test', - metadata: 'some-metadata', - body: 'Body' - } - ]; - - transformKiro(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'kiro/.kiro/skills/test/SKILL.md'), 'utf-8'); - expect(content).toContain('metadata: some-metadata'); - }); - - test('should not include user-invocable in frontmatter (Kiro does not use it)', () => { - const skills = [ - { - name: 'test', - description: 'Test', - userInvocable: true, - body: 'Body' - } - ]; - - transformKiro(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'kiro/.kiro/skills/test/SKILL.md'), 'utf-8'); - expect(content).not.toContain('user-invocable'); - }); - - test('should handle multiple skills', () => { - const skills = [ - { name: 'skill1', description: 'Skill 1', body: 'Body 1' }, - { name: 'skill2', description: 'Skill 2', body: 'Body 2' }, - { name: 'skill3', description: 'Skill 3', body: 'Body 3' } - ]; - - transformKiro(skills, TEST_DIR); - - expect(fs.existsSync(path.join(TEST_DIR, 'kiro/.kiro/skills/skill1/SKILL.md'))).toBe(true); - expect(fs.existsSync(path.join(TEST_DIR, 'kiro/.kiro/skills/skill2/SKILL.md'))).toBe(true); - expect(fs.existsSync(path.join(TEST_DIR, 'kiro/.kiro/skills/skill3/SKILL.md'))).toBe(true); - }); - - test('should replace {{model}} placeholder', () => { - const skills = [ - { - name: 'test', - description: 'Test', - body: 'Ask {{model}} for help.' - } - ]; - - transformKiro(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'kiro/.kiro/skills/test/SKILL.md'), 'utf-8'); - expect(content).toContain('Ask Claude for help.'); - }); - - test('should replace {{config_file}} placeholder', () => { - const skills = [ - { - name: 'test', - description: 'Test', - body: 'See {{config_file}} for details.' - } - ]; - - transformKiro(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'kiro/.kiro/skills/test/SKILL.md'), 'utf-8'); - expect(content).toContain('See .kiro/settings.json for details.'); - }); - - test('should replace {{available_commands}} placeholder', () => { - const skills = [ - { name: 'audit', description: 'Audit', userInvocable: true, body: 'Available: {{available_commands}}' }, - { name: 'polish', description: 'Polish', userInvocable: true, body: 'Polish body.' } - ]; - - transformKiro(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'kiro/.kiro/skills/audit/SKILL.md'), 'utf-8'); - expect(content).toContain('Available: /audit, /polish'); - }); - - test('should copy reference files', () => { - const skills = [ - { - name: 'frontend-design', - description: 'Design skill', - body: 'Design instructions.', - references: [ - { name: 'typography', content: 'Typography reference', filePath: '/fake/path/typography.md' }, - { name: 'color', content: 'Color reference', filePath: '/fake/path/color.md' } - ] - } - ]; - - transformKiro(skills, TEST_DIR); - - const typoPath = path.join(TEST_DIR, 'kiro/.kiro/skills/frontend-design/reference/typography.md'); - const colorPath = path.join(TEST_DIR, 'kiro/.kiro/skills/frontend-design/reference/color.md'); - - expect(fs.existsSync(typoPath)).toBe(true); - expect(fs.existsSync(colorPath)).toBe(true); - expect(fs.readFileSync(typoPath, 'utf-8')).toBe('Typography reference'); - }); - - test('should replace placeholders in reference files without commandNames', () => { - const skills = [ - { - name: 'test', - description: 'Test', - userInvocable: true, - body: 'Body with {{available_commands}}.', - references: [ - { name: 'ref', content: 'Use {{model}} with {{config_file}}. Commands: {{available_commands}}.', filePath: '/fake/ref.md' } - ] - } - ]; - - transformKiro(skills, TEST_DIR); - - const refContent = fs.readFileSync(path.join(TEST_DIR, 'kiro/.kiro/skills/test/reference/ref.md'), 'utf-8'); - expect(refContent).toContain('Use Claude with .kiro/settings.json.'); - // Reference files should NOT get commandNames, so {{available_commands}} becomes empty string - expect(refContent).toContain('Commands: .'); - }); - - test('should support prefix option', () => { - const skills = [ - { name: 'audit', description: 'Audit', userInvocable: true, body: 'Audit body' } - ]; - - transformKiro(skills, TEST_DIR, null, { prefix: 'i-', outputSuffix: '-prefixed' }); - - const outputPath = path.join(TEST_DIR, 'kiro-prefixed/.kiro/skills/i-audit/SKILL.md'); - expect(fs.existsSync(outputPath)).toBe(true); - - const content = fs.readFileSync(outputPath, 'utf-8'); - expect(content).toContain('name: i-audit'); - }); - - test('should prefix skill references in body when prefix is set', () => { - const skills = [ - { name: 'audit', description: 'Audit', userInvocable: true, body: 'Run /polish after the audit skill.' }, - { name: 'polish', description: 'Polish', userInvocable: true, body: 'Polish body.' } - ]; - - transformKiro(skills, TEST_DIR, null, { prefix: 'i-', outputSuffix: '-prefixed' }); - - const content = fs.readFileSync(path.join(TEST_DIR, 'kiro-prefixed/.kiro/skills/i-audit/SKILL.md'), 'utf-8'); - expect(content).toContain('/i-polish'); - expect(content).toContain('the i-audit skill'); - }); - - test('should clean existing directory before writing', () => { - const existingDir = path.join(TEST_DIR, 'kiro/.kiro/skills/old'); - fs.mkdirSync(existingDir, { recursive: true }); - fs.writeFileSync(path.join(existingDir, 'SKILL.md'), 'old'); - - const skills = [{ name: 'new', description: 'New', body: 'New' }]; - transformKiro(skills, TEST_DIR); - - expect(fs.existsSync(path.join(TEST_DIR, 'kiro/.kiro/skills/old/SKILL.md'))).toBe(false); - expect(fs.existsSync(path.join(TEST_DIR, 'kiro/.kiro/skills/new/SKILL.md'))).toBe(true); - }); - - test('should log correct summary', () => { - const consoleMock = mock(() => {}); - const originalLog = console.log; - console.log = consoleMock; - - const skills = [ - { name: 'skill1', description: 'Test', userInvocable: true, body: 'body' }, - { name: 'skill2', description: 'Test', userInvocable: false, body: 'body' } - ]; - - transformKiro(skills, TEST_DIR); - - console.log = originalLog; - - expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('✓ Kiro:')); - expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('2 skills')); - expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('1 user-invocable')); - }); - - test('should log reference file count', () => { - const consoleMock = mock(() => {}); - const originalLog = console.log; - console.log = consoleMock; - - const skills = [ - { - name: 'test', - description: 'Test', - body: 'Body.', - references: [ - { name: 'ref1', content: 'Ref 1', filePath: '/fake/ref1.md' } - ] - } - ]; - - transformKiro(skills, TEST_DIR); - - console.log = originalLog; - - expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('1 reference files')); - }); - - test('should handle empty skills array', () => { - transformKiro([], TEST_DIR); - - const skillDirs = fs.readdirSync(path.join(TEST_DIR, 'kiro/.kiro/skills')); - expect(skillDirs).toHaveLength(0); - }); - - test('should not include license if empty', () => { - const skills = [ - { - name: 'test', - description: 'Test', - license: '', - body: 'Body' - } - ]; - - transformKiro(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'kiro/.kiro/skills/test/SKILL.md'), 'utf-8'); - expect(content).not.toContain('license:'); - }); -}); diff --git a/tests/lib/transformers/opencode.test.js b/tests/lib/transformers/opencode.test.js deleted file mode 100644 index e6b3b558a..000000000 --- a/tests/lib/transformers/opencode.test.js +++ /dev/null @@ -1,395 +0,0 @@ -import { describe, test, expect, beforeEach, afterEach, mock } from 'bun:test'; -import fs from 'fs'; -import path from 'path'; -import { transformOpenCode } from '../../../scripts/lib/transformers/opencode.js'; -import { parseFrontmatter } from '../../../scripts/lib/utils.js'; - -const TEST_DIR = path.join(process.cwd(), 'test-tmp-opencode'); - -describe('transformOpenCode', () => { - beforeEach(() => { - if (fs.existsSync(TEST_DIR)) { - fs.rmSync(TEST_DIR, { recursive: true, force: true }); - } - }); - - afterEach(() => { - if (fs.existsSync(TEST_DIR)) { - fs.rmSync(TEST_DIR, { recursive: true, force: true }); - } - }); - - test('should create correct directory structure', () => { - transformOpenCode([], TEST_DIR); - expect(fs.existsSync(path.join(TEST_DIR, 'opencode/.opencode/skills'))).toBe(true); - }); - - test('should create skill with full frontmatter', () => { - const skills = [ - { - name: 'test-skill', - description: 'A test skill', - license: 'MIT', - body: 'Skill instructions.' - } - ]; - - transformOpenCode(skills, TEST_DIR); - - const outputPath = path.join(TEST_DIR, 'opencode/.opencode/skills/test-skill/SKILL.md'); - expect(fs.existsSync(outputPath)).toBe(true); - - const content = fs.readFileSync(outputPath, 'utf-8'); - const parsed = parseFrontmatter(content); - - expect(parsed.frontmatter.name).toBe('test-skill'); - expect(parsed.frontmatter.description).toBe('A test skill'); - expect(parsed.frontmatter.license).toBe('MIT'); - expect(parsed.body).toBe('Skill instructions.'); - }); - - test('should add user-invocable flag for user-invocable skills', () => { - const skills = [ - { - name: 'audit', - description: 'Audit command', - userInvocable: true, - body: 'Audit the code.' - } - ]; - - transformOpenCode(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'opencode/.opencode/skills/audit/SKILL.md'), 'utf-8'); - const parsed = parseFrontmatter(content); - - expect(parsed.frontmatter['user-invocable']).toBe(true); - }); - - test('should not add user-invocable flag for non-user-invocable skills', () => { - const skills = [ - { - name: 'helper', - description: 'Helper skill', - body: 'Helper body.' - } - ]; - - transformOpenCode(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'opencode/.opencode/skills/helper/SKILL.md'), 'utf-8'); - const parsed = parseFrontmatter(content); - - expect(parsed.frontmatter['user-invocable']).toBeUndefined(); - }); - - test('should include args in frontmatter', () => { - const skills = [ - { - name: 'with-args', - description: 'Command with args', - userInvocable: true, - args: [ - { name: 'target', description: 'Target element', required: false } - ], - body: 'Process {{target}}.' - } - ]; - - transformOpenCode(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'opencode/.opencode/skills/with-args/SKILL.md'), 'utf-8'); - const parsed = parseFrontmatter(content); - - expect(parsed.frontmatter.args).toBeArray(); - expect(parsed.frontmatter.args).toHaveLength(1); - expect(parsed.frontmatter.args[0].name).toBe('target'); - }); - - test('should not include args when empty', () => { - const skills = [ - { - name: 'no-args', - description: 'No args', - userInvocable: true, - args: [], - body: 'Simple body.' - } - ]; - - transformOpenCode(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'opencode/.opencode/skills/no-args/SKILL.md'), 'utf-8'); - const parsed = parseFrontmatter(content); - - expect(parsed.frontmatter.args).toBeUndefined(); - }); - - test('should include allowed-tools in frontmatter', () => { - const skills = [ - { - name: 'test', - description: 'Test', - allowedTools: 'Bash,Edit', - body: 'Body' - } - ]; - - transformOpenCode(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'opencode/.opencode/skills/test/SKILL.md'), 'utf-8'); - expect(content).toContain('allowed-tools: Bash,Edit'); - }); - - test('should include compatibility in frontmatter', () => { - const skills = [ - { - name: 'test', - description: 'Test', - compatibility: 'opencode', - body: 'Body' - } - ]; - - transformOpenCode(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'opencode/.opencode/skills/test/SKILL.md'), 'utf-8'); - expect(content).toContain('compatibility: opencode'); - }); - - test('should include metadata in frontmatter', () => { - const skills = [ - { - name: 'test', - description: 'Test', - metadata: 'some-metadata', - body: 'Body' - } - ]; - - transformOpenCode(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'opencode/.opencode/skills/test/SKILL.md'), 'utf-8'); - expect(content).toContain('metadata: some-metadata'); - }); - - test('should handle multiple skills', () => { - const skills = [ - { name: 'skill1', description: 'Skill 1', body: 'Body 1' }, - { name: 'skill2', description: 'Skill 2', body: 'Body 2' }, - { name: 'skill3', description: 'Skill 3', body: 'Body 3' } - ]; - - transformOpenCode(skills, TEST_DIR); - - expect(fs.existsSync(path.join(TEST_DIR, 'opencode/.opencode/skills/skill1/SKILL.md'))).toBe(true); - expect(fs.existsSync(path.join(TEST_DIR, 'opencode/.opencode/skills/skill2/SKILL.md'))).toBe(true); - expect(fs.existsSync(path.join(TEST_DIR, 'opencode/.opencode/skills/skill3/SKILL.md'))).toBe(true); - }); - - test('should replace {{model}} placeholder', () => { - const skills = [ - { - name: 'test', - description: 'Test', - body: 'Ask {{model}} for help.' - } - ]; - - transformOpenCode(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'opencode/.opencode/skills/test/SKILL.md'), 'utf-8'); - expect(content).toContain('Ask Claude for help.'); - }); - - test('should replace {{config_file}} placeholder', () => { - const skills = [ - { - name: 'test', - description: 'Test', - body: 'See {{config_file}} for details.' - } - ]; - - transformOpenCode(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'opencode/.opencode/skills/test/SKILL.md'), 'utf-8'); - expect(content).toContain('See AGENTS.md for details.'); - }); - - test('should replace {{ask_instruction}} placeholder', () => { - const skills = [ - { - name: 'test', - description: 'Test', - body: 'If unclear, {{ask_instruction}}' - } - ]; - - transformOpenCode(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'opencode/.opencode/skills/test/SKILL.md'), 'utf-8'); - expect(content).toContain('STOP and call the `question` tool to clarify.'); - }); - - test('should replace {{available_commands}} placeholder', () => { - const skills = [ - { name: 'audit', description: 'Audit', userInvocable: true, body: 'Available: {{available_commands}}' }, - { name: 'polish', description: 'Polish', userInvocable: true, body: 'Polish body.' } - ]; - - transformOpenCode(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'opencode/.opencode/skills/audit/SKILL.md'), 'utf-8'); - expect(content).toContain('Available: /audit, /polish'); - }); - - test('should copy reference files', () => { - const skills = [ - { - name: 'frontend-design', - description: 'Design skill', - body: 'Design instructions.', - references: [ - { name: 'typography', content: 'Typography reference', filePath: '/fake/path/typography.md' }, - { name: 'color', content: 'Color reference', filePath: '/fake/path/color.md' } - ] - } - ]; - - transformOpenCode(skills, TEST_DIR); - - const typoPath = path.join(TEST_DIR, 'opencode/.opencode/skills/frontend-design/reference/typography.md'); - const colorPath = path.join(TEST_DIR, 'opencode/.opencode/skills/frontend-design/reference/color.md'); - - expect(fs.existsSync(typoPath)).toBe(true); - expect(fs.existsSync(colorPath)).toBe(true); - expect(fs.readFileSync(typoPath, 'utf-8')).toBe('Typography reference'); - }); - - test('should replace placeholders in reference files without commandNames', () => { - const skills = [ - { - name: 'test', - description: 'Test', - userInvocable: true, - body: 'Body with {{available_commands}}.', - references: [ - { name: 'ref', content: 'Use {{model}} with {{config_file}}. Commands: {{available_commands}}.', filePath: '/fake/ref.md' } - ] - } - ]; - - transformOpenCode(skills, TEST_DIR); - - const refContent = fs.readFileSync(path.join(TEST_DIR, 'opencode/.opencode/skills/test/reference/ref.md'), 'utf-8'); - expect(refContent).toContain('Use Claude with AGENTS.md.'); - // Reference files should NOT get commandNames, so {{available_commands}} becomes empty string - expect(refContent).toContain('Commands: .'); - }); - - test('should support prefix option', () => { - const skills = [ - { name: 'audit', description: 'Audit', userInvocable: true, body: 'Audit body' } - ]; - - transformOpenCode(skills, TEST_DIR, null, { prefix: 'i-', outputSuffix: '-prefixed' }); - - const outputPath = path.join(TEST_DIR, 'opencode-prefixed/.opencode/skills/i-audit/SKILL.md'); - expect(fs.existsSync(outputPath)).toBe(true); - - const content = fs.readFileSync(outputPath, 'utf-8'); - expect(content).toContain('name: i-audit'); - }); - - test('should prefix skill references in body when prefix is set', () => { - const skills = [ - { name: 'audit', description: 'Audit', userInvocable: true, body: 'Run /polish after the audit skill.' }, - { name: 'polish', description: 'Polish', userInvocable: true, body: 'Polish body.' } - ]; - - transformOpenCode(skills, TEST_DIR, null, { prefix: 'i-', outputSuffix: '-prefixed' }); - - const content = fs.readFileSync(path.join(TEST_DIR, 'opencode-prefixed/.opencode/skills/i-audit/SKILL.md'), 'utf-8'); - expect(content).toContain('/i-polish'); - expect(content).toContain('the i-audit skill'); - }); - - test('should clean existing directory before writing', () => { - const existingDir = path.join(TEST_DIR, 'opencode/.opencode/skills/old'); - fs.mkdirSync(existingDir, { recursive: true }); - fs.writeFileSync(path.join(existingDir, 'SKILL.md'), 'old'); - - const skills = [{ name: 'new', description: 'New', body: 'New' }]; - transformOpenCode(skills, TEST_DIR); - - expect(fs.existsSync(path.join(TEST_DIR, 'opencode/.opencode/skills/old/SKILL.md'))).toBe(false); - expect(fs.existsSync(path.join(TEST_DIR, 'opencode/.opencode/skills/new/SKILL.md'))).toBe(true); - }); - - test('should log correct summary', () => { - const consoleMock = mock(() => {}); - const originalLog = console.log; - console.log = consoleMock; - - const skills = [ - { name: 'skill1', description: 'Test', userInvocable: true, body: 'body' }, - { name: 'skill2', description: 'Test', userInvocable: false, body: 'body' } - ]; - - transformOpenCode(skills, TEST_DIR); - - console.log = originalLog; - - expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('✓ OpenCode:')); - expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('2 skills')); - expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('1 user-invocable')); - }); - - test('should log reference file count', () => { - const consoleMock = mock(() => {}); - const originalLog = console.log; - console.log = consoleMock; - - const skills = [ - { - name: 'test', - description: 'Test', - body: 'Body.', - references: [ - { name: 'ref1', content: 'Ref 1', filePath: '/fake/ref1.md' }, - { name: 'ref2', content: 'Ref 2', filePath: '/fake/ref2.md' } - ] - } - ]; - - transformOpenCode(skills, TEST_DIR); - - console.log = originalLog; - - expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('2 reference files')); - }); - - test('should handle empty skills array', () => { - transformOpenCode([], TEST_DIR); - - const skillDirs = fs.readdirSync(path.join(TEST_DIR, 'opencode/.opencode/skills')); - expect(skillDirs).toHaveLength(0); - }); - - test('should not include license if empty', () => { - const skills = [ - { - name: 'test', - description: 'Test', - license: '', - body: 'Body' - } - ]; - - transformOpenCode(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'opencode/.opencode/skills/test/SKILL.md'), 'utf-8'); - expect(content).not.toContain('license:'); - }); -}); diff --git a/tests/lib/transformers/pi.test.js b/tests/lib/transformers/pi.test.js deleted file mode 100644 index 1d4fa87b5..000000000 --- a/tests/lib/transformers/pi.test.js +++ /dev/null @@ -1,302 +0,0 @@ -import { describe, test, expect, beforeEach, afterEach, mock } from 'bun:test'; -import fs from 'fs'; -import path from 'path'; -import { transformPi } from '../../../scripts/lib/transformers/pi.js'; -import { parseFrontmatter } from '../../../scripts/lib/utils.js'; - -const TEST_DIR = path.join(process.cwd(), 'test-tmp-pi'); - -describe('transformPi', () => { - beforeEach(() => { - if (fs.existsSync(TEST_DIR)) { - fs.rmSync(TEST_DIR, { recursive: true, force: true }); - } - }); - - afterEach(() => { - if (fs.existsSync(TEST_DIR)) { - fs.rmSync(TEST_DIR, { recursive: true, force: true }); - } - }); - - test('should create correct directory structure', () => { - transformPi([], TEST_DIR); - expect(fs.existsSync(path.join(TEST_DIR, 'pi/.pi/skills'))).toBe(true); - }); - - test('should create skill with full frontmatter', () => { - const skills = [ - { - name: 'test-skill', - description: 'A test skill', - license: 'MIT', - body: 'Skill instructions.' - } - ]; - - transformPi(skills, TEST_DIR); - - const outputPath = path.join(TEST_DIR, 'pi/.pi/skills/test-skill/SKILL.md'); - expect(fs.existsSync(outputPath)).toBe(true); - - const content = fs.readFileSync(outputPath, 'utf-8'); - const parsed = parseFrontmatter(content); - - expect(parsed.frontmatter.name).toBe('test-skill'); - expect(parsed.frontmatter.description).toBe('A test skill'); - expect(parsed.frontmatter.license).toBe('MIT'); - expect(parsed.body).toBe('Skill instructions.'); - }); - - test('should include compatibility in frontmatter', () => { - const skills = [ - { - name: 'test', - description: 'Test', - compatibility: 'pi', - body: 'Body' - } - ]; - - transformPi(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'pi/.pi/skills/test/SKILL.md'), 'utf-8'); - expect(content).toContain('compatibility: pi'); - }); - - test('should include metadata in frontmatter', () => { - const skills = [ - { - name: 'test', - description: 'Test', - metadata: 'some-metadata', - body: 'Body' - } - ]; - - transformPi(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'pi/.pi/skills/test/SKILL.md'), 'utf-8'); - expect(content).toContain('metadata: some-metadata'); - }); - - test('should not include user-invocable in frontmatter (Pi does not use it)', () => { - const skills = [ - { - name: 'test', - description: 'Test', - userInvocable: true, - body: 'Body' - } - ]; - - transformPi(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'pi/.pi/skills/test/SKILL.md'), 'utf-8'); - expect(content).not.toContain('user-invocable'); - }); - - test('should handle multiple skills', () => { - const skills = [ - { name: 'skill1', description: 'Skill 1', body: 'Body 1' }, - { name: 'skill2', description: 'Skill 2', body: 'Body 2' }, - { name: 'skill3', description: 'Skill 3', body: 'Body 3' } - ]; - - transformPi(skills, TEST_DIR); - - expect(fs.existsSync(path.join(TEST_DIR, 'pi/.pi/skills/skill1/SKILL.md'))).toBe(true); - expect(fs.existsSync(path.join(TEST_DIR, 'pi/.pi/skills/skill2/SKILL.md'))).toBe(true); - expect(fs.existsSync(path.join(TEST_DIR, 'pi/.pi/skills/skill3/SKILL.md'))).toBe(true); - }); - - test('should replace {{model}} placeholder', () => { - const skills = [ - { - name: 'test', - description: 'Test', - body: 'Ask {{model}} for help.' - } - ]; - - transformPi(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'pi/.pi/skills/test/SKILL.md'), 'utf-8'); - expect(content).toContain('Ask the model for help.'); - }); - - test('should replace {{config_file}} placeholder', () => { - const skills = [ - { - name: 'test', - description: 'Test', - body: 'See {{config_file}} for details.' - } - ]; - - transformPi(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'pi/.pi/skills/test/SKILL.md'), 'utf-8'); - expect(content).toContain('See AGENTS.md for details.'); - }); - - test('should replace {{available_commands}} placeholder', () => { - const skills = [ - { name: 'audit', description: 'Audit', userInvocable: true, body: 'Available: {{available_commands}}' }, - { name: 'polish', description: 'Polish', userInvocable: true, body: 'Polish body.' } - ]; - - transformPi(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'pi/.pi/skills/audit/SKILL.md'), 'utf-8'); - expect(content).toContain('Available: /audit, /polish'); - }); - - test('should copy reference files', () => { - const skills = [ - { - name: 'frontend-design', - description: 'Design skill', - body: 'Design instructions.', - references: [ - { name: 'typography', content: 'Typography reference', filePath: '/fake/path/typography.md' }, - { name: 'color', content: 'Color reference', filePath: '/fake/path/color.md' } - ] - } - ]; - - transformPi(skills, TEST_DIR); - - const typoPath = path.join(TEST_DIR, 'pi/.pi/skills/frontend-design/reference/typography.md'); - const colorPath = path.join(TEST_DIR, 'pi/.pi/skills/frontend-design/reference/color.md'); - - expect(fs.existsSync(typoPath)).toBe(true); - expect(fs.existsSync(colorPath)).toBe(true); - expect(fs.readFileSync(typoPath, 'utf-8')).toBe('Typography reference'); - }); - - test('should replace placeholders in reference files without commandNames', () => { - const skills = [ - { - name: 'test', - description: 'Test', - userInvocable: true, - body: 'Body with {{available_commands}}.', - references: [ - { name: 'ref', content: 'Use {{model}} with {{config_file}}. Commands: {{available_commands}}.', filePath: '/fake/ref.md' } - ] - } - ]; - - transformPi(skills, TEST_DIR); - - const refContent = fs.readFileSync(path.join(TEST_DIR, 'pi/.pi/skills/test/reference/ref.md'), 'utf-8'); - expect(refContent).toContain('Use the model with AGENTS.md.'); - // Reference files should NOT get commandNames, so {{available_commands}} becomes empty string - expect(refContent).toContain('Commands: .'); - }); - - test('should support prefix option', () => { - const skills = [ - { name: 'audit', description: 'Audit', userInvocable: true, body: 'Audit body' } - ]; - - transformPi(skills, TEST_DIR, null, { prefix: 'i-', outputSuffix: '-prefixed' }); - - const outputPath = path.join(TEST_DIR, 'pi-prefixed/.pi/skills/i-audit/SKILL.md'); - expect(fs.existsSync(outputPath)).toBe(true); - - const content = fs.readFileSync(outputPath, 'utf-8'); - expect(content).toContain('name: i-audit'); - }); - - test('should prefix skill references in body when prefix is set', () => { - const skills = [ - { name: 'audit', description: 'Audit', userInvocable: true, body: 'Run /polish after the audit skill.' }, - { name: 'polish', description: 'Polish', userInvocable: true, body: 'Polish body.' } - ]; - - transformPi(skills, TEST_DIR, null, { prefix: 'i-', outputSuffix: '-prefixed' }); - - const content = fs.readFileSync(path.join(TEST_DIR, 'pi-prefixed/.pi/skills/i-audit/SKILL.md'), 'utf-8'); - expect(content).toContain('/i-polish'); - expect(content).toContain('the i-audit skill'); - }); - - test('should clean existing directory before writing', () => { - const existingDir = path.join(TEST_DIR, 'pi/.pi/skills/old'); - fs.mkdirSync(existingDir, { recursive: true }); - fs.writeFileSync(path.join(existingDir, 'SKILL.md'), 'old'); - - const skills = [{ name: 'new', description: 'New', body: 'New' }]; - transformPi(skills, TEST_DIR); - - expect(fs.existsSync(path.join(TEST_DIR, 'pi/.pi/skills/old/SKILL.md'))).toBe(false); - expect(fs.existsSync(path.join(TEST_DIR, 'pi/.pi/skills/new/SKILL.md'))).toBe(true); - }); - - test('should log correct summary', () => { - const consoleMock = mock(() => {}); - const originalLog = console.log; - console.log = consoleMock; - - const skills = [ - { name: 'skill1', description: 'Test', userInvocable: true, body: 'body' }, - { name: 'skill2', description: 'Test', userInvocable: false, body: 'body' } - ]; - - transformPi(skills, TEST_DIR); - - console.log = originalLog; - - expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('✓ Pi:')); - expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('2 skills')); - expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('1 user-invocable')); - }); - - test('should log reference file count', () => { - const consoleMock = mock(() => {}); - const originalLog = console.log; - console.log = consoleMock; - - const skills = [ - { - name: 'test', - description: 'Test', - body: 'Body.', - references: [ - { name: 'ref1', content: 'Ref 1', filePath: '/fake/ref1.md' } - ] - } - ]; - - transformPi(skills, TEST_DIR); - - console.log = originalLog; - - expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('1 reference files')); - }); - - test('should handle empty skills array', () => { - transformPi([], TEST_DIR); - - const skillDirs = fs.readdirSync(path.join(TEST_DIR, 'pi/.pi/skills')); - expect(skillDirs).toHaveLength(0); - }); - - test('should not include license if empty', () => { - const skills = [ - { - name: 'test', - description: 'Test', - license: '', - body: 'Body' - } - ]; - - transformPi(skills, TEST_DIR); - - const content = fs.readFileSync(path.join(TEST_DIR, 'pi/.pi/skills/test/SKILL.md'), 'utf-8'); - expect(content).not.toContain('license:'); - }); -}); diff --git a/tests/lib/transformers/providers.test.js b/tests/lib/transformers/providers.test.js new file mode 100644 index 000000000..67b278856 --- /dev/null +++ b/tests/lib/transformers/providers.test.js @@ -0,0 +1,304 @@ +import { describe, test, expect, beforeEach, afterEach } from 'bun:test'; +import fs from 'fs'; +import path from 'path'; +import { PROVIDERS } from '../../../scripts/lib/transformers/providers.js'; +import { createTransformer } from '../../../scripts/lib/transformers/factory.js'; +import { parseFrontmatter, PROVIDER_PLACEHOLDERS } from '../../../scripts/lib/utils.js'; + +const TEST_DIR = path.join(process.cwd(), 'test-tmp-providers'); + +function providerTestDir(provider, suffix = '') { + return path.join(TEST_DIR, `${provider}${suffix}`); +} + +function skillPath(config, skillName, suffix = '') { + return path.join(TEST_DIR, `${config.provider}${suffix}/${config.configDir}/skills/${skillName}/SKILL.md`); +} + +// Test every provider config +for (const [key, config] of Object.entries(PROVIDERS)) { + describe(`Provider: ${config.displayName} (${key})`, () => { + const transform = createTransformer(config); + + beforeEach(() => { + if (fs.existsSync(TEST_DIR)) { + fs.rmSync(TEST_DIR, { recursive: true, force: true }); + } + }); + + afterEach(() => { + if (fs.existsSync(TEST_DIR)) { + fs.rmSync(TEST_DIR, { recursive: true, force: true }); + } + }); + + test('should create correct directory structure', () => { + transform([], TEST_DIR); + expect(fs.existsSync(path.join(TEST_DIR, `${config.provider}/${config.configDir}/skills`))).toBe(true); + }); + + test('should replace {{model}} placeholder correctly', () => { + const skills = [{ name: 'test', description: 'Test', body: 'Ask {{model}} for help.' }]; + transform(skills, TEST_DIR); + const content = fs.readFileSync(skillPath(config, 'test'), 'utf-8'); + const expected = PROVIDER_PLACEHOLDERS[config.provider].model; + expect(content).toContain(`Ask ${expected} for help.`); + }); + + test('should replace {{config_file}} placeholder correctly', () => { + const skills = [{ name: 'test', description: 'Test', body: 'See {{config_file}}.' }]; + transform(skills, TEST_DIR); + const content = fs.readFileSync(skillPath(config, 'test'), 'utf-8'); + const expected = PROVIDER_PLACEHOLDERS[config.provider].config_file; + expect(content).toContain(`See ${expected}.`); + }); + + test('should support prefix option', () => { + const skills = [{ name: 'audit', description: 'Audit', userInvocable: true, body: 'Body' }]; + transform(skills, TEST_DIR, { prefix: 'i-', outputSuffix: '-prefixed' }); + expect(fs.existsSync(skillPath(config, 'i-audit', '-prefixed'))).toBe(true); + const content = fs.readFileSync(skillPath(config, 'i-audit', '-prefixed'), 'utf-8'); + expect(content).toContain('name: i-audit'); + }); + + test('should copy reference files', () => { + const skills = [{ + name: 'test', + description: 'Test', + body: 'Body', + references: [{ name: 'ref', content: 'Ref content', filePath: '/fake/ref.md' }] + }]; + transform(skills, TEST_DIR); + const refPath = path.join(TEST_DIR, `${config.provider}/${config.configDir}/skills/test/reference/ref.md`); + expect(fs.existsSync(refPath)).toBe(true); + }); + + // Field-specific tests based on provider config + if (config.frontmatterFields.includes('user-invocable')) { + test('should emit user-invocable for user-invocable skills', () => { + const skills = [{ name: 'test', description: 'Test', userInvocable: true, body: 'Body' }]; + transform(skills, TEST_DIR); + const parsed = parseFrontmatter(fs.readFileSync(skillPath(config, 'test'), 'utf-8')); + expect(parsed.frontmatter['user-invocable']).toBe(true); + }); + + test('should omit user-invocable for non-user-invocable skills', () => { + const skills = [{ name: 'test', description: 'Test', userInvocable: false, body: 'Body' }]; + transform(skills, TEST_DIR); + const parsed = parseFrontmatter(fs.readFileSync(skillPath(config, 'test'), 'utf-8')); + expect(parsed.frontmatter['user-invocable']).toBeUndefined(); + }); + } + + if (config.frontmatterFields.includes('argument-hint')) { + test('should emit argument-hint for user-invocable skills', () => { + const skills = [{ name: 'test', description: 'Test', userInvocable: true, argumentHint: '[target]', body: 'Body' }]; + transform(skills, TEST_DIR); + const parsed = parseFrontmatter(fs.readFileSync(skillPath(config, 'test'), 'utf-8')); + expect(parsed.frontmatter['argument-hint']).toBe('[target]'); + }); + + test('should not emit argument-hint for non-user-invocable skills', () => { + const skills = [{ name: 'test', description: 'Test', userInvocable: false, argumentHint: '[target]', body: 'Body' }]; + transform(skills, TEST_DIR); + const parsed = parseFrontmatter(fs.readFileSync(skillPath(config, 'test'), 'utf-8')); + expect(parsed.frontmatter['argument-hint']).toBeUndefined(); + }); + } + + if (config.frontmatterFields.includes('license')) { + test('should emit license when present', () => { + const skills = [{ name: 'test', description: 'Test', license: 'MIT', body: 'Body' }]; + transform(skills, TEST_DIR); + const parsed = parseFrontmatter(fs.readFileSync(skillPath(config, 'test'), 'utf-8')); + expect(parsed.frontmatter.license).toBe('MIT'); + }); + + test('should omit license when empty', () => { + const skills = [{ name: 'test', description: 'Test', license: '', body: 'Body' }]; + transform(skills, TEST_DIR); + const parsed = parseFrontmatter(fs.readFileSync(skillPath(config, 'test'), 'utf-8')); + expect(parsed.frontmatter.license).toBeUndefined(); + }); + } + + if (config.frontmatterFields.includes('compatibility')) { + test('should emit compatibility when present', () => { + const skills = [{ name: 'test', description: 'Test', compatibility: config.provider, body: 'Body' }]; + transform(skills, TEST_DIR); + const content = fs.readFileSync(skillPath(config, 'test'), 'utf-8'); + expect(content).toContain(`compatibility: ${config.provider}`); + }); + } + + if (config.frontmatterFields.includes('metadata')) { + test('should emit metadata when present', () => { + const skills = [{ name: 'test', description: 'Test', metadata: 'some-metadata', body: 'Body' }]; + transform(skills, TEST_DIR); + const content = fs.readFileSync(skillPath(config, 'test'), 'utf-8'); + expect(content).toContain('metadata: some-metadata'); + }); + } + + if (config.frontmatterFields.includes('allowed-tools')) { + test('should emit allowed-tools when present', () => { + const skills = [{ name: 'test', description: 'Test', allowedTools: 'Bash,Edit', body: 'Body' }]; + transform(skills, TEST_DIR); + const content = fs.readFileSync(skillPath(config, 'test'), 'utf-8'); + expect(content).toContain('allowed-tools: Bash,Edit'); + }); + } + + // Fields NOT in this provider's allowlist should not appear + const allOptionalFields = ['user-invocable', 'argument-hint', 'license', 'compatibility', 'metadata', 'allowed-tools']; + const excludedFields = allOptionalFields.filter(f => !config.frontmatterFields.includes(f)); + + if (excludedFields.length > 0) { + test('should not emit fields outside allowlist', () => { + const skills = [{ + name: 'test', + description: 'Test', + userInvocable: true, + argumentHint: '[target]', + license: 'MIT', + compatibility: 'all', + metadata: 'meta', + allowedTools: 'Bash', + body: 'Body' + }]; + transform(skills, TEST_DIR); + const content = fs.readFileSync(skillPath(config, 'test'), 'utf-8'); + + for (const field of excludedFields) { + const yamlKey = field; // field names match yaml keys + // Check the raw content doesn't contain the yaml key + const lines = content.split('\n'); + const frontmatterLines = []; + let inFrontmatter = false; + for (const line of lines) { + if (line === '---') { + if (inFrontmatter) break; + inFrontmatter = true; + continue; + } + if (inFrontmatter) frontmatterLines.push(line); + } + const hasForbiddenField = frontmatterLines.some(l => l.startsWith(`${yamlKey}:`)); + expect(hasForbiddenField).toBe(false); + } + }); + } + }); +} + +// Provider-specific body transform tests +describe('Codex body transform', () => { + const transform = createTransformer(PROVIDERS.codex); + + beforeEach(() => { + if (fs.existsSync(TEST_DIR)) fs.rmSync(TEST_DIR, { recursive: true, force: true }); + }); + afterEach(() => { + if (fs.existsSync(TEST_DIR)) fs.rmSync(TEST_DIR, { recursive: true, force: true }); + }); + + test('should transform {{argname}} to $ARGNAME for user-invocable skills', () => { + const skills = [{ + name: 'test', + description: 'Test', + userInvocable: true, + argumentHint: '[target]', + body: 'Process {{target}} now.' + }]; + transform(skills, TEST_DIR); + const content = fs.readFileSync( + path.join(TEST_DIR, `codex/.codex/skills/test/SKILL.md`), 'utf-8' + ); + expect(content).toContain('Process $TARGET now.'); + }); + + test('should not transform {{argname}} for non-user-invocable skills', () => { + const skills = [{ + name: 'test', + description: 'Test', + userInvocable: false, + body: 'Process {{target}} now.' + }]; + transform(skills, TEST_DIR); + const content = fs.readFileSync( + path.join(TEST_DIR, `codex/.codex/skills/test/SKILL.md`), 'utf-8' + ); + // {{target}} should have been left as-is (not transformed by replacePlaceholders either, + // since 'target' is not a known placeholder) + expect(content).toContain('Process {{target}} now.'); + }); + + test('should replace system placeholders before body transform', () => { + const skills = [{ + name: 'test', + description: 'Test', + userInvocable: true, + body: 'Ask {{model}} about {{target}}.' + }]; + transform(skills, TEST_DIR); + const content = fs.readFileSync( + path.join(TEST_DIR, `codex/.codex/skills/test/SKILL.md`), 'utf-8' + ); + // {{model}} replaced first by replacePlaceholders, then {{target}} by bodyTransform + expect(content).toContain('Ask GPT about $TARGET.'); + }); +}); + +describe('Gemini body transform', () => { + const transform = createTransformer(PROVIDERS.gemini); + + beforeEach(() => { + if (fs.existsSync(TEST_DIR)) fs.rmSync(TEST_DIR, { recursive: true, force: true }); + }); + afterEach(() => { + if (fs.existsSync(TEST_DIR)) fs.rmSync(TEST_DIR, { recursive: true, force: true }); + }); + + test('should transform all remaining {{*}} to {{args}} for user-invocable skills', () => { + const skills = [{ + name: 'test', + description: 'Test', + userInvocable: true, + body: 'Process {{target}} and {{format}}.' + }]; + transform(skills, TEST_DIR); + const content = fs.readFileSync( + path.join(TEST_DIR, `gemini/.gemini/skills/test/SKILL.md`), 'utf-8' + ); + expect(content).toContain('Process {{args}} and {{args}}.'); + }); + + test('should not transform for non-user-invocable skills', () => { + const skills = [{ + name: 'test', + description: 'Test', + userInvocable: false, + body: 'Process {{target}}.' + }]; + transform(skills, TEST_DIR); + const content = fs.readFileSync( + path.join(TEST_DIR, `gemini/.gemini/skills/test/SKILL.md`), 'utf-8' + ); + expect(content).toContain('Process {{target}}.'); + }); + + test('should replace system placeholders before body transform', () => { + const skills = [{ + name: 'test', + description: 'Test', + userInvocable: true, + body: 'Ask {{model}} about {{target}}.' + }]; + transform(skills, TEST_DIR); + const content = fs.readFileSync( + path.join(TEST_DIR, `gemini/.gemini/skills/test/SKILL.md`), 'utf-8' + ); + // {{model}} replaced first, then remaining {{target}} becomes {{args}} + expect(content).toContain('Ask Gemini about {{args}}.'); + }); +}); diff --git a/tests/lib/utils.test.js b/tests/lib/utils.test.js index 81009e66a..898e87b28 100644 --- a/tests/lib/utils.test.js +++ b/tests/lib/utils.test.js @@ -32,29 +32,18 @@ This is the body content.`; expect(result.body).toBe('This is the body content.'); }); - test('should parse frontmatter with args array', () => { + test('should parse frontmatter with argument-hint', () => { const content = `--- name: test-skill description: A test skill -args: - - name: target - description: The target to normalize - required: false - - name: output - description: Output format - required: true +argument-hint: [TARGET=] --- Body here.`; const result = parseFrontmatter(content); expect(result.frontmatter.name).toBe('test-skill'); - expect(result.frontmatter.args).toBeArray(); - expect(result.frontmatter.args).toHaveLength(2); - expect(result.frontmatter.args[0].name).toBe('target'); - expect(result.frontmatter.args[0].description).toBe('The target to normalize'); - expect(result.frontmatter.args[0].required).toBe(false); - expect(result.frontmatter.args[1].required).toBe(true); + expect(result.frontmatter['argument-hint']).toBe(' [TARGET=]'); }); test('should return empty frontmatter when no frontmatter present', () => { @@ -110,8 +99,8 @@ user-invocable: 'true' Body.`; const result = parseFrontmatter(content); - // The parseFrontmatter function doesn't strip quotes from YAML string values - expect(result.frontmatter['user-invocable']).toBe("'true'"); + // parseFrontmatter strips YAML quotes, so 'true' becomes boolean true + expect(result.frontmatter['user-invocable']).toBe(true); }); test('should parse allowed-tools field', () => { @@ -140,22 +129,15 @@ describe('generateYamlFrontmatter', () => { expect(result).toContain('description: A test'); }); - test('should generate frontmatter with args array', () => { + test('should generate frontmatter with argument-hint', () => { const data = { name: 'test', description: 'Test skill', - args: [ - { name: 'target', description: 'The target', required: false }, - { name: 'output', description: 'Output format', required: true } - ] + 'argument-hint': ' [TARGET=]' }; const result = generateYamlFrontmatter(data); - expect(result).toContain('args:'); - expect(result).toContain('- name: target'); - expect(result).toContain('description: The target'); - expect(result).toContain('required: false'); - expect(result).toContain('required: true'); + expect(result).toContain('argument-hint: [TARGET=]'); }); test('should generate frontmatter with boolean', () => { @@ -173,9 +155,7 @@ describe('generateYamlFrontmatter', () => { const original = { name: 'roundtrip-test', description: 'Testing roundtrip', - args: [ - { name: 'arg1', description: 'First arg', required: true } - ] + 'argument-hint': '' }; const yaml = generateYamlFrontmatter(original); @@ -184,8 +164,7 @@ describe('generateYamlFrontmatter', () => { expect(parsed.frontmatter.name).toBe(original.name); expect(parsed.frontmatter.description).toBe(original.description); - expect(parsed.frontmatter.args).toBeArray(); - expect(parsed.frontmatter.args[0].name).toBe('arg1'); + expect(parsed.frontmatter['argument-hint']).toBe(''); }); }); From 2d4c7899ddf6741842543266be9f0949d1a1eab8 Mon Sep 17 00:00:00 2001 From: Paul Bakaus Date: Tue, 24 Mar 2026 10:46:59 -0700 Subject: [PATCH 2/3] Update provider configs from official docs, add HARNESSES.md reference Research each harness's official documentation to verify and correct provider frontmatter configs. Remove Codex/Gemini body transforms that targeted their commands systems, not skills. - Add compatibility + metadata to Cursor and Agents (Copilot) - Add allowed-tools to Pi - Remove Codex $ARGNAME and Gemini {{args}} body transforms - Add HARNESSES.md as source of truth for harness capabilities Co-Authored-By: Claude Opus 4.6 (1M context) --- .agents/skills/frontend-design/SKILL.md | 1 + HARNESSES.md | 81 ++++++++++++++++ scripts/lib/transformers/providers.js | 18 +--- tests/build.test.js | 4 +- tests/lib/transformers/providers.test.js | 112 ----------------------- 5 files changed, 87 insertions(+), 129 deletions(-) create mode 100644 HARNESSES.md diff --git a/.agents/skills/frontend-design/SKILL.md b/.agents/skills/frontend-design/SKILL.md index 87f4a1516..367b4d4d4 100644 --- a/.agents/skills/frontend-design/SKILL.md +++ b/.agents/skills/frontend-design/SKILL.md @@ -1,6 +1,7 @@ --- name: frontend-design description: Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications. Generates creative, polished code that avoids generic AI aesthetics. +license: Apache 2.0. Based on Anthropic's frontend-design skill. See NOTICE.md for attribution. --- This skill guides creation of distinctive, production-grade frontend interfaces that avoid generic "AI slop" aesthetics. Implement real working code with exceptional attention to aesthetic details and creative choices. diff --git a/HARNESSES.md b/HARNESSES.md new file mode 100644 index 000000000..f9b6dec69 --- /dev/null +++ b/HARNESSES.md @@ -0,0 +1,81 @@ +# Harness Skills Capabilities Reference + +Source of truth for what each AI coding harness supports in terms of agent skills. +Used to inform provider configs in `scripts/lib/transformers/providers.js`. + +Last verified: 2026-03-24 + +## Official Documentation + +| Harness | Docs URL | +|---------|----------| +| Claude Code | https://code.claude.com/docs/en/skills | +| Cursor | https://cursor.com/docs/context/skills | +| Gemini CLI | https://geminicli.com/docs/cli/skills/ | +| Codex CLI | https://developers.openai.com/codex/skills | +| GitHub Copilot (Agents) | https://code.visualstudio.com/docs/copilot/customization/agent-skills | +| Kiro | https://kiro.dev/docs/skills/ | +| OpenCode | https://opencode.ai/docs/skills/ | +| Pi | https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/docs/skills.md | + +## Spec Compliance + +All harnesses follow the [Agent Skills specification](https://agentskills.io/specification) to varying degrees. The spec defines these frontmatter fields: `name`, `description`, `license`, `compatibility`, `metadata`, `allowed-tools`. + +Provider-specific extensions beyond the spec: `user-invocable`, `argument-hint`, `disable-model-invocation`, `allowed-tools` (extended syntax), `model`, `effort`, `context`, `agent`, `hooks`, `subtask`, `mcp`. + +## Frontmatter Support + +Fields marked with * are spec-standard. Others are provider extensions. + +| Field | Claude Code | Cursor | Gemini | Codex | Copilot | Kiro | OpenCode | Pi | +|-------|:-----------:|:------:|:------:|:-----:|:-------:|:----:|:--------:|:--:| +| `name`* | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | +| `description`* | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | +| `license`* | Yes | Yes | Ignored | No | Yes | Yes | Yes | Yes | +| `compatibility`* | Yes | Yes | Ignored | No | Yes | Yes | Yes | Yes | +| `metadata`* | Yes | Yes | Ignored | No | Yes | Yes | Yes | Yes | +| `allowed-tools`* | Yes | No | Ignored | No | No | No | Yes | Yes | +| `user-invocable` | Yes | No | No | No | Yes | No | Yes | No | +| `argument-hint` | Yes | No | No | No | Yes | No | Yes | No | +| `disable-model-invocation` | Yes | Yes | No | No | Yes | No | Yes | Yes | +| `model` | Yes | No | No | No | No | No | Yes | No | +| `effort` | Yes | No | No | No | No | No | No | No | +| `context` | Yes | No | No | No | No | No | No | No | +| `agent` | Yes | No | No | No | No | No | Yes | No | +| `hooks` | Yes | No | No | No | No | No | No | No | + +Notes: +- Gemini CLI validates only `name` and `description`; other spec fields are parsed but ignored. +- Codex CLI uses a separate `agents/openai.yaml` sidecar for extended metadata (icons, branding, MCP tools, invocation control). +- Kiro recognizes `user-invocable` and `disable-model-invocation` per community reports but does not formally document them. +- Unknown fields are silently ignored by all harnesses. + +## Skill Directory Structure + +| Harness | Native directory | Also reads | +|---------|-----------------|------------| +| Claude Code | `.claude/skills/` | - | +| Cursor | `.cursor/skills/` | `.agents/skills/`, `.claude/skills/`, `.codex/skills/` | +| Gemini CLI | `.gemini/skills/` | `.agents/skills/` | +| Codex CLI | `.agents/skills/` (primary) | - | +| GitHub Copilot | `.github/skills/` | `.agents/skills/`, `.claude/skills/` | +| Kiro | `.kiro/skills/` | - | +| OpenCode | `.opencode/skills/` | `.agents/skills/`, `.claude/skills/` | +| Pi | `.pi/skills/` | `.agents/skills/` | + +All harnesses support the `{skill-name}/SKILL.md` directory structure with optional `reference/`, `scripts/`, and `assets/` subdirectories. + +## Placeholder / Variable Substitution + +Claude Code supports runtime variable substitution directly in SKILL.md bodies: `$ARGUMENTS`, `$0`-`$N`, `${CLAUDE_SKILL_DIR}`, `${CLAUDE_SESSION_ID}`. No other harness supports substitution in skills. + +Some harnesses have separate "custom commands" systems (distinct from skills) with their own substitution: + +| Harness | Command system | Substitution syntax | +|---------|---------------|-------------------| +| Gemini CLI | `.gemini/commands/` (TOML) | `{{args}}`, `!{shell}`, `@{file}` | +| Codex CLI | `.codex/prompts/` | `$ARGNAME` | +| OpenCode | `.opencode/commands/` | `$ARGUMENTS`, `$1`-`$N`, `` !`shell` `` | + +Our build system handles cross-provider placeholders at compile time via `replacePlaceholders()` for `{{model}}`, `{{config_file}}`, `{{ask_instruction}}`, and `{{available_commands}}`. diff --git a/scripts/lib/transformers/providers.js b/scripts/lib/transformers/providers.js index d8afc7c07..38595b5aa 100644 --- a/scripts/lib/transformers/providers.js +++ b/scripts/lib/transformers/providers.js @@ -13,7 +13,7 @@ export const PROVIDERS = { provider: 'cursor', configDir: '.cursor', displayName: 'Cursor', - frontmatterFields: ['license'], + frontmatterFields: ['license', 'compatibility', 'metadata'], }, 'claude-code': { provider: 'claude-code', @@ -26,30 +26,18 @@ export const PROVIDERS = { configDir: '.gemini', displayName: 'Gemini', frontmatterFields: [], - bodyTransform: (body, skill) => { - if (skill.userInvocable) { - return body.replace(/\{\{[^}]+\}\}/g, '{{args}}'); - } - return body; - }, }, codex: { provider: 'codex', configDir: '.codex', displayName: 'Codex', frontmatterFields: ['argument-hint', 'license'], - bodyTransform: (body, skill) => { - if (skill.userInvocable) { - return body.replace(/\{\{([^}]+)\}\}/g, (_, argName) => `$${argName.toUpperCase()}`); - } - return body; - }, }, agents: { provider: 'agents', configDir: '.agents', displayName: 'Agents', - frontmatterFields: ['user-invocable', 'argument-hint'], + frontmatterFields: ['user-invocable', 'argument-hint', 'license', 'compatibility', 'metadata'], }, kiro: { provider: 'kiro', @@ -67,6 +55,6 @@ export const PROVIDERS = { provider: 'pi', configDir: '.pi', displayName: 'Pi', - frontmatterFields: ['license', 'compatibility', 'metadata'], + frontmatterFields: ['license', 'compatibility', 'metadata', 'allowed-tools'], }, }; diff --git a/tests/build.test.js b/tests/build.test.js index 78a831f83..7a1872262 100644 --- a/tests/build.test.js +++ b/tests/build.test.js @@ -203,13 +203,13 @@ Please audit {{target}} for technical quality. Ask {{model}} for help.`; // Verify Gemini: skill in skills directory expect(fs.existsSync(path.join(DIST_DIR, 'gemini/.gemini/skills/audit/SKILL.md'))).toBe(true); const geminiContent = fs.readFileSync(path.join(DIST_DIR, 'gemini/.gemini/skills/audit/SKILL.md'), 'utf-8'); - expect(geminiContent).toContain('{{args}}'); // Replaced for user-invocable in Gemini + expect(geminiContent).toContain('{{target}}'); // No body transform, placeholder preserved expect(geminiContent).toContain('Gemini'); // Verify Codex: skill in skills directory expect(fs.existsSync(path.join(DIST_DIR, 'codex/.codex/skills/audit/SKILL.md'))).toBe(true); const codexContent = fs.readFileSync(path.join(DIST_DIR, 'codex/.codex/skills/audit/SKILL.md'), 'utf-8'); - expect(codexContent).toContain('$TARGET'); // Replaced for user-invocable in Codex + expect(codexContent).toContain('{{target}}'); // No body transform, placeholder preserved expect(codexContent).toContain('GPT'); }); diff --git a/tests/lib/transformers/providers.test.js b/tests/lib/transformers/providers.test.js index 67b278856..5d38b3810 100644 --- a/tests/lib/transformers/providers.test.js +++ b/tests/lib/transformers/providers.test.js @@ -190,115 +190,3 @@ for (const [key, config] of Object.entries(PROVIDERS)) { } }); } - -// Provider-specific body transform tests -describe('Codex body transform', () => { - const transform = createTransformer(PROVIDERS.codex); - - beforeEach(() => { - if (fs.existsSync(TEST_DIR)) fs.rmSync(TEST_DIR, { recursive: true, force: true }); - }); - afterEach(() => { - if (fs.existsSync(TEST_DIR)) fs.rmSync(TEST_DIR, { recursive: true, force: true }); - }); - - test('should transform {{argname}} to $ARGNAME for user-invocable skills', () => { - const skills = [{ - name: 'test', - description: 'Test', - userInvocable: true, - argumentHint: '[target]', - body: 'Process {{target}} now.' - }]; - transform(skills, TEST_DIR); - const content = fs.readFileSync( - path.join(TEST_DIR, `codex/.codex/skills/test/SKILL.md`), 'utf-8' - ); - expect(content).toContain('Process $TARGET now.'); - }); - - test('should not transform {{argname}} for non-user-invocable skills', () => { - const skills = [{ - name: 'test', - description: 'Test', - userInvocable: false, - body: 'Process {{target}} now.' - }]; - transform(skills, TEST_DIR); - const content = fs.readFileSync( - path.join(TEST_DIR, `codex/.codex/skills/test/SKILL.md`), 'utf-8' - ); - // {{target}} should have been left as-is (not transformed by replacePlaceholders either, - // since 'target' is not a known placeholder) - expect(content).toContain('Process {{target}} now.'); - }); - - test('should replace system placeholders before body transform', () => { - const skills = [{ - name: 'test', - description: 'Test', - userInvocable: true, - body: 'Ask {{model}} about {{target}}.' - }]; - transform(skills, TEST_DIR); - const content = fs.readFileSync( - path.join(TEST_DIR, `codex/.codex/skills/test/SKILL.md`), 'utf-8' - ); - // {{model}} replaced first by replacePlaceholders, then {{target}} by bodyTransform - expect(content).toContain('Ask GPT about $TARGET.'); - }); -}); - -describe('Gemini body transform', () => { - const transform = createTransformer(PROVIDERS.gemini); - - beforeEach(() => { - if (fs.existsSync(TEST_DIR)) fs.rmSync(TEST_DIR, { recursive: true, force: true }); - }); - afterEach(() => { - if (fs.existsSync(TEST_DIR)) fs.rmSync(TEST_DIR, { recursive: true, force: true }); - }); - - test('should transform all remaining {{*}} to {{args}} for user-invocable skills', () => { - const skills = [{ - name: 'test', - description: 'Test', - userInvocable: true, - body: 'Process {{target}} and {{format}}.' - }]; - transform(skills, TEST_DIR); - const content = fs.readFileSync( - path.join(TEST_DIR, `gemini/.gemini/skills/test/SKILL.md`), 'utf-8' - ); - expect(content).toContain('Process {{args}} and {{args}}.'); - }); - - test('should not transform for non-user-invocable skills', () => { - const skills = [{ - name: 'test', - description: 'Test', - userInvocable: false, - body: 'Process {{target}}.' - }]; - transform(skills, TEST_DIR); - const content = fs.readFileSync( - path.join(TEST_DIR, `gemini/.gemini/skills/test/SKILL.md`), 'utf-8' - ); - expect(content).toContain('Process {{target}}.'); - }); - - test('should replace system placeholders before body transform', () => { - const skills = [{ - name: 'test', - description: 'Test', - userInvocable: true, - body: 'Ask {{model}} about {{target}}.' - }]; - transform(skills, TEST_DIR); - const content = fs.readFileSync( - path.join(TEST_DIR, `gemini/.gemini/skills/test/SKILL.md`), 'utf-8' - ); - // {{model}} replaced first, then remaining {{target}} becomes {{args}} - expect(content).toContain('Ask Gemini about {{args}}.'); - }); -}); From 83b01ecfa29031e38230ffa03d345a36002cf118 Mon Sep 17 00:00:00 2001 From: Paul Bakaus Date: Tue, 24 Mar 2026 15:24:29 -0700 Subject: [PATCH 3/3] Use {{command_prefix}} for skill invocation references Skill cross-references like "Use the frontend-design skill" and "run teach-impeccable" now use {{command_prefix}} so each provider gets the correct invocation syntax (/ for most, $ for Codex). Prose references to skill content ("guidelines in the frontend-design skill") are left unchanged since they're not invocation instructions. Co-Authored-By: Claude Opus 4.6 (1M context) --- .agents/skills/adapt/SKILL.md | 2 +- .agents/skills/animate/SKILL.md | 2 +- .agents/skills/arrange/SKILL.md | 2 +- .agents/skills/audit/SKILL.md | 2 +- .agents/skills/bolder/SKILL.md | 2 +- .agents/skills/clarify/SKILL.md | 2 +- .agents/skills/colorize/SKILL.md | 2 +- .agents/skills/critique/SKILL.md | 2 +- .agents/skills/delight/SKILL.md | 2 +- .agents/skills/distill/SKILL.md | 2 +- .agents/skills/frontend-design/SKILL.md | 2 +- .agents/skills/normalize/SKILL.md | 2 +- .agents/skills/onboard/SKILL.md | 2 +- .agents/skills/overdrive/SKILL.md | 2 +- .agents/skills/polish/SKILL.md | 2 +- .agents/skills/quieter/SKILL.md | 2 +- .agents/skills/typeset/SKILL.md | 2 +- .claude/skills/adapt/SKILL.md | 2 +- .claude/skills/animate/SKILL.md | 2 +- .claude/skills/arrange/SKILL.md | 2 +- .claude/skills/audit/SKILL.md | 2 +- .claude/skills/bolder/SKILL.md | 2 +- .claude/skills/clarify/SKILL.md | 2 +- .claude/skills/colorize/SKILL.md | 2 +- .claude/skills/critique/SKILL.md | 2 +- .claude/skills/delight/SKILL.md | 2 +- .claude/skills/distill/SKILL.md | 2 +- .claude/skills/frontend-design/SKILL.md | 2 +- .claude/skills/normalize/SKILL.md | 2 +- .claude/skills/onboard/SKILL.md | 2 +- .claude/skills/overdrive/SKILL.md | 2 +- .claude/skills/polish/SKILL.md | 2 +- .claude/skills/quieter/SKILL.md | 2 +- .claude/skills/typeset/SKILL.md | 2 +- .codex/skills/adapt/SKILL.md | 2 +- .codex/skills/animate/SKILL.md | 2 +- .codex/skills/arrange/SKILL.md | 2 +- .codex/skills/audit/SKILL.md | 2 +- .codex/skills/bolder/SKILL.md | 2 +- .codex/skills/clarify/SKILL.md | 2 +- .codex/skills/colorize/SKILL.md | 2 +- .codex/skills/critique/SKILL.md | 2 +- .codex/skills/delight/SKILL.md | 2 +- .codex/skills/distill/SKILL.md | 2 +- .codex/skills/frontend-design/SKILL.md | 2 +- .codex/skills/normalize/SKILL.md | 2 +- .codex/skills/onboard/SKILL.md | 2 +- .codex/skills/overdrive/SKILL.md | 2 +- .codex/skills/polish/SKILL.md | 2 +- .codex/skills/quieter/SKILL.md | 2 +- .codex/skills/typeset/SKILL.md | 2 +- .cursor/skills/adapt/SKILL.md | 2 +- .cursor/skills/animate/SKILL.md | 2 +- .cursor/skills/arrange/SKILL.md | 2 +- .cursor/skills/audit/SKILL.md | 2 +- .cursor/skills/bolder/SKILL.md | 2 +- .cursor/skills/clarify/SKILL.md | 2 +- .cursor/skills/colorize/SKILL.md | 2 +- .cursor/skills/critique/SKILL.md | 2 +- .cursor/skills/delight/SKILL.md | 2 +- .cursor/skills/distill/SKILL.md | 2 +- .cursor/skills/frontend-design/SKILL.md | 2 +- .cursor/skills/normalize/SKILL.md | 2 +- .cursor/skills/onboard/SKILL.md | 2 +- .cursor/skills/overdrive/SKILL.md | 2 +- .cursor/skills/polish/SKILL.md | 2 +- .cursor/skills/quieter/SKILL.md | 2 +- .cursor/skills/typeset/SKILL.md | 2 +- .gemini/skills/adapt/SKILL.md | 2 +- .gemini/skills/animate/SKILL.md | 2 +- .gemini/skills/arrange/SKILL.md | 2 +- .gemini/skills/audit/SKILL.md | 2 +- .gemini/skills/bolder/SKILL.md | 2 +- .gemini/skills/clarify/SKILL.md | 2 +- .gemini/skills/colorize/SKILL.md | 2 +- .gemini/skills/critique/SKILL.md | 2 +- .gemini/skills/delight/SKILL.md | 2 +- .gemini/skills/distill/SKILL.md | 2 +- .gemini/skills/frontend-design/SKILL.md | 2 +- .gemini/skills/normalize/SKILL.md | 2 +- .gemini/skills/onboard/SKILL.md | 2 +- .gemini/skills/overdrive/SKILL.md | 2 +- .gemini/skills/polish/SKILL.md | 2 +- .gemini/skills/quieter/SKILL.md | 2 +- .gemini/skills/typeset/SKILL.md | 2 +- .kiro/skills/adapt/SKILL.md | 2 +- .kiro/skills/animate/SKILL.md | 2 +- .kiro/skills/arrange/SKILL.md | 2 +- .kiro/skills/audit/SKILL.md | 2 +- .kiro/skills/bolder/SKILL.md | 2 +- .kiro/skills/clarify/SKILL.md | 2 +- .kiro/skills/colorize/SKILL.md | 2 +- .kiro/skills/critique/SKILL.md | 2 +- .kiro/skills/delight/SKILL.md | 2 +- .kiro/skills/distill/SKILL.md | 2 +- .kiro/skills/frontend-design/SKILL.md | 2 +- .kiro/skills/normalize/SKILL.md | 2 +- .kiro/skills/onboard/SKILL.md | 2 +- .kiro/skills/overdrive/SKILL.md | 2 +- .kiro/skills/polish/SKILL.md | 2 +- .kiro/skills/quieter/SKILL.md | 2 +- .kiro/skills/typeset/SKILL.md | 2 +- .opencode/skills/adapt/SKILL.md | 2 +- .opencode/skills/animate/SKILL.md | 2 +- .opencode/skills/arrange/SKILL.md | 2 +- .opencode/skills/audit/SKILL.md | 2 +- .opencode/skills/bolder/SKILL.md | 2 +- .opencode/skills/clarify/SKILL.md | 2 +- .opencode/skills/colorize/SKILL.md | 2 +- .opencode/skills/critique/SKILL.md | 2 +- .opencode/skills/delight/SKILL.md | 2 +- .opencode/skills/distill/SKILL.md | 2 +- .opencode/skills/frontend-design/SKILL.md | 2 +- .opencode/skills/normalize/SKILL.md | 2 +- .opencode/skills/onboard/SKILL.md | 2 +- .opencode/skills/overdrive/SKILL.md | 2 +- .opencode/skills/polish/SKILL.md | 2 +- .opencode/skills/quieter/SKILL.md | 2 +- .opencode/skills/typeset/SKILL.md | 2 +- .pi/skills/adapt/SKILL.md | 2 +- .pi/skills/animate/SKILL.md | 2 +- .pi/skills/arrange/SKILL.md | 2 +- .pi/skills/audit/SKILL.md | 2 +- .pi/skills/bolder/SKILL.md | 2 +- .pi/skills/clarify/SKILL.md | 2 +- .pi/skills/colorize/SKILL.md | 2 +- .pi/skills/critique/SKILL.md | 2 +- .pi/skills/delight/SKILL.md | 2 +- .pi/skills/distill/SKILL.md | 2 +- .pi/skills/frontend-design/SKILL.md | 2 +- .pi/skills/normalize/SKILL.md | 2 +- .pi/skills/onboard/SKILL.md | 2 +- .pi/skills/overdrive/SKILL.md | 2 +- .pi/skills/polish/SKILL.md | 2 +- .pi/skills/quieter/SKILL.md | 2 +- .pi/skills/typeset/SKILL.md | 2 +- source/skills/adapt/SKILL.md | 2 +- source/skills/animate/SKILL.md | 2 +- source/skills/arrange/SKILL.md | 2 +- source/skills/audit/SKILL.md | 2 +- source/skills/bolder/SKILL.md | 2 +- source/skills/clarify/SKILL.md | 2 +- source/skills/colorize/SKILL.md | 2 +- source/skills/critique/SKILL.md | 2 +- source/skills/delight/SKILL.md | 2 +- source/skills/distill/SKILL.md | 2 +- source/skills/frontend-design/SKILL.md | 2 +- source/skills/normalize/SKILL.md | 2 +- source/skills/onboard/SKILL.md | 2 +- source/skills/overdrive/SKILL.md | 2 +- source/skills/polish/SKILL.md | 2 +- source/skills/quieter/SKILL.md | 2 +- source/skills/typeset/SKILL.md | 2 +- 153 files changed, 153 insertions(+), 153 deletions(-) diff --git a/.agents/skills/adapt/SKILL.md b/.agents/skills/adapt/SKILL.md index 9a0703af0..84f24d847 100644 --- a/.agents/skills/adapt/SKILL.md +++ b/.agents/skills/adapt/SKILL.md @@ -9,7 +9,7 @@ Adapt existing designs to work effectively across different contexts - different ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: target platforms/devices and usage contexts. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: target platforms/devices and usage contexts. --- diff --git a/.agents/skills/animate/SKILL.md b/.agents/skills/animate/SKILL.md index 420f9863e..7a00cf6b8 100644 --- a/.agents/skills/animate/SKILL.md +++ b/.agents/skills/animate/SKILL.md @@ -9,7 +9,7 @@ Analyze a feature and strategically add animations and micro-interactions that e ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: performance constraints. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: performance constraints. --- diff --git a/.agents/skills/arrange/SKILL.md b/.agents/skills/arrange/SKILL.md index 2f22f3faa..3b29230f0 100644 --- a/.agents/skills/arrange/SKILL.md +++ b/.agents/skills/arrange/SKILL.md @@ -9,7 +9,7 @@ Assess and improve layout and spacing that feels monotonous, crowded, or structu ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. --- diff --git a/.agents/skills/audit/SKILL.md b/.agents/skills/audit/SKILL.md index f11131ae0..185f87cfc 100644 --- a/.agents/skills/audit/SKILL.md +++ b/.agents/skills/audit/SKILL.md @@ -7,7 +7,7 @@ argument-hint: "[area (feature, page, component...)]" Run systematic quality checks and generate a comprehensive audit report with prioritized issues and actionable recommendations. Don't fix issues - document them for other commands to address. -**First**: Use the frontend-design skill for design principles and anti-patterns. +**First**: Invoke /frontend-design for design principles and anti-patterns. ## Diagnostic Scan diff --git a/.agents/skills/bolder/SKILL.md b/.agents/skills/bolder/SKILL.md index d375ad1f6..cf2babce4 100644 --- a/.agents/skills/bolder/SKILL.md +++ b/.agents/skills/bolder/SKILL.md @@ -9,7 +9,7 @@ Increase visual impact and personality in designs that are too safe, generic, or ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. --- diff --git a/.agents/skills/clarify/SKILL.md b/.agents/skills/clarify/SKILL.md index f0f602fa4..940cc1450 100644 --- a/.agents/skills/clarify/SKILL.md +++ b/.agents/skills/clarify/SKILL.md @@ -9,7 +9,7 @@ Identify and improve unclear, confusing, or poorly written interface text to mak ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: audience technical level and users' mental state in context. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: audience technical level and users' mental state in context. --- diff --git a/.agents/skills/colorize/SKILL.md b/.agents/skills/colorize/SKILL.md index 7a72c3e57..bd66f584f 100644 --- a/.agents/skills/colorize/SKILL.md +++ b/.agents/skills/colorize/SKILL.md @@ -9,7 +9,7 @@ Strategically introduce color to designs that are too monochromatic, gray, or la ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: existing brand colors. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: existing brand colors. --- diff --git a/.agents/skills/critique/SKILL.md b/.agents/skills/critique/SKILL.md index 62e2552fd..e3d93c3e8 100644 --- a/.agents/skills/critique/SKILL.md +++ b/.agents/skills/critique/SKILL.md @@ -7,7 +7,7 @@ argument-hint: "[area (feature, page, component...)]" ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: what the interface is trying to accomplish. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: what the interface is trying to accomplish. --- diff --git a/.agents/skills/delight/SKILL.md b/.agents/skills/delight/SKILL.md index 9eb1edaf1..4fd29863c 100644 --- a/.agents/skills/delight/SKILL.md +++ b/.agents/skills/delight/SKILL.md @@ -9,7 +9,7 @@ Identify opportunities to add moments of joy, personality, and unexpected polish ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: what's appropriate for the domain (playful vs professional vs quirky vs elegant). +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: what's appropriate for the domain (playful vs professional vs quirky vs elegant). --- diff --git a/.agents/skills/distill/SKILL.md b/.agents/skills/distill/SKILL.md index 8d7a50faf..c17549f4d 100644 --- a/.agents/skills/distill/SKILL.md +++ b/.agents/skills/distill/SKILL.md @@ -9,7 +9,7 @@ Remove unnecessary complexity from designs, revealing the essential elements and ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. --- diff --git a/.agents/skills/frontend-design/SKILL.md b/.agents/skills/frontend-design/SKILL.md index 367b4d4d4..8a85856a8 100644 --- a/.agents/skills/frontend-design/SKILL.md +++ b/.agents/skills/frontend-design/SKILL.md @@ -22,7 +22,7 @@ Individual skills may require additional context — check the skill's preparati **Gathering order:** 1. **Check current instructions (instant)**: If your loaded instructions already contain a **Design Context** section, proceed immediately. 2. **Check .impeccable.md (fast)**: If not in instructions, read `.impeccable.md` from the project root. If it exists and contains the required context, proceed. -3. **Run teach-impeccable (REQUIRED)**: If neither source has context, you MUST run the teach-impeccable skill NOW before doing anything else. Do NOT skip this step. Do NOT attempt to infer context from the codebase instead. +3. **Run teach-impeccable (REQUIRED)**: If neither source has context, you MUST run /teach-impeccable NOW before doing anything else. Do NOT skip this step. Do NOT attempt to infer context from the codebase instead. --- diff --git a/.agents/skills/normalize/SKILL.md b/.agents/skills/normalize/SKILL.md index f8af99752..a5396755f 100644 --- a/.agents/skills/normalize/SKILL.md +++ b/.agents/skills/normalize/SKILL.md @@ -9,7 +9,7 @@ Analyze and redesign the feature to perfectly match our design system standards, ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. --- diff --git a/.agents/skills/onboard/SKILL.md b/.agents/skills/onboard/SKILL.md index 10f3618b1..b11fbbfe1 100644 --- a/.agents/skills/onboard/SKILL.md +++ b/.agents/skills/onboard/SKILL.md @@ -7,7 +7,7 @@ argument-hint: "[target]" ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: the "aha moment" you want users to reach, and users' experience level. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: the "aha moment" you want users to reach, and users' experience level. --- diff --git a/.agents/skills/overdrive/SKILL.md b/.agents/skills/overdrive/SKILL.md index 36dfa8b87..95c6819f4 100644 --- a/.agents/skills/overdrive/SKILL.md +++ b/.agents/skills/overdrive/SKILL.md @@ -16,7 +16,7 @@ Push an interface past conventional limits. This isn't just about visual effects ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. **EXTRA IMPORTANT FOR THIS SKILL**: Context determines what "extraordinary" means. A particle system on a creative portfolio is impressive. The same particle system on a settings page is embarrassing. But a settings page with instant optimistic saves and animated state transitions? That's extraordinary too. Understand the project's personality and goals before deciding what's appropriate. diff --git a/.agents/skills/polish/SKILL.md b/.agents/skills/polish/SKILL.md index fe9e27687..efa8004ed 100644 --- a/.agents/skills/polish/SKILL.md +++ b/.agents/skills/polish/SKILL.md @@ -7,7 +7,7 @@ argument-hint: "[target]" ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: quality bar (MVP vs flagship). +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: quality bar (MVP vs flagship). --- diff --git a/.agents/skills/quieter/SKILL.md b/.agents/skills/quieter/SKILL.md index a542c3ee9..4779dd977 100644 --- a/.agents/skills/quieter/SKILL.md +++ b/.agents/skills/quieter/SKILL.md @@ -9,7 +9,7 @@ Reduce visual intensity in designs that are too bold, aggressive, or overstimula ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. --- diff --git a/.agents/skills/typeset/SKILL.md b/.agents/skills/typeset/SKILL.md index d331e2745..8957bba80 100644 --- a/.agents/skills/typeset/SKILL.md +++ b/.agents/skills/typeset/SKILL.md @@ -9,7 +9,7 @@ Assess and improve typography that feels generic, inconsistent, or poorly struct ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. --- diff --git a/.claude/skills/adapt/SKILL.md b/.claude/skills/adapt/SKILL.md index 9a0703af0..84f24d847 100644 --- a/.claude/skills/adapt/SKILL.md +++ b/.claude/skills/adapt/SKILL.md @@ -9,7 +9,7 @@ Adapt existing designs to work effectively across different contexts - different ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: target platforms/devices and usage contexts. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: target platforms/devices and usage contexts. --- diff --git a/.claude/skills/animate/SKILL.md b/.claude/skills/animate/SKILL.md index f1d74000d..68f113741 100644 --- a/.claude/skills/animate/SKILL.md +++ b/.claude/skills/animate/SKILL.md @@ -9,7 +9,7 @@ Analyze a feature and strategically add animations and micro-interactions that e ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: performance constraints. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: performance constraints. --- diff --git a/.claude/skills/arrange/SKILL.md b/.claude/skills/arrange/SKILL.md index 2f22f3faa..3b29230f0 100644 --- a/.claude/skills/arrange/SKILL.md +++ b/.claude/skills/arrange/SKILL.md @@ -9,7 +9,7 @@ Assess and improve layout and spacing that feels monotonous, crowded, or structu ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. --- diff --git a/.claude/skills/audit/SKILL.md b/.claude/skills/audit/SKILL.md index f11131ae0..185f87cfc 100644 --- a/.claude/skills/audit/SKILL.md +++ b/.claude/skills/audit/SKILL.md @@ -7,7 +7,7 @@ argument-hint: "[area (feature, page, component...)]" Run systematic quality checks and generate a comprehensive audit report with prioritized issues and actionable recommendations. Don't fix issues - document them for other commands to address. -**First**: Use the frontend-design skill for design principles and anti-patterns. +**First**: Invoke /frontend-design for design principles and anti-patterns. ## Diagnostic Scan diff --git a/.claude/skills/bolder/SKILL.md b/.claude/skills/bolder/SKILL.md index 19c37abc1..e228ecd1c 100644 --- a/.claude/skills/bolder/SKILL.md +++ b/.claude/skills/bolder/SKILL.md @@ -9,7 +9,7 @@ Increase visual impact and personality in designs that are too safe, generic, or ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. --- diff --git a/.claude/skills/clarify/SKILL.md b/.claude/skills/clarify/SKILL.md index f0f602fa4..940cc1450 100644 --- a/.claude/skills/clarify/SKILL.md +++ b/.claude/skills/clarify/SKILL.md @@ -9,7 +9,7 @@ Identify and improve unclear, confusing, or poorly written interface text to mak ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: audience technical level and users' mental state in context. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: audience technical level and users' mental state in context. --- diff --git a/.claude/skills/colorize/SKILL.md b/.claude/skills/colorize/SKILL.md index e7424d47a..e0c85ae77 100644 --- a/.claude/skills/colorize/SKILL.md +++ b/.claude/skills/colorize/SKILL.md @@ -9,7 +9,7 @@ Strategically introduce color to designs that are too monochromatic, gray, or la ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: existing brand colors. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: existing brand colors. --- diff --git a/.claude/skills/critique/SKILL.md b/.claude/skills/critique/SKILL.md index 62e2552fd..e3d93c3e8 100644 --- a/.claude/skills/critique/SKILL.md +++ b/.claude/skills/critique/SKILL.md @@ -7,7 +7,7 @@ argument-hint: "[area (feature, page, component...)]" ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: what the interface is trying to accomplish. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: what the interface is trying to accomplish. --- diff --git a/.claude/skills/delight/SKILL.md b/.claude/skills/delight/SKILL.md index 0b10c822d..6119339db 100644 --- a/.claude/skills/delight/SKILL.md +++ b/.claude/skills/delight/SKILL.md @@ -9,7 +9,7 @@ Identify opportunities to add moments of joy, personality, and unexpected polish ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: what's appropriate for the domain (playful vs professional vs quirky vs elegant). +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: what's appropriate for the domain (playful vs professional vs quirky vs elegant). --- diff --git a/.claude/skills/distill/SKILL.md b/.claude/skills/distill/SKILL.md index b8d2b86f6..3d6269a81 100644 --- a/.claude/skills/distill/SKILL.md +++ b/.claude/skills/distill/SKILL.md @@ -9,7 +9,7 @@ Remove unnecessary complexity from designs, revealing the essential elements and ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. --- diff --git a/.claude/skills/frontend-design/SKILL.md b/.claude/skills/frontend-design/SKILL.md index df336117b..33db0ba7e 100644 --- a/.claude/skills/frontend-design/SKILL.md +++ b/.claude/skills/frontend-design/SKILL.md @@ -22,7 +22,7 @@ Individual skills may require additional context — check the skill's preparati **Gathering order:** 1. **Check current instructions (instant)**: If your loaded instructions already contain a **Design Context** section, proceed immediately. 2. **Check .impeccable.md (fast)**: If not in instructions, read `.impeccable.md` from the project root. If it exists and contains the required context, proceed. -3. **Run teach-impeccable (REQUIRED)**: If neither source has context, you MUST run the teach-impeccable skill NOW before doing anything else. Do NOT skip this step. Do NOT attempt to infer context from the codebase instead. +3. **Run teach-impeccable (REQUIRED)**: If neither source has context, you MUST run /teach-impeccable NOW before doing anything else. Do NOT skip this step. Do NOT attempt to infer context from the codebase instead. --- diff --git a/.claude/skills/normalize/SKILL.md b/.claude/skills/normalize/SKILL.md index f8af99752..a5396755f 100644 --- a/.claude/skills/normalize/SKILL.md +++ b/.claude/skills/normalize/SKILL.md @@ -9,7 +9,7 @@ Analyze and redesign the feature to perfectly match our design system standards, ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. --- diff --git a/.claude/skills/onboard/SKILL.md b/.claude/skills/onboard/SKILL.md index 10f3618b1..b11fbbfe1 100644 --- a/.claude/skills/onboard/SKILL.md +++ b/.claude/skills/onboard/SKILL.md @@ -7,7 +7,7 @@ argument-hint: "[target]" ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: the "aha moment" you want users to reach, and users' experience level. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: the "aha moment" you want users to reach, and users' experience level. --- diff --git a/.claude/skills/overdrive/SKILL.md b/.claude/skills/overdrive/SKILL.md index 3011b3c21..8c07fa8e0 100644 --- a/.claude/skills/overdrive/SKILL.md +++ b/.claude/skills/overdrive/SKILL.md @@ -16,7 +16,7 @@ Push an interface past conventional limits. This isn't just about visual effects ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. **EXTRA IMPORTANT FOR THIS SKILL**: Context determines what "extraordinary" means. A particle system on a creative portfolio is impressive. The same particle system on a settings page is embarrassing. But a settings page with instant optimistic saves and animated state transitions? That's extraordinary too. Understand the project's personality and goals before deciding what's appropriate. diff --git a/.claude/skills/polish/SKILL.md b/.claude/skills/polish/SKILL.md index fe9e27687..efa8004ed 100644 --- a/.claude/skills/polish/SKILL.md +++ b/.claude/skills/polish/SKILL.md @@ -7,7 +7,7 @@ argument-hint: "[target]" ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: quality bar (MVP vs flagship). +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: quality bar (MVP vs flagship). --- diff --git a/.claude/skills/quieter/SKILL.md b/.claude/skills/quieter/SKILL.md index 8cc092848..c97bb20f6 100644 --- a/.claude/skills/quieter/SKILL.md +++ b/.claude/skills/quieter/SKILL.md @@ -9,7 +9,7 @@ Reduce visual intensity in designs that are too bold, aggressive, or overstimula ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. --- diff --git a/.claude/skills/typeset/SKILL.md b/.claude/skills/typeset/SKILL.md index d331e2745..8957bba80 100644 --- a/.claude/skills/typeset/SKILL.md +++ b/.claude/skills/typeset/SKILL.md @@ -9,7 +9,7 @@ Assess and improve typography that feels generic, inconsistent, or poorly struct ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. --- diff --git a/.codex/skills/adapt/SKILL.md b/.codex/skills/adapt/SKILL.md index 0a5403f8b..739c3b6e3 100644 --- a/.codex/skills/adapt/SKILL.md +++ b/.codex/skills/adapt/SKILL.md @@ -8,7 +8,7 @@ Adapt existing designs to work effectively across different contexts - different ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: target platforms/devices and usage contexts. +Invoke $frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run $teach-impeccable first. Additionally gather: target platforms/devices and usage contexts. --- diff --git a/.codex/skills/animate/SKILL.md b/.codex/skills/animate/SKILL.md index 52eb0bf5d..750f6d65e 100644 --- a/.codex/skills/animate/SKILL.md +++ b/.codex/skills/animate/SKILL.md @@ -8,7 +8,7 @@ Analyze a feature and strategically add animations and micro-interactions that e ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: performance constraints. +Invoke $frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run $teach-impeccable first. Additionally gather: performance constraints. --- diff --git a/.codex/skills/arrange/SKILL.md b/.codex/skills/arrange/SKILL.md index 27cad8e59..c5822381f 100644 --- a/.codex/skills/arrange/SKILL.md +++ b/.codex/skills/arrange/SKILL.md @@ -8,7 +8,7 @@ Assess and improve layout and spacing that feels monotonous, crowded, or structu ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke $frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run $teach-impeccable first. --- diff --git a/.codex/skills/audit/SKILL.md b/.codex/skills/audit/SKILL.md index 1fdb07106..926225461 100644 --- a/.codex/skills/audit/SKILL.md +++ b/.codex/skills/audit/SKILL.md @@ -6,7 +6,7 @@ argument-hint: "[area (feature, page, component...)]" Run systematic quality checks and generate a comprehensive audit report with prioritized issues and actionable recommendations. Don't fix issues - document them for other commands to address. -**First**: Use the frontend-design skill for design principles and anti-patterns. +**First**: Invoke $frontend-design for design principles and anti-patterns. ## Diagnostic Scan diff --git a/.codex/skills/bolder/SKILL.md b/.codex/skills/bolder/SKILL.md index 2680f00cb..5e2fe889d 100644 --- a/.codex/skills/bolder/SKILL.md +++ b/.codex/skills/bolder/SKILL.md @@ -8,7 +8,7 @@ Increase visual impact and personality in designs that are too safe, generic, or ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke $frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run $teach-impeccable first. --- diff --git a/.codex/skills/clarify/SKILL.md b/.codex/skills/clarify/SKILL.md index 88941b26e..33ff829ea 100644 --- a/.codex/skills/clarify/SKILL.md +++ b/.codex/skills/clarify/SKILL.md @@ -8,7 +8,7 @@ Identify and improve unclear, confusing, or poorly written interface text to mak ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: audience technical level and users' mental state in context. +Invoke $frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run $teach-impeccable first. Additionally gather: audience technical level and users' mental state in context. --- diff --git a/.codex/skills/colorize/SKILL.md b/.codex/skills/colorize/SKILL.md index 0e511001d..7ec6fe8a8 100644 --- a/.codex/skills/colorize/SKILL.md +++ b/.codex/skills/colorize/SKILL.md @@ -8,7 +8,7 @@ Strategically introduce color to designs that are too monochromatic, gray, or la ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: existing brand colors. +Invoke $frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run $teach-impeccable first. Additionally gather: existing brand colors. --- diff --git a/.codex/skills/critique/SKILL.md b/.codex/skills/critique/SKILL.md index 1cf158408..b60342887 100644 --- a/.codex/skills/critique/SKILL.md +++ b/.codex/skills/critique/SKILL.md @@ -6,7 +6,7 @@ argument-hint: "[area (feature, page, component...)]" ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: what the interface is trying to accomplish. +Invoke $frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run $teach-impeccable first. Additionally gather: what the interface is trying to accomplish. --- diff --git a/.codex/skills/delight/SKILL.md b/.codex/skills/delight/SKILL.md index 42ea11367..856096ae4 100644 --- a/.codex/skills/delight/SKILL.md +++ b/.codex/skills/delight/SKILL.md @@ -8,7 +8,7 @@ Identify opportunities to add moments of joy, personality, and unexpected polish ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: what's appropriate for the domain (playful vs professional vs quirky vs elegant). +Invoke $frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run $teach-impeccable first. Additionally gather: what's appropriate for the domain (playful vs professional vs quirky vs elegant). --- diff --git a/.codex/skills/distill/SKILL.md b/.codex/skills/distill/SKILL.md index 7f3d9ff49..f7d1d7ba1 100644 --- a/.codex/skills/distill/SKILL.md +++ b/.codex/skills/distill/SKILL.md @@ -8,7 +8,7 @@ Remove unnecessary complexity from designs, revealing the essential elements and ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke $frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run $teach-impeccable first. --- diff --git a/.codex/skills/frontend-design/SKILL.md b/.codex/skills/frontend-design/SKILL.md index 7e8c8aefd..d7c23aa78 100644 --- a/.codex/skills/frontend-design/SKILL.md +++ b/.codex/skills/frontend-design/SKILL.md @@ -22,7 +22,7 @@ Individual skills may require additional context — check the skill's preparati **Gathering order:** 1. **Check current instructions (instant)**: If your loaded instructions already contain a **Design Context** section, proceed immediately. 2. **Check .impeccable.md (fast)**: If not in instructions, read `.impeccable.md` from the project root. If it exists and contains the required context, proceed. -3. **Run teach-impeccable (REQUIRED)**: If neither source has context, you MUST run the teach-impeccable skill NOW before doing anything else. Do NOT skip this step. Do NOT attempt to infer context from the codebase instead. +3. **Run teach-impeccable (REQUIRED)**: If neither source has context, you MUST run $teach-impeccable NOW before doing anything else. Do NOT skip this step. Do NOT attempt to infer context from the codebase instead. --- diff --git a/.codex/skills/normalize/SKILL.md b/.codex/skills/normalize/SKILL.md index 08ff9286a..2683b48dc 100644 --- a/.codex/skills/normalize/SKILL.md +++ b/.codex/skills/normalize/SKILL.md @@ -8,7 +8,7 @@ Analyze and redesign the feature to perfectly match our design system standards, ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke $frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run $teach-impeccable first. --- diff --git a/.codex/skills/onboard/SKILL.md b/.codex/skills/onboard/SKILL.md index 82ddb5874..56d109cb8 100644 --- a/.codex/skills/onboard/SKILL.md +++ b/.codex/skills/onboard/SKILL.md @@ -6,7 +6,7 @@ argument-hint: "[target]" ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: the "aha moment" you want users to reach, and users' experience level. +Invoke $frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run $teach-impeccable first. Additionally gather: the "aha moment" you want users to reach, and users' experience level. --- diff --git a/.codex/skills/overdrive/SKILL.md b/.codex/skills/overdrive/SKILL.md index 0b990230f..c9d160b04 100644 --- a/.codex/skills/overdrive/SKILL.md +++ b/.codex/skills/overdrive/SKILL.md @@ -15,7 +15,7 @@ Push an interface past conventional limits. This isn't just about visual effects ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke $frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run $teach-impeccable first. **EXTRA IMPORTANT FOR THIS SKILL**: Context determines what "extraordinary" means. A particle system on a creative portfolio is impressive. The same particle system on a settings page is embarrassing. But a settings page with instant optimistic saves and animated state transitions? That's extraordinary too. Understand the project's personality and goals before deciding what's appropriate. diff --git a/.codex/skills/polish/SKILL.md b/.codex/skills/polish/SKILL.md index 4f8dea20e..bcd14a9c3 100644 --- a/.codex/skills/polish/SKILL.md +++ b/.codex/skills/polish/SKILL.md @@ -6,7 +6,7 @@ argument-hint: "[target]" ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: quality bar (MVP vs flagship). +Invoke $frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run $teach-impeccable first. Additionally gather: quality bar (MVP vs flagship). --- diff --git a/.codex/skills/quieter/SKILL.md b/.codex/skills/quieter/SKILL.md index e4ba957f8..fb7635fb5 100644 --- a/.codex/skills/quieter/SKILL.md +++ b/.codex/skills/quieter/SKILL.md @@ -8,7 +8,7 @@ Reduce visual intensity in designs that are too bold, aggressive, or overstimula ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke $frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run $teach-impeccable first. --- diff --git a/.codex/skills/typeset/SKILL.md b/.codex/skills/typeset/SKILL.md index 57a1d9dfc..3e3aea3b5 100644 --- a/.codex/skills/typeset/SKILL.md +++ b/.codex/skills/typeset/SKILL.md @@ -8,7 +8,7 @@ Assess and improve typography that feels generic, inconsistent, or poorly struct ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke $frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run $teach-impeccable first. --- diff --git a/.cursor/skills/adapt/SKILL.md b/.cursor/skills/adapt/SKILL.md index 9c6f504ec..e344cc695 100644 --- a/.cursor/skills/adapt/SKILL.md +++ b/.cursor/skills/adapt/SKILL.md @@ -7,7 +7,7 @@ Adapt existing designs to work effectively across different contexts - different ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: target platforms/devices and usage contexts. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: target platforms/devices and usage contexts. --- diff --git a/.cursor/skills/animate/SKILL.md b/.cursor/skills/animate/SKILL.md index 036861c5c..629b842c7 100644 --- a/.cursor/skills/animate/SKILL.md +++ b/.cursor/skills/animate/SKILL.md @@ -7,7 +7,7 @@ Analyze a feature and strategically add animations and micro-interactions that e ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: performance constraints. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: performance constraints. --- diff --git a/.cursor/skills/arrange/SKILL.md b/.cursor/skills/arrange/SKILL.md index 9876fc984..94bfd35f7 100644 --- a/.cursor/skills/arrange/SKILL.md +++ b/.cursor/skills/arrange/SKILL.md @@ -7,7 +7,7 @@ Assess and improve layout and spacing that feels monotonous, crowded, or structu ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. --- diff --git a/.cursor/skills/audit/SKILL.md b/.cursor/skills/audit/SKILL.md index 7e5205e20..86349386e 100644 --- a/.cursor/skills/audit/SKILL.md +++ b/.cursor/skills/audit/SKILL.md @@ -5,7 +5,7 @@ description: Perform comprehensive audit of interface quality across accessibili Run systematic quality checks and generate a comprehensive audit report with prioritized issues and actionable recommendations. Don't fix issues - document them for other commands to address. -**First**: Use the frontend-design skill for design principles and anti-patterns. +**First**: Invoke /frontend-design for design principles and anti-patterns. ## Diagnostic Scan diff --git a/.cursor/skills/bolder/SKILL.md b/.cursor/skills/bolder/SKILL.md index fc84f2921..7690716af 100644 --- a/.cursor/skills/bolder/SKILL.md +++ b/.cursor/skills/bolder/SKILL.md @@ -7,7 +7,7 @@ Increase visual impact and personality in designs that are too safe, generic, or ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. --- diff --git a/.cursor/skills/clarify/SKILL.md b/.cursor/skills/clarify/SKILL.md index 2e32e01da..c11b240af 100644 --- a/.cursor/skills/clarify/SKILL.md +++ b/.cursor/skills/clarify/SKILL.md @@ -7,7 +7,7 @@ Identify and improve unclear, confusing, or poorly written interface text to mak ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: audience technical level and users' mental state in context. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: audience technical level and users' mental state in context. --- diff --git a/.cursor/skills/colorize/SKILL.md b/.cursor/skills/colorize/SKILL.md index 0c8bec581..93aa6bd28 100644 --- a/.cursor/skills/colorize/SKILL.md +++ b/.cursor/skills/colorize/SKILL.md @@ -7,7 +7,7 @@ Strategically introduce color to designs that are too monochromatic, gray, or la ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: existing brand colors. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: existing brand colors. --- diff --git a/.cursor/skills/critique/SKILL.md b/.cursor/skills/critique/SKILL.md index 9e63a7468..3479c10d3 100644 --- a/.cursor/skills/critique/SKILL.md +++ b/.cursor/skills/critique/SKILL.md @@ -5,7 +5,7 @@ description: Evaluate design effectiveness from a UX perspective. Assesses visua ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: what the interface is trying to accomplish. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: what the interface is trying to accomplish. --- diff --git a/.cursor/skills/delight/SKILL.md b/.cursor/skills/delight/SKILL.md index c20e33090..e544934f6 100644 --- a/.cursor/skills/delight/SKILL.md +++ b/.cursor/skills/delight/SKILL.md @@ -7,7 +7,7 @@ Identify opportunities to add moments of joy, personality, and unexpected polish ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: what's appropriate for the domain (playful vs professional vs quirky vs elegant). +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: what's appropriate for the domain (playful vs professional vs quirky vs elegant). --- diff --git a/.cursor/skills/distill/SKILL.md b/.cursor/skills/distill/SKILL.md index 345779318..e37121d7b 100644 --- a/.cursor/skills/distill/SKILL.md +++ b/.cursor/skills/distill/SKILL.md @@ -7,7 +7,7 @@ Remove unnecessary complexity from designs, revealing the essential elements and ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. --- diff --git a/.cursor/skills/frontend-design/SKILL.md b/.cursor/skills/frontend-design/SKILL.md index 367b4d4d4..8a85856a8 100644 --- a/.cursor/skills/frontend-design/SKILL.md +++ b/.cursor/skills/frontend-design/SKILL.md @@ -22,7 +22,7 @@ Individual skills may require additional context — check the skill's preparati **Gathering order:** 1. **Check current instructions (instant)**: If your loaded instructions already contain a **Design Context** section, proceed immediately. 2. **Check .impeccable.md (fast)**: If not in instructions, read `.impeccable.md` from the project root. If it exists and contains the required context, proceed. -3. **Run teach-impeccable (REQUIRED)**: If neither source has context, you MUST run the teach-impeccable skill NOW before doing anything else. Do NOT skip this step. Do NOT attempt to infer context from the codebase instead. +3. **Run teach-impeccable (REQUIRED)**: If neither source has context, you MUST run /teach-impeccable NOW before doing anything else. Do NOT skip this step. Do NOT attempt to infer context from the codebase instead. --- diff --git a/.cursor/skills/normalize/SKILL.md b/.cursor/skills/normalize/SKILL.md index 91b2949a5..42df54fa8 100644 --- a/.cursor/skills/normalize/SKILL.md +++ b/.cursor/skills/normalize/SKILL.md @@ -7,7 +7,7 @@ Analyze and redesign the feature to perfectly match our design system standards, ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. --- diff --git a/.cursor/skills/onboard/SKILL.md b/.cursor/skills/onboard/SKILL.md index 317ce93bd..6541d7838 100644 --- a/.cursor/skills/onboard/SKILL.md +++ b/.cursor/skills/onboard/SKILL.md @@ -5,7 +5,7 @@ description: Design or improve onboarding flows, empty states, and first-time us ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: the "aha moment" you want users to reach, and users' experience level. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: the "aha moment" you want users to reach, and users' experience level. --- diff --git a/.cursor/skills/overdrive/SKILL.md b/.cursor/skills/overdrive/SKILL.md index 5677d8799..bcd7c8c49 100644 --- a/.cursor/skills/overdrive/SKILL.md +++ b/.cursor/skills/overdrive/SKILL.md @@ -14,7 +14,7 @@ Push an interface past conventional limits. This isn't just about visual effects ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. **EXTRA IMPORTANT FOR THIS SKILL**: Context determines what "extraordinary" means. A particle system on a creative portfolio is impressive. The same particle system on a settings page is embarrassing. But a settings page with instant optimistic saves and animated state transitions? That's extraordinary too. Understand the project's personality and goals before deciding what's appropriate. diff --git a/.cursor/skills/polish/SKILL.md b/.cursor/skills/polish/SKILL.md index eaec5e644..d3e8f4505 100644 --- a/.cursor/skills/polish/SKILL.md +++ b/.cursor/skills/polish/SKILL.md @@ -5,7 +5,7 @@ description: Final quality pass before shipping. Fixes alignment, spacing, consi ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: quality bar (MVP vs flagship). +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: quality bar (MVP vs flagship). --- diff --git a/.cursor/skills/quieter/SKILL.md b/.cursor/skills/quieter/SKILL.md index 4a55a1596..049a6201d 100644 --- a/.cursor/skills/quieter/SKILL.md +++ b/.cursor/skills/quieter/SKILL.md @@ -7,7 +7,7 @@ Reduce visual intensity in designs that are too bold, aggressive, or overstimula ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. --- diff --git a/.cursor/skills/typeset/SKILL.md b/.cursor/skills/typeset/SKILL.md index 563dc5330..53872040a 100644 --- a/.cursor/skills/typeset/SKILL.md +++ b/.cursor/skills/typeset/SKILL.md @@ -7,7 +7,7 @@ Assess and improve typography that feels generic, inconsistent, or poorly struct ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. --- diff --git a/.gemini/skills/adapt/SKILL.md b/.gemini/skills/adapt/SKILL.md index 9c6f504ec..e344cc695 100644 --- a/.gemini/skills/adapt/SKILL.md +++ b/.gemini/skills/adapt/SKILL.md @@ -7,7 +7,7 @@ Adapt existing designs to work effectively across different contexts - different ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: target platforms/devices and usage contexts. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: target platforms/devices and usage contexts. --- diff --git a/.gemini/skills/animate/SKILL.md b/.gemini/skills/animate/SKILL.md index 036861c5c..629b842c7 100644 --- a/.gemini/skills/animate/SKILL.md +++ b/.gemini/skills/animate/SKILL.md @@ -7,7 +7,7 @@ Analyze a feature and strategically add animations and micro-interactions that e ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: performance constraints. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: performance constraints. --- diff --git a/.gemini/skills/arrange/SKILL.md b/.gemini/skills/arrange/SKILL.md index 9876fc984..94bfd35f7 100644 --- a/.gemini/skills/arrange/SKILL.md +++ b/.gemini/skills/arrange/SKILL.md @@ -7,7 +7,7 @@ Assess and improve layout and spacing that feels monotonous, crowded, or structu ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. --- diff --git a/.gemini/skills/audit/SKILL.md b/.gemini/skills/audit/SKILL.md index 7e5205e20..86349386e 100644 --- a/.gemini/skills/audit/SKILL.md +++ b/.gemini/skills/audit/SKILL.md @@ -5,7 +5,7 @@ description: Perform comprehensive audit of interface quality across accessibili Run systematic quality checks and generate a comprehensive audit report with prioritized issues and actionable recommendations. Don't fix issues - document them for other commands to address. -**First**: Use the frontend-design skill for design principles and anti-patterns. +**First**: Invoke /frontend-design for design principles and anti-patterns. ## Diagnostic Scan diff --git a/.gemini/skills/bolder/SKILL.md b/.gemini/skills/bolder/SKILL.md index fc84f2921..7690716af 100644 --- a/.gemini/skills/bolder/SKILL.md +++ b/.gemini/skills/bolder/SKILL.md @@ -7,7 +7,7 @@ Increase visual impact and personality in designs that are too safe, generic, or ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. --- diff --git a/.gemini/skills/clarify/SKILL.md b/.gemini/skills/clarify/SKILL.md index 2e32e01da..c11b240af 100644 --- a/.gemini/skills/clarify/SKILL.md +++ b/.gemini/skills/clarify/SKILL.md @@ -7,7 +7,7 @@ Identify and improve unclear, confusing, or poorly written interface text to mak ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: audience technical level and users' mental state in context. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: audience technical level and users' mental state in context. --- diff --git a/.gemini/skills/colorize/SKILL.md b/.gemini/skills/colorize/SKILL.md index 0c8bec581..93aa6bd28 100644 --- a/.gemini/skills/colorize/SKILL.md +++ b/.gemini/skills/colorize/SKILL.md @@ -7,7 +7,7 @@ Strategically introduce color to designs that are too monochromatic, gray, or la ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: existing brand colors. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: existing brand colors. --- diff --git a/.gemini/skills/critique/SKILL.md b/.gemini/skills/critique/SKILL.md index 9e63a7468..3479c10d3 100644 --- a/.gemini/skills/critique/SKILL.md +++ b/.gemini/skills/critique/SKILL.md @@ -5,7 +5,7 @@ description: Evaluate design effectiveness from a UX perspective. Assesses visua ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: what the interface is trying to accomplish. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: what the interface is trying to accomplish. --- diff --git a/.gemini/skills/delight/SKILL.md b/.gemini/skills/delight/SKILL.md index c20e33090..e544934f6 100644 --- a/.gemini/skills/delight/SKILL.md +++ b/.gemini/skills/delight/SKILL.md @@ -7,7 +7,7 @@ Identify opportunities to add moments of joy, personality, and unexpected polish ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: what's appropriate for the domain (playful vs professional vs quirky vs elegant). +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: what's appropriate for the domain (playful vs professional vs quirky vs elegant). --- diff --git a/.gemini/skills/distill/SKILL.md b/.gemini/skills/distill/SKILL.md index 345779318..e37121d7b 100644 --- a/.gemini/skills/distill/SKILL.md +++ b/.gemini/skills/distill/SKILL.md @@ -7,7 +7,7 @@ Remove unnecessary complexity from designs, revealing the essential elements and ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. --- diff --git a/.gemini/skills/frontend-design/SKILL.md b/.gemini/skills/frontend-design/SKILL.md index a1e13bab8..0bb59fa35 100644 --- a/.gemini/skills/frontend-design/SKILL.md +++ b/.gemini/skills/frontend-design/SKILL.md @@ -21,7 +21,7 @@ Individual skills may require additional context — check the skill's preparati **Gathering order:** 1. **Check current instructions (instant)**: If your loaded instructions already contain a **Design Context** section, proceed immediately. 2. **Check .impeccable.md (fast)**: If not in instructions, read `.impeccable.md` from the project root. If it exists and contains the required context, proceed. -3. **Run teach-impeccable (REQUIRED)**: If neither source has context, you MUST run the teach-impeccable skill NOW before doing anything else. Do NOT skip this step. Do NOT attempt to infer context from the codebase instead. +3. **Run teach-impeccable (REQUIRED)**: If neither source has context, you MUST run /teach-impeccable NOW before doing anything else. Do NOT skip this step. Do NOT attempt to infer context from the codebase instead. --- diff --git a/.gemini/skills/normalize/SKILL.md b/.gemini/skills/normalize/SKILL.md index 91b2949a5..42df54fa8 100644 --- a/.gemini/skills/normalize/SKILL.md +++ b/.gemini/skills/normalize/SKILL.md @@ -7,7 +7,7 @@ Analyze and redesign the feature to perfectly match our design system standards, ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. --- diff --git a/.gemini/skills/onboard/SKILL.md b/.gemini/skills/onboard/SKILL.md index 317ce93bd..6541d7838 100644 --- a/.gemini/skills/onboard/SKILL.md +++ b/.gemini/skills/onboard/SKILL.md @@ -5,7 +5,7 @@ description: Design or improve onboarding flows, empty states, and first-time us ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: the "aha moment" you want users to reach, and users' experience level. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: the "aha moment" you want users to reach, and users' experience level. --- diff --git a/.gemini/skills/overdrive/SKILL.md b/.gemini/skills/overdrive/SKILL.md index 5677d8799..bcd7c8c49 100644 --- a/.gemini/skills/overdrive/SKILL.md +++ b/.gemini/skills/overdrive/SKILL.md @@ -14,7 +14,7 @@ Push an interface past conventional limits. This isn't just about visual effects ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. **EXTRA IMPORTANT FOR THIS SKILL**: Context determines what "extraordinary" means. A particle system on a creative portfolio is impressive. The same particle system on a settings page is embarrassing. But a settings page with instant optimistic saves and animated state transitions? That's extraordinary too. Understand the project's personality and goals before deciding what's appropriate. diff --git a/.gemini/skills/polish/SKILL.md b/.gemini/skills/polish/SKILL.md index eaec5e644..d3e8f4505 100644 --- a/.gemini/skills/polish/SKILL.md +++ b/.gemini/skills/polish/SKILL.md @@ -5,7 +5,7 @@ description: Final quality pass before shipping. Fixes alignment, spacing, consi ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: quality bar (MVP vs flagship). +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: quality bar (MVP vs flagship). --- diff --git a/.gemini/skills/quieter/SKILL.md b/.gemini/skills/quieter/SKILL.md index 4a55a1596..049a6201d 100644 --- a/.gemini/skills/quieter/SKILL.md +++ b/.gemini/skills/quieter/SKILL.md @@ -7,7 +7,7 @@ Reduce visual intensity in designs that are too bold, aggressive, or overstimula ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. --- diff --git a/.gemini/skills/typeset/SKILL.md b/.gemini/skills/typeset/SKILL.md index 563dc5330..53872040a 100644 --- a/.gemini/skills/typeset/SKILL.md +++ b/.gemini/skills/typeset/SKILL.md @@ -7,7 +7,7 @@ Assess and improve typography that feels generic, inconsistent, or poorly struct ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. --- diff --git a/.kiro/skills/adapt/SKILL.md b/.kiro/skills/adapt/SKILL.md index 9c6f504ec..e344cc695 100644 --- a/.kiro/skills/adapt/SKILL.md +++ b/.kiro/skills/adapt/SKILL.md @@ -7,7 +7,7 @@ Adapt existing designs to work effectively across different contexts - different ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: target platforms/devices and usage contexts. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: target platforms/devices and usage contexts. --- diff --git a/.kiro/skills/animate/SKILL.md b/.kiro/skills/animate/SKILL.md index 036861c5c..629b842c7 100644 --- a/.kiro/skills/animate/SKILL.md +++ b/.kiro/skills/animate/SKILL.md @@ -7,7 +7,7 @@ Analyze a feature and strategically add animations and micro-interactions that e ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: performance constraints. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: performance constraints. --- diff --git a/.kiro/skills/arrange/SKILL.md b/.kiro/skills/arrange/SKILL.md index 9876fc984..94bfd35f7 100644 --- a/.kiro/skills/arrange/SKILL.md +++ b/.kiro/skills/arrange/SKILL.md @@ -7,7 +7,7 @@ Assess and improve layout and spacing that feels monotonous, crowded, or structu ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. --- diff --git a/.kiro/skills/audit/SKILL.md b/.kiro/skills/audit/SKILL.md index 7e5205e20..86349386e 100644 --- a/.kiro/skills/audit/SKILL.md +++ b/.kiro/skills/audit/SKILL.md @@ -5,7 +5,7 @@ description: Perform comprehensive audit of interface quality across accessibili Run systematic quality checks and generate a comprehensive audit report with prioritized issues and actionable recommendations. Don't fix issues - document them for other commands to address. -**First**: Use the frontend-design skill for design principles and anti-patterns. +**First**: Invoke /frontend-design for design principles and anti-patterns. ## Diagnostic Scan diff --git a/.kiro/skills/bolder/SKILL.md b/.kiro/skills/bolder/SKILL.md index fc84f2921..7690716af 100644 --- a/.kiro/skills/bolder/SKILL.md +++ b/.kiro/skills/bolder/SKILL.md @@ -7,7 +7,7 @@ Increase visual impact and personality in designs that are too safe, generic, or ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. --- diff --git a/.kiro/skills/clarify/SKILL.md b/.kiro/skills/clarify/SKILL.md index 2e32e01da..c11b240af 100644 --- a/.kiro/skills/clarify/SKILL.md +++ b/.kiro/skills/clarify/SKILL.md @@ -7,7 +7,7 @@ Identify and improve unclear, confusing, or poorly written interface text to mak ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: audience technical level and users' mental state in context. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: audience technical level and users' mental state in context. --- diff --git a/.kiro/skills/colorize/SKILL.md b/.kiro/skills/colorize/SKILL.md index 0c8bec581..93aa6bd28 100644 --- a/.kiro/skills/colorize/SKILL.md +++ b/.kiro/skills/colorize/SKILL.md @@ -7,7 +7,7 @@ Strategically introduce color to designs that are too monochromatic, gray, or la ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: existing brand colors. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: existing brand colors. --- diff --git a/.kiro/skills/critique/SKILL.md b/.kiro/skills/critique/SKILL.md index 9e63a7468..3479c10d3 100644 --- a/.kiro/skills/critique/SKILL.md +++ b/.kiro/skills/critique/SKILL.md @@ -5,7 +5,7 @@ description: Evaluate design effectiveness from a UX perspective. Assesses visua ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: what the interface is trying to accomplish. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: what the interface is trying to accomplish. --- diff --git a/.kiro/skills/delight/SKILL.md b/.kiro/skills/delight/SKILL.md index c20e33090..e544934f6 100644 --- a/.kiro/skills/delight/SKILL.md +++ b/.kiro/skills/delight/SKILL.md @@ -7,7 +7,7 @@ Identify opportunities to add moments of joy, personality, and unexpected polish ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: what's appropriate for the domain (playful vs professional vs quirky vs elegant). +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: what's appropriate for the domain (playful vs professional vs quirky vs elegant). --- diff --git a/.kiro/skills/distill/SKILL.md b/.kiro/skills/distill/SKILL.md index 345779318..e37121d7b 100644 --- a/.kiro/skills/distill/SKILL.md +++ b/.kiro/skills/distill/SKILL.md @@ -7,7 +7,7 @@ Remove unnecessary complexity from designs, revealing the essential elements and ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. --- diff --git a/.kiro/skills/frontend-design/SKILL.md b/.kiro/skills/frontend-design/SKILL.md index df336117b..33db0ba7e 100644 --- a/.kiro/skills/frontend-design/SKILL.md +++ b/.kiro/skills/frontend-design/SKILL.md @@ -22,7 +22,7 @@ Individual skills may require additional context — check the skill's preparati **Gathering order:** 1. **Check current instructions (instant)**: If your loaded instructions already contain a **Design Context** section, proceed immediately. 2. **Check .impeccable.md (fast)**: If not in instructions, read `.impeccable.md` from the project root. If it exists and contains the required context, proceed. -3. **Run teach-impeccable (REQUIRED)**: If neither source has context, you MUST run the teach-impeccable skill NOW before doing anything else. Do NOT skip this step. Do NOT attempt to infer context from the codebase instead. +3. **Run teach-impeccable (REQUIRED)**: If neither source has context, you MUST run /teach-impeccable NOW before doing anything else. Do NOT skip this step. Do NOT attempt to infer context from the codebase instead. --- diff --git a/.kiro/skills/normalize/SKILL.md b/.kiro/skills/normalize/SKILL.md index 91b2949a5..42df54fa8 100644 --- a/.kiro/skills/normalize/SKILL.md +++ b/.kiro/skills/normalize/SKILL.md @@ -7,7 +7,7 @@ Analyze and redesign the feature to perfectly match our design system standards, ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. --- diff --git a/.kiro/skills/onboard/SKILL.md b/.kiro/skills/onboard/SKILL.md index 317ce93bd..6541d7838 100644 --- a/.kiro/skills/onboard/SKILL.md +++ b/.kiro/skills/onboard/SKILL.md @@ -5,7 +5,7 @@ description: Design or improve onboarding flows, empty states, and first-time us ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: the "aha moment" you want users to reach, and users' experience level. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: the "aha moment" you want users to reach, and users' experience level. --- diff --git a/.kiro/skills/overdrive/SKILL.md b/.kiro/skills/overdrive/SKILL.md index 5677d8799..bcd7c8c49 100644 --- a/.kiro/skills/overdrive/SKILL.md +++ b/.kiro/skills/overdrive/SKILL.md @@ -14,7 +14,7 @@ Push an interface past conventional limits. This isn't just about visual effects ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. **EXTRA IMPORTANT FOR THIS SKILL**: Context determines what "extraordinary" means. A particle system on a creative portfolio is impressive. The same particle system on a settings page is embarrassing. But a settings page with instant optimistic saves and animated state transitions? That's extraordinary too. Understand the project's personality and goals before deciding what's appropriate. diff --git a/.kiro/skills/polish/SKILL.md b/.kiro/skills/polish/SKILL.md index eaec5e644..d3e8f4505 100644 --- a/.kiro/skills/polish/SKILL.md +++ b/.kiro/skills/polish/SKILL.md @@ -5,7 +5,7 @@ description: Final quality pass before shipping. Fixes alignment, spacing, consi ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: quality bar (MVP vs flagship). +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: quality bar (MVP vs flagship). --- diff --git a/.kiro/skills/quieter/SKILL.md b/.kiro/skills/quieter/SKILL.md index 4a55a1596..049a6201d 100644 --- a/.kiro/skills/quieter/SKILL.md +++ b/.kiro/skills/quieter/SKILL.md @@ -7,7 +7,7 @@ Reduce visual intensity in designs that are too bold, aggressive, or overstimula ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. --- diff --git a/.kiro/skills/typeset/SKILL.md b/.kiro/skills/typeset/SKILL.md index 563dc5330..53872040a 100644 --- a/.kiro/skills/typeset/SKILL.md +++ b/.kiro/skills/typeset/SKILL.md @@ -7,7 +7,7 @@ Assess and improve typography that feels generic, inconsistent, or poorly struct ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. --- diff --git a/.opencode/skills/adapt/SKILL.md b/.opencode/skills/adapt/SKILL.md index 9a0703af0..84f24d847 100644 --- a/.opencode/skills/adapt/SKILL.md +++ b/.opencode/skills/adapt/SKILL.md @@ -9,7 +9,7 @@ Adapt existing designs to work effectively across different contexts - different ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: target platforms/devices and usage contexts. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: target platforms/devices and usage contexts. --- diff --git a/.opencode/skills/animate/SKILL.md b/.opencode/skills/animate/SKILL.md index ef9497c3f..c3a85e24e 100644 --- a/.opencode/skills/animate/SKILL.md +++ b/.opencode/skills/animate/SKILL.md @@ -9,7 +9,7 @@ Analyze a feature and strategically add animations and micro-interactions that e ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: performance constraints. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: performance constraints. --- diff --git a/.opencode/skills/arrange/SKILL.md b/.opencode/skills/arrange/SKILL.md index 2f22f3faa..3b29230f0 100644 --- a/.opencode/skills/arrange/SKILL.md +++ b/.opencode/skills/arrange/SKILL.md @@ -9,7 +9,7 @@ Assess and improve layout and spacing that feels monotonous, crowded, or structu ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. --- diff --git a/.opencode/skills/audit/SKILL.md b/.opencode/skills/audit/SKILL.md index f11131ae0..185f87cfc 100644 --- a/.opencode/skills/audit/SKILL.md +++ b/.opencode/skills/audit/SKILL.md @@ -7,7 +7,7 @@ argument-hint: "[area (feature, page, component...)]" Run systematic quality checks and generate a comprehensive audit report with prioritized issues and actionable recommendations. Don't fix issues - document them for other commands to address. -**First**: Use the frontend-design skill for design principles and anti-patterns. +**First**: Invoke /frontend-design for design principles and anti-patterns. ## Diagnostic Scan diff --git a/.opencode/skills/bolder/SKILL.md b/.opencode/skills/bolder/SKILL.md index 75500e712..caa3dc6bd 100644 --- a/.opencode/skills/bolder/SKILL.md +++ b/.opencode/skills/bolder/SKILL.md @@ -9,7 +9,7 @@ Increase visual impact and personality in designs that are too safe, generic, or ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. --- diff --git a/.opencode/skills/clarify/SKILL.md b/.opencode/skills/clarify/SKILL.md index f0f602fa4..940cc1450 100644 --- a/.opencode/skills/clarify/SKILL.md +++ b/.opencode/skills/clarify/SKILL.md @@ -9,7 +9,7 @@ Identify and improve unclear, confusing, or poorly written interface text to mak ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: audience technical level and users' mental state in context. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: audience technical level and users' mental state in context. --- diff --git a/.opencode/skills/colorize/SKILL.md b/.opencode/skills/colorize/SKILL.md index c503c4b6c..495611f10 100644 --- a/.opencode/skills/colorize/SKILL.md +++ b/.opencode/skills/colorize/SKILL.md @@ -9,7 +9,7 @@ Strategically introduce color to designs that are too monochromatic, gray, or la ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: existing brand colors. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: existing brand colors. --- diff --git a/.opencode/skills/critique/SKILL.md b/.opencode/skills/critique/SKILL.md index 62e2552fd..e3d93c3e8 100644 --- a/.opencode/skills/critique/SKILL.md +++ b/.opencode/skills/critique/SKILL.md @@ -7,7 +7,7 @@ argument-hint: "[area (feature, page, component...)]" ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: what the interface is trying to accomplish. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: what the interface is trying to accomplish. --- diff --git a/.opencode/skills/delight/SKILL.md b/.opencode/skills/delight/SKILL.md index 5aa6d85e5..c4b127583 100644 --- a/.opencode/skills/delight/SKILL.md +++ b/.opencode/skills/delight/SKILL.md @@ -9,7 +9,7 @@ Identify opportunities to add moments of joy, personality, and unexpected polish ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: what's appropriate for the domain (playful vs professional vs quirky vs elegant). +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: what's appropriate for the domain (playful vs professional vs quirky vs elegant). --- diff --git a/.opencode/skills/distill/SKILL.md b/.opencode/skills/distill/SKILL.md index 0c476f598..883a6ec33 100644 --- a/.opencode/skills/distill/SKILL.md +++ b/.opencode/skills/distill/SKILL.md @@ -9,7 +9,7 @@ Remove unnecessary complexity from designs, revealing the essential elements and ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. --- diff --git a/.opencode/skills/frontend-design/SKILL.md b/.opencode/skills/frontend-design/SKILL.md index df336117b..33db0ba7e 100644 --- a/.opencode/skills/frontend-design/SKILL.md +++ b/.opencode/skills/frontend-design/SKILL.md @@ -22,7 +22,7 @@ Individual skills may require additional context — check the skill's preparati **Gathering order:** 1. **Check current instructions (instant)**: If your loaded instructions already contain a **Design Context** section, proceed immediately. 2. **Check .impeccable.md (fast)**: If not in instructions, read `.impeccable.md` from the project root. If it exists and contains the required context, proceed. -3. **Run teach-impeccable (REQUIRED)**: If neither source has context, you MUST run the teach-impeccable skill NOW before doing anything else. Do NOT skip this step. Do NOT attempt to infer context from the codebase instead. +3. **Run teach-impeccable (REQUIRED)**: If neither source has context, you MUST run /teach-impeccable NOW before doing anything else. Do NOT skip this step. Do NOT attempt to infer context from the codebase instead. --- diff --git a/.opencode/skills/normalize/SKILL.md b/.opencode/skills/normalize/SKILL.md index f8af99752..a5396755f 100644 --- a/.opencode/skills/normalize/SKILL.md +++ b/.opencode/skills/normalize/SKILL.md @@ -9,7 +9,7 @@ Analyze and redesign the feature to perfectly match our design system standards, ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. --- diff --git a/.opencode/skills/onboard/SKILL.md b/.opencode/skills/onboard/SKILL.md index 10f3618b1..b11fbbfe1 100644 --- a/.opencode/skills/onboard/SKILL.md +++ b/.opencode/skills/onboard/SKILL.md @@ -7,7 +7,7 @@ argument-hint: "[target]" ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: the "aha moment" you want users to reach, and users' experience level. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: the "aha moment" you want users to reach, and users' experience level. --- diff --git a/.opencode/skills/overdrive/SKILL.md b/.opencode/skills/overdrive/SKILL.md index 57a60bda3..202c78827 100644 --- a/.opencode/skills/overdrive/SKILL.md +++ b/.opencode/skills/overdrive/SKILL.md @@ -16,7 +16,7 @@ Push an interface past conventional limits. This isn't just about visual effects ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. **EXTRA IMPORTANT FOR THIS SKILL**: Context determines what "extraordinary" means. A particle system on a creative portfolio is impressive. The same particle system on a settings page is embarrassing. But a settings page with instant optimistic saves and animated state transitions? That's extraordinary too. Understand the project's personality and goals before deciding what's appropriate. diff --git a/.opencode/skills/polish/SKILL.md b/.opencode/skills/polish/SKILL.md index fe9e27687..efa8004ed 100644 --- a/.opencode/skills/polish/SKILL.md +++ b/.opencode/skills/polish/SKILL.md @@ -7,7 +7,7 @@ argument-hint: "[target]" ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: quality bar (MVP vs flagship). +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: quality bar (MVP vs flagship). --- diff --git a/.opencode/skills/quieter/SKILL.md b/.opencode/skills/quieter/SKILL.md index 09bcdcbe5..dca6a24f1 100644 --- a/.opencode/skills/quieter/SKILL.md +++ b/.opencode/skills/quieter/SKILL.md @@ -9,7 +9,7 @@ Reduce visual intensity in designs that are too bold, aggressive, or overstimula ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. --- diff --git a/.opencode/skills/typeset/SKILL.md b/.opencode/skills/typeset/SKILL.md index d331e2745..8957bba80 100644 --- a/.opencode/skills/typeset/SKILL.md +++ b/.opencode/skills/typeset/SKILL.md @@ -9,7 +9,7 @@ Assess and improve typography that feels generic, inconsistent, or poorly struct ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. --- diff --git a/.pi/skills/adapt/SKILL.md b/.pi/skills/adapt/SKILL.md index 9c6f504ec..e344cc695 100644 --- a/.pi/skills/adapt/SKILL.md +++ b/.pi/skills/adapt/SKILL.md @@ -7,7 +7,7 @@ Adapt existing designs to work effectively across different contexts - different ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: target platforms/devices and usage contexts. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: target platforms/devices and usage contexts. --- diff --git a/.pi/skills/animate/SKILL.md b/.pi/skills/animate/SKILL.md index 036861c5c..629b842c7 100644 --- a/.pi/skills/animate/SKILL.md +++ b/.pi/skills/animate/SKILL.md @@ -7,7 +7,7 @@ Analyze a feature and strategically add animations and micro-interactions that e ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: performance constraints. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: performance constraints. --- diff --git a/.pi/skills/arrange/SKILL.md b/.pi/skills/arrange/SKILL.md index 9876fc984..94bfd35f7 100644 --- a/.pi/skills/arrange/SKILL.md +++ b/.pi/skills/arrange/SKILL.md @@ -7,7 +7,7 @@ Assess and improve layout and spacing that feels monotonous, crowded, or structu ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. --- diff --git a/.pi/skills/audit/SKILL.md b/.pi/skills/audit/SKILL.md index 7e5205e20..86349386e 100644 --- a/.pi/skills/audit/SKILL.md +++ b/.pi/skills/audit/SKILL.md @@ -5,7 +5,7 @@ description: Perform comprehensive audit of interface quality across accessibili Run systematic quality checks and generate a comprehensive audit report with prioritized issues and actionable recommendations. Don't fix issues - document them for other commands to address. -**First**: Use the frontend-design skill for design principles and anti-patterns. +**First**: Invoke /frontend-design for design principles and anti-patterns. ## Diagnostic Scan diff --git a/.pi/skills/bolder/SKILL.md b/.pi/skills/bolder/SKILL.md index fc84f2921..7690716af 100644 --- a/.pi/skills/bolder/SKILL.md +++ b/.pi/skills/bolder/SKILL.md @@ -7,7 +7,7 @@ Increase visual impact and personality in designs that are too safe, generic, or ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. --- diff --git a/.pi/skills/clarify/SKILL.md b/.pi/skills/clarify/SKILL.md index 2e32e01da..c11b240af 100644 --- a/.pi/skills/clarify/SKILL.md +++ b/.pi/skills/clarify/SKILL.md @@ -7,7 +7,7 @@ Identify and improve unclear, confusing, or poorly written interface text to mak ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: audience technical level and users' mental state in context. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: audience technical level and users' mental state in context. --- diff --git a/.pi/skills/colorize/SKILL.md b/.pi/skills/colorize/SKILL.md index 0c8bec581..93aa6bd28 100644 --- a/.pi/skills/colorize/SKILL.md +++ b/.pi/skills/colorize/SKILL.md @@ -7,7 +7,7 @@ Strategically introduce color to designs that are too monochromatic, gray, or la ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: existing brand colors. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: existing brand colors. --- diff --git a/.pi/skills/critique/SKILL.md b/.pi/skills/critique/SKILL.md index 9e63a7468..3479c10d3 100644 --- a/.pi/skills/critique/SKILL.md +++ b/.pi/skills/critique/SKILL.md @@ -5,7 +5,7 @@ description: Evaluate design effectiveness from a UX perspective. Assesses visua ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: what the interface is trying to accomplish. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: what the interface is trying to accomplish. --- diff --git a/.pi/skills/delight/SKILL.md b/.pi/skills/delight/SKILL.md index c20e33090..e544934f6 100644 --- a/.pi/skills/delight/SKILL.md +++ b/.pi/skills/delight/SKILL.md @@ -7,7 +7,7 @@ Identify opportunities to add moments of joy, personality, and unexpected polish ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: what's appropriate for the domain (playful vs professional vs quirky vs elegant). +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: what's appropriate for the domain (playful vs professional vs quirky vs elegant). --- diff --git a/.pi/skills/distill/SKILL.md b/.pi/skills/distill/SKILL.md index 345779318..e37121d7b 100644 --- a/.pi/skills/distill/SKILL.md +++ b/.pi/skills/distill/SKILL.md @@ -7,7 +7,7 @@ Remove unnecessary complexity from designs, revealing the essential elements and ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. --- diff --git a/.pi/skills/frontend-design/SKILL.md b/.pi/skills/frontend-design/SKILL.md index 367b4d4d4..8a85856a8 100644 --- a/.pi/skills/frontend-design/SKILL.md +++ b/.pi/skills/frontend-design/SKILL.md @@ -22,7 +22,7 @@ Individual skills may require additional context — check the skill's preparati **Gathering order:** 1. **Check current instructions (instant)**: If your loaded instructions already contain a **Design Context** section, proceed immediately. 2. **Check .impeccable.md (fast)**: If not in instructions, read `.impeccable.md` from the project root. If it exists and contains the required context, proceed. -3. **Run teach-impeccable (REQUIRED)**: If neither source has context, you MUST run the teach-impeccable skill NOW before doing anything else. Do NOT skip this step. Do NOT attempt to infer context from the codebase instead. +3. **Run teach-impeccable (REQUIRED)**: If neither source has context, you MUST run /teach-impeccable NOW before doing anything else. Do NOT skip this step. Do NOT attempt to infer context from the codebase instead. --- diff --git a/.pi/skills/normalize/SKILL.md b/.pi/skills/normalize/SKILL.md index 91b2949a5..42df54fa8 100644 --- a/.pi/skills/normalize/SKILL.md +++ b/.pi/skills/normalize/SKILL.md @@ -7,7 +7,7 @@ Analyze and redesign the feature to perfectly match our design system standards, ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. --- diff --git a/.pi/skills/onboard/SKILL.md b/.pi/skills/onboard/SKILL.md index 317ce93bd..6541d7838 100644 --- a/.pi/skills/onboard/SKILL.md +++ b/.pi/skills/onboard/SKILL.md @@ -5,7 +5,7 @@ description: Design or improve onboarding flows, empty states, and first-time us ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: the "aha moment" you want users to reach, and users' experience level. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: the "aha moment" you want users to reach, and users' experience level. --- diff --git a/.pi/skills/overdrive/SKILL.md b/.pi/skills/overdrive/SKILL.md index 5677d8799..bcd7c8c49 100644 --- a/.pi/skills/overdrive/SKILL.md +++ b/.pi/skills/overdrive/SKILL.md @@ -14,7 +14,7 @@ Push an interface past conventional limits. This isn't just about visual effects ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. **EXTRA IMPORTANT FOR THIS SKILL**: Context determines what "extraordinary" means. A particle system on a creative portfolio is impressive. The same particle system on a settings page is embarrassing. But a settings page with instant optimistic saves and animated state transitions? That's extraordinary too. Understand the project's personality and goals before deciding what's appropriate. diff --git a/.pi/skills/polish/SKILL.md b/.pi/skills/polish/SKILL.md index eaec5e644..d3e8f4505 100644 --- a/.pi/skills/polish/SKILL.md +++ b/.pi/skills/polish/SKILL.md @@ -5,7 +5,7 @@ description: Final quality pass before shipping. Fixes alignment, spacing, consi ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: quality bar (MVP vs flagship). +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. Additionally gather: quality bar (MVP vs flagship). --- diff --git a/.pi/skills/quieter/SKILL.md b/.pi/skills/quieter/SKILL.md index 4a55a1596..049a6201d 100644 --- a/.pi/skills/quieter/SKILL.md +++ b/.pi/skills/quieter/SKILL.md @@ -7,7 +7,7 @@ Reduce visual intensity in designs that are too bold, aggressive, or overstimula ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. --- diff --git a/.pi/skills/typeset/SKILL.md b/.pi/skills/typeset/SKILL.md index 563dc5330..53872040a 100644 --- a/.pi/skills/typeset/SKILL.md +++ b/.pi/skills/typeset/SKILL.md @@ -7,7 +7,7 @@ Assess and improve typography that feels generic, inconsistent, or poorly struct ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke /frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run /teach-impeccable first. --- diff --git a/source/skills/adapt/SKILL.md b/source/skills/adapt/SKILL.md index 796eea6d2..aafb43160 100644 --- a/source/skills/adapt/SKILL.md +++ b/source/skills/adapt/SKILL.md @@ -9,7 +9,7 @@ Adapt existing designs to work effectively across different contexts - different ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: target platforms/devices and usage contexts. +Invoke {{command_prefix}}frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run {{command_prefix}}teach-impeccable first. Additionally gather: target platforms/devices and usage contexts. --- diff --git a/source/skills/animate/SKILL.md b/source/skills/animate/SKILL.md index 458164574..86818fd60 100644 --- a/source/skills/animate/SKILL.md +++ b/source/skills/animate/SKILL.md @@ -9,7 +9,7 @@ Analyze a feature and strategically add animations and micro-interactions that e ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: performance constraints. +Invoke {{command_prefix}}frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run {{command_prefix}}teach-impeccable first. Additionally gather: performance constraints. --- diff --git a/source/skills/arrange/SKILL.md b/source/skills/arrange/SKILL.md index aa9b9d6c5..519c4bcfd 100644 --- a/source/skills/arrange/SKILL.md +++ b/source/skills/arrange/SKILL.md @@ -9,7 +9,7 @@ Assess and improve layout and spacing that feels monotonous, crowded, or structu ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke {{command_prefix}}frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run {{command_prefix}}teach-impeccable first. --- diff --git a/source/skills/audit/SKILL.md b/source/skills/audit/SKILL.md index 072636456..2ae7ba441 100644 --- a/source/skills/audit/SKILL.md +++ b/source/skills/audit/SKILL.md @@ -7,7 +7,7 @@ user-invocable: true Run systematic quality checks and generate a comprehensive audit report with prioritized issues and actionable recommendations. Don't fix issues - document them for other commands to address. -**First**: Use the frontend-design skill for design principles and anti-patterns. +**First**: Invoke {{command_prefix}}frontend-design for design principles and anti-patterns. ## Diagnostic Scan diff --git a/source/skills/bolder/SKILL.md b/source/skills/bolder/SKILL.md index a71a88d39..5bcafbde7 100644 --- a/source/skills/bolder/SKILL.md +++ b/source/skills/bolder/SKILL.md @@ -9,7 +9,7 @@ Increase visual impact and personality in designs that are too safe, generic, or ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke {{command_prefix}}frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run {{command_prefix}}teach-impeccable first. --- diff --git a/source/skills/clarify/SKILL.md b/source/skills/clarify/SKILL.md index 10a1d1422..4b6389ab3 100644 --- a/source/skills/clarify/SKILL.md +++ b/source/skills/clarify/SKILL.md @@ -9,7 +9,7 @@ Identify and improve unclear, confusing, or poorly written interface text to mak ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: audience technical level and users' mental state in context. +Invoke {{command_prefix}}frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run {{command_prefix}}teach-impeccable first. Additionally gather: audience technical level and users' mental state in context. --- diff --git a/source/skills/colorize/SKILL.md b/source/skills/colorize/SKILL.md index 1bc2da5d3..f2e252b85 100644 --- a/source/skills/colorize/SKILL.md +++ b/source/skills/colorize/SKILL.md @@ -9,7 +9,7 @@ Strategically introduce color to designs that are too monochromatic, gray, or la ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: existing brand colors. +Invoke {{command_prefix}}frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run {{command_prefix}}teach-impeccable first. Additionally gather: existing brand colors. --- diff --git a/source/skills/critique/SKILL.md b/source/skills/critique/SKILL.md index dcada50ff..303dd2a1d 100644 --- a/source/skills/critique/SKILL.md +++ b/source/skills/critique/SKILL.md @@ -7,7 +7,7 @@ user-invocable: true ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: what the interface is trying to accomplish. +Invoke {{command_prefix}}frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run {{command_prefix}}teach-impeccable first. Additionally gather: what the interface is trying to accomplish. --- diff --git a/source/skills/delight/SKILL.md b/source/skills/delight/SKILL.md index e228375ce..2261d3b38 100644 --- a/source/skills/delight/SKILL.md +++ b/source/skills/delight/SKILL.md @@ -9,7 +9,7 @@ Identify opportunities to add moments of joy, personality, and unexpected polish ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: what's appropriate for the domain (playful vs professional vs quirky vs elegant). +Invoke {{command_prefix}}frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run {{command_prefix}}teach-impeccable first. Additionally gather: what's appropriate for the domain (playful vs professional vs quirky vs elegant). --- diff --git a/source/skills/distill/SKILL.md b/source/skills/distill/SKILL.md index 8a8b2466d..61da5cc73 100644 --- a/source/skills/distill/SKILL.md +++ b/source/skills/distill/SKILL.md @@ -9,7 +9,7 @@ Remove unnecessary complexity from designs, revealing the essential elements and ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke {{command_prefix}}frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run {{command_prefix}}teach-impeccable first. --- diff --git a/source/skills/frontend-design/SKILL.md b/source/skills/frontend-design/SKILL.md index c0486f7d2..50f4e45a0 100644 --- a/source/skills/frontend-design/SKILL.md +++ b/source/skills/frontend-design/SKILL.md @@ -22,7 +22,7 @@ Individual skills may require additional context — check the skill's preparati **Gathering order:** 1. **Check current instructions (instant)**: If your loaded instructions already contain a **Design Context** section, proceed immediately. 2. **Check .impeccable.md (fast)**: If not in instructions, read `.impeccable.md` from the project root. If it exists and contains the required context, proceed. -3. **Run teach-impeccable (REQUIRED)**: If neither source has context, you MUST run the teach-impeccable skill NOW before doing anything else. Do NOT skip this step. Do NOT attempt to infer context from the codebase instead. +3. **Run teach-impeccable (REQUIRED)**: If neither source has context, you MUST run {{command_prefix}}teach-impeccable NOW before doing anything else. Do NOT skip this step. Do NOT attempt to infer context from the codebase instead. --- diff --git a/source/skills/normalize/SKILL.md b/source/skills/normalize/SKILL.md index 9f5b0af9f..20f10deda 100644 --- a/source/skills/normalize/SKILL.md +++ b/source/skills/normalize/SKILL.md @@ -9,7 +9,7 @@ Analyze and redesign the feature to perfectly match our design system standards, ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke {{command_prefix}}frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run {{command_prefix}}teach-impeccable first. --- diff --git a/source/skills/onboard/SKILL.md b/source/skills/onboard/SKILL.md index 6a9e7260c..0234a988f 100644 --- a/source/skills/onboard/SKILL.md +++ b/source/skills/onboard/SKILL.md @@ -7,7 +7,7 @@ user-invocable: true ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: the "aha moment" you want users to reach, and users' experience level. +Invoke {{command_prefix}}frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run {{command_prefix}}teach-impeccable first. Additionally gather: the "aha moment" you want users to reach, and users' experience level. --- diff --git a/source/skills/overdrive/SKILL.md b/source/skills/overdrive/SKILL.md index ba64be3bb..33aa9d51c 100644 --- a/source/skills/overdrive/SKILL.md +++ b/source/skills/overdrive/SKILL.md @@ -16,7 +16,7 @@ Push an interface past conventional limits. This isn't just about visual effects ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke {{command_prefix}}frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run {{command_prefix}}teach-impeccable first. **EXTRA IMPORTANT FOR THIS SKILL**: Context determines what "extraordinary" means. A particle system on a creative portfolio is impressive. The same particle system on a settings page is embarrassing. But a settings page with instant optimistic saves and animated state transitions? That's extraordinary too. Understand the project's personality and goals before deciding what's appropriate. diff --git a/source/skills/polish/SKILL.md b/source/skills/polish/SKILL.md index 927ac3d63..18e3c64ed 100644 --- a/source/skills/polish/SKILL.md +++ b/source/skills/polish/SKILL.md @@ -7,7 +7,7 @@ user-invocable: true ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. Additionally gather: quality bar (MVP vs flagship). +Invoke {{command_prefix}}frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run {{command_prefix}}teach-impeccable first. Additionally gather: quality bar (MVP vs flagship). --- diff --git a/source/skills/quieter/SKILL.md b/source/skills/quieter/SKILL.md index c05009f10..95165a6b5 100644 --- a/source/skills/quieter/SKILL.md +++ b/source/skills/quieter/SKILL.md @@ -9,7 +9,7 @@ Reduce visual intensity in designs that are too bold, aggressive, or overstimula ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke {{command_prefix}}frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run {{command_prefix}}teach-impeccable first. --- diff --git a/source/skills/typeset/SKILL.md b/source/skills/typeset/SKILL.md index 755bba324..31830c61c 100644 --- a/source/skills/typeset/SKILL.md +++ b/source/skills/typeset/SKILL.md @@ -9,7 +9,7 @@ Assess and improve typography that feels generic, inconsistent, or poorly struct ## MANDATORY PREPARATION -Use the frontend-design skill — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run teach-impeccable first. +Invoke {{command_prefix}}frontend-design — it contains design principles, anti-patterns, and the **Context Gathering Protocol**. Follow the protocol before proceeding — if no design context exists yet, you MUST run {{command_prefix}}teach-impeccable first. ---