From 9d63fb29a6ee0d3b84bbaa12fb4aa3999812bebb Mon Sep 17 00:00:00 2001 From: Christian Ndu Date: Sun, 28 Jun 2026 13:15:02 +0100 Subject: [PATCH 1/6] add github token config to settings, remove deprecated skills and migrations --- .agents/skills/accessibility/SKILL.md | 522 - .../skills/accessibility/references/WCAG.md | 162 - .../SKILL.md | 156 - .agents/skills/copywriting/SKILL.md | 252 - .agents/skills/copywriting/evals/evals.json | 111 - .../copywriting/references/copy-frameworks.md | 344 - .../references/natural-transitions.md | 272 - .../database-query-optimization/SKILL.md | 217 - .agents/skills/frontend-design/LICENSE.txt | 177 - .agents/skills/frontend-design/SKILL.md | 42 - .agents/skills/interface-design/SKILL.md | 391 - .../interface-design/references/critique.md | 67 - .../interface-design/references/example.md | 86 - .../interface-design/references/principles.md | 235 - .../interface-design/references/validation.md | 48 - .../skills/profiling-optimization/SKILL.md | 430 - .../react-performance-optimization/SKILL.md | 202 - .../references/code-splitting.md | 105 - .../references/common-pitfalls.md | 371 - .../references/concurrent-features.md | 140 - .../references/memoization.md | 140 - .../references/profiling-debugging.md | 215 - .../references/state-management.md | 164 - .../references/virtualization.md | 117 - .../validation/rubric.yaml | 67 - .agents/skills/responsive-design/SKILL.md | 524 - .../references/breakpoint-strategies.md | 591 - .../references/container-queries.md | 564 - .../references/fluid-layouts.md | 538 - .agents/skills/web-design-guidelines/SKILL.md | 39 - .gitignore | 12 +- README.md | 2 +- dev.db | Bin 8855552 -> 0 bytes drizzle_migrations/0000_green_mandarin.sql | 75 - drizzle_migrations/0001_busy_falcon.sql | 2 - .../0002_large_fabian_cortez.sql | 1 - drizzle_migrations/meta/0000_snapshot.json | 533 - drizzle_migrations/meta/0001_snapshot.json | 534 - drizzle_migrations/meta/0002_snapshot.json | 542 - drizzle_migrations/meta/_journal.json | 27 - package-lock.json | 9504 ----------------- skills-lock.json | 55 - sqlite.db | 0 src/app/api/explain/commit/route.ts | 6 +- src/app/api/github/token/route.ts | 99 + .../repos/[id]/commits/[sha]/content/route.ts | 4 +- .../repos/[id]/commits/[sha]/diff/route.ts | 4 +- src/app/api/repos/[id]/commits/[sha]/route.ts | 4 +- src/app/api/repos/[id]/compare/route.ts | 4 +- src/app/api/repos/branches/route.ts | 4 +- src/app/api/repos/route.ts | 5 + src/components/SettingsModal.module.css | 760 +- src/components/SettingsModal.tsx | 639 +- src/hooks/use-github-token.ts | 18 + src/lib/platform/context.ts | 21 + src/services/ai-credentials.ts | 37 +- src/services/github.ts | 58 +- src/services/ingest.ts | 11 +- 58 files changed, 1351 insertions(+), 18899 deletions(-) delete mode 100644 .agents/skills/accessibility/SKILL.md delete mode 100644 .agents/skills/accessibility/references/WCAG.md delete mode 100644 .agents/skills/application-performance-performance-optimization/SKILL.md delete mode 100644 .agents/skills/copywriting/SKILL.md delete mode 100644 .agents/skills/copywriting/evals/evals.json delete mode 100644 .agents/skills/copywriting/references/copy-frameworks.md delete mode 100644 .agents/skills/copywriting/references/natural-transitions.md delete mode 100644 .agents/skills/database-query-optimization/SKILL.md delete mode 100644 .agents/skills/frontend-design/LICENSE.txt delete mode 100644 .agents/skills/frontend-design/SKILL.md delete mode 100644 .agents/skills/interface-design/SKILL.md delete mode 100644 .agents/skills/interface-design/references/critique.md delete mode 100644 .agents/skills/interface-design/references/example.md delete mode 100644 .agents/skills/interface-design/references/principles.md delete mode 100644 .agents/skills/interface-design/references/validation.md delete mode 100644 .agents/skills/profiling-optimization/SKILL.md delete mode 100644 .agents/skills/react-performance-optimization/SKILL.md delete mode 100644 .agents/skills/react-performance-optimization/references/code-splitting.md delete mode 100644 .agents/skills/react-performance-optimization/references/common-pitfalls.md delete mode 100644 .agents/skills/react-performance-optimization/references/concurrent-features.md delete mode 100644 .agents/skills/react-performance-optimization/references/memoization.md delete mode 100644 .agents/skills/react-performance-optimization/references/profiling-debugging.md delete mode 100644 .agents/skills/react-performance-optimization/references/state-management.md delete mode 100644 .agents/skills/react-performance-optimization/references/virtualization.md delete mode 100644 .agents/skills/react-performance-optimization/validation/rubric.yaml delete mode 100644 .agents/skills/responsive-design/SKILL.md delete mode 100644 .agents/skills/responsive-design/references/breakpoint-strategies.md delete mode 100644 .agents/skills/responsive-design/references/container-queries.md delete mode 100644 .agents/skills/responsive-design/references/fluid-layouts.md delete mode 100644 .agents/skills/web-design-guidelines/SKILL.md delete mode 100644 dev.db delete mode 100644 drizzle_migrations/0000_green_mandarin.sql delete mode 100644 drizzle_migrations/0001_busy_falcon.sql delete mode 100644 drizzle_migrations/0002_large_fabian_cortez.sql delete mode 100644 drizzle_migrations/meta/0000_snapshot.json delete mode 100644 drizzle_migrations/meta/0001_snapshot.json delete mode 100644 drizzle_migrations/meta/0002_snapshot.json delete mode 100644 drizzle_migrations/meta/_journal.json delete mode 100644 package-lock.json delete mode 100644 skills-lock.json delete mode 100644 sqlite.db create mode 100644 src/app/api/github/token/route.ts create mode 100644 src/hooks/use-github-token.ts diff --git a/.agents/skills/accessibility/SKILL.md b/.agents/skills/accessibility/SKILL.md deleted file mode 100644 index 702e93e..0000000 --- a/.agents/skills/accessibility/SKILL.md +++ /dev/null @@ -1,522 +0,0 @@ ---- -name: accessibility -description: Audit and improve web accessibility following WCAG 2.1 guidelines. Use when asked to "improve accessibility", "a11y audit", "WCAG compliance", "screen reader support", "keyboard navigation", or "make accessible". -license: MIT -metadata: - author: web-quality-skills - version: "1.0" ---- - -# Accessibility (a11y) - -Comprehensive accessibility guidelines based on WCAG 2.1 and Lighthouse accessibility audits. Goal: make content usable by everyone, including people with disabilities. - -## WCAG Principles: POUR - -| Principle | Description | -|-----------|-------------| -| **P**erceivable | Content can be perceived through different senses | -| **O**perable | Interface can be operated by all users | -| **U**nderstandable | Content and interface are understandable | -| **R**obust | Content works with assistive technologies | - -## Conformance levels - -| Level | Requirement | Target | -|-------|-------------|--------| -| **A** | Minimum accessibility | Must pass | -| **AA** | Standard compliance | Should pass (legal requirement in many jurisdictions) | -| **AAA** | Enhanced accessibility | Nice to have | - ---- - -## Perceivable - -### Text alternatives (1.1) - -**Images require alt text:** -```html - - - - -Bar chart showing 40% increase in Q3 sales - - - - - -
- 2024 market trends infographic -
- -
-
-``` - -**Icon buttons need accessible names:** -```html - - - - - - - - -``` - -**Visually hidden class:** -```css -.visually-hidden { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - clip: rect(0, 0, 0, 0); - white-space: nowrap; - border: 0; -} -``` - -### Color contrast (1.4.3, 1.4.6) - -| Text Size | AA minimum | AAA enhanced | -|-----------|------------|--------------| -| Normal text (< 18px / < 14px bold) | 4.5:1 | 7:1 | -| Large text (≥ 18px / ≥ 14px bold) | 3:1 | 4.5:1 | -| UI components & graphics | 3:1 | 3:1 | - -```css -/* ❌ Low contrast (2.5:1) */ -.low-contrast { - color: #999; - background: #fff; -} - -/* ✅ Sufficient contrast (7:1) */ -.high-contrast { - color: #333; - background: #fff; -} - -/* ✅ Focus states need contrast too */ -:focus-visible { - outline: 2px solid #005fcc; - outline-offset: 2px; -} -``` - -**Don't rely on color alone:** -```html - - - - - -
- - - - Please enter a valid email address - -
-``` - -### Media alternatives (1.2) - -```html - - - - - -
- Transcript -

Full transcript text...

-
-``` - ---- - -## Operable - -### Keyboard accessible (2.1) - -**All functionality must be keyboard accessible:** -```javascript -// ❌ Only handles click -element.addEventListener('click', handleAction); - -// ✅ Handles both click and keyboard -element.addEventListener('click', handleAction); -element.addEventListener('keydown', (e) => { - if (e.key === 'Enter' || e.key === ' ') { - e.preventDefault(); - handleAction(); - } -}); -``` - -**No keyboard traps:** -```javascript -// Modal focus management -function openModal(modal) { - const focusableElements = modal.querySelectorAll( - 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' - ); - const firstElement = focusableElements[0]; - const lastElement = focusableElements[focusableElements.length - 1]; - - // Trap focus within modal - modal.addEventListener('keydown', (e) => { - if (e.key === 'Tab') { - if (e.shiftKey && document.activeElement === firstElement) { - e.preventDefault(); - lastElement.focus(); - } else if (!e.shiftKey && document.activeElement === lastElement) { - e.preventDefault(); - firstElement.focus(); - } - } - if (e.key === 'Escape') { - closeModal(); - } - }); - - firstElement.focus(); -} -``` - -### Focus visible (2.4.7) - -```css -/* ❌ Never remove focus outlines */ -*:focus { outline: none; } - -/* ✅ Use :focus-visible for keyboard-only focus */ -:focus { - outline: none; -} - -:focus-visible { - outline: 2px solid #005fcc; - outline-offset: 2px; -} - -/* ✅ Or custom focus styles */ -button:focus-visible { - box-shadow: 0 0 0 3px rgba(0, 95, 204, 0.5); -} -``` - -### Skip links (2.4.1) - -```html - - -
-
- -
- -``` - -```css -.skip-link { - position: absolute; - top: -40px; - left: 0; - background: #000; - color: #fff; - padding: 8px 16px; - z-index: 100; -} - -.skip-link:focus { - top: 0; -} -``` - -### Timing (2.2) - -```javascript -// Allow users to extend time limits -function showSessionWarning() { - const modal = createModal({ - title: 'Session Expiring', - content: 'Your session will expire in 2 minutes.', - actions: [ - { label: 'Extend session', action: extendSession }, - { label: 'Log out', action: logout } - ], - timeout: 120000 // 2 minutes to respond - }); -} -``` - -### Motion (2.3) - -```css -/* Respect reduced motion preference */ -@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; - } -} -``` - ---- - -## Understandable - -### Page language (3.1.1) - -```html - - - - - - - -

The French word for hello is bonjour.

-``` - -### Consistent navigation (3.2.3) - -```html - - -``` - -### Form labels (3.3.2) - -```html - - - - - - - - - - - - - -

- Must be at least 8 characters with one number. -

-``` - -### Error handling (3.3.1, 3.3.3) - -```html - -
-
- - - -
-
-``` - -```javascript -// Focus first error on submit -form.addEventListener('submit', (e) => { - const firstError = form.querySelector('[aria-invalid="true"]'); - if (firstError) { - e.preventDefault(); - firstError.focus(); - - // Announce error summary - const errorSummary = document.getElementById('error-summary'); - errorSummary.textContent = `${errors.length} errors found. Please fix them and try again.`; - errorSummary.focus(); - } -}); -``` - ---- - -## Robust - -### Valid HTML (4.1.1) - -```html - -
...
-
...
- - - - - -
...
- - - -Click -``` - -### ARIA usage (4.1.2) - -**Prefer native elements:** -```html - -
Click me
- - - - - -
Option
- - - -``` - -**When ARIA is needed:** -```html - -
- - -
-
- -
- -``` - -### Live regions (4.1.3) - -```html - -
- -
- - -
- -
-``` - -```javascript -// Announce dynamic content changes -function showNotification(message, type = 'polite') { - const container = document.getElementById(`${type}-announcer`); - container.textContent = ''; // Clear first - requestAnimationFrame(() => { - container.textContent = message; - }); -} -``` - ---- - -## Testing checklist - -### Automated testing -```bash -# Lighthouse accessibility audit -npx lighthouse https://example.com --only-categories=accessibility - -# axe-core -npm install @axe-core/cli -g -axe https://example.com -``` - -### Manual testing - -- [ ] **Keyboard navigation:** Tab through entire page, use Enter/Space to activate -- [ ] **Screen reader:** Test with VoiceOver (Mac), NVDA (Windows), or TalkBack (Android) -- [ ] **Zoom:** Content usable at 200% zoom -- [ ] **High contrast:** Test with Windows High Contrast Mode -- [ ] **Reduced motion:** Test with `prefers-reduced-motion: reduce` -- [ ] **Focus order:** Logical and follows visual order - -### Screen reader commands - -| Action | VoiceOver (Mac) | NVDA (Windows) | -|--------|-----------------|----------------| -| Start/Stop | ⌘ + F5 | Ctrl + Alt + N | -| Next item | VO + → | ↓ | -| Previous item | VO + ← | ↑ | -| Activate | VO + Space | Enter | -| Headings list | VO + U, then arrows | H / Shift + H | -| Links list | VO + U | K / Shift + K | - ---- - -## Common issues by impact - -### Critical (fix immediately) -1. Missing form labels -2. Missing image alt text -3. Insufficient color contrast -4. Keyboard traps -5. No focus indicators - -### Serious (fix before launch) -1. Missing page language -2. Missing heading structure -3. Non-descriptive link text -4. Auto-playing media -5. Missing skip links - -### Moderate (fix soon) -1. Missing ARIA labels on icons -2. Inconsistent navigation -3. Missing error identification -4. Timing without controls -5. Missing landmark regions - -## References - -- [WCAG 2.1 Quick Reference](https://www.w3.org/WAI/WCAG21/quickref/) -- [WAI-ARIA Authoring Practices](https://www.w3.org/WAI/ARIA/apg/) -- [Deque axe Rules](https://dequeuniversity.com/rules/axe/) -- [Web Quality Audit](../web-quality-audit/SKILL.md) diff --git a/.agents/skills/accessibility/references/WCAG.md b/.agents/skills/accessibility/references/WCAG.md deleted file mode 100644 index a2b7c98..0000000 --- a/.agents/skills/accessibility/references/WCAG.md +++ /dev/null @@ -1,162 +0,0 @@ -# WCAG 2.1 Quick Reference - -## Success criteria by level - -### Level A (minimum) - -| Criterion | Description | -|-----------|-------------| -| **1.1.1** Non-text Content | All images, icons have text alternatives | -| **1.2.1** Audio-only/Video-only | Provide transcript or audio description | -| **1.2.2** Captions | Video with audio has captions | -| **1.2.3** Audio Description | Video has audio description | -| **1.3.1** Info and Relationships | Information conveyed through presentation is available programmatically | -| **1.3.2** Meaningful Sequence | Reading order is logical | -| **1.3.3** Sensory Characteristics | Instructions don't rely solely on shape, color, size, location, orientation, or sound | -| **1.4.1** Use of Color | Color is not the only visual means of conveying information | -| **1.4.2** Audio Control | Audio playing automatically can be paused/stopped | -| **2.1.1** Keyboard | All functionality available via keyboard | -| **2.1.2** No Keyboard Trap | Keyboard focus can be moved away from any component | -| **2.1.4** Character Key Shortcuts | Single-key shortcuts can be turned off or remapped | -| **2.2.1** Timing Adjustable | Time limits can be extended | -| **2.2.2** Pause, Stop, Hide | Moving/blinking content can be paused | -| **2.3.1** Three Flashes | Nothing flashes more than 3 times per second | -| **2.4.1** Bypass Blocks | Skip link or landmark navigation available | -| **2.4.2** Page Titled | Pages have descriptive titles | -| **2.4.3** Focus Order | Focus order preserves meaning | -| **2.4.4** Link Purpose | Link purpose clear from link text or context | -| **2.5.1** Pointer Gestures | Multi-point gestures have single-pointer alternatives | -| **2.5.2** Pointer Cancellation | Down-event doesn't trigger action (use up-event or click) | -| **2.5.3** Label in Name | Accessible name contains visible label text | -| **2.5.4** Motion Actuation | Motion-triggered functions have alternatives | -| **3.1.1** Language of Page | Default language specified in HTML | -| **3.2.1** On Focus | Focus doesn't trigger unexpected changes | -| **3.2.2** On Input | Input doesn't trigger unexpected changes | -| **3.3.1** Error Identification | Input errors clearly described | -| **3.3.2** Labels or Instructions | Form inputs have labels or instructions | -| **4.1.1** Parsing | HTML is well-formed (no duplicate IDs, proper nesting) | -| **4.1.2** Name, Role, Value | UI components have accessible names and correct roles | - -### Level AA (standard) - -| Criterion | Description | -|-----------|-------------| -| **1.2.4** Captions (Live) | Live audio has captions | -| **1.2.5** Audio Description | Pre-recorded video has audio description | -| **1.3.4** Orientation | Content doesn't restrict orientation | -| **1.3.5** Identify Input Purpose | Input purpose can be programmatically determined | -| **1.4.3** Contrast (Minimum) | 4.5:1 for normal text, 3:1 for large text | -| **1.4.4** Resize Text | Text can be resized to 200% without loss of functionality | -| **1.4.5** Images of Text | Text used instead of images of text | -| **1.4.10** Reflow | Content reflows at 320px width without horizontal scroll | -| **1.4.11** Non-text Contrast | UI components have 3:1 contrast | -| **1.4.12** Text Spacing | Content adapts to text spacing changes | -| **1.4.13** Content on Hover/Focus | Additional content is dismissible, hoverable, persistent | -| **2.4.5** Multiple Ways | Multiple ways to find pages | -| **2.4.6** Headings and Labels | Headings and labels are descriptive | -| **2.4.7** Focus Visible | Focus indicator is visible | -| **3.1.2** Language of Parts | Language changes are marked | -| **3.2.3** Consistent Navigation | Navigation is consistent across pages | -| **3.2.4** Consistent Identification | Same functionality uses same labels | -| **3.3.3** Error Suggestion | Error corrections suggested when known | -| **3.3.4** Error Prevention (Legal) | Actions can be reversed or confirmed | -| **4.1.3** Status Messages | Status messages announced to screen readers | - -### Level AAA (enhanced) - -| Criterion | Description | -|-----------|-------------| -| **1.4.6** Contrast (Enhanced) | 7:1 for normal text, 4.5:1 for large text | -| **1.4.8** Visual Presentation | Foreground/background colors can be selected | -| **1.4.9** Images of Text (No Exception) | No images of text | -| **2.1.3** Keyboard (No Exception) | All functionality keyboard accessible | -| **2.2.3** No Timing | No time limits | -| **2.2.4** Interruptions | Interruptions can be postponed | -| **2.2.5** Re-authenticating | Data preserved on re-authentication | -| **2.2.6** Timeouts | Users warned about data loss from inactivity | -| **2.3.2** Three Flashes | No content flashes more than 3 times | -| **2.3.3** Animation from Interactions | Motion animation can be disabled | -| **2.4.8** Location | User location within site is available | -| **2.4.9** Link Purpose (Link Only) | Link purpose clear from link text alone | -| **2.4.10** Section Headings | Sections have headings | -| **3.1.3** Unusual Words | Definitions available for unusual words | -| **3.1.4** Abbreviations | Abbreviations expanded | -| **3.1.5** Reading Level | Alternative content for complex text | -| **3.1.6** Pronunciation | Pronunciation available where needed | -| **3.2.5** Change on Request | Changes initiated only by user | -| **3.3.5** Help | Context-sensitive help available | -| **3.3.6** Error Prevention (All) | All form submissions can be reviewed | - -## Common ARIA patterns - -### Buttons -```html - - - -``` - -### Links -```html -Descriptive link text - - - External site - (opens in new tab) - -``` - -### Form fields -```html - - -

We'll never share your email.

-``` - -### Error states -```html - - - -``` - -### Navigation -```html - -``` - -### Modals -```html -
-

Confirm Action

- -
-``` - -### Live regions -```html - -
Status update here
- - -
Error message here
- - -
Loading complete
-``` - -## Testing tools - -| Tool | Type | URL | -|------|------|-----| -| axe DevTools | Browser extension | [deque.com/axe](https://www.deque.com/axe/) | -| WAVE | Browser extension | [wave.webaim.org](https://wave.webaim.org/) | -| Lighthouse | Built into Chrome | DevTools → Lighthouse | -| NVDA | Screen reader (Windows) | [nvaccess.org](https://www.nvaccess.org/) | -| VoiceOver | Screen reader (Mac) | Built into macOS | -| Colour Contrast Analyser | Desktop app | [tpgi.com](https://www.tpgi.com/color-contrast-checker/) | diff --git a/.agents/skills/application-performance-performance-optimization/SKILL.md b/.agents/skills/application-performance-performance-optimization/SKILL.md deleted file mode 100644 index 6d10be7..0000000 --- a/.agents/skills/application-performance-performance-optimization/SKILL.md +++ /dev/null @@ -1,156 +0,0 @@ ---- -name: application-performance-performance-optimization -description: "Optimize end-to-end application performance with profiling, observability, and backend/frontend tuning. Use when coordinating performance optimization across the stack." -risk: unknown -source: community ---- - -Optimize application performance end-to-end using specialized performance and optimization agents: - -[Extended thinking: This workflow orchestrates a comprehensive performance optimization process across the entire application stack. Starting with deep profiling and baseline establishment, the workflow progresses through targeted optimizations in each system layer, validates improvements through load testing, and establishes continuous monitoring for sustained performance. Each phase builds on insights from previous phases, creating a data-driven optimization strategy that addresses real bottlenecks rather than theoretical improvements. The workflow emphasizes modern observability practices, user-centric performance metrics, and cost-effective optimization strategies.] - -## Use this skill when - -- Coordinating performance optimization across backend, frontend, and infrastructure -- Establishing baselines and profiling to identify bottlenecks -- Designing load tests, performance budgets, or capacity plans -- Building observability for performance and reliability targets - -## Do not use this skill when - -- The task is a small localized fix with no broader performance goals -- There is no access to metrics, tracing, or profiling data -- The request is unrelated to performance or scalability - -## Instructions - -1. Confirm performance goals, constraints, and target metrics. -2. Establish baselines with profiling, tracing, and real-user data. -3. Execute phased optimizations across the stack with measurable impact. -4. Validate improvements and set guardrails to prevent regressions. - -## Safety - -- Avoid load testing production without approvals and safeguards. -- Roll out performance changes gradually with rollback plans. - -## Phase 1: Performance Profiling & Baseline - -### 1. Comprehensive Performance Profiling - -- Use Task tool with subagent_type="performance-engineer" -- Prompt: "Profile application performance comprehensively for: $ARGUMENTS. Generate flame graphs for CPU usage, heap dumps for memory analysis, trace I/O operations, and identify hot paths. Use APM tools like DataDog or New Relic if available. Include database query profiling, API response times, and frontend rendering metrics. Establish performance baselines for all critical user journeys." -- Context: Initial performance investigation -- Output: Detailed performance profile with flame graphs, memory analysis, bottleneck identification, baseline metrics - -### 2. Observability Stack Assessment - -- Use Task tool with subagent_type="observability-engineer" -- Prompt: "Assess current observability setup for: $ARGUMENTS. Review existing monitoring, distributed tracing with OpenTelemetry, log aggregation, and metrics collection. Identify gaps in visibility, missing metrics, and areas needing better instrumentation. Recommend APM tool integration and custom metrics for business-critical operations." -- Context: Performance profile from step 1 -- Output: Observability assessment report, instrumentation gaps, monitoring recommendations - -### 3. User Experience Analysis - -- Use Task tool with subagent_type="performance-engineer" -- Prompt: "Analyze user experience metrics for: $ARGUMENTS. Measure Core Web Vitals (LCP, FID, CLS), page load times, time to interactive, and perceived performance. Use Real User Monitoring (RUM) data if available. Identify user journeys with poor performance and their business impact." -- Context: Performance baselines from step 1 -- Output: UX performance report, Core Web Vitals analysis, user impact assessment - -## Phase 2: Database & Backend Optimization - -### 4. Database Performance Optimization - -- Use Task tool with subagent_type="database-cloud-optimization::database-optimizer" -- Prompt: "Optimize database performance for: $ARGUMENTS based on profiling data: {context_from_phase_1}. Analyze slow query logs, create missing indexes, optimize execution plans, implement query result caching with Redis/Memcached. Review connection pooling, prepared statements, and batch processing opportunities. Consider read replicas and database sharding if needed." -- Context: Performance bottlenecks from phase 1 -- Output: Optimized queries, new indexes, caching strategy, connection pool configuration - -### 5. Backend Code & API Optimization - -- Use Task tool with subagent_type="backend-development::backend-architect" -- Prompt: "Optimize backend services for: $ARGUMENTS targeting bottlenecks: {context_from_phase_1}. Implement efficient algorithms, add application-level caching, optimize N+1 queries, use async/await patterns effectively. Implement pagination, response compression, GraphQL query optimization, and batch API operations. Add circuit breakers and bulkheads for resilience." -- Context: Database optimizations from step 4, profiling data from phase 1 -- Output: Optimized backend code, caching implementation, API improvements, resilience patterns - -### 6. Microservices & Distributed System Optimization - -- Use Task tool with subagent_type="performance-engineer" -- Prompt: "Optimize distributed system performance for: $ARGUMENTS. Analyze service-to-service communication, implement service mesh optimizations, optimize message queue performance (Kafka/RabbitMQ), reduce network hops. Implement distributed caching strategies and optimize serialization/deserialization." -- Context: Backend optimizations from step 5 -- Output: Service communication improvements, message queue optimization, distributed caching setup - -## Phase 3: Frontend & CDN Optimization - -### 7. Frontend Bundle & Loading Optimization - -- Use Task tool with subagent_type="frontend-developer" -- Prompt: "Optimize frontend performance for: $ARGUMENTS targeting Core Web Vitals: {context_from_phase_1}. Implement code splitting, tree shaking, lazy loading, and dynamic imports. Optimize bundle sizes with webpack/rollup analysis. Implement resource hints (prefetch, preconnect, preload). Optimize critical rendering path and eliminate render-blocking resources." -- Context: UX analysis from phase 1, backend optimizations from phase 2 -- Output: Optimized bundles, lazy loading implementation, improved Core Web Vitals - -### 8. CDN & Edge Optimization - -- Use Task tool with subagent_type="cloud-infrastructure::cloud-architect" -- Prompt: "Optimize CDN and edge performance for: $ARGUMENTS. Configure CloudFlare/CloudFront for optimal caching, implement edge functions for dynamic content, set up image optimization with responsive images and WebP/AVIF formats. Configure HTTP/2 and HTTP/3, implement Brotli compression. Set up geographic distribution for global users." -- Context: Frontend optimizations from step 7 -- Output: CDN configuration, edge caching rules, compression setup, geographic optimization - -### 9. Mobile & Progressive Web App Optimization - -- Use Task tool with subagent_type="frontend-mobile-development::mobile-developer" -- Prompt: "Optimize mobile experience for: $ARGUMENTS. Implement service workers for offline functionality, optimize for slow networks with adaptive loading. Reduce JavaScript execution time for mobile CPUs. Implement virtual scrolling for long lists. Optimize touch responsiveness and smooth animations. Consider React Native/Flutter specific optimizations if applicable." -- Context: Frontend optimizations from steps 7-8 -- Output: Mobile-optimized code, PWA implementation, offline functionality - -## Phase 4: Load Testing & Validation - -### 10. Comprehensive Load Testing - -- Use Task tool with subagent_type="performance-engineer" -- Prompt: "Conduct comprehensive load testing for: $ARGUMENTS using k6/Gatling/Artillery. Design realistic load scenarios based on production traffic patterns. Test normal load, peak load, and stress scenarios. Include API testing, browser-based testing, and WebSocket testing if applicable. Measure response times, throughput, error rates, and resource utilization at various load levels." -- Context: All optimizations from phases 1-3 -- Output: Load test results, performance under load, breaking points, scalability analysis - -### 11. Performance Regression Testing - -- Use Task tool with subagent_type="performance-testing-review::test-automator" -- Prompt: "Create automated performance regression tests for: $ARGUMENTS. Set up performance budgets for key metrics, integrate with CI/CD pipeline using GitHub Actions or similar. Create Lighthouse CI tests for frontend, API performance tests with Artillery, and database performance benchmarks. Implement automatic rollback triggers for performance regressions." -- Context: Load test results from step 10, baseline metrics from phase 1 -- Output: Performance test suite, CI/CD integration, regression prevention system - -## Phase 5: Monitoring & Continuous Optimization - -### 12. Production Monitoring Setup - -- Use Task tool with subagent_type="observability-engineer" -- Prompt: "Implement production performance monitoring for: $ARGUMENTS. Set up APM with DataDog/New Relic/Dynatrace, configure distributed tracing with OpenTelemetry, implement custom business metrics. Create Grafana dashboards for key metrics, set up PagerDuty alerts for performance degradation. Define SLIs/SLOs for critical services with error budgets." -- Context: Performance improvements from all previous phases -- Output: Monitoring dashboards, alert rules, SLI/SLO definitions, runbooks - -### 13. Continuous Performance Optimization - -- Use Task tool with subagent_type="performance-engineer" -- Prompt: "Establish continuous optimization process for: $ARGUMENTS. Create performance budget tracking, implement A/B testing for performance changes, set up continuous profiling in production. Document optimization opportunities backlog, create capacity planning models, and establish regular performance review cycles." -- Context: Monitoring setup from step 12, all previous optimization work -- Output: Performance budget tracking, optimization backlog, capacity planning, review process - -## Configuration Options - -- **performance_focus**: "latency" | "throughput" | "cost" | "balanced" (default: "balanced") -- **optimization_depth**: "quick-wins" | "comprehensive" | "enterprise" (default: "comprehensive") -- **tools_available**: ["datadog", "newrelic", "prometheus", "grafana", "k6", "gatling"] -- **budget_constraints**: Set maximum acceptable costs for infrastructure changes -- **user_impact_tolerance**: "zero-downtime" | "maintenance-window" | "gradual-rollout" - -## Success Criteria - -- **Response Time**: P50 < 200ms, P95 < 1s, P99 < 2s for critical endpoints -- **Core Web Vitals**: LCP < 2.5s, FID < 100ms, CLS < 0.1 -- **Throughput**: Support 2x current peak load with <1% error rate -- **Database Performance**: Query P95 < 100ms, no queries > 1s -- **Resource Utilization**: CPU < 70%, Memory < 80% under normal load -- **Cost Efficiency**: Performance per dollar improved by minimum 30% -- **Monitoring Coverage**: 100% of critical paths instrumented with alerting - -Performance optimization target: $ARGUMENTS diff --git a/.agents/skills/copywriting/SKILL.md b/.agents/skills/copywriting/SKILL.md deleted file mode 100644 index cbf9adf..0000000 --- a/.agents/skills/copywriting/SKILL.md +++ /dev/null @@ -1,252 +0,0 @@ ---- -name: copywriting -description: When the user wants to write, rewrite, or improve marketing copy for any page — including homepage, landing pages, pricing pages, feature pages, about pages, or product pages. Also use when the user says "write copy for," "improve this copy," "rewrite this page," "marketing copy," "headline help," "CTA copy," "value proposition," "tagline," "subheadline," "hero section copy," "above the fold," "this copy is weak," "make this more compelling," or "help me describe my product." Use this whenever someone is working on website text that needs to persuade or convert. For email copy, see email-sequence. For popup copy, see popup-cro. For editing existing copy, see copy-editing. -metadata: - version: 1.1.0 ---- - -# Copywriting - -You are an expert conversion copywriter. Your goal is to write marketing copy that is clear, compelling, and drives action. - -## Before Writing - -**Check for product marketing context first:** -If `.agents/product-marketing-context.md` exists (or `.claude/product-marketing-context.md` in older setups), read it before asking questions. Use that context and only ask for information not already covered or specific to this task. - -Gather this context (ask if not provided): - -### 1. Page Purpose -- What type of page? (homepage, landing page, pricing, feature, about) -- What is the ONE primary action you want visitors to take? - -### 2. Audience -- Who is the ideal customer? -- What problem are they trying to solve? -- What objections or hesitations do they have? -- What language do they use to describe their problem? - -### 3. Product/Offer -- What are you selling or offering? -- What makes it different from alternatives? -- What's the key transformation or outcome? -- Any proof points (numbers, testimonials, case studies)? - -### 4. Context -- Where is traffic coming from? (ads, organic, email) -- What do visitors already know before arriving? - ---- - -## Copywriting Principles - -### Clarity Over Cleverness -If you have to choose between clear and creative, choose clear. - -### Benefits Over Features -Features: What it does. Benefits: What that means for the customer. - -### Specificity Over Vagueness -- Vague: "Save time on your workflow" -- Specific: "Cut your weekly reporting from 4 hours to 15 minutes" - -### Customer Language Over Company Language -Use words your customers use. Mirror voice-of-customer from reviews, interviews, support tickets. - -### One Idea Per Section -Each section should advance one argument. Build a logical flow down the page. - ---- - -## Writing Style Rules - -### Core Principles - -1. **Simple over complex** — "Use" not "utilize," "help" not "facilitate" -2. **Specific over vague** — Avoid "streamline," "optimize," "innovative" -3. **Active over passive** — "We generate reports" not "Reports are generated" -4. **Confident over qualified** — Remove "almost," "very," "really" -5. **Show over tell** — Describe the outcome instead of using adverbs -6. **Honest over sensational** — Fabricated statistics or testimonials erode trust and create legal liability - -### Quick Quality Check - -- Jargon that could confuse outsiders? -- Sentences trying to do too much? -- Passive voice constructions? -- Exclamation points? (remove them) -- Marketing buzzwords without substance? - -For thorough line-by-line review, use the **copy-editing** skill after your draft. - ---- - -## Best Practices - -### Be Direct -Get to the point. Don't bury the value in qualifications. - -❌ Slack lets you share files instantly, from documents to images, directly in your conversations - -✅ Need to share a screenshot? Send as many documents, images, and audio files as your heart desires. - -### Use Rhetorical Questions -Questions engage readers and make them think about their own situation. -- "Hate returning stuff to Amazon?" -- "Tired of chasing approvals?" - -### Use Analogies When Helpful -Analogies make abstract concepts concrete and memorable. - -### Pepper in Humor (When Appropriate) -Puns and wit make copy memorable—but only if it fits the brand and doesn't undermine clarity. - ---- - -## Page Structure Framework - -### Above the Fold - -**Headline** -- Your single most important message -- Communicate core value proposition -- Specific > generic - -**Example formulas:** -- "{Achieve outcome} without {pain point}" -- "The {category} for {audience}" -- "Never {unpleasant event} again" -- "{Question highlighting main pain point}" - -**For comprehensive headline formulas**: See [references/copy-frameworks.md](references/copy-frameworks.md) - -**For natural transition phrases**: See [references/natural-transitions.md](references/natural-transitions.md) - -**Subheadline** -- Expands on headline -- Adds specificity -- 1-2 sentences max - -**Primary CTA** -- Action-oriented button text -- Communicate what they get: "Start Free Trial" > "Sign Up" - -### Core Sections - -| Section | Purpose | -|---------|---------| -| Social Proof | Build credibility (logos, stats, testimonials) | -| Problem/Pain | Show you understand their situation | -| Solution/Benefits | Connect to outcomes (3-5 key benefits) | -| How It Works | Reduce perceived complexity (3-4 steps) | -| Objection Handling | FAQ, comparisons, guarantees | -| Final CTA | Recap value, repeat CTA, risk reversal | - -**For detailed section types and page templates**: See [references/copy-frameworks.md](references/copy-frameworks.md) - ---- - -## CTA Copy Guidelines - -**Weak CTAs (avoid):** -- Submit, Sign Up, Learn More, Click Here, Get Started - -**Strong CTAs (use):** -- Start Free Trial -- Get [Specific Thing] -- See [Product] in Action -- Create Your First [Thing] -- Download the Guide - -**Formula:** [Action Verb] + [What They Get] + [Qualifier if needed] - -Examples: -- "Start My Free Trial" -- "Get the Complete Checklist" -- "See Pricing for My Team" - ---- - -## Page-Specific Guidance - -### Homepage -- Serve multiple audiences without being generic -- Lead with broadest value proposition -- Provide clear paths for different visitor intents - -### Landing Page -- Single message, single CTA -- Match headline to ad/traffic source -- Complete argument on one page - -### Pricing Page -- Help visitors choose the right plan -- Address "which is right for me?" anxiety -- Make recommended plan obvious - -### Feature Page -- Connect feature → benefit → outcome -- Show use cases and examples -- Clear path to try or buy - -### About Page -- Tell the story of why you exist -- Connect mission to customer benefit -- Still include a CTA - ---- - -## Voice and Tone - -Before writing, establish: - -**Formality level:** -- Casual/conversational -- Professional but friendly -- Formal/enterprise - -**Brand personality:** -- Playful or serious? -- Bold or understated? -- Technical or accessible? - -Maintain consistency, but adjust intensity: -- Headlines can be bolder -- Body copy should be clearer -- CTAs should be action-oriented - ---- - -## Output Format - -When writing copy, provide: - -### Page Copy -Organized by section: -- Headline, Subheadline, CTA -- Section headers and body copy -- Secondary CTAs - -### Annotations -For key elements, explain: -- Why you made this choice -- What principle it applies - -### Alternatives -For headlines and CTAs, provide 2-3 options: -- Option A: [copy] — [rationale] -- Option B: [copy] — [rationale] - -### Meta Content (if relevant) -- Page title (for SEO) -- Meta description - ---- - -## Related Skills - -- **copy-editing**: For polishing existing copy (use after your draft) -- **page-cro**: If page structure/strategy needs work, not just copy -- **email-sequence**: For email copywriting -- **popup-cro**: For popup and modal copy -- **ab-test-setup**: To test copy variations diff --git a/.agents/skills/copywriting/evals/evals.json b/.agents/skills/copywriting/evals/evals.json deleted file mode 100644 index 1721cc1..0000000 --- a/.agents/skills/copywriting/evals/evals.json +++ /dev/null @@ -1,111 +0,0 @@ -{ - "skill_name": "copywriting", - "evals": [ - { - "id": 1, - "prompt": "Write homepage copy for a SaaS tool that automates employee onboarding. Target audience is HR directors at mid-size companies (200-2000 employees). Main differentiator is that it integrates with all major HRIS systems and cuts onboarding time from 2 weeks to 2 days.", - "expected_output": "Should check for product-marketing-context.md first. Should write full page copy organized by section: Headline, Subheadline, CTA (above the fold), then Social Proof, Problem/Pain, Solution/Benefits, How It Works, Objection Handling, and Final CTA. Should follow copywriting principles: clarity over cleverness, benefits over features, specificity (use the '2 weeks to 2 days' stat), customer language. Headline should communicate core value proposition. CTAs should be action-oriented ('Start Free Trial' not 'Submit'). Should provide 2-3 headline alternatives with rationale. Should include annotations explaining key copy choices. Should include meta content (SEO page title and meta description).", - "assertions": [ - "Checks for product-marketing-context.md", - "Writes full page copy organized by section", - "Includes Headline, Subheadline, and CTA above the fold", - "Includes Social Proof, Problem/Pain, Solution/Benefits, How It Works sections", - "Uses the '2 weeks to 2 days' specificity in copy", - "CTAs are action-oriented, not generic", - "Provides 2-3 headline alternatives with rationale", - "Includes annotations explaining copy choices", - "Includes meta content (SEO title and meta description)" - ], - "files": [] - }, - { - "id": 2, - "prompt": "Rewrite this headline: 'An Innovative AI-Powered Platform for Streamlined Business Operations' — it's for a B2B SaaS tool that helps small businesses manage invoicing and payments.", - "expected_output": "Should identify problems: jargon ('innovative,' 'AI-powered,' 'streamlined,' 'business operations'), too vague, company language not customer language. Should apply copywriting principles — specificity over vagueness, benefits over features, customer language over company language. Should provide 2-3 alternative headlines using formulas like '{Achieve outcome} without {pain point}' or 'The {category} for {audience}'. Each alternative should include rationale. Should also suggest a subheadline that adds specificity.", - "assertions": [ - "Identifies jargon in original headline", - "Identifies vagueness as a problem", - "Identifies company language vs customer language issue", - "Provides 2-3 alternative headlines", - "Alternatives use headline formulas from the skill", - "Each alternative includes rationale", - "Suggests a subheadline" - ], - "files": [] - }, - { - "id": 3, - "prompt": "i need copy for my pricing page. we have three plans: starter ($29/mo), pro ($79/mo), business ($199/mo). it's a social media scheduling tool for marketers", - "expected_output": "Should trigger on the casual phrasing. Should ask or infer audience context. Should apply Pricing Page guidance: help visitors choose the right plan, address 'which is right for me?' anxiety, make recommended plan obvious. Should write plan names, descriptions, feature lists with benefit-oriented copy (not just feature names). Should include a page headline that addresses the pricing decision. CTAs should be specific per plan. Should handle objection handling (FAQ copy). Should provide alternatives for key elements.", - "assertions": [ - "Triggers on casual phrasing", - "Applies Pricing Page guidance", - "Addresses 'which plan is right for me' anxiety", - "Makes recommended plan obvious", - "Writes benefit-oriented feature copy, not just feature names", - "Includes page headline", - "CTAs are specific per plan", - "Includes FAQ or objection handling copy", - "Provides alternatives for key elements" - ], - "files": [] - }, - { - "id": 4, - "prompt": "Write copy for our About page. We're a 3-person startup that built a developer tool for database migrations. Founded because we kept losing data during migrations at our last jobs. Tone should be professional but human.", - "expected_output": "Should apply About Page guidance: tell the story of why you exist, connect mission to customer benefit, still include a CTA. Should adapt voice and tone to 'professional but human' as specified. Should tell the founder origin story authentically. Should connect the personal pain to the customer's pain. Should include a CTA even on the About page. Copy should follow style rules: active voice, confident, specific. Should NOT be overly corporate or generic.", - "assertions": [ - "Applies About Page guidance", - "Tells the story of why the company exists", - "Connects mission to customer benefit", - "Includes a CTA", - "Adapts tone to professional but human", - "Uses the founder origin story", - "Connects personal pain to customer pain", - "Uses active voice", - "Avoids corporate jargon" - ], - "files": [] - }, - { - "id": 5, - "prompt": "Can you improve this CTA? We currently have 'Learn More' on our feature page for our analytics dashboard product.", - "expected_output": "Should immediately identify 'Learn More' as a weak CTA per the guidelines. Should apply the CTA formula: [Action Verb] + [What They Get] + [Qualifier]. Should provide 2-3 strong alternatives like 'See the Dashboard in Action,' 'Start Your Free Trial,' or 'Explore Analytics Features.' Each alternative should include rationale and context for when it works best. Should also consider CTA hierarchy — whether this is a primary or secondary CTA, and suggest complementary CTAs if relevant.", - "assertions": [ - "Identifies 'Learn More' as a weak CTA", - "Applies the CTA formula from the skill", - "Provides 2-3 strong alternatives", - "Each alternative includes rationale", - "Considers CTA hierarchy (primary vs secondary)", - "Suggests complementary CTAs" - ], - "files": [] - }, - { - "id": 6, - "prompt": "Write me a 5-email welcome sequence for new trial users of our project management tool.", - "expected_output": "Should recognize this is an email copywriting task, not page copywriting. Should defer to or cross-reference the email-sequence skill, which specifically handles email sequences, drip campaigns, and lifecycle emails. May provide brief general guidance but should make clear that email-sequence is the right skill for this task.", - "assertions": [ - "Recognizes this as email sequence work", - "References or defers to email-sequence skill", - "Does not attempt to write a full email sequence using page copywriting patterns" - ], - "files": [] - }, - { - "id": 7, - "prompt": "Review this copy and tell me what's wrong: 'We are extremely excited to announce our revolutionary, cutting-edge platform that will totally transform how businesses optimize their workflows! Sign up now!!'", - "expected_output": "Should apply the Quick Quality Check. Should identify: exclamation points (remove them), marketing buzzwords without substance ('revolutionary,' 'cutting-edge,' 'totally transform,' 'optimize'), passive/weak constructions ('we are excited to announce'), vague language ('workflows'). Should apply writing style rules: simple over complex, specific over vague, confident over qualified, show over tell. Should rewrite the copy following these principles. Should provide 2-3 alternatives.", - "assertions": [ - "Identifies exclamation point overuse", - "Identifies marketing buzzwords without substance", - "Identifies vague language", - "Applies writing style rules", - "Rewrites the copy following principles", - "Provides alternatives", - "Result is specific, clear, and jargon-free" - ], - "files": [] - } - ] -} diff --git a/.agents/skills/copywriting/references/copy-frameworks.md b/.agents/skills/copywriting/references/copy-frameworks.md deleted file mode 100644 index 0abc812..0000000 --- a/.agents/skills/copywriting/references/copy-frameworks.md +++ /dev/null @@ -1,344 +0,0 @@ -# Copy Frameworks Reference - -Headline formulas, page section types, and structural templates. - -## Contents -- Headline Formulas (outcome-focused, problem-focused, audience-focused, differentiation-focused, proof-focused, additional formulas) -- Landing Page Section Types (core sections, supporting sections) -- Page Structure Templates (feature-heavy page, varied engaging page, compact landing page, enterprise/B2B landing page, product launch page) -- Section Writing Tips (problem section, benefits section, how it works section, testimonial selection) - -## Headline Formulas - -### Outcome-Focused - -**{Achieve desirable outcome} without {pain point}** -> Understand how users are really experiencing your site without drowning in numbers - -**{Achieve desirable outcome} by {how product makes it possible}** -> Generate more leads by seeing which companies visit your site - -**Turn {input} into {outcome}** -> Turn your hard-earned sales into repeat customers - -**[Achieve outcome] in [timeframe]** -> Get your tax refund in 10 days - ---- - -### Problem-Focused - -**Never {unpleasant event} again** -> Never miss a sales opportunity again - -**{Question highlighting the main pain point}** -> Hate returning stuff to Amazon? - -**Stop [pain]. Start [pleasure].** -> Stop chasing invoices. Start getting paid on time. - ---- - -### Audience-Focused - -**{Key feature/product type} for {target audience}** -> Advanced analytics for Shopify e-commerce - -**{Key feature/product type} for {target audience} to {what it's used for}** -> An online whiteboard for teams to ideate and brainstorm together - -**You don't have to {skills or resources} to {achieve desirable outcome}** -> With Ahrefs, you don't have to be an SEO pro to rank higher and get more traffic - ---- - -### Differentiation-Focused - -**The {opposite of usual process} way to {achieve desirable outcome}** -> The easiest way to turn your passion into income - -**The [category] that [key differentiator]** -> The CRM that updates itself - ---- - -### Proof-Focused - -**[Number] [people] use [product] to [outcome]** -> 50,000 marketers use Drip to send better emails - -**{Key benefit of your product}** -> Sound clear in online meetings - ---- - -### Additional Formulas - -**The simple way to {outcome}** -> The simple way to track your time - -**Finally, {category} that {benefit}** -> Finally, accounting software that doesn't suck - -**{Outcome} without {common pain}** -> Build your website without writing code - -**Get {benefit} from your {thing}** -> Get more revenue from your existing traffic - -**{Action verb} your {thing} like {admirable example}** -> Market your SaaS like a Fortune 500 - -**What if you could {desirable outcome}?** -> What if you could close deals 30% faster? - -**Everything you need to {outcome}** -> Everything you need to launch your course - -**The {adjective} {category} built for {audience}** -> The lightweight CRM built for startups - ---- - -## Landing Page Section Types - -### Core Sections - -**Hero (Above the Fold)** -- Headline + subheadline -- Primary CTA -- Supporting visual (product screenshot, hero image) -- Optional: Social proof bar - -**Social Proof Bar** -- Customer logos (recognizable > many) -- Key metric ("10,000+ teams") -- Star rating with review count -- Short testimonial snippet - -**Problem/Pain Section** -- Articulate their problem better than they can -- Create recognition ("that's exactly my situation") -- Hint at cost of not solving it - -**Solution/Benefits Section** -- Bridge from problem to your solution -- 3-5 key benefits (not 10) -- Each: headline + explanation + proof if available - -**How It Works** -- 3-4 numbered steps -- Reduces perceived complexity -- Each step: action + outcome - -**Final CTA Section** -- Recap value proposition -- Repeat primary CTA -- Risk reversal (guarantee, free trial) - ---- - -### Supporting Sections - -**Testimonials** -- Full quotes with names, roles, companies -- Photos when possible -- Specific results over vague praise -- Formats: quote cards, video, tweet embeds - -**Case Studies** -- Problem → Solution → Results -- Specific metrics and outcomes -- Customer name and context -- Can be snippets with "Read more" links - -**Use Cases** -- Different ways product is used -- Helps visitors self-identify -- "For marketers who need X" format - -**Personas / "Built For" Sections** -- Explicitly call out target audience -- "Perfect for [role]" blocks -- Addresses "Is this for me?" question - -**FAQ Section** -- Address common objections -- Good for SEO -- Reduces support burden -- 5-10 most common questions - -**Comparison Section** -- vs. competitors (name them or don't) -- vs. status quo (spreadsheets, manual processes) -- Tables or side-by-side format - -**Integrations / Partners** -- Logos of tools you connect with -- "Works with your stack" messaging -- Builds credibility - -**Founder Story / Manifesto** -- Why you built this -- What you believe -- Emotional connection -- Differentiates from faceless competitors - -**Demo / Product Tour** -- Interactive demos -- Video walkthroughs -- GIF previews -- Shows product in action - -**Pricing Preview** -- Teaser even on non-pricing pages -- Starting price or "from $X/mo" -- Moves decision-makers forward - -**Guarantee / Risk Reversal** -- Money-back guarantee -- Free trial terms -- "Cancel anytime" -- Reduces friction - -**Stats Section** -- Key metrics that build credibility -- "10,000+ customers" -- "4.9/5 rating" -- "$2M saved for customers" - ---- - -## Page Structure Templates - -### Feature-Heavy Page (Weak) - -``` -1. Hero -2. Feature 1 -3. Feature 2 -4. Feature 3 -5. Feature 4 -6. CTA -``` - -This is a list, not a persuasive narrative. - ---- - -### Varied, Engaging Page (Strong) - -``` -1. Hero with clear value prop -2. Social proof bar (logos or stats) -3. Problem/pain section -4. How it works (3 steps) -5. Key benefits (2-3, not 10) -6. Testimonial -7. Use cases or personas -8. Comparison to alternatives -9. Case study snippet -10. FAQ -11. Final CTA with guarantee -``` - -This tells a story and addresses objections. - ---- - -### Compact Landing Page - -``` -1. Hero (headline, subhead, CTA, image) -2. Social proof bar -3. 3 key benefits with icons -4. Testimonial -5. How it works (3 steps) -6. Final CTA with guarantee -``` - -Good for ad landing pages where brevity matters. - ---- - -### Enterprise/B2B Landing Page - -``` -1. Hero (outcome-focused headline) -2. Logo bar (recognizable companies) -3. Problem section (business pain) -4. Solution overview -5. Use cases by role/department -6. Security/compliance section -7. Integration logos -8. Case study with metrics -9. ROI/value section -10. Contact/demo CTA -``` - -Addresses enterprise buyer concerns. - ---- - -### Product Launch Page - -``` -1. Hero with launch announcement -2. Video demo or walkthrough -3. Feature highlights (3-5) -4. Before/after comparison -5. Early testimonials -6. Launch pricing or early access offer -7. CTA with urgency -``` - -Good for ProductHunt, launches, or announcements. - ---- - -## Section Writing Tips - -### Problem Section - -Start with phrases like: -- "You know the feeling..." -- "If you're like most [role]..." -- "Every day, [audience] struggles with..." -- "We've all been there..." - -Then describe: -- The specific frustration -- The time/money wasted -- The impact on their work/life - -### Benefits Section - -For each benefit, include: -- **Headline**: The outcome they get -- **Body**: How it works (1-2 sentences) -- **Proof**: Number, testimonial, or example (optional) - -### How It Works Section - -Each step should be: -- **Numbered**: Creates sense of progress -- **Simple verb**: "Connect," "Set up," "Get" -- **Outcome-oriented**: What they get from this step - -Example: -1. Connect your tools (takes 2 minutes) -2. Set your preferences -3. Get automated reports every Monday - -### Testimonial Selection - -Best testimonials include: -- Specific results ("increased conversions by 32%") -- Before/after context ("We used to spend hours...") -- Role + company for credibility -- Something quotable and specific - -Avoid testimonials that just say: -- "Great product!" -- "Love it!" -- "Easy to use!" diff --git a/.agents/skills/copywriting/references/natural-transitions.md b/.agents/skills/copywriting/references/natural-transitions.md deleted file mode 100644 index ee72faa..0000000 --- a/.agents/skills/copywriting/references/natural-transitions.md +++ /dev/null @@ -1,272 +0,0 @@ -# Natural Transitions - -Transitional phrases to guide readers through your content. Good signposting improves readability, user engagement, and helps search engines understand content structure. - -Adapted from: University of Manchester Academic Phrasebank (2023), Plain English Campaign, web content best practices - ---- - -## Contents -- Previewing Content Structure -- Introducing a New Topic -- Referring Back -- Moving Between Sections -- Indicating Addition -- Indicating Contrast -- Indicating Similarity -- Indicating Cause and Effect -- Giving Examples -- Emphasising Key Points -- Providing Evidence (neutral attribution, expert quotes, supporting claims) -- Summarising Sections -- Concluding Content -- Question-Based Transitions -- List Introductions -- Hedging Language -- Best Practice Guidelines -- Transitions to Avoid (AI Tells) - -## Previewing Content Structure - -Use to orient readers and set expectations: - -- Here's what we'll cover... -- This guide walks you through... -- Below, you'll find... -- We'll start with X, then move to Y... -- First, let's look at... -- Let's break this down step by step. -- The sections below explain... - ---- - -## Introducing a New Topic - -- When it comes to X,... -- Regarding X,... -- Speaking of X,... -- Now let's talk about X. -- Another key factor is... -- X is worth exploring because... - ---- - -## Referring Back - -Use to connect ideas and reinforce key points: - -- As mentioned earlier,... -- As we covered above,... -- Remember when we discussed X? -- Building on that point,... -- Going back to X,... -- Earlier, we explained that... - ---- - -## Moving Between Sections - -- Now let's look at... -- Next up:... -- Moving on to... -- With that covered, let's turn to... -- Now that you understand X, here's Y. -- That brings us to... - ---- - -## Indicating Addition - -- Also,... -- Plus,... -- On top of that,... -- What's more,... -- Another benefit is... -- Beyond that,... -- In addition,... -- There's also... - -**Note:** Use "moreover" and "furthermore" sparingly. They can sound AI-generated when overused. - ---- - -## Indicating Contrast - -- However,... -- But,... -- That said,... -- On the flip side,... -- In contrast,... -- Unlike X, Y... -- While X is true, Y... -- Despite this,... - ---- - -## Indicating Similarity - -- Similarly,... -- Likewise,... -- In the same way,... -- Just like X, Y also... -- This mirrors... -- The same applies to... - ---- - -## Indicating Cause and Effect - -- So,... -- This means... -- As a result,... -- That's why... -- Because of this,... -- This leads to... -- The outcome?... -- Here's what happens:... - ---- - -## Giving Examples - -- For example,... -- For instance,... -- Here's an example:... -- Take X, for instance. -- Consider this:... -- A good example is... -- To illustrate,... -- Like when... -- Say you want to... - ---- - -## Emphasising Key Points - -- Here's the key takeaway:... -- The important thing is... -- What matters most is... -- Don't miss this:... -- Pay attention to... -- This is critical:... -- The bottom line?... - ---- - -## Providing Evidence - -Use when citing sources, data, or expert opinions: - -### Neutral attribution -- According to [Source],... -- [Source] reports that... -- Research shows that... -- Data from [Source] indicates... -- A study by [Source] found... - -### Expert quotes -- As [Expert] puts it,... -- [Expert] explains,... -- In the words of [Expert],... -- [Expert] notes that... - -### Supporting claims -- This is backed by... -- Evidence suggests... -- The numbers confirm... -- This aligns with findings from... - ---- - -## Summarising Sections - -- To recap,... -- Here's the short version:... -- In short,... -- The takeaway?... -- So what does this mean?... -- Let's pull this together:... -- Quick summary:... - ---- - -## Concluding Content - -- Wrapping up,... -- The bottom line is... -- Here's what to do next:... -- To sum up,... -- Final thoughts:... -- Ready to get started?... -- Now it's your turn. - -**Note:** Avoid "In conclusion" at the start of a paragraph. It's overused and signals AI writing. - ---- - -## Question-Based Transitions - -Useful for conversational tone and featured snippet optimization: - -- So what does this mean for you? -- But why does this matter? -- How do you actually do this? -- What's the catch? -- Sound complicated? It's not. -- Wondering where to start? -- Still not sure? Here's the breakdown. - ---- - -## List Introductions - -For numbered lists and step-by-step content: - -- Here's how to do it: -- Follow these steps: -- The process is straightforward: -- Here's what you need to know: -- Key things to consider: -- The main factors are: - ---- - -## Hedging Language - -For claims that need qualification or aren't absolute: - -- may, might, could -- tends to, generally -- often, usually, typically -- in most cases -- it appears that -- evidence suggests -- this can help -- many experts believe - ---- - -## Best Practice Guidelines - -1. **Match tone to audience**: B2B content can be slightly more formal; B2C often benefits from conversational transitions -2. **Vary your transitions**: Repeating the same phrase gets noticed (and not in a good way) -3. **Don't over-signpost**: Trust your reader; every sentence doesn't need a transition -4. **Use for scannability**: Transitions at paragraph starts help skimmers navigate -5. **Keep it natural**: Read aloud; if it sounds forced, simplify -6. **Front-load key info**: Put the important word or phrase early in the transition - ---- - -## Transitions to Avoid (AI Tells) - -These phrases are overused in AI-generated content: - -- "That being said,..." -- "It's worth noting that..." -- "At its core,..." -- "In today's digital landscape,..." -- "When it comes to the realm of..." -- "This begs the question..." -- "Let's delve into..." - -See the seo-audit skill's `references/ai-writing-detection.md` for a complete list of AI writing tells. diff --git a/.agents/skills/database-query-optimization/SKILL.md b/.agents/skills/database-query-optimization/SKILL.md deleted file mode 100644 index 9d6ed42..0000000 --- a/.agents/skills/database-query-optimization/SKILL.md +++ /dev/null @@ -1,217 +0,0 @@ ---- -name: database-query-optimization -description: Improve database query performance through indexing, query optimization, and execution plan analysis. Reduce response times and database load. ---- - -# Database Query Optimization - -## Overview - -Slow database queries are a common performance bottleneck. Optimization through indexing, efficient queries, and caching dramatically improves application performance. - -## When to Use - -- Slow response times -- High database CPU usage -- Performance regression -- New feature deployment -- Regular maintenance - -## Instructions - -### 1. **Query Analysis** - -```sql --- Analyze query performance - -EXPLAIN ANALYZE -SELECT users.id, users.name, COUNT(orders.id) as order_count -FROM users -LEFT JOIN orders ON users.id = orders.user_id -WHERE users.created_at > '2024-01-01' -GROUP BY users.id, users.name -ORDER BY order_count DESC; - --- Results show: --- - Seq Scan (slow) vs Index Scan (fast) --- - Rows: actual vs planned (high variance = bad) --- - Execution time (milliseconds) - --- Key metrics: --- - Sequential Scan: Full table read (slow) --- - Index Scan: Uses index (fast) --- - Nested Loop: Joins with loops --- - Sort: In-memory or disk sort -``` - -### 2. **Indexing Strategy** - -```yaml -Index Types: - -Single Column: - CREATE INDEX idx_users_email ON users(email); - Use: WHERE email = ? - Size: Small, quick to create - -Composite Index: - CREATE INDEX idx_orders_user_date - ON orders(user_id, created_at); - Use: WHERE user_id = ? AND created_at > ? - Order: Most selective first - -Covering Index: - CREATE INDEX idx_orders_covering - ON orders(user_id) INCLUDE (total_amount); - Benefit: No table lookup needed - -Partial Index: - CREATE INDEX idx_active_users - ON users(id) WHERE status = 'active'; - Benefit: Smaller, faster - -Full Text: - CREATE FULLTEXT INDEX idx_search - ON articles(title, content); - Use: Text search queries - ---- - -Index Rules: - -- Create indexes for WHERE conditions -- Create indexes for JOIN columns -- Create indexes for ORDER BY -- Don't over-index (slows writes) -- Monitor index usage -- Remove unused indexes -- Update statistics regularly -- Partial indexes for filtered queries - -Missing Index Query: -SELECT object_name, equality_columns -FROM sys.dm_db_missing_index_details -ORDER BY equality_columns; -``` - -### 3. **Query Optimization Techniques** - -```python -# Common optimization patterns - -# BEFORE (N+1 queries) -for user in users: - orders = db.query("SELECT * FROM orders WHERE user_id = ?", user.id) - # 1 + N queries - -# AFTER (single query with JOIN) -orders = db.query(""" - SELECT u.*, o.* FROM users u - LEFT JOIN orders o ON u.id = o.user_id - WHERE u.created_at > ? -""", date_threshold) - -# BEFORE (inefficient WHERE) -SELECT * FROM users -WHERE LOWER(email) = LOWER('Test@Example.com') -# Can't use index (function used) - -# AFTER (index-friendly) -SELECT * FROM users -WHERE email = 'test@example.com' -# Case-insensitive constraint + index - -# BEFORE (wildcard at start) -SELECT * FROM users WHERE email LIKE '%example.com' -# Can't use index (wildcard at start) - -# AFTER (wildcard at end) -SELECT * FROM users WHERE email LIKE 'user%' -# Can use index - -# BEFORE (slow aggregation) -SELECT user_id, COUNT(*) as cnt -FROM orders -GROUP BY user_id -ORDER BY cnt DESC -LIMIT 10 - -# AFTER (pre-aggregated) -SELECT user_id, order_count -FROM user_order_stats -WHERE order_count IS NOT NULL -ORDER BY order_count DESC -LIMIT 10 -``` - -### 4. **Optimization Checklist** - -```yaml -Analysis: - [ ] Run EXPLAIN ANALYZE on slow queries - [ ] Check actual vs estimated rows - [ ] Look for sequential scans - [ ] Identify expensive operations - [ ] Compare execution plans - -Indexing: - [ ] Index WHERE columns - [ ] Index JOIN columns - [ ] Index ORDER BY columns - [ ] Check unused indexes - [ ] Remove duplicate indexes - [ ] Create composite indexes strategically - [ ] Analyze index statistics - -Query Optimization: - [ ] Remove unnecessary columns (SELECT *) - [ ] Use JOINs instead of subqueries - [ ] Avoid functions in WHERE - [ ] Use wildcards carefully (avoid %) - [ ] Batch operations - [ ] Use LIMIT for result sets - [ ] Archive old data - -Caching: - [ ] Implement query caching - [ ] Cache aggregations - [ ] Use Redis for hot data - [ ] Invalidate strategically - -Monitoring: - [ ] Track slow queries - [ ] Monitor index usage - [ ] Set up alerts - [ ] Regular statistics update - [ ] Measure improvements - ---- - -Expected Improvements: - -With Proper Indexing: - - Sequential Scan → Index Scan - - Response time: 5 seconds → 50ms (100x faster) - - CPU usage: 80% → 20% - - Concurrent users: 100 → 1000 - -Quick Wins: - - Add index to frequently filtered column - - Fix N+1 queries - - Use LIMIT for large results - - Archive old data - - Expected: 20-50% improvement -``` - -## Key Points - -- EXPLAIN ANALYZE shows query execution -- Indexes must match WHERE/JOIN/ORDER BY -- Avoid functions in WHERE clauses -- Fix N+1 queries (join instead of loop) -- Monitor slow query log regularly -- Stats updates needed for accuracy -- Pre-calculate aggregations -- Archive historical data -- Use explain plans before/after -- Measure and monitor continuously diff --git a/.agents/skills/frontend-design/LICENSE.txt b/.agents/skills/frontend-design/LICENSE.txt deleted file mode 100644 index f433b1a..0000000 --- a/.agents/skills/frontend-design/LICENSE.txt +++ /dev/null @@ -1,177 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS diff --git a/.agents/skills/frontend-design/SKILL.md b/.agents/skills/frontend-design/SKILL.md deleted file mode 100644 index 5be498e..0000000 --- a/.agents/skills/frontend-design/SKILL.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -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 (examples include websites, landing pages, dashboards, React components, HTML/CSS layouts, or when styling/beautifying any web UI). Generates creative, polished code and UI design that avoids generic AI aesthetics. -license: Complete terms in LICENSE.txt ---- - -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. - -The user provides frontend requirements: a component, page, application, or interface to build. They may include context about the purpose, audience, or technical constraints. - -## Design Thinking - -Before coding, understand the context and commit to a BOLD aesthetic direction: -- **Purpose**: What problem does this interface solve? Who uses it? -- **Tone**: Pick an extreme: brutally minimal, maximalist chaos, retro-futuristic, organic/natural, luxury/refined, playful/toy-like, editorial/magazine, brutalist/raw, art deco/geometric, soft/pastel, industrial/utilitarian, etc. There are so many flavors to choose from. Use these for inspiration but design one that is true to the aesthetic direction. -- **Constraints**: Technical requirements (framework, performance, accessibility). -- **Differentiation**: What makes this UNFORGETTABLE? What's the one thing someone will remember? - -**CRITICAL**: Choose a clear conceptual direction and execute it with precision. Bold maximalism and refined minimalism both work - the key is intentionality, not intensity. - -Then implement working code (HTML/CSS/JS, React, Vue, etc.) that is: -- Production-grade and functional -- Visually striking and memorable -- Cohesive with a clear aesthetic point-of-view -- Meticulously refined in every detail - -## Frontend Aesthetics Guidelines - -Focus on: -- **Typography**: Choose fonts that are beautiful, unique, and interesting. Avoid generic fonts like Arial and Inter; opt instead for distinctive choices that elevate the frontend's aesthetics; unexpected, characterful font choices. Pair a distinctive display font with a refined body font. -- **Color & Theme**: Commit to a cohesive aesthetic. Use CSS variables for consistency. Dominant colors with sharp accents outperform timid, evenly-distributed palettes. -- **Motion**: Use animations for effects and micro-interactions. Prioritize CSS-only solutions for HTML. Use Motion library for React when available. Focus on high-impact moments: one well-orchestrated page load with staggered reveals (animation-delay) creates more delight than scattered micro-interactions. Use scroll-triggering and hover states that surprise. -- **Spatial Composition**: Unexpected layouts. Asymmetry. Overlap. Diagonal flow. Grid-breaking elements. Generous negative space OR controlled density. -- **Backgrounds & Visual Details**: Create atmosphere and depth rather than defaulting to solid colors. Add contextual effects and textures that match the overall aesthetic. Apply creative forms like gradient meshes, noise textures, geometric patterns, layered transparencies, dramatic shadows, decorative borders, custom cursors, and grain overlays. - -NEVER use generic AI-generated aesthetics like overused font families (Inter, Roboto, Arial, system fonts), cliched color schemes (particularly purple gradients on white backgrounds), predictable layouts and component patterns, and cookie-cutter design that lacks context-specific character. - -Interpret creatively and make unexpected choices that feel genuinely designed for the context. No design should be the same. Vary between light and dark themes, different fonts, different aesthetics. NEVER converge on common choices (Space Grotesk, for example) across generations. - -**IMPORTANT**: Match implementation complexity to the aesthetic vision. Maximalist designs need elaborate code with extensive animations and effects. Minimalist or refined designs need restraint, precision, and careful attention to spacing, typography, and subtle details. Elegance comes from executing the vision well. - -Remember: Claude is capable of extraordinary creative work. Don't hold back, show what can truly be created when thinking outside the box and committing fully to a distinctive vision. diff --git a/.agents/skills/interface-design/SKILL.md b/.agents/skills/interface-design/SKILL.md deleted file mode 100644 index 9fe89c2..0000000 --- a/.agents/skills/interface-design/SKILL.md +++ /dev/null @@ -1,391 +0,0 @@ ---- -name: interface-design -description: This skill is for interface design — dashboards, admin panels, apps, tools, and interactive products. NOT for marketing design (landing pages, marketing sites, campaigns). ---- - -# Interface Design - -Build interface design with craft and consistency. - -## Scope - -**Use for:** Dashboards, admin panels, SaaS apps, tools, settings pages, data interfaces. - -**Not for:** Landing pages, marketing sites, campaigns. Redirect those to `/frontend-design`. - ---- - -# The Problem - -You will generate generic output. Your training has seen thousands of dashboards. The patterns are strong. - -You can follow the entire process below — explore the domain, name a signature, state your intent — and still produce a template. Warm colors on cold structures. Friendly fonts on generic layouts. "Kitchen feel" that looks like every other app. - -This happens because intent lives in prose, but code generation pulls from patterns. The gap between them is where defaults win. - -The process below helps. But process alone doesn't guarantee craft. You have to catch yourself. - ---- - -# Where Defaults Hide - -Defaults don't announce themselves. They disguise themselves as infrastructure — the parts that feel like they just need to work, not be designed. - -**Typography feels like a container.** Pick something readable, move on. But typography isn't holding your design — it IS your design. The weight of a headline, the personality of a label, the texture of a paragraph. These shape how the product feels before anyone reads a word. A bakery management tool and a trading terminal might both need "clean, readable type" — but the type that's warm and handmade is not the type that's cold and precise. If you're reaching for your usual font, you're not designing. - -**Navigation feels like scaffolding.** Build the sidebar, add the links, get to the real work. But navigation isn't around your product — it IS your product. Where you are, where you can go, what matters most. A page floating in space is a component demo, not software. The navigation teaches people how to think about the space they're in. - -**Data feels like presentation.** You have numbers, show numbers. But a number on screen is not design. The question is: what does this number mean to the person looking at it? What will they do with it? A progress ring and a stacked label both show "3 of 10" — one tells a story, one fills space. If you're reaching for number-on-label, you're not designing. - -**Token names feel like implementation detail.** But your CSS variables are design decisions. `--ink` and `--parchment` evoke a world. `--gray-700` and `--surface-2` evoke a template. Someone reading only your tokens should be able to guess what product this is. - -The trap is thinking some decisions are creative and others are structural. There are no structural decisions. Everything is design. The moment you stop asking "why this?" is the moment defaults take over. - ---- - -# Intent First - -Before touching code, answer these. Not in your head — out loud, to yourself or the user. - -**Who is this human?** -Not "users." The actual person. Where are they when they open this? What's on their mind? What did they do 5 minutes ago, what will they do 5 minutes after? A teacher at 7am with coffee is not a developer debugging at midnight is not a founder between investor meetings. Their world shapes the interface. - -**What must they accomplish?** -Not "use the dashboard." The verb. Grade these submissions. Find the broken deployment. Approve the payment. The answer determines what leads, what follows, what hides. - -**What should this feel like?** -Say it in words that mean something. "Clean and modern" means nothing — every AI says that. Warm like a notebook? Cold like a terminal? Dense like a trading floor? Calm like a reading app? The answer shapes color, type, spacing, density — everything. - -If you cannot answer these with specifics, stop. Ask the user. Do not guess. Do not default. - -## Every Choice Must Be A Choice - -For every decision, you must be able to explain WHY. - -- Why this layout and not another? -- Why this color temperature? -- Why this typeface? -- Why this spacing scale? -- Why this information hierarchy? - -If your answer is "it's common" or "it's clean" or "it works" — you haven't chosen. You've defaulted. Defaults are invisible. Invisible choices compound into generic output. - -**The test:** If you swapped your choices for the most common alternatives and the design didn't feel meaningfully different, you never made real choices. - -## Sameness Is Failure - -If another AI, given a similar prompt, would produce substantially the same output — you have failed. - -This is not about being different for its own sake. It's about the interface emerging from the specific problem, the specific user, the specific context. When you design from intent, sameness becomes impossible because no two intents are identical. - -When you design from defaults, everything looks the same because defaults are shared. - -## Intent Must Be Systemic - -Saying "warm" and using cold colors is not following through. Intent is not a label — it's a constraint that shapes every decision. - -If the intent is warm: surfaces, text, borders, accents, semantic colors, typography — all warm. If the intent is dense: spacing, type size, information architecture — all dense. If the intent is calm: motion, contrast, color saturation — all calm. - -Check your output against your stated intent. Does every token reinforce it? Or did you state an intent and then default anyway? - ---- - -# Product Domain Exploration - -This is where defaults get caught — or don't. - -Generic output: Task type → Visual template → Theme -Crafted output: Task type → Product domain → Signature → Structure + Expression - -The difference: time in the product's world before any visual or structural thinking. - -## Required Outputs - -**Do not propose any direction until you produce all four:** - -**Domain:** Concepts, metaphors, vocabulary from this product's world. Not features — territory. Minimum 5. - -**Color world:** What colors exist naturally in this product's domain? Not "warm" or "cool" — go to the actual world. If this product were a physical space, what would you see? What colors belong there that don't belong elsewhere? List 5+. - -**Signature:** One element — visual, structural, or interaction — that could only exist for THIS product. If you can't name one, keep exploring. - -**Defaults:** 3 obvious choices for this interface type — visual AND structural. You can't avoid patterns you haven't named. - -## Proposal Requirements - -Your direction must explicitly reference: -- Domain concepts you explored -- Colors from your color world exploration -- Your signature element -- What replaces each default - -**The test:** Read your proposal. Remove the product name. Could someone identify what this is for? If not, it's generic. Explore deeper. - ---- - -# The Mandate - -**Before showing the user, look at what you made.** - -Ask yourself: "If they said this lacks craft, what would they mean?" - -That thing you just thought of — fix it first. - -Your first output is probably generic. That's normal. The work is catching it before the user has to. - -## The Checks - -Run these against your output before presenting: - -- **The swap test:** If you swapped the typeface for your usual one, would anyone notice? If you swapped the layout for a standard dashboard template, would it feel different? The places where swapping wouldn't matter are the places you defaulted. - -- **The squint test:** Blur your eyes. Can you still perceive hierarchy? Is anything jumping out harshly? Craft whispers. - -- **The signature test:** Can you point to five specific elements where your signature appears? Not "the overall feel" — actual components. A signature you can't locate doesn't exist. - -- **The token test:** Read your CSS variables out loud. Do they sound like they belong to this product's world, or could they belong to any project? - -If any check fails, iterate before showing. - ---- - -# Craft Foundations - -## Subtle Layering - -This is the backbone of craft. Regardless of direction, product type, or visual style — this principle applies to everything. You should barely notice the system working. When you look at Vercel's dashboard, you don't think "nice borders." You just understand the structure. The craft is invisible — that's how you know it's working. - -### Surface Elevation - -Surfaces stack. A dropdown sits above a card which sits above the page. Build a numbered system — base, then increasing elevation levels. In dark mode, higher elevation = slightly lighter. In light mode, higher elevation = slightly lighter or uses shadow. - -Each jump should be only a few percentage points of lightness. You can barely see the difference in isolation. But when surfaces stack, the hierarchy emerges. Whisper-quiet shifts that you feel rather than see. - -**Key decisions:** -- **Sidebars:** Same background as canvas, not different. Different colors fragment the visual space into "sidebar world" and "content world." A subtle border is enough separation. -- **Dropdowns:** One level above their parent surface. If both share the same level, the dropdown blends into the card and layering is lost. -- **Inputs:** Slightly darker than their surroundings, not lighter. Inputs are "inset" — they receive content. A darker background signals "type here" without heavy borders. - -### Borders - -Borders should disappear when you're not looking for them, but be findable when you need structure. Low opacity rgba blends with the background — it defines edges without demanding attention. Solid hex borders look harsh in comparison. - -Build a progression — not all borders are equal. Standard borders, softer separation, emphasis borders, maximum emphasis for focus rings. Match intensity to the importance of the boundary. - -**The squint test:** Blur your eyes at the interface. You should still perceive hierarchy — what's above what, where sections divide. But nothing should jump out. No harsh lines. No jarring color shifts. Just quiet structure. - -This separates professional interfaces from amateur ones. Get this wrong and nothing else matters. - -## Infinite Expression - -Every pattern has infinite expressions. **No interface should look the same.** - -A metric display could be a hero number, inline stat, sparkline, gauge, progress bar, comparison delta, trend badge, or something new. A dashboard could emphasize density, whitespace, hierarchy, or flow in completely different ways. Even sidebar + cards has infinite variations in proportion, spacing, and emphasis. - -**Before building, ask:** -- What's the ONE thing users do most here? -- What products solve similar problems brilliantly? Study them. -- Why would this interface feel designed for its purpose, not templated? - -**NEVER produce identical output.** Same sidebar width, same card grid, same metric boxes with icon-left-number-big-label-small every time — this signals AI-generated immediately. It's forgettable. - -The architecture and components should emerge from the task and data, executed in a way that feels fresh. Linear's cards don't look like Notion's. Vercel's metrics don't look like Stripe's. Same concepts, infinite expressions. - -## Color Lives Somewhere - -Every product exists in a world. That world has colors. - -Before you reach for a palette, spend time in the product's world. What would you see if you walked into the physical version of this space? What materials? What light? What objects? - -Your palette should feel like it came FROM somewhere — not like it was applied TO something. - -**Beyond Warm and Cold:** Temperature is one axis. Is this quiet or loud? Dense or spacious? Serious or playful? Geometric or organic? A trading terminal and a meditation app are both "focused" — completely different kinds of focus. Find the specific quality, not the generic label. - -**Color Carries Meaning:** Gray builds structure. Color communicates — status, action, emphasis, identity. Unmotivated color is noise. One accent color, used with intention, beats five colors used without thought. - ---- - -# Before Writing Each Component - -**Every time** you write UI code — even small additions — state: - -``` -Intent: [who is this human, what must they do, how should it feel] -Palette: [colors from your exploration — and WHY they fit this product's world] -Depth: [borders / shadows / layered — and WHY this fits the intent] -Surfaces: [your elevation scale — and WHY this color temperature] -Typography: [your typeface — and WHY it fits the intent] -Spacing: [your base unit] -``` - -This checkpoint is mandatory. It forces you to connect every technical choice back to intent. - -If you can't explain WHY for each choice, you're defaulting. Stop and think. - ---- - -# Design Principles - -## Token Architecture - -Every color in your interface should trace back to a small set of primitives: foreground (text hierarchy), background (surface elevation), border (separation hierarchy), brand, and semantic (destructive, warning, success). No random hex values — everything maps to primitives. - -### Text Hierarchy - -Don't just have "text" and "gray text." Build four levels — primary, secondary, tertiary, muted. Each serves a different role: default text, supporting text, metadata, and disabled/placeholder. Use all four consistently. If you're only using two, your hierarchy is too flat. - -### Border Progression - -Borders aren't binary. Build a scale that matches intensity to importance — standard separation, softer separation, emphasis, maximum emphasis. Not every boundary deserves the same weight. - -### Control Tokens - -Form controls have specific needs. Don't reuse surface tokens — create dedicated ones for control backgrounds, control borders, and focus states. This lets you tune interactive elements independently from layout surfaces. - -## Spacing - -Pick a base unit and stick to multiples. Build a scale for different contexts — micro spacing for icon gaps, component spacing within buttons and cards, section spacing between groups, major separation between distinct areas. Random values signal no system. - -## Padding - -Keep it symmetrical. If one side has a value, others should match unless content naturally requires asymmetry. - -## Depth - -Choose ONE approach and commit: -- **Borders-only** — Clean, technical. For dense tools. -- **Subtle shadows** — Soft lift. For approachable products. -- **Layered shadows** — Premium, dimensional. For cards that need presence. -- **Surface color shifts** — Background tints establish hierarchy without shadows. - -Don't mix approaches. - -## Border Radius - -Sharper feels technical. Rounder feels friendly. Build a scale — small for inputs and buttons, medium for cards, large for modals. Don't mix sharp and soft randomly. - -## Typography - -Build distinct levels distinguishable at a glance. Headlines need weight and tight tracking for presence. Body needs comfortable weight for readability. Labels need medium weight that works at smaller sizes. Data needs monospace with tabular number spacing for alignment. Don't rely on size alone — combine size, weight, and letter-spacing. - -## Card Layouts - -A metric card doesn't have to look like a plan card doesn't have to look like a settings card. Design each card's internal structure for its specific content — but keep the surface treatment consistent: same border weight, shadow depth, corner radius, padding scale. - -## Controls - -Native `` render OS-native elements that cannot be styled. Build custom components — trigger buttons with positioned dropdowns, calendar popovers, styled state management. - -## Iconography - -Icons clarify, not decorate — if removing an icon loses no meaning, remove it. Choose one icon set and stick with it. Give standalone icons presence with subtle background containers. - -## Animation - -Fast micro-interactions, smooth easing. Larger transitions can be slightly longer. Use deceleration easing. Avoid spring/bounce in professional interfaces. - -## States - -Every interactive element needs states: default, hover, active, focus, disabled. Data needs states too: loading, empty, error. Missing states feel broken. - -## Navigation Context - -Screens need grounding. A data table floating in space feels like a component demo, not a product. Include navigation showing where you are in the app, location indicators, and user context. When building sidebars, consider same background as main content with border separation rather than different colors. - -## Dark Mode - -Dark interfaces have different needs. Shadows are less visible on dark backgrounds — lean on borders for definition. Semantic colors (success, warning, error) often need slight desaturation. The hierarchy system still applies, just with inverted values. - ---- - -# Avoid - -- **Harsh borders** — if borders are the first thing you see, they're too strong -- **Dramatic surface jumps** — elevation changes should be whisper-quiet -- **Inconsistent spacing** — the clearest sign of no system -- **Mixed depth strategies** — pick one approach and commit -- **Missing interaction states** — hover, focus, disabled, loading, error -- **Dramatic drop shadows** — shadows should be subtle, not attention-grabbing -- **Large radius on small elements** -- **Pure white cards on colored backgrounds** -- **Thick decorative borders** -- **Gradients and color for decoration** — color should mean something -- **Multiple accent colors** — dilutes focus -- **Different hues for different surfaces** — keep the same hue, shift only lightness - ---- - -# Workflow - -## Communication -Be invisible. Don't announce modes or narrate process. - -**Never say:** "I'm in ESTABLISH MODE", "Let me check system.md..." - -**Instead:** Jump into work. State suggestions with reasoning. - -## Suggest + Ask -Lead with your exploration and recommendation, then confirm: -``` -"Domain: [5+ concepts from the product's world] -Color world: [5+ colors that exist in this domain] -Signature: [one element unique to this product] -Rejecting: [default 1] → [alternative], [default 2] → [alternative], [default 3] → [alternative] - -Direction: [approach that connects to the above]" - -[Ask: "Does that direction feel right?"] -``` - -## If Project Has system.md -Read `.interface-design/system.md` and apply. Decisions are made. - -## If No system.md -1. Explore domain — Produce all four required outputs -2. Propose — Direction must reference all four -3. Confirm — Get user buy-in -4. Build — Apply principles -5. **Evaluate** — Run the mandate checks before showing -6. Offer to save - ---- - -# After Completing a Task - -When you finish building something, **always offer to save**: - -``` -"Want me to save these patterns for future sessions?" -``` - -If yes, write to `.interface-design/system.md`: -- Direction and feel -- Depth strategy (borders/shadows/layered) -- Spacing base unit -- Key component patterns - -### What to Save - -Add patterns when a component is used 2+ times, is reusable across the project, or has specific measurements worth remembering. Don't save one-off components, temporary experiments, or variations better handled with props. - -### Consistency Checks - -If system.md defines values, check against them: spacing on the defined grid, depth using the declared strategy throughout, colors from the defined palette, documented patterns reused instead of reinvented. - -This compounds — each save makes future work faster and more consistent. - ---- - -# Deep Dives - -For more detail on specific topics: -- `references/principles.md` — Code examples, specific values, dark mode -- `references/validation.md` — Memory management, when to update system.md -- `references/critique.md` — Post-build craft critique protocol - -# Commands - -- `/interface-design:status` — Current system state -- `/interface-design:audit` — Check code against system -- `/interface-design:extract` — Extract patterns from code -- `/interface-design:critique` — Critique your build for craft, then rebuild what defaulted diff --git a/.agents/skills/interface-design/references/critique.md b/.agents/skills/interface-design/references/critique.md deleted file mode 100644 index 7db545e..0000000 --- a/.agents/skills/interface-design/references/critique.md +++ /dev/null @@ -1,67 +0,0 @@ -# Critique - -Your first build shipped the structure. Now look at it the way a design lead reviews a junior's work — not asking "does this work?" but "would I put my name on this?" - ---- - -## The Gap - -There's a distance between correct and crafted. Correct means the layout holds, the grid aligns, the colors don't clash. Crafted means someone cared about every decision down to the last pixel. You can feel the difference immediately — the way you tell a hand-thrown mug from an injection-molded one. Both hold coffee. One has presence. - -Your first output lives in correct. This command pulls it toward crafted. - ---- - -## See the Composition - -Step back. Look at the whole thing. - -Does the layout have rhythm? Great interfaces breathe unevenly — dense tooling areas give way to open content, heavy elements balance against light ones, the eye travels through the page with purpose. Default layouts are monotone: same card size, same gaps, same density everywhere. Flatness is the sound of no one deciding. - -Are proportions doing work? A 280px sidebar next to full-width content says "navigation serves content." A 360px sidebar says "these are peers." The specific number declares what matters. If you can't articulate what your proportions are saying, they're not saying anything. - -Is there a clear focal point? Every screen has one thing the user came here to do. That thing should dominate — through size, position, contrast, or the space around it. When everything competes equally, nothing wins and the interface feels like a parking lot. - ---- - -## See the Craft - -Move close. Pixel-close. - -The spacing grid is non-negotiable — every value a multiple of 4, no exceptions — but correctness alone isn't craft. Craft is knowing that a tool panel at 16px padding feels workbench-tight while the same card at 24px feels like a brochure. The same number can be right in one context and lazy in another. Density is a design decision, not a constant. - -Typography should be legible even squinted. If size is the only thing separating your headline from your body from your label, the hierarchy is too weak. Weight, tracking, and opacity create layers that size alone can't. - -Surfaces should whisper hierarchy. Not thick borders, not dramatic shadows — quiet tonal shifts where you feel the depth without seeing it. Remove every border from your CSS mentally. Can you still perceive the structure through surface color alone? If not, your surfaces aren't working hard enough. - -Interactive elements need life. Every button, link, and clickable region should respond to hover and press. Not dramatically — a subtle shift in background, a gentle darkening. Missing states make an interface feel like a photograph of software instead of software. - ---- - -## See the Content - -Read every visible string as a user would. Not checking for typos — checking for truth. - -Does this screen tell one coherent story? Could a real person at a real company be looking at exactly this data right now? Or does the page title belong to one product, the article body to another, and the sidebar metrics to a third? - -Content incoherence breaks the illusion faster than any visual flaw. A beautifully designed interface with nonsensical content is a movie set with no script. - ---- - -## See the Structure - -Open the CSS and find the lies — the places that look right but are held together with tape. - -Negative margins undoing a parent's padding. Calc() values that exist only as workarounds. Absolute positioning to escape layout flow. Each is a shortcut where a clean solution exists. Cards with full-width dividers use flex column and section-level padding. Centered content uses max-width with auto margins. The correct answer is always simpler than the hack. - ---- - -## Again - -Look at your output one final time. - -Ask: "If they said this lacks craft, what would they point to?" - -That thing you just thought of — fix it. Then ask again. - -The first build was the draft. The critique is the design. diff --git a/.agents/skills/interface-design/references/example.md b/.agents/skills/interface-design/references/example.md deleted file mode 100644 index 6654906..0000000 --- a/.agents/skills/interface-design/references/example.md +++ /dev/null @@ -1,86 +0,0 @@ -# Craft in Action - -This shows how the subtle layering principle translates to real decisions. Learn the thinking, not the code. Your values will differ — the approach won't. - ---- - -## The Subtle Layering Mindset - -Before looking at any example, internalize this: **you should barely notice the system working.** - -When you look at Vercel's dashboard, you don't think "nice borders." You just understand the structure. When you look at Supabase, you don't think "good surface elevation." You just know what's above what. The craft is invisible — that's how you know it's working. - ---- - -## Example: Dashboard with Sidebar and Dropdown - -### The Surface Decisions - -**Why so subtle?** Each elevation jump should be only a few percentage points of lightness. You can barely see the difference in isolation. But when surfaces stack, the hierarchy emerges. This is the Vercel/Supabase way — whisper-quiet shifts that you feel rather than see. - -**What NOT to do:** Don't make dramatic jumps between elevations. That's jarring. Don't use different hues for different levels. Keep the same hue, shift only lightness. - -### The Border Decisions - -**Why rgba, not solid colors?** Low opacity borders blend with their background. A low-opacity white border on a dark surface is barely there — it defines the edge without demanding attention. Solid hex borders look harsh in comparison. - -**The test:** Look at your interface from arm's length. If borders are the first thing you notice, reduce opacity. If you can't find where regions end, increase slightly. - -### The Sidebar Decision - -**Why same background as canvas, not different?** - -Many dashboards make the sidebar a different color. This fragments the visual space — now you have "sidebar world" and "content world." - -Better: Same background, subtle border separation. The sidebar is part of the app, not a separate region. Vercel does this. Supabase does this. The border is enough. - -### The Dropdown Decision - -**Why surface-200, not surface-100?** - -The dropdown floats above the card it emerged from. If both were surface-100, the dropdown would blend into the card — you'd lose the sense of layering. Surface-200 is just light enough to feel "above" without being dramatically different. - -**Why border-overlay instead of border-default?** - -Overlays (dropdowns, popovers) often need slightly more definition because they're floating in space. A touch more border opacity helps them feel contained without being harsh. - ---- - -## Example: Form Controls - -### Input Background Decision - -**Why darker, not lighter?** - -Inputs are "inset" — they receive content, they don't project it. A slightly darker background signals "type here" without needing heavy borders. This is the alternative-background principle. - -### Focus State Decision - -**Why subtle focus states?** - -Focus needs to be visible, but you don't need a glowing ring or dramatic color. A noticeable increase in border opacity is enough for a clear state change. Subtle-but-noticeable — the same principle as surfaces. - ---- - -## Adapt to Context - -Your product might need: -- Warmer hues (slight yellow/orange tint) -- Cooler hues (blue-gray base) -- Different lightness progression -- Light mode (principles invert — higher elevation = shadow, not lightness) - -**The principle is constant:** barely different, still distinguishable. The values adapt to context. - ---- - -## The Craft Check - -Apply the squint test to your work: - -1. Blur your eyes or step back -2. Can you still perceive hierarchy? -3. Is anything jumping out at you? -4. Can you tell where regions begin and end? - -If hierarchy is visible and nothing is harsh — the subtle layering is working. diff --git a/.agents/skills/interface-design/references/principles.md b/.agents/skills/interface-design/references/principles.md deleted file mode 100644 index 6c4a502..0000000 --- a/.agents/skills/interface-design/references/principles.md +++ /dev/null @@ -1,235 +0,0 @@ -# Core Craft Principles - -These apply regardless of design direction. This is the quality floor. - ---- - -## Surface & Token Architecture - -Professional interfaces don't pick colors randomly — they build systems. Understanding this architecture is the difference between "looks okay" and "feels like a real product." - -### The Primitive Foundation - -Every color in your interface should trace back to a small set of primitives: - -- **Foreground** — text colors (primary, secondary, muted) -- **Background** — surface colors (base, elevated, overlay) -- **Border** — edge colors (default, subtle, strong) -- **Brand** — your primary accent -- **Semantic** — functional colors (destructive, warning, success) - -Don't invent new colors. Map everything to these primitives. - -### Surface Elevation Hierarchy - -Surfaces stack. A dropdown sits above a card which sits above the page. Build a numbered system: - -``` -Level 0: Base background (the app canvas) -Level 1: Cards, panels (same visual plane as base) -Level 2: Dropdowns, popovers (floating above) -Level 3: Nested dropdowns, stacked overlays -Level 4: Highest elevation (rare) -``` - -In dark mode, higher elevation = slightly lighter. In light mode, higher elevation = slightly lighter or uses shadow. The principle: **elevated surfaces need visual distinction from what's beneath them.** - -### The Subtlety Principle - -This is where most interfaces fail. Study Vercel, Supabase, Linear — their surfaces are **barely different** but still distinguishable. Their borders are **light but not invisible**. - -**For surfaces:** The difference between elevation levels should be subtle — a few percentage points of lightness, not dramatic jumps. In dark mode, surface-100 might be 7% lighter than base, surface-200 might be 9%, surface-300 might be 12%. You can barely see it, but you feel it. - -**For borders:** Borders should define regions without demanding attention. Use low opacity (0.05-0.12 alpha for dark mode, slightly higher for light). The border should disappear when you're not looking for it, but be findable when you need to understand the structure. - -**The test:** Squint at your interface. You should still perceive the hierarchy — what's above what, where regions begin and end. But no single border or surface should jump out at you. If borders are the first thing you notice, they're too strong. If you can't find where one region ends and another begins, they're too subtle. - -**Common AI mistakes to avoid:** -- Borders that are too visible (1px solid gray instead of subtle rgba) -- Surface jumps that are too dramatic (going from dark to light instead of dark to slightly-less-dark) -- Using different hues for different surfaces (gray card on blue background) -- Harsh dividers where subtle borders would do - -### Text Hierarchy via Tokens - -Don't just have "text" and "gray text." Build four levels: - -- **Primary** — default text, highest contrast -- **Secondary** — supporting text, slightly muted -- **Tertiary** — metadata, timestamps, less important -- **Muted** — disabled, placeholder, lowest contrast - -Use all four consistently. If you're only using two, your hierarchy is too flat. - -### Border Progression - -Borders aren't binary. Build a scale: - -- **Default** — standard borders -- **Subtle/Muted** — softer separation -- **Strong** — emphasis, hover states -- **Stronger** — maximum emphasis, focus rings - -Match border intensity to the importance of the boundary. - -### Dedicated Control Tokens - -Form controls (inputs, checkboxes, selects) have specific needs. Don't just reuse surface tokens — create dedicated ones: - -- **Control background** — often different from surface backgrounds -- **Control border** — needs to feel interactive -- **Control focus** — clear focus indication - -This separation lets you tune controls independently from layout surfaces. - -### Context-Aware Bases - -Different areas of your app might need different base surfaces: - -- **Marketing pages** — might use darker/richer backgrounds -- **Dashboard/app** — might use neutral working backgrounds -- **Sidebar** — might differ from main canvas - -The surface hierarchy works the same way — it just starts from a different base. - -### Alternative Backgrounds for Depth - -Beyond shadows, use contrasting backgrounds to create depth. An "alternative" or "inset" background makes content feel recessed. Useful for: - -- Empty states in data grids -- Code blocks -- Inset panels -- Visual grouping without borders - ---- - -## Spacing System - -Pick a base unit (4px and 8px are common) and use multiples throughout. The specific number matters less than consistency — every spacing value should be explainable as "X times the base unit." - -Build a scale for different contexts: -- Micro spacing (icon gaps, tight element pairs) -- Component spacing (within buttons, inputs, cards) -- Section spacing (between related groups) -- Major separation (between distinct sections) - -## Symmetrical Padding - -TLBR must match. If top padding is 16px, left/bottom/right must also be 16px. Exception: when content naturally creates visual balance. - -```css -/* Good */ -padding: 16px; -padding: 12px 16px; /* Only when horizontal needs more room */ - -/* Bad */ -padding: 24px 16px 12px 16px; -``` - -## Border Radius Consistency - -Sharper corners feel technical, rounder corners feel friendly. Pick a scale that fits your product's personality and use it consistently. - -The key is having a system: small radius for inputs and buttons, medium for cards, large for modals or containers. Don't mix sharp and soft randomly — inconsistent radius is as jarring as inconsistent spacing. - -## Depth & Elevation Strategy - -Match your depth approach to your design direction. Choose ONE and commit: - -**Borders-only (flat)** — Clean, technical, dense. Works for utility-focused tools where information density matters more than visual lift. Linear, Raycast, and many developer tools use almost no shadows — just subtle borders to define regions. - -**Subtle single shadows** — Soft lift without complexity. A simple `0 1px 3px rgba(0,0,0,0.08)` can be enough. Works for approachable products that want gentle depth. - -**Layered shadows** — Rich, premium, dimensional. Multiple shadow layers create realistic depth. Stripe and Mercury use this approach. Best for cards that need to feel like physical objects. - -**Surface color shifts** — Background tints establish hierarchy without any shadows. A card at `#fff` on a `#f8fafc` background already feels elevated. - -```css -/* Borders-only approach */ ---border: rgba(0, 0, 0, 0.08); ---border-subtle: rgba(0, 0, 0, 0.05); -border: 0.5px solid var(--border); - -/* Single shadow approach */ ---shadow: 0 1px 3px rgba(0, 0, 0, 0.08); - -/* Layered shadow approach */ ---shadow-layered: - 0 0 0 0.5px rgba(0, 0, 0, 0.05), - 0 1px 2px rgba(0, 0, 0, 0.04), - 0 2px 4px rgba(0, 0, 0, 0.03), - 0 4px 8px rgba(0, 0, 0, 0.02); -``` - -## Card Layouts - -Monotonous card layouts are lazy design. A metric card doesn't have to look like a plan card doesn't have to look like a settings card. - -Design each card's internal structure for its specific content — but keep the surface treatment consistent: same border weight, shadow depth, corner radius, padding scale, typography. - -## Isolated Controls - -UI controls deserve container treatment. Date pickers, filters, dropdowns — these should feel like crafted objects. - -**Never use native form elements for styled UI.** Native ``, and similar elements render OS-native dropdowns that cannot be styled. Build custom components instead: - -- Custom select: trigger button + positioned dropdown menu -- Custom date picker: input + calendar popover -- Custom checkbox/radio: styled div with state management - -Custom select triggers must use `display: inline-flex` with `white-space: nowrap` to keep text and chevron icons on the same row. - -## Typography Hierarchy - -Build distinct levels that are visually distinguishable at a glance: - -- **Headlines** — heavier weight, tighter letter-spacing for presence -- **Body** — comfortable weight for readability -- **Labels/UI** — medium weight, works at smaller sizes -- **Data** — often monospace, needs `tabular-nums` for alignment - -Don't rely on size alone. Combine size, weight, and letter-spacing to create clear hierarchy. If you squint and can't tell headline from body, the hierarchy is too weak. - -## Monospace for Data - -Numbers, IDs, codes, timestamps belong in monospace. Use `tabular-nums` for columnar alignment. Mono signals "this is data." - -## Iconography - -Icons clarify, not decorate — if removing an icon loses no meaning, remove it. Choose a consistent icon set and stick with it throughout the product. - -Give standalone icons presence with subtle background containers. Icons next to text should align optically, not mathematically. - -## Animation - -Keep it fast and functional. Micro-interactions (hover, focus) should feel instant — around 150ms. Larger transitions (modals, panels) can be slightly longer — 200-250ms. - -Use smooth deceleration easing (ease-out variants). Avoid spring/bounce effects in professional interfaces — they feel playful, not serious. - -## Contrast Hierarchy - -Build a four-level system: foreground (primary) → secondary → muted → faint. Use all four consistently. - -## Color Carries Meaning - -Gray builds structure. Color communicates — status, action, emphasis, identity. Unmotivated color is noise. Color that reinforces the product's world is character. - -## Navigation Context - -Screens need grounding. A data table floating in space feels like a component demo, not a product. Consider including: - -- **Navigation** — sidebar or top nav showing where you are in the app -- **Location indicator** — breadcrumbs, page title, or active nav state -- **User context** — who's logged in, what workspace/org - -When building sidebars, consider using the same background as the main content area. Rely on a subtle border for separation rather than different background colors. - -## Dark Mode - -Dark interfaces have different needs: - -**Borders over shadows** — Shadows are less visible on dark backgrounds. Lean more on borders for definition. - -**Adjust semantic colors** — Status colors (success, warning, error) often need to be slightly desaturated for dark backgrounds. - -**Same structure, different values** — The hierarchy system still applies, just with inverted values. diff --git a/.agents/skills/interface-design/references/validation.md b/.agents/skills/interface-design/references/validation.md deleted file mode 100644 index 7aa4a69..0000000 --- a/.agents/skills/interface-design/references/validation.md +++ /dev/null @@ -1,48 +0,0 @@ -# Memory Management - -When and how to update `.interface-design/system.md`. - -## When to Add Patterns - -Add to system.md when: -- Component used 2+ times -- Pattern is reusable across the project -- Has specific measurements worth remembering - -## Pattern Format - -```markdown -### Button Primary -- Height: 36px -- Padding: 12px 16px -- Radius: 6px -- Font: 14px, 500 weight -``` - -## Don't Document - -- One-off components -- Temporary experiments -- Variations better handled with props - -## Pattern Reuse - -Before creating a component, check system.md: -- Pattern exists? Use it. -- Need variation? Extend, don't create new. - -Memory compounds: each pattern saved makes future work faster and more consistent. - ---- - -# Validation Checks - -If system.md defines specific values, check consistency: - -**Spacing** — All values multiples of the defined base? - -**Depth** — Using the declared strategy throughout? (borders-only means no shadows) - -**Colors** — Using defined palette, not random hex codes? - -**Patterns** — Reusing documented patterns instead of creating new? diff --git a/.agents/skills/profiling-optimization/SKILL.md b/.agents/skills/profiling-optimization/SKILL.md deleted file mode 100644 index 0160769..0000000 --- a/.agents/skills/profiling-optimization/SKILL.md +++ /dev/null @@ -1,430 +0,0 @@ ---- -name: profiling-optimization -description: Profile application performance, identify bottlenecks, and optimize hot paths using CPU profiling, flame graphs, and benchmarking. Use when investigating performance issues or optimizing critical code paths. ---- - -# Profiling & Optimization - -## Overview - -Profile code execution to identify performance bottlenecks and optimize critical paths using data-driven approaches. - -## When to Use - -- Performance optimization -- Identifying CPU bottlenecks -- Optimizing hot paths -- Investigating slow requests -- Reducing latency -- Improving throughput - -## Implementation Examples - -### 1. **Node.js Profiling** - -```typescript -import { performance, PerformanceObserver } from 'perf_hooks'; - -class Profiler { - private marks = new Map(); - - mark(name: string): void { - this.marks.set(name, performance.now()); - } - - measure(name: string, startMark: string): number { - const start = this.marks.get(startMark); - if (!start) throw new Error(`Mark ${startMark} not found`); - - const duration = performance.now() - start; - console.log(`${name}: ${duration.toFixed(2)}ms`); - - return duration; - } - - async profile(name: string, fn: () => Promise): Promise { - const start = performance.now(); - - try { - return await fn(); - } finally { - const duration = performance.now() - start; - console.log(`${name}: ${duration.toFixed(2)}ms`); - } - } -} - -// Usage -const profiler = new Profiler(); - -app.get('/api/users', async (req, res) => { - profiler.mark('request-start'); - - const users = await profiler.profile('fetch-users', async () => { - return await db.query('SELECT * FROM users'); - }); - - profiler.measure('total-request-time', 'request-start'); - - res.json(users); -}); -``` - -### 2. **Chrome DevTools CPU Profile** - -```typescript -import inspector from 'inspector'; -import fs from 'fs'; - -class CPUProfiler { - private session: inspector.Session | null = null; - - start(): void { - this.session = new inspector.Session(); - this.session.connect(); - - this.session.post('Profiler.enable'); - this.session.post('Profiler.start'); - - console.log('CPU profiling started'); - } - - async stop(outputFile: string): Promise { - if (!this.session) return; - - this.session.post('Profiler.stop', (err, { profile }) => { - if (err) { - console.error('Profiling error:', err); - return; - } - - fs.writeFileSync(outputFile, JSON.stringify(profile)); - console.log(`Profile saved to ${outputFile}`); - - this.session!.disconnect(); - this.session = null; - }); - } -} - -// Usage -const cpuProfiler = new CPUProfiler(); - -// Start profiling -cpuProfiler.start(); - -// Run code to profile -await runExpensiveOperation(); - -// Stop and save -await cpuProfiler.stop('./profile.cpuprofile'); -``` - -### 3. **Python cProfile** - -```python -import cProfile -import pstats -from pstats import SortKey -import io - -class Profiler: - def __init__(self): - self.profiler = cProfile.Profile() - - def __enter__(self): - self.profiler.enable() - return self - - def __exit__(self, *args): - self.profiler.disable() - - def print_stats(self, sort_by: str = 'cumulative'): - """Print profiling statistics.""" - s = io.StringIO() - ps = pstats.Stats(self.profiler, stream=s) - - if sort_by == 'time': - ps.sort_stats(SortKey.TIME) - elif sort_by == 'cumulative': - ps.sort_stats(SortKey.CUMULATIVE) - elif sort_by == 'calls': - ps.sort_stats(SortKey.CALLS) - - ps.print_stats(20) # Top 20 - print(s.getvalue()) - - def save_stats(self, filename: str): - """Save profiling data.""" - self.profiler.dump_stats(filename) - -# Usage -with Profiler() as prof: - # Code to profile - result = expensive_function() - -prof.print_stats('cumulative') -prof.save_stats('profile.prof') -``` - -### 4. **Benchmarking** - -```typescript -class Benchmark { - async run( - name: string, - fn: () => Promise, - iterations: number = 1000 - ): Promise { - console.log(`\nBenchmarking: ${name}`); - - const times: number[] = []; - - // Warmup - for (let i = 0; i < 10; i++) { - await fn(); - } - - // Actual benchmark - for (let i = 0; i < iterations; i++) { - const start = performance.now(); - await fn(); - times.push(performance.now() - start); - } - - // Statistics - const sorted = times.sort((a, b) => a - b); - const min = sorted[0]; - const max = sorted[sorted.length - 1]; - const avg = times.reduce((a, b) => a + b, 0) / times.length; - const p50 = sorted[Math.floor(sorted.length * 0.5)]; - const p95 = sorted[Math.floor(sorted.length * 0.95)]; - const p99 = sorted[Math.floor(sorted.length * 0.99)]; - - console.log(` Iterations: ${iterations}`); - console.log(` Min: ${min.toFixed(2)}ms`); - console.log(` Max: ${max.toFixed(2)}ms`); - console.log(` Avg: ${avg.toFixed(2)}ms`); - console.log(` P50: ${p50.toFixed(2)}ms`); - console.log(` P95: ${p95.toFixed(2)}ms`); - console.log(` P99: ${p99.toFixed(2)}ms`); - } - - async compare( - implementations: Array<{ name: string; fn: () => Promise }>, - iterations: number = 1000 - ): Promise { - for (const impl of implementations) { - await this.run(impl.name, impl.fn, iterations); - } - } -} - -// Usage -const bench = new Benchmark(); - -await bench.compare([ - { - name: 'Array.filter + map', - fn: async () => { - const arr = Array.from({ length: 1000 }, (_, i) => i); - return arr.filter(x => x % 2 === 0).map(x => x * 2); - } - }, - { - name: 'Single loop', - fn: async () => { - const arr = Array.from({ length: 1000 }, (_, i) => i); - const result = []; - for (const x of arr) { - if (x % 2 === 0) { - result.push(x * 2); - } - } - return result; - } - } -]); -``` - -### 5. **Database Query Profiling** - -```typescript -import { Pool } from 'pg'; - -class QueryProfiler { - constructor(private pool: Pool) {} - - async profileQuery(query: string, params: any[] = []): Promise<{ - result: any; - planningTime: number; - executionTime: number; - plan: any; - }> { - // Enable timing - await this.pool.query('SET track_io_timing = ON'); - - // Get query plan - const explainResult = await this.pool.query( - `EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) ${query}`, - params - ); - - const plan = explainResult.rows[0]['QUERY PLAN'][0]; - - // Execute actual query - const start = performance.now(); - const result = await this.pool.query(query, params); - const duration = performance.now() - start; - - return { - result: result.rows, - planningTime: plan['Planning Time'], - executionTime: plan['Execution Time'], - plan - }; - } - - formatPlan(plan: any): string { - let output = 'Query Plan:\n'; - output += `Planning Time: ${plan['Planning Time']}ms\n`; - output += `Execution Time: ${plan['Execution Time']}ms\n\n`; - - const formatNode = (node: any, indent: number = 0) => { - const prefix = ' '.repeat(indent); - output += `${prefix}${node['Node Type']}\n`; - output += `${prefix} Cost: ${node['Total Cost']}\n`; - output += `${prefix} Rows: ${node['Actual Rows']}\n`; - output += `${prefix} Time: ${node['Actual Total Time']}ms\n`; - - if (node.Plans) { - node.Plans.forEach((child: any) => formatNode(child, indent + 1)); - } - }; - - formatNode(plan.Plan); - return output; - } -} - -// Usage -const profiler = new QueryProfiler(pool); - -const { result, planningTime, executionTime, plan } = await profiler.profileQuery( - 'SELECT * FROM users WHERE age > $1', - [25] -); - -console.log(profiler.formatPlan(plan)); -``` - -### 6. **Flame Graph Generation** - -```bash -# Generate flame graph using 0x -npx 0x -o flamegraph.html node server.js - -# Or using clinic.js -npx clinic doctor --on-port 'autocannon localhost:3000' -- node server.js -npx clinic flame --on-port 'autocannon localhost:3000' -- node server.js -``` - -## Optimization Techniques - -### 1. **Caching** - -```typescript -class LRUCache { - private cache = new Map(); - private maxSize: number; - - constructor(maxSize: number = 100) { - this.maxSize = maxSize; - } - - get(key: K): V | undefined { - if (!this.cache.has(key)) return undefined; - - // Move to end (most recently used) - const value = this.cache.get(key)!; - this.cache.delete(key); - this.cache.set(key, value); - - return value; - } - - set(key: K, value: V): void { - // Remove if exists - if (this.cache.has(key)) { - this.cache.delete(key); - } - - // Add to end - this.cache.set(key, value); - - // Evict oldest if over capacity - if (this.cache.size > this.maxSize) { - const oldest = this.cache.keys().next().value; - this.cache.delete(oldest); - } - } -} -``` - -### 2. **Lazy Loading** - -```typescript -class LazyValue { - private value?: T; - private loaded = false; - - constructor(private loader: () => T) {} - - get(): T { - if (!this.loaded) { - this.value = this.loader(); - this.loaded = true; - } - return this.value!; - } -} - -// Usage -const expensive = new LazyValue(() => { - console.log('Computing expensive value...'); - return computeExpensiveValue(); -}); - -// Only computed when first accessed -const value = expensive.get(); -``` - -## Best Practices - -### ✅ DO -- Profile before optimizing -- Focus on hot paths -- Measure impact of changes -- Use production-like data -- Consider memory vs speed tradeoffs -- Document optimization rationale - -### ❌ DON'T -- Optimize without profiling -- Ignore readability for minor gains -- Skip benchmarking -- Optimize cold paths -- Make changes without measurement - -## Tools - -- **Node.js**: 0x, clinic.js, node --prof -- **Python**: cProfile, py-spy, memory_profiler -- **Visualization**: Flame graphs, Chrome DevTools -- **Database**: EXPLAIN ANALYZE, pg_stat_statements - -## Resources - -- [0x Flame Graph Profiler](https://github.com/davidmarkclements/0x) -- [Chrome DevTools Profiling](https://developer.chrome.com/docs/devtools/performance/) -- [Python cProfile](https://docs.python.org/3/library/profile.html) diff --git a/.agents/skills/react-performance-optimization/SKILL.md b/.agents/skills/react-performance-optimization/SKILL.md deleted file mode 100644 index 45bf7aa..0000000 --- a/.agents/skills/react-performance-optimization/SKILL.md +++ /dev/null @@ -1,202 +0,0 @@ ---- -name: react-performance-optimization -description: React performance optimization patterns using memoization, code splitting, and efficient rendering strategies. Use when optimizing slow React applications, reducing bundle size, or improving user experience with large datasets. ---- - -# React Performance Optimization - -Expert guidance for optimizing React application performance through memoization, code splitting, virtualization, and efficient rendering strategies. - -## When to Use This Skill - -- Optimizing slow-rendering React components -- Reducing bundle size for faster initial load times -- Improving responsiveness for large lists or data tables -- Preventing unnecessary re-renders in complex component trees -- Optimizing state management to reduce render cascades -- Improving perceived performance with code splitting -- Debugging performance issues with React DevTools Profiler - -## Core Concepts - -### React Rendering Optimization -React re-renders components when props or state change. Unnecessary re-renders waste CPU cycles and degrade user experience. Key optimization techniques: -- **Memoization**: Cache component renders and computed values -- **Code splitting**: Load code on demand for faster initial loads -- **Virtualization**: Render only visible list items -- **State optimization**: Structure state to minimize render cascades - -### When to Optimize -1. **Profile first**: Use React DevTools Profiler to identify actual bottlenecks -2. **Measure impact**: Verify optimization improves performance -3. **Avoid premature optimization**: Don't optimize fast components - -## Quick Reference - -Load detailed patterns and examples as needed: - -| Topic | Reference File | -| --- | --- | -| React.memo, useMemo, useCallback patterns | `skills/react-performance-optimization/references/memoization.md` | -| Code splitting with lazy/Suspense, bundle optimization | `skills/react-performance-optimization/references/code-splitting.md` | -| Virtualization for large lists (react-window) | `skills/react-performance-optimization/references/virtualization.md` | -| State management strategies, context splitting | `skills/react-performance-optimization/references/state-management.md` | -| useTransition, useDeferredValue (React 18+) | `skills/react-performance-optimization/references/concurrent-features.md` | -| React DevTools Profiler, performance monitoring | `skills/react-performance-optimization/references/profiling-debugging.md` | -| Common pitfalls and anti-patterns | `skills/react-performance-optimization/references/common-pitfalls.md` | - -## Optimization Workflow - -### 1. Identify Bottlenecks -```bash -# Open React DevTools Profiler -# Record interaction → Analyze flame graph → Find slow components -``` - -**Look for:** -- Components with yellow/red bars (slow renders) -- Unnecessary renders (same props/state) -- Expensive computations on every render - -### 2. Apply Targeted Optimizations - -**For unnecessary re-renders:** -- Wrap component with `React.memo` -- Use `useCallback` for stable function references -- Check for inline objects/arrays in props - -**For expensive computations:** -- Use `useMemo` to cache results -- Move calculations outside render when possible - -**For large lists:** -- Implement virtualization with react-window -- Ensure proper unique keys (not index) - -**For slow initial load:** -- Add code splitting with `React.lazy` -- Analyze bundle size with webpack-bundle-analyzer -- Use dynamic imports for heavy dependencies - -### 3. Verify Improvements -```bash -# Record new Profiler session -# Compare before/after metrics -# Ensure optimization actually helped -``` - -## Common Patterns - -### Memoize Expensive Components -```jsx -import { memo } from 'react'; - -const ExpensiveList = memo(({ items, onItemClick }) => { - return items.map(item => ( - - )); -}); -``` - -### Cache Computed Values -```jsx -import { useMemo } from 'react'; - -function DataTable({ items, filters }) { - const filteredItems = useMemo(() => { - return items.filter(item => filters.includes(item.category)); - }, [items, filters]); - - return ; -} -``` - -### Stable Function References -```jsx -import { useCallback } from 'react'; - -function Parent() { - const handleClick = useCallback((id) => { - console.log('Clicked:', id); - }, []); - - return ; -} -``` - -### Code Split Routes -```jsx -import { lazy, Suspense } from 'react'; - -const Dashboard = lazy(() => import('./Dashboard')); -const Reports = lazy(() => import('./Reports')); - -function App() { - return ( - }> - - } /> - } /> - - - ); -} -``` - -### Virtualize Large Lists -```jsx -import { FixedSizeList } from 'react-window'; - -function VirtualList({ items }) { - return ( - - {({ index, style }) => ( -
{items[index].name}
- )} -
- ); -} -``` - -## Common Mistakes - -1. **Over-memoization**: Don't memoize simple, fast components (adds overhead) -2. **Inline objects/arrays**: New references break memoization (`config={{ theme: 'dark' }}`) -3. **Missing dependencies**: Stale closures in useCallback/useMemo -4. **Index as key**: Breaks reconciliation when list order changes -5. **Single large context**: Causes widespread re-renders on any update -6. **No profiling**: Optimizing without measuring wastes time - -## Performance Checklist - -Before optimizing: -- [ ] Profile with React DevTools to identify bottlenecks -- [ ] Measure baseline performance metrics - -Optimization targets: -- [ ] Memoize expensive components with stable props -- [ ] Cache computed values with useMemo (if actually expensive) -- [ ] Use useCallback for functions passed to memoized children -- [ ] Implement code splitting for routes and heavy components -- [ ] Virtualize lists with >100 items -- [ ] Provide stable keys for list items (unique IDs, not index) -- [ ] Split state by update frequency -- [ ] Use concurrent features (useTransition, useDeferredValue) for responsiveness - -After optimizing: -- [ ] Profile again to verify improvements -- [ ] Check bundle size reduction (if applicable) -- [ ] Ensure no regressions in functionality - -## Resources - -- **React Docs - Performance**: https://react.dev/learn/render-and-commit -- **React DevTools**: Browser extension for profiling -- **react-window**: https://github.com/bvaughn/react-window -- **Bundle analyzers**: webpack-bundle-analyzer, rollup-plugin-visualizer -- **Lighthouse**: Chrome DevTools performance audit diff --git a/.agents/skills/react-performance-optimization/references/code-splitting.md b/.agents/skills/react-performance-optimization/references/code-splitting.md deleted file mode 100644 index 552d9a3..0000000 --- a/.agents/skills/react-performance-optimization/references/code-splitting.md +++ /dev/null @@ -1,105 +0,0 @@ -# Code Splitting Patterns - -## React.lazy and Suspense - -**Load components on demand for smaller initial bundles:** -```jsx -import { lazy, Suspense } from 'react'; -import { BrowserRouter, Routes, Route } from 'react-router-dom'; - -// Lazy-loaded route components -const Dashboard = lazy(() => import('./pages/Dashboard')); -const Reports = lazy(() => import('./pages/Reports')); -const Settings = lazy(() => import('./pages/Settings')); - -// Component-level code splitting -const HeavyChart = lazy(() => import('./components/HeavyChart')); - -function App() { - return ( - - }> - - } /> - } /> - } /> - - - - ); -} - -function DataVisualization({ data, showChart }) { - return ( -
-

Data Overview

- {showChart && ( - Loading chart...
}> - - - )} - - ); -} -``` - -**Benefits:** -- Reduces initial bundle size (faster First Contentful Paint) -- Loads code only when needed (better caching) -- Route-based splitting: Users only download visited pages - -**Best practices:** -- Split by routes first (biggest impact) -- Split heavy components (charts, editors, modals) -- Provide meaningful loading fallbacks -- Preload critical routes with `` - -## Bundle Optimization - -**Reduce bundle size with smart imports and tree shaking:** -```jsx -// BAD: Imports entire library -import _ from 'lodash'; -import { Button, Modal, Table, Form } from 'antd'; - -// GOOD: Import only needed functions -import debounce from 'lodash/debounce'; -import groupBy from 'lodash/groupBy'; - -// GOOD: Tree-shakeable imports (if library supports it) -import { Button } from 'antd/es/button'; -import { Modal } from 'antd/es/modal'; - -// Dynamic imports for heavy libraries -const PDFViewer = lazy(() => import('react-pdf-viewer')); -const CodeEditor = lazy(() => import('@monaco-editor/react')); - -// Conditional polyfill loading -async function loadPolyfills() { - if (!window.IntersectionObserver) { - await import('intersection-observer'); - } -} -``` - -## Bundle Analysis Tools - -```bash -# Webpack Bundle Analyzer -npm install --save-dev webpack-bundle-analyzer - -# Vite Bundle Visualizer -npm install --save-dev rollup-plugin-visualizer - -# Analyze bundle composition -npm run build -- --stats -npx webpack-bundle-analyzer dist/stats.json -``` - -**Analysis workflow:** -1. Generate production build with stats -2. Open bundle visualizer -3. Identify large dependencies -4. Check for duplicate code -5. Find optimization opportunities (lazy loading, tree shaking) -6. Measure improvement after changes diff --git a/.agents/skills/react-performance-optimization/references/common-pitfalls.md b/.agents/skills/react-performance-optimization/references/common-pitfalls.md deleted file mode 100644 index 334f99d..0000000 --- a/.agents/skills/react-performance-optimization/references/common-pitfalls.md +++ /dev/null @@ -1,371 +0,0 @@ -# Common Performance Pitfalls - -## 1. Inline Object/Array Props - -### The Problem -```jsx -// BAD: New object every render defeats memo -function Parent() { - return ; -} - -const Component = memo(({ config }) => { - // Re-renders every time because config is a new object - return
{config.theme}
; -}); -``` - -### Solutions -```jsx -// GOOD: Stable reference with useMemo -function Parent() { - const config = useMemo(() => ({ theme: 'dark' }), []); - return ; -} - -// BEST: Extract to constant if truly static -const CONFIG = { theme: 'dark' }; - -function Parent() { - return ; -} - -// ALSO GOOD: Pass primitives directly -function Parent() { - return ; -} -``` - -## 2. Anonymous Functions in JSX - -### The Problem -```jsx -// BAD: New function every render -function List({ items }) { - return items.map(item => ( - handleClick(item.id)} - /> - )); -} -``` - -### Solutions -```jsx -// GOOD: useCallback with stable reference -function List({ items }) { - const handleItemClick = useCallback((id) => { - handleClick(id); - }, []); - - return items.map(item => ( - handleItemClick(item.id)} - /> - )); -} - -// ACCEPTABLE: For top-level handlers (not passed to memoized children) -function Form() { - return ( - - ); -} -``` - -## 3. Over-Memoization - -### The Problem -```jsx -// BAD: Unnecessary memoization adds overhead -const SimpleComponent = memo(({ text }) => {text}); - -const number = useMemo(() => 2 + 2, []); // Pointless - -const handleClick = useCallback(() => { - console.log('clicked'); -}, []); // Only useful if passed to memoized child -``` - -### When to Memoize -```jsx -// GOOD: Only memoize if expensive or frequently re-rendered with same props -const ExpensiveComponent = memo(({ data }) => { - // Complex rendering logic - const processed = processLargeDataset(data); - return ; -}); - -// GOOD: useMemo for actual expensive computations -const sortedData = useMemo(() => { - return largeArray.sort((a, b) => b.score - a.score); -}, [largeArray]); - -// GOOD: useCallback when passing to memoized children -const MemoizedChild = memo(ChildComponent); - -function Parent() { - const handleAction = useCallback(() => { - // handler logic - }, []); - - return ; -} -``` - -## 4. Deriving State Unnecessarily - -### The Problem -```jsx -// BAD: Duplicate state causes sync issues -function BadComponent({ items }) { - const [itemsState, setItemsState] = useState(items); - const [itemCount, setItemCount] = useState(items.length); - - // Easy to forget to update itemCount - const addItem = (item) => { - setItemsState([...itemsState, item]); - setItemCount(itemCount + 1); // Can get out of sync - }; -} -``` - -### Solutions -```jsx -// GOOD: Derive during render -function GoodComponent({ items }) { - const [itemsState, setItemsState] = useState(items); - const itemCount = itemsState.length; // Always in sync - - const addItem = (item) => { - setItemsState([...itemsState, item]); - }; -} - -// GOOD: useMemo for expensive derivations only -function ComponentWithExpensiveCalc({ items }) { - const statistics = useMemo(() => { - return { - count: items.length, - total: items.reduce((sum, item) => sum + item.value, 0), - average: items.reduce((sum, item) => sum + item.value, 0) / items.length - }; - }, [items]); -} -``` - -## 5. Incorrect Dependencies - -### The Problem -```jsx -// BAD: Missing dependencies (stale closures) -function Search() { - const [filter, setFilter] = useState(''); - - const fetchData = useCallback(() => { - fetch(`/api/data?filter=${filter}`); - }, []); // Missing filter dependency! -} - -// BAD: Object/array dependencies (always new reference) -function DataComponent() { - const config = { url: '/api', filter }; - - useEffect(() => { - fetchData(config); - }, [config]); // New object every render = runs every render -} -``` - -### Solutions -```jsx -// GOOD: Include all dependencies -function Search() { - const [filter, setFilter] = useState(''); - - const fetchData = useCallback(() => { - fetch(`/api/data?filter=${filter}`); - }, [filter]); // Includes filter -} - -// GOOD: Primitive dependencies -function DataComponent() { - const [filter, setFilter] = useState(''); - - useEffect(() => { - fetchData({ url: '/api', filter }); - }, [filter]); // Only primitive -} - -// GOOD: Stable reference with useMemo -function DataComponent() { - const [filter, setFilter] = useState(''); - const config = useMemo(() => ({ url: '/api', filter }), [filter]); - - useEffect(() => { - fetchData(config); - }, [config]); -} -``` - -## 6. Context Performance Issues - -### The Problem -```jsx -// BAD: Single context with everything causes widespread re-renders -const AppContext = createContext(); - -function App() { - const [user, setUser] = useState({}); - const [theme, setTheme] = useState('light'); - const [data, setData] = useState([]); - - return ( - - - - ); -} - -// Every component re-renders when ANY value changes -``` - -### Solutions -```jsx -// GOOD: Split contexts by update frequency -const UserContext = createContext(); -const ThemeContext = createContext(); -const DataContext = createContext(); - -function App() { - const [user, setUser] = useState({}); - const [theme, setTheme] = useState('light'); - const [data, setData] = useState([]); - - return ( - - - - - - - - ); -} - -// Components only re-render when their specific context changes -function UserProfile() { - const { user } = useContext(UserContext); // Only re-renders on user change - return
{user.name}
; -} -``` - -## 7. Large Component Files - -### The Problem -- Difficult to optimize specific parts -- Hard to identify performance bottlenecks -- Monolithic re-renders - -### Solution -```jsx -// Split into smaller, focused components -// Each can be optimized independently - -// Before: One large component -function Dashboard() { - return ( -
- {/* 500 lines of JSX */} -
- ); -} - -// After: Focused components -function Dashboard() { - return ( -
-
- - -
-
- ); -} - -const Header = memo(HeaderComponent); -const Sidebar = memo(SidebarComponent); -const MainContent = memo(MainContentComponent); -``` - -## 8. Image Loading Issues - -### The Problem -```jsx -// BAD: All images load immediately -function Gallery({ images }) { - return ( -
- {images.map(img => ( - {img.alt} - ))} -
- ); -} -``` - -### Solutions -```jsx -// GOOD: Native lazy loading -function Gallery({ images }) { - return ( -
- {images.map(img => ( - {img.alt} - ))} -
- ); -} - -// BETTER: Intersection Observer for custom loading -import { useEffect, useRef, useState } from 'react'; - -function LazyImage({ src, alt }) { - const [isLoaded, setIsLoaded] = useState(false); - const imgRef = useRef(); - - useEffect(() => { - const observer = new IntersectionObserver( - ([entry]) => { - if (entry.isIntersecting) { - setIsLoaded(true); - observer.disconnect(); - } - }, - { rootMargin: '50px' } - ); - - if (imgRef.current) observer.observe(imgRef.current); - - return () => observer.disconnect(); - }, []); - - return ( - {alt} - ); -} -``` diff --git a/.agents/skills/react-performance-optimization/references/concurrent-features.md b/.agents/skills/react-performance-optimization/references/concurrent-features.md deleted file mode 100644 index cd12fd4..0000000 --- a/.agents/skills/react-performance-optimization/references/concurrent-features.md +++ /dev/null @@ -1,140 +0,0 @@ -# Concurrent Features (React 18+) - -## useTransition - -**Mark non-urgent updates for better responsiveness:** -```jsx -import { useState, useTransition } from 'react'; - -function SearchApp() { - const [query, setQuery] = useState(''); - const [results, setResults] = useState([]); - const [isPending, startTransition] = useTransition(); - - const handleSearch = (value) => { - setQuery(value); // Urgent: Update input immediately - - startTransition(() => { - // Non-urgent: Can be interrupted - setResults(searchItems(value)); - }); - }; - - return ( -
- handleSearch(e.target.value)} /> - {isPending && } - -
- ); -} -``` - -**When to use:** -- Search filtering with live results -- Tab switching with heavy content -- Any UI update that can be delayed for responsiveness - -## useDeferredValue - -**Defer expensive renders without explicit transitions:** -```jsx -import { useState, useDeferredValue, useMemo } from 'react'; - -function FilteredList({ items, searchTerm }) { - const deferredSearchTerm = useDeferredValue(searchTerm); - - // Filters using deferred value (doesn't block typing) - const filteredItems = useMemo(() => { - return items.filter(item => - item.name.toLowerCase().includes(deferredSearchTerm.toLowerCase()) - ); - }, [items, deferredSearchTerm]); - - return ( -
-

Showing {filteredItems.length} results

- {filteredItems.map(item => )} -
- ); -} -``` - -**Difference from useTransition:** -- `useTransition`: You control when to defer (wrap updates) -- `useDeferredValue`: React controls when to defer (wrap values) - -## Concurrent Rendering Benefits - -### Interruptible Rendering -- React can pause expensive work -- Prioritizes user interactions (clicks, typing) -- Resumes work when browser is idle - -### Automatic Prioritization -- Urgent updates (user input) render immediately -- Non-urgent updates (filtering, sorting) can wait -- Smoother user experience without manual debouncing - -### Better Loading States -```jsx -function App() { - const [tab, setTab] = useState('home'); - const [isPending, startTransition] = useTransition(); - - const handleTabChange = (newTab) => { - startTransition(() => { - setTab(newTab); - }); - }; - - return ( -
- - -
- ); -} -``` - -## Migration Guide - -### Before React 18 -```jsx -// Manual debouncing for performance -const debouncedSearch = debounce((value) => { - setResults(searchItems(value)); -}, 300); - - debouncedSearch(e.target.value)} /> -``` - -### With React 18 -```jsx -// Automatic prioritization with useTransition -const handleSearch = (value) => { - setQuery(value); // Immediate - startTransition(() => { - setResults(searchItems(value)); // Deferred - }); -}; - - handleSearch(e.target.value)} /> -``` - -## Performance Comparison - -**Without concurrent features:** -- User types → UI freezes during expensive filter -- Perceived lag and unresponsiveness -- Manual debouncing required - -**With concurrent features:** -- User types → Input updates immediately -- Filter runs in background -- UI stays responsive -- No manual optimization needed diff --git a/.agents/skills/react-performance-optimization/references/memoization.md b/.agents/skills/react-performance-optimization/references/memoization.md deleted file mode 100644 index 51b30a8..0000000 --- a/.agents/skills/react-performance-optimization/references/memoization.md +++ /dev/null @@ -1,140 +0,0 @@ -# Memoization Patterns - -## React.memo for Component Memoization - -**Prevent unnecessary re-renders of functional components:** -```jsx -import React, { memo } from 'react'; - -const ExpensiveComponent = memo(({ data, onAction }) => { - console.log('Rendering ExpensiveComponent'); - - return ( -
-

{data.title}

-

{data.description}

- -
- ); -}); - -// Custom comparison for complex props -const UserCard = memo( - ({ user, settings }) => ( -
-

{user.name}

- {user.email} -
- ), - (prevProps, nextProps) => { - // Return true if props are equal (skip render) - return prevProps.user.id === nextProps.user.id && - prevProps.settings.theme === nextProps.settings.theme; - } -); -``` - -**When to use:** -- Component renders with same props frequently -- Expensive rendering logic (complex JSX, heavy computations) -- Child components in frequently updating parent -- List items with stable props - -**When NOT to use:** -- Props change on every render (comparison overhead) -- Simple, fast-rendering components (unnecessary optimization) - -## useMemo for Expensive Computations - -**Cache expensive calculation results:** -```jsx -import { useMemo } from 'react'; - -function DataAnalyzer({ items, filters }) { - // Recalculates only when items or filters change - const filteredAndSorted = useMemo(() => { - console.log('Computing filtered data'); - - return items - .filter(item => filters.categories.includes(item.category)) - .filter(item => item.price >= filters.minPrice) - .sort((a, b) => b.score - a.score); - }, [items, filters]); - - const statistics = useMemo(() => { - return { - total: filteredAndSorted.length, - average: filteredAndSorted.reduce((sum, item) => sum + item.price, 0) / - filteredAndSorted.length, - maxPrice: Math.max(...filteredAndSorted.map(item => item.price)) - }; - }, [filteredAndSorted]); - - return ( -
-

Total items: {statistics.total}

-

Average price: ${statistics.average.toFixed(2)}

-
- ); -} -``` - -**Use cases:** -- Expensive array operations (filter, map, sort, reduce) -- Complex mathematical calculations -- Data transformations and aggregations -- Creating derived data structures - -**Performance impact:** -- Without useMemo: Computation runs every render -- With useMemo: Computation runs only when dependencies change - -## useCallback for Stable Function References - -**Prevent child re-renders caused by function reference changes:** -```jsx -import { useState, useCallback, memo } from 'react'; - -const ListItem = memo(({ item, onDelete, onEdit }) => { - console.log('Rendering ListItem:', item.id); - return ( -
- {item.name} - - -
- ); -}); - -function ItemList({ items }) { - const [selectedId, setSelectedId] = useState(null); - - // Stable function reference across renders - const handleDelete = useCallback((id) => { - console.log('Deleting:', id); - // API call to delete - }, []); // No dependencies = never recreated - - const handleEdit = useCallback((id) => { - setSelectedId(id); - // Open edit modal - }, [setSelectedId]); // Recreated only if setSelectedId changes - - return ( -
- {items.map(item => ( - - ))} -
- ); -} -``` - -**Critical rule:** -- Use `useCallback` when passing functions to memoized child components -- Without it, new function reference on every render defeats memo optimization diff --git a/.agents/skills/react-performance-optimization/references/profiling-debugging.md b/.agents/skills/react-performance-optimization/references/profiling-debugging.md deleted file mode 100644 index 9ac8e6d..0000000 --- a/.agents/skills/react-performance-optimization/references/profiling-debugging.md +++ /dev/null @@ -1,215 +0,0 @@ -# Profiling and Debugging - -## React DevTools Profiler - -**Identify and diagnose performance bottlenecks:** - -### Programmatic Profiling -```jsx -import { Profiler } from 'react'; - -function onRenderCallback( - id, // Component being profiled - phase, // "mount" or "update" - actualDuration, // Time spent rendering - baseDuration, // Estimated time without memoization - startTime, // When render started - commitTime, // When committed to DOM - interactions // Set of interactions -) { - console.log(`${id} (${phase}) took ${actualDuration}ms`); - - // Send to analytics - if (actualDuration > 16) { // More than one frame (60fps) - sendToAnalytics({ id, phase, actualDuration }); - } -} - -function App() { - return ( - - - - - - - ); -} -``` - -### DevTools Profiler Workflow - -**Step 1: Record a profile** -1. Open React DevTools → Profiler tab -2. Click record button (red circle) -3. Interact with your app (click, type, navigate) -4. Click stop button - -**Step 2: Analyze flame graph** -- Yellow/red bars = slow components -- Bar width = time spent rendering -- Click bar to see component details -- Look for unexpected renders - -**Step 3: Check ranked view** -- Components sorted by render time -- Identify most expensive renders -- Focus optimization efforts here - -**Step 4: Investigate renders** -- Why did this component render? -- Did props/state actually change? -- Is memo/useMemo working correctly? - -**Step 5: Compare profiles** -- Record baseline profile -- Apply optimizations -- Record new profile -- Compare improvements - -## Browser Performance API - -**Custom performance measurements:** -```jsx -// Measure component render time -performance.mark('render-start'); -// ... component render logic ... -performance.mark('render-end'); -performance.measure('component-render', 'render-start', 'render-end'); - -const measure = performance.getEntriesByName('component-render')[0]; -console.log(`Render took: ${measure.duration}ms`); - -// Measure async operations -async function fetchData() { - performance.mark('fetch-start'); - const data = await fetch('/api/data'); - performance.mark('fetch-end'); - performance.measure('data-fetch', 'fetch-start', 'fetch-end'); -} -``` - -## Why Did You Render - -**Debug unnecessary re-renders in development:** -```bash -npm install --save-dev @welldone-software/why-did-you-render -``` - -```jsx -// wdyr.js -import React from 'react'; - -if (process.env.NODE_ENV === 'development') { - const whyDidYouRender = require('@welldone-software/why-did-you-render'); - whyDidYouRender(React, { - trackAllPureComponents: true, - trackHooks: true, - logOnDifferentValues: true, - }); -} -``` - -```jsx -// Component.jsx -function MyComponent(props) { - // ... -} - -MyComponent.whyDidYouRender = true; - -export default MyComponent; -``` - -## Chrome DevTools Performance Tab - -**Identify non-React performance issues:** - -1. Open Chrome DevTools → Performance tab -2. Click record → Interact with app → Stop -3. Analyze timeline: - - **Scripting** (yellow): JavaScript execution - - **Rendering** (purple): Style calculations, layout - - **Painting** (green): Pixel painting - - **System** (gray): Browser overhead - -**Look for:** -- Long tasks (>50ms) blocking the main thread -- Excessive layout recalculations -- Forced synchronous layout (layout thrashing) -- Large JavaScript bundles - -## Lighthouse Performance Audit - -**Automated performance analysis:** -```bash -# Install Lighthouse CLI -npm install -g lighthouse - -# Run audit -lighthouse https://your-app.com --view - -# CI integration -lighthouse https://your-app.com --output json --output-path ./report.json -``` - -**Key metrics:** -- **First Contentful Paint (FCP)**: < 1.8s -- **Largest Contentful Paint (LCP)**: < 2.5s -- **Time to Interactive (TTI)**: < 3.8s -- **Total Blocking Time (TBT)**: < 200ms -- **Cumulative Layout Shift (CLS)**: < 0.1 - -## Performance Monitoring in Production - -**Real User Monitoring (RUM):** -```jsx -import { useEffect } from 'react'; - -function App() { - useEffect(() => { - // Report Web Vitals - import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { - getCLS(sendToAnalytics); - getFID(sendToAnalytics); - getFCP(sendToAnalytics); - getLCP(sendToAnalytics); - getTTFB(sendToAnalytics); - }); - }, []); - - return
...
; -} - -function sendToAnalytics({ name, value, id }) { - // Send to analytics service (Google Analytics, Datadog, etc.) - gtag('event', name, { - event_category: 'Web Vitals', - value: Math.round(value), - event_label: id, - non_interaction: true, - }); -} -``` - -## Common Performance Patterns to Look For - -### 1. Unnecessary Re-renders -- Component renders but props/state unchanged -- Parent re-renders causing child cascade -- Missing memo on expensive child components - -### 2. Expensive Computations -- Complex calculations on every render -- Missing useMemo for derived data -- Sorting/filtering large arrays without memoization - -### 3. Memory Leaks -- Event listeners not cleaned up -- Timers not cleared -- Subscriptions not unsubscribed - -### 4. Bundle Size Issues -- Large dependencies imported unnecessarily -- Missing code splitting -- Duplicate dependencies in bundle diff --git a/.agents/skills/react-performance-optimization/references/state-management.md b/.agents/skills/react-performance-optimization/references/state-management.md deleted file mode 100644 index 54538ee..0000000 --- a/.agents/skills/react-performance-optimization/references/state-management.md +++ /dev/null @@ -1,164 +0,0 @@ -# State Management for Performance - -## State Structure Optimization - -**Optimize state structure to minimize re-renders:** -```jsx -import { useState, createContext, useContext } from 'react'; - -// BAD: Single large state object causes many re-renders -function BadApp() { - const [state, setState] = useState({ - user: {}, - settings: {}, - data: [], - ui: { modal: false, sidebar: true } - }); - - // Changing modal state re-renders entire tree - const toggleModal = () => setState(prev => ({ - ...prev, - ui: { ...prev.ui, modal: !prev.ui.modal } - })); -} - -// GOOD: Split state by update frequency -function GoodApp() { - const [user, setUser] = useState({}); - const [settings, setSettings] = useState({}); - const [data, setData] = useState([]); - const [modalOpen, setModalOpen] = useState(false); - - // Only components using modalOpen re-render -} -``` - -## Context Splitting - -**Prevent unnecessary context re-renders:** -```jsx -// BEST: Context splitting for shared state -const UserContext = createContext(); -const DataContext = createContext(); - -function App() { - const [user, setUser] = useState({}); - const [data, setData] = useState([]); - - return ( - - - - - - ); -} - -// Components only subscribe to needed context -function UserProfile() { - const { user } = useContext(UserContext); // Only re-renders on user change - return
{user.name}
; -} -``` - -## State Management Strategies - -**Choose the right tool for the job:** - -### Local State First -```jsx -// useState for component-level state -const [count, setCount] = useState(0); - -// useReducer for complex state logic -const [state, dispatch] = useReducer(reducer, initialState); -``` - -### Context for Shared State -- Split by update frequency -- Avoid putting everything in one context -- Use memo to prevent unnecessary re-renders - -### External State Managers -**Zustand** (Recommended for simplicity) -```jsx -import create from 'zustand'; - -const useStore = create((set) => ({ - count: 0, - increment: () => set((state) => ({ count: state.count + 1 })) -})); - -function Counter() { - const count = useStore((state) => state.count); - const increment = useStore((state) => state.increment); - return ; -} -``` - -**Jotai** (Atomic state management) -```jsx -import { atom, useAtom } from 'jotai'; - -const countAtom = atom(0); - -function Counter() { - const [count, setCount] = useAtom(countAtom); - return ; -} -``` - -**Redux Toolkit** (Complex apps) -- Use for large applications -- Time-travel debugging -- DevTools integration -- Middleware ecosystem - -### Server State Libraries - -**React Query** (Recommended) -```jsx -import { useQuery } from '@tanstack/react-query'; - -function UserProfile({ userId }) { - const { data, isLoading } = useQuery({ - queryKey: ['user', userId], - queryFn: () => fetchUser(userId), - staleTime: 5000, - }); - - if (isLoading) return ; - return
{data.name}
; -} -``` - -**SWR** (Alternative) -```jsx -import useSWR from 'swr'; - -function UserProfile({ userId }) { - const { data, error } = useSWR(`/api/user/${userId}`, fetcher); - - if (error) return ; - if (!data) return ; - return
{data.name}
; -} -``` - -## Avoiding Derived State - -```jsx -// BAD: Duplicate state causes sync issues -const [items, setItems] = useState([]); -const [itemCount, setItemCount] = useState(0); - -// GOOD: Derive during render -const [items, setItems] = useState([]); -const itemCount = items.length; // Always in sync - -// GOOD: useMemo for expensive derivations -const expensiveValue = useMemo(() => - items.reduce((sum, item) => sum + item.value, 0), - [items] -); -``` diff --git a/.agents/skills/react-performance-optimization/references/virtualization.md b/.agents/skills/react-performance-optimization/references/virtualization.md deleted file mode 100644 index a4738c4..0000000 --- a/.agents/skills/react-performance-optimization/references/virtualization.md +++ /dev/null @@ -1,117 +0,0 @@ -# Virtualization for Large Lists - -## react-window Basics - -**Render only visible items to handle thousands of rows:** -```jsx -import { FixedSizeList } from 'react-window'; - -function VirtualizedList({ items }) { - const Row = ({ index, style }) => ( -
-

{items[index].title}

-

{items[index].description}

-
- ); - - return ( - - {Row} - - ); -} -``` - -## Variable Size Lists - -```jsx -import { VariableSizeList } from 'react-window'; - -function DynamicList({ items }) { - const getItemSize = (index) => { - return items[index].type === 'header' ? 60 : 40; - }; - - return ( - - {({ index, style }) => ( -
{items[index].content}
- )} -
- ); -} -``` - -## Performance Impact - -**Traditional list with 10,000 items:** -- DOM nodes: 10,000 -- Memory usage: High -- Scroll performance: Poor - -**Virtualized list:** -- DOM nodes: ~20 (only visible + buffer) -- Memory usage: Low -- Scroll performance: Smooth 60fps -- Result: 500x reduction in DOM nodes - -## Library Comparison - -**react-window** (Recommended) -- Lightweight (7KB gzipped) -- Simple API -- Excellent performance -- Good for most use cases - -**react-virtualized** -- Feature-rich (27KB gzipped) -- Advanced components (Grid, Masonry, Table) -- More configuration options -- Better for complex layouts - -**@tanstack/react-virtual** -- Modern, headless virtualization -- Framework agnostic core -- Maximum flexibility -- Better for custom implementations - -## List Keys Optimization - -**Proper keys prevent unnecessary re-renders:** -```jsx -// BAD: Index as key (breaks when reordering/filtering) -{items.map((item, index) => ( - -))} - -// BAD: Random keys (forces complete re-render every time) -{items.map(item => ( - -))} - -// GOOD: Stable unique identifier -{items.map(item => ( - -))} - -// GOOD: Composite key when no unique ID exists -{items.map(item => ( - -))} -``` - -**Why keys matter:** -- React uses keys to track element identity -- Stable keys enable efficient diffing and reconciliation -- Index keys break when list order changes -- Missing keys force React to destroy/recreate components diff --git a/.agents/skills/react-performance-optimization/validation/rubric.yaml b/.agents/skills/react-performance-optimization/validation/rubric.yaml deleted file mode 100644 index 05f4cee..0000000 --- a/.agents/skills/react-performance-optimization/validation/rubric.yaml +++ /dev/null @@ -1,67 +0,0 @@ -# React Performance Optimization Skill Quality Rubric -version: "1.0.0" -skill_name: react-performance-optimization -evaluated_date: "2026-01-05" - -dimensions: - clarity: - weight: 25 - description: "Clear React performance concepts" - criteria: - - "Re-render causes are clearly explained" - - "Memoization strategies are visual" - - "Bundle optimization steps are clear" - - "Profiler usage is step-by-step" - - completeness: - weight: 25 - description: "Comprehensive React performance coverage" - criteria: - - "Covers React.memo, useMemo, useCallback" - - "Includes code splitting/lazy loading" - - "Has virtualization for large lists" - - "Covers state management perf" - - accuracy: - weight: 30 - description: "Correct React 18+ patterns" - criteria: - - "Follows React 18 best practices" - - "Concurrent features correctly used" - - "No deprecated patterns (componentWillMount)" - - "Hooks rules followed" - - usefulness: - weight: 20 - description: "Practical React optimization" - criteria: - - "Before/after performance comparisons" - - "Lighthouse metrics explained" - - "Real-world optimization examples" - - "Debugging slow components" - -passing_criteria: - minimum_score: 3.5 - target_score: 4.0 - exceptional_score: 4.5 - required_dimensions: - - accuracy - blocking_issues: - - "Uses deprecated lifecycle methods" - - "Incorrect hooks usage" - - "Anti-pattern examples without warning" - - "Class components without functional alternatives" - -scoring_guide: - clarity: - "1": "Performance concepts jumbled" - "2": "Confusing optimization advice" - "3": "Understandable basics" - "4": "Clear with profiler examples" - "5": "Exceptional visual comparisons" - accuracy: - "1": "Outdated React patterns" - "2": "Class component focus" - "3": "Mostly modern patterns" - "4": "React 18+ best practices" - "5": "Cutting-edge, future-proof" diff --git a/.agents/skills/responsive-design/SKILL.md b/.agents/skills/responsive-design/SKILL.md deleted file mode 100644 index f4232ea..0000000 --- a/.agents/skills/responsive-design/SKILL.md +++ /dev/null @@ -1,524 +0,0 @@ ---- -name: responsive-design -description: Implement modern responsive layouts using container queries, fluid typography, CSS Grid, and mobile-first breakpoint strategies. Use when building adaptive interfaces, implementing fluid layouts, or creating component-level responsive behavior. ---- - -# Responsive Design - -Master modern responsive design techniques to create interfaces that adapt seamlessly across all screen sizes and device contexts. - -## When to Use This Skill - -- Implementing mobile-first responsive layouts -- Using container queries for component-based responsiveness -- Creating fluid typography and spacing scales -- Building complex layouts with CSS Grid and Flexbox -- Designing breakpoint strategies for design systems -- Implementing responsive images and media -- Creating adaptive navigation patterns -- Building responsive tables and data displays - -## Core Capabilities - -### 1. Container Queries - -- Component-level responsiveness independent of viewport -- Container query units (cqi, cqw, cqh) -- Style queries for conditional styling -- Fallbacks for browser support - -### 2. Fluid Typography & Spacing - -- CSS clamp() for fluid scaling -- Viewport-relative units (vw, vh, dvh) -- Fluid type scales with min/max bounds -- Responsive spacing systems - -### 3. Layout Patterns - -- CSS Grid for 2D layouts -- Flexbox for 1D distribution -- Intrinsic layouts (content-based sizing) -- Subgrid for nested grid alignment - -### 4. Breakpoint Strategy - -- Mobile-first media queries -- Content-based breakpoints -- Design token integration -- Feature queries (@supports) - -## Quick Reference - -### Modern Breakpoint Scale - -```css -/* Mobile-first breakpoints */ -/* Base: Mobile (< 640px) */ -@media (min-width: 640px) { - /* sm: Landscape phones, small tablets */ -} -@media (min-width: 768px) { - /* md: Tablets */ -} -@media (min-width: 1024px) { - /* lg: Laptops, small desktops */ -} -@media (min-width: 1280px) { - /* xl: Desktops */ -} -@media (min-width: 1536px) { - /* 2xl: Large desktops */ -} - -/* Tailwind CSS equivalent */ -/* sm: @media (min-width: 640px) */ -/* md: @media (min-width: 768px) */ -/* lg: @media (min-width: 1024px) */ -/* xl: @media (min-width: 1280px) */ -/* 2xl: @media (min-width: 1536px) */ -``` - -## Key Patterns - -### Pattern 1: Container Queries - -```css -/* Define a containment context */ -.card-container { - container-type: inline-size; - container-name: card; -} - -/* Query the container, not the viewport */ -@container card (min-width: 400px) { - .card { - display: grid; - grid-template-columns: 200px 1fr; - gap: 1rem; - } - - .card-image { - aspect-ratio: 1; - } -} - -@container card (min-width: 600px) { - .card { - grid-template-columns: 250px 1fr; - } - - .card-title { - font-size: 1.5rem; - } -} - -/* Container query units */ -.card-title { - /* 5% of container width, clamped between 1rem and 2rem */ - font-size: clamp(1rem, 5cqi, 2rem); -} -``` - -```tsx -// React component with container queries -function ResponsiveCard({ title, image, description }) { - return ( -
-
- -
-

- {title} -

-

- {description} -

-
-
-
- ); -} -``` - -### Pattern 2: Fluid Typography - -```css -/* Fluid type scale using clamp() */ -:root { - /* Min size, preferred (fluid), max size */ - --text-xs: clamp(0.75rem, 0.7rem + 0.25vw, 0.875rem); - --text-sm: clamp(0.875rem, 0.8rem + 0.375vw, 1rem); - --text-base: clamp(1rem, 0.9rem + 0.5vw, 1.125rem); - --text-lg: clamp(1.125rem, 1rem + 0.625vw, 1.25rem); - --text-xl: clamp(1.25rem, 1rem + 1.25vw, 1.5rem); - --text-2xl: clamp(1.5rem, 1.25rem + 1.25vw, 2rem); - --text-3xl: clamp(1.875rem, 1.5rem + 1.875vw, 2.5rem); - --text-4xl: clamp(2.25rem, 1.75rem + 2.5vw, 3.5rem); -} - -/* Usage */ -h1 { - font-size: var(--text-4xl); -} -h2 { - font-size: var(--text-3xl); -} -h3 { - font-size: var(--text-2xl); -} -p { - font-size: var(--text-base); -} - -/* Fluid spacing scale */ -:root { - --space-xs: clamp(0.25rem, 0.2rem + 0.25vw, 0.5rem); - --space-sm: clamp(0.5rem, 0.4rem + 0.5vw, 0.75rem); - --space-md: clamp(1rem, 0.8rem + 1vw, 1.5rem); - --space-lg: clamp(1.5rem, 1.2rem + 1.5vw, 2.5rem); - --space-xl: clamp(2rem, 1.5rem + 2.5vw, 4rem); -} -``` - -```tsx -// Utility function for fluid values -function fluidValue( - minSize: number, - maxSize: number, - minWidth = 320, - maxWidth = 1280, -) { - const slope = (maxSize - minSize) / (maxWidth - minWidth); - const yAxisIntersection = -minWidth * slope + minSize; - - return `clamp(${minSize}rem, ${yAxisIntersection.toFixed(4)}rem + ${(slope * 100).toFixed(4)}vw, ${maxSize}rem)`; -} - -// Generate fluid type scale -const fluidTypeScale = { - sm: fluidValue(0.875, 1), - base: fluidValue(1, 1.125), - lg: fluidValue(1.25, 1.5), - xl: fluidValue(1.5, 2), - "2xl": fluidValue(2, 3), -}; -``` - -### Pattern 3: CSS Grid Responsive Layout - -```css -/* Auto-fit grid - items wrap automatically */ -.grid-auto { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr)); - gap: 1.5rem; -} - -/* Auto-fill grid - maintains empty columns */ -.grid-auto-fill { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); - gap: 1rem; -} - -/* Responsive grid with named areas */ -.page-layout { - display: grid; - grid-template-areas: - "header" - "main" - "sidebar" - "footer"; - gap: 1rem; -} - -@media (min-width: 768px) { - .page-layout { - grid-template-columns: 1fr 300px; - grid-template-areas: - "header header" - "main sidebar" - "footer footer"; - } -} - -@media (min-width: 1024px) { - .page-layout { - grid-template-columns: 250px 1fr 300px; - grid-template-areas: - "header header header" - "nav main sidebar" - "footer footer footer"; - } -} - -.header { - grid-area: header; -} -.main { - grid-area: main; -} -.sidebar { - grid-area: sidebar; -} -.footer { - grid-area: footer; -} -``` - -```tsx -// Responsive grid component -function ResponsiveGrid({ children, minItemWidth = "250px", gap = "1.5rem" }) { - return ( -
- {children} -
- ); -} - -// Usage with Tailwind -function ProductGrid({ products }) { - return ( -
- {products.map((product) => ( - - ))} -
- ); -} -``` - -### Pattern 4: Responsive Navigation - -```tsx -function ResponsiveNav({ items }) { - const [isOpen, setIsOpen] = useState(false); - - return ( - - ); -} -``` - -### Pattern 5: Responsive Images - -```tsx -// Responsive image with art direction -function ResponsiveHero() { - return ( - - {/* Art direction: different crops for different screens */} - - - - - {/* Fallback */} - Hero image description - - ); -} - -// Responsive image with srcset for resolution switching -function ProductImage({ product }) { - return ( - {product.name} - ); -} -``` - -### Pattern 6: Responsive Tables - -```tsx -// Responsive table with horizontal scroll -function ResponsiveTable({ data, columns }) { - return ( -
-
- - - {columns.map((col) => ( - - ))} - - - - {data.map((row, i) => ( - - {columns.map((col) => ( - - ))} - - ))} - -
- {col.label} -
- {row[col.key]} -
- - ); -} - -// Card-based table for mobile -function ResponsiveDataTable({ data, columns }) { - return ( - <> - {/* Desktop table */} - - {/* ... standard table */} -
- - {/* Mobile cards */} -
- {data.map((row, i) => ( -
- {columns.map((col) => ( -
- - {col.label} - - {row[col.key]} -
- ))} -
- ))} -
- - ); -} -``` - -## Viewport Units - -```css -/* Standard viewport units */ -.full-height { - height: 100vh; /* May cause issues on mobile */ -} - -/* Dynamic viewport units (recommended for mobile) */ -.full-height-dynamic { - height: 100dvh; /* Accounts for mobile browser UI */ -} - -/* Small viewport (minimum) */ -.min-full-height { - min-height: 100svh; -} - -/* Large viewport (maximum) */ -.max-full-height { - max-height: 100lvh; -} - -/* Viewport-relative font sizing */ -.hero-title { - /* 5vw with min/max bounds */ - font-size: clamp(2rem, 5vw, 4rem); -} -``` - -## Best Practices - -1. **Mobile-First**: Start with mobile styles, enhance for larger screens -2. **Content Breakpoints**: Set breakpoints based on content, not devices -3. **Fluid Over Fixed**: Use fluid values for typography and spacing -4. **Container Queries**: Use for component-level responsiveness -5. **Test Real Devices**: Simulators don't catch all issues -6. **Performance**: Optimize images, lazy load off-screen content -7. **Touch Targets**: Maintain 44x44px minimum on mobile -8. **Logical Properties**: Use inline/block for internationalization - -## Common Issues - -- **Horizontal Overflow**: Content breaking out of viewport -- **Fixed Widths**: Using px instead of relative units -- **Viewport Height**: 100vh issues on mobile browsers -- **Font Size**: Text too small on mobile -- **Touch Targets**: Buttons too small to tap accurately -- **Aspect Ratio**: Images squishing or stretching -- **Z-Index Stacking**: Overlays breaking on different screens - -## Resources - -- [CSS Container Queries](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_container_queries) -- [Utopia Fluid Type Calculator](https://utopia.fyi/type/calculator/) -- [Every Layout](https://every-layout.dev/) -- [Responsive Images Guide](https://web.dev/responsive-images/) -- [CSS Grid Garden](https://cssgridgarden.com/) diff --git a/.agents/skills/responsive-design/references/breakpoint-strategies.md b/.agents/skills/responsive-design/references/breakpoint-strategies.md deleted file mode 100644 index 97620dd..0000000 --- a/.agents/skills/responsive-design/references/breakpoint-strategies.md +++ /dev/null @@ -1,591 +0,0 @@ -# Breakpoint Strategies - -## Overview - -Effective breakpoint strategies focus on content needs rather than device sizes. Modern responsive design uses fewer, content-driven breakpoints combined with fluid techniques. - -## Mobile-First Approach - -### Core Philosophy - -Start with the smallest screen, then progressively enhance for larger screens. - -```css -/* Base styles (mobile first) */ -.component { - display: flex; - flex-direction: column; - padding: 1rem; -} - -/* Enhance for larger screens */ -@media (min-width: 640px) { - .component { - flex-direction: row; - padding: 1.5rem; - } -} - -@media (min-width: 1024px) { - .component { - padding: 2rem; - } -} -``` - -### Benefits - -1. **Performance**: Mobile devices load only necessary CSS -2. **Progressive Enhancement**: Features add rather than subtract -3. **Content Priority**: Forces focus on essential content first -4. **Simplicity**: Easier to reason about cascading styles - -## Common Breakpoint Scales - -### Tailwind CSS Default - -```css -/* Tailwind breakpoints */ -/* sm: 640px - Landscape phones */ -/* md: 768px - Tablets */ -/* lg: 1024px - Laptops */ -/* xl: 1280px - Desktops */ -/* 2xl: 1536px - Large desktops */ - -@media (min-width: 640px) { - /* sm */ -} -@media (min-width: 768px) { - /* md */ -} -@media (min-width: 1024px) { - /* lg */ -} -@media (min-width: 1280px) { - /* xl */ -} -@media (min-width: 1536px) { - /* 2xl */ -} -``` - -### Bootstrap 5 - -```css -/* Bootstrap breakpoints */ -/* sm: 576px */ -/* md: 768px */ -/* lg: 992px */ -/* xl: 1200px */ -/* xxl: 1400px */ - -@media (min-width: 576px) { - /* sm */ -} -@media (min-width: 768px) { - /* md */ -} -@media (min-width: 992px) { - /* lg */ -} -@media (min-width: 1200px) { - /* xl */ -} -@media (min-width: 1400px) { - /* xxl */ -} -``` - -### Minimalist Scale - -```css -/* Simplified 3-breakpoint system */ -/* Base: Mobile (< 600px) */ -/* Medium: Tablets and small laptops (600px - 1024px) */ -/* Large: Desktops (> 1024px) */ - -:root { - --bp-md: 600px; - --bp-lg: 1024px; -} - -@media (min-width: 600px) { - /* Medium */ -} -@media (min-width: 1024px) { - /* Large */ -} -``` - -## Content-Based Breakpoints - -### Finding Natural Breakpoints - -Instead of using device-based breakpoints, identify where your content naturally needs to change. - -```css -/* Bad: Device-based thinking */ -@media (min-width: 768px) { - /* iPad breakpoint */ -} - -/* Good: Content-based thinking */ -/* Breakpoint where sidebar fits comfortably next to content */ -@media (min-width: 50rem) { - .layout { - display: grid; - grid-template-columns: 1fr 300px; - } -} - -/* Breakpoint where cards can show 3 across without crowding */ -@media (min-width: 65rem) { - .card-grid { - grid-template-columns: repeat(3, 1fr); - } -} -``` - -### Testing Content Breakpoints - -```javascript -// Find where content breaks -function findBreakpoints(selector) { - const element = document.querySelector(selector); - const breakpoints = []; - - for (let width = 320; width <= 1920; width += 10) { - element.style.width = `${width}px`; - - // Check for overflow, wrapping, or layout issues - if (element.scrollWidth > element.clientWidth) { - breakpoints.push({ width, issue: "overflow" }); - } - } - - return breakpoints; -} -``` - -## Design Token Integration - -### Breakpoint Tokens - -```css -:root { - /* Breakpoint values */ - --breakpoint-sm: 640px; - --breakpoint-md: 768px; - --breakpoint-lg: 1024px; - --breakpoint-xl: 1280px; - --breakpoint-2xl: 1536px; - - /* Container widths for each breakpoint */ - --container-sm: 640px; - --container-md: 768px; - --container-lg: 1024px; - --container-xl: 1280px; - --container-2xl: 1536px; -} - -.container { - width: 100%; - max-width: var(--container-lg); - margin-inline: auto; - padding-inline: var(--space-4); -} -``` - -### JavaScript Integration - -```typescript -// Breakpoint constants -export const breakpoints = { - sm: 640, - md: 768, - lg: 1024, - xl: 1280, - "2xl": 1536, -} as const; - -// Media query hook -function useMediaQuery(query: string): boolean { - const [matches, setMatches] = useState(false); - - useEffect(() => { - const media = window.matchMedia(query); - setMatches(media.matches); - - const listener = () => setMatches(media.matches); - media.addEventListener("change", listener); - return () => media.removeEventListener("change", listener); - }, [query]); - - return matches; -} - -// Breakpoint hook -function useBreakpoint() { - const isSmall = useMediaQuery(`(min-width: ${breakpoints.sm}px)`); - const isMedium = useMediaQuery(`(min-width: ${breakpoints.md}px)`); - const isLarge = useMediaQuery(`(min-width: ${breakpoints.lg}px)`); - const isXLarge = useMediaQuery(`(min-width: ${breakpoints.xl}px)`); - - return { - isMobile: !isSmall, - isTablet: isSmall && !isLarge, - isDesktop: isLarge, - current: isXLarge - ? "xl" - : isLarge - ? "lg" - : isMedium - ? "md" - : isSmall - ? "sm" - : "base", - }; -} -``` - -## Feature Queries - -### @supports for Progressive Enhancement - -```css -/* Feature detection instead of browser detection */ -@supports (display: grid) { - .layout { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); - } -} - -@supports (container-type: inline-size) { - .card-container { - container-type: inline-size; - } - - @container (min-width: 400px) { - .card { - flex-direction: row; - } - } -} - -@supports (aspect-ratio: 16/9) { - .video-container { - aspect-ratio: 16/9; - } -} - -/* Fallback for older browsers */ -@supports not (gap: 1rem) { - .flex-container > * + * { - margin-left: 1rem; - } -} -``` - -### Combining Feature and Size Queries - -```css -/* Only apply grid layout if supported and screen is large enough */ -@supports (display: grid) { - @media (min-width: 768px) { - .layout { - display: grid; - grid-template-columns: 250px 1fr; - } - } -} -``` - -## Responsive Patterns by Component - -### Navigation - -```css -.nav { - /* Mobile: vertical stack */ - display: flex; - flex-direction: column; -} - -@media (min-width: 768px) { - .nav { - /* Tablet+: horizontal */ - flex-direction: row; - align-items: center; - } -} - -/* Or with container queries */ -.nav-container { - container-type: inline-size; -} - -@container (min-width: 600px) { - .nav { - flex-direction: row; - } -} -``` - -### Cards Grid - -```css -.cards { - display: grid; - gap: 1.5rem; - grid-template-columns: 1fr; -} - -@media (min-width: 640px) { - .cards { - grid-template-columns: repeat(2, 1fr); - } -} - -@media (min-width: 1024px) { - .cards { - grid-template-columns: repeat(3, 1fr); - } -} - -@media (min-width: 1280px) { - .cards { - grid-template-columns: repeat(4, 1fr); - } -} - -/* Better: auto-fit with minimum size */ -.cards-auto { - display: grid; - gap: 1.5rem; - grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr)); -} -``` - -### Hero Section - -```css -.hero { - min-height: 50vh; - padding: var(--space-lg) var(--space-md); - text-align: center; -} - -.hero-title { - font-size: clamp(2rem, 5vw + 1rem, 4rem); -} - -.hero-subtitle { - font-size: clamp(1rem, 2vw + 0.5rem, 1.5rem); -} - -@media (min-width: 768px) { - .hero { - min-height: 70vh; - display: flex; - align-items: center; - text-align: left; - } - - .hero-content { - max-width: 60%; - } -} - -@media (min-width: 1024px) { - .hero { - min-height: 80vh; - } - - .hero-content { - max-width: 50%; - } -} -``` - -### Tables - -```css -/* Mobile: cards or horizontal scroll */ -.table-container { - overflow-x: auto; -} - -.responsive-table { - min-width: 600px; -} - -/* Alternative: transform to cards on mobile */ -@media (max-width: 639px) { - .responsive-table { - min-width: 0; - } - - .responsive-table thead { - display: none; - } - - .responsive-table tr { - display: block; - margin-bottom: 1rem; - border: 1px solid var(--border); - border-radius: 0.5rem; - padding: 1rem; - } - - .responsive-table td { - display: flex; - justify-content: space-between; - padding: 0.5rem 0; - border: none; - } - - .responsive-table td::before { - content: attr(data-label); - font-weight: 600; - } -} -``` - -## Print Styles - -```css -@media print { - /* Remove non-essential elements */ - .nav, - .sidebar, - .footer, - .ads { - display: none; - } - - /* Reset colors and backgrounds */ - * { - background: white !important; - color: black !important; - box-shadow: none !important; - } - - /* Ensure content fits on page */ - .container { - max-width: 100%; - padding: 0; - } - - /* Handle page breaks */ - h1, - h2, - h3 { - page-break-after: avoid; - } - - img, - table { - page-break-inside: avoid; - } - - /* Show URLs for links */ - a[href^="http"]::after { - content: " (" attr(href) ")"; - font-size: 0.8em; - } -} -``` - -## Preference Queries - -```css -/* Dark mode preference */ -@media (prefers-color-scheme: dark) { - :root { - --bg: #1a1a1a; - --text: #f0f0f0; - } -} - -/* Reduced motion preference */ -@media (prefers-reduced-motion: reduce) { - *, - *::before, - *::after { - animation-duration: 0.01ms !important; - animation-iteration-count: 1 !important; - transition-duration: 0.01ms !important; - } -} - -/* High contrast preference */ -@media (prefers-contrast: high) { - :root { - --text: #000; - --bg: #fff; - --border: #000; - } - - .button { - border: 2px solid currentColor; - } -} - -/* Reduced data preference */ -@media (prefers-reduced-data: reduce) { - .hero-video { - display: none; - } - - .hero-image { - display: block; - } -} -``` - -## Testing Breakpoints - -```javascript -// Automated breakpoint testing -async function testBreakpoints(page, breakpoints) { - const results = []; - - for (const [name, width] of Object.entries(breakpoints)) { - await page.setViewportSize({ width, height: 800 }); - - // Check for horizontal overflow - const hasOverflow = await page.evaluate(() => { - return ( - document.documentElement.scrollWidth > - document.documentElement.clientWidth - ); - }); - - // Check for elements going off-screen - const offscreenElements = await page.evaluate(() => { - const elements = document.querySelectorAll("*"); - return Array.from(elements).filter((el) => { - const rect = el.getBoundingClientRect(); - return rect.right > window.innerWidth || rect.left < 0; - }).length; - }); - - results.push({ - breakpoint: name, - width, - hasOverflow, - offscreenElements, - }); - } - - return results; -} -``` - -## Resources - -- [Tailwind CSS Breakpoints](https://tailwindcss.com/docs/responsive-design) -- [The 100% Correct Way to Do CSS Breakpoints](https://www.freecodecamp.org/news/the-100-correct-way-to-do-css-breakpoints-88d6a5ba1862/) -- [Modern CSS Solutions](https://moderncss.dev/) -- [Defensive CSS](https://defensivecss.dev/) diff --git a/.agents/skills/responsive-design/references/container-queries.md b/.agents/skills/responsive-design/references/container-queries.md deleted file mode 100644 index 98471f8..0000000 --- a/.agents/skills/responsive-design/references/container-queries.md +++ /dev/null @@ -1,564 +0,0 @@ -# Container Queries Deep Dive - -## Overview - -Container queries enable component-based responsive design by allowing elements to respond to their container's size rather than the viewport. This paradigm shift makes truly reusable components possible. - -## Browser Support - -Container queries have excellent modern browser support (Chrome 105+, Firefox 110+, Safari 16+). For older browsers, provide graceful fallbacks. - -## Containment Basics - -### Container Types - -```css -/* Size containment - queries based on inline and block size */ -.container { - container-type: size; -} - -/* Inline-size containment - queries based on inline (width) size only */ -/* Most common and recommended */ -.container { - container-type: inline-size; -} - -/* Normal - style queries only, no size queries */ -.container { - container-type: normal; -} -``` - -### Named Containers - -```css -/* Named container for targeted queries */ -.card-wrapper { - container-type: inline-size; - container-name: card; -} - -/* Shorthand */ -.card-wrapper { - container: card / inline-size; -} - -/* Query specific container */ -@container card (min-width: 400px) { - .card-content { - display: flex; - } -} -``` - -## Container Query Syntax - -### Width-Based Queries - -```css -.container { - container-type: inline-size; -} - -/* Minimum width */ -@container (min-width: 300px) { - .element { - /* styles */ - } -} - -/* Maximum width */ -@container (max-width: 500px) { - .element { - /* styles */ - } -} - -/* Range syntax */ -@container (300px <= width <= 600px) { - .element { - /* styles */ - } -} - -/* Exact width */ -@container (width: 400px) { - .element { - /* styles */ - } -} -``` - -### Combining Conditions - -```css -/* AND condition */ -@container (min-width: 400px) and (max-width: 800px) { - .element { - /* styles */ - } -} - -/* OR condition */ -@container (max-width: 300px) or (min-width: 800px) { - .element { - /* styles */ - } -} - -/* NOT condition */ -@container not (min-width: 400px) { - .element { - /* styles */ - } -} -``` - -### Named Container Queries - -```css -/* Multiple named containers */ -.page-wrapper { - container: page / inline-size; -} - -.sidebar-wrapper { - container: sidebar / inline-size; -} - -/* Target specific containers */ -@container page (min-width: 1024px) { - .main-content { - max-width: 800px; - } -} - -@container sidebar (min-width: 300px) { - .sidebar-widget { - display: grid; - grid-template-columns: 1fr 1fr; - } -} -``` - -## Container Query Units - -```css -/* Container query length units */ -.element { - /* Container query width - 1cqw = 1% of container width */ - width: 50cqw; - - /* Container query height - 1cqh = 1% of container height */ - height: 50cqh; - - /* Container query inline - 1cqi = 1% of container inline size */ - padding-inline: 5cqi; - - /* Container query block - 1cqb = 1% of container block size */ - padding-block: 3cqb; - - /* Container query min - smaller of cqi and cqb */ - font-size: 5cqmin; - - /* Container query max - larger of cqi and cqb */ - margin: 2cqmax; -} - -/* Practical example: fluid typography based on container */ -.card-title { - font-size: clamp(1rem, 4cqi, 2rem); -} - -.card-body { - padding: clamp(0.75rem, 4cqi, 1.5rem); -} -``` - -## Style Queries - -Style queries allow querying CSS custom property values. Currently limited support. - -```css -/* Define a custom property */ -.card { - --layout: stack; -} - -/* Query the property value */ -@container style(--layout: stack) { - .card-content { - display: flex; - flex-direction: column; - } -} - -@container style(--layout: inline) { - .card-content { - display: flex; - flex-direction: row; - } -} - -/* Toggle layout via custom property */ -.card.horizontal { - --layout: inline; -} -``` - -## Practical Patterns - -### Responsive Card Component - -```css -.card-container { - container: card / inline-size; -} - -.card { - display: flex; - flex-direction: column; - gap: 1rem; - padding: clamp(1rem, 4cqi, 2rem); -} - -.card-image { - aspect-ratio: 16/9; - width: 100%; - object-fit: cover; - border-radius: 0.5rem; -} - -.card-title { - font-size: clamp(1rem, 4cqi, 1.5rem); - font-weight: 600; -} - -/* Medium container: side-by-side layout */ -@container card (min-width: 400px) { - .card { - flex-direction: row; - align-items: flex-start; - } - - .card-image { - width: 40%; - aspect-ratio: 1; - } - - .card-content { - flex: 1; - } -} - -/* Large container: enhanced layout */ -@container card (min-width: 600px) { - .card-image { - width: 250px; - } - - .card-title { - font-size: 1.5rem; - } - - .card-actions { - display: flex; - gap: 0.5rem; - } -} -``` - -### Responsive Grid Items - -```css -.grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); - gap: 1.5rem; -} - -.grid-item { - container-type: inline-size; -} - -.item-content { - padding: 1rem; -} - -/* Item adapts to its own size, not the viewport */ -@container (min-width: 350px) { - .item-content { - padding: 1.5rem; - } - - .item-title { - font-size: 1.25rem; - } -} - -@container (min-width: 500px) { - .item-content { - display: grid; - grid-template-columns: auto 1fr; - gap: 1rem; - } -} -``` - -### Dashboard Widget - -```css -.widget-container { - container: widget / inline-size; -} - -.widget { - --chart-height: 150px; - padding: 1rem; -} - -.widget-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 1rem; -} - -.widget-chart { - height: var(--chart-height); -} - -.widget-stats { - display: grid; - grid-template-columns: 1fr; - gap: 0.5rem; -} - -@container widget (min-width: 300px) { - .widget { - --chart-height: 200px; - } - - .widget-stats { - grid-template-columns: repeat(2, 1fr); - } -} - -@container widget (min-width: 500px) { - .widget { - --chart-height: 250px; - padding: 1.5rem; - } - - .widget-stats { - grid-template-columns: repeat(4, 1fr); - } - - .widget-actions { - display: flex; - gap: 0.5rem; - } -} -``` - -### Navigation Component - -```css -.nav-container { - container: nav / inline-size; -} - -.nav { - display: flex; - flex-direction: column; - gap: 0.5rem; -} - -.nav-link { - display: flex; - align-items: center; - gap: 0.75rem; - padding: 0.75rem 1rem; - border-radius: 0.5rem; -} - -.nav-link-text { - display: none; -} - -.nav-link-icon { - width: 1.5rem; - height: 1.5rem; -} - -/* Show text when container is wide enough */ -@container nav (min-width: 200px) { - .nav-link-text { - display: block; - } -} - -/* Horizontal layout for wider containers */ -@container nav (min-width: 600px) { - .nav { - flex-direction: row; - } - - .nav-link { - padding: 0.5rem 1rem; - } -} -``` - -## Tailwind CSS Integration - -```tsx -// Tailwind v3.2+ supports container queries -// tailwind.config.js -module.exports = { - plugins: [require("@tailwindcss/container-queries")], -}; - -// Component usage -function Card({ title, image, description }) { - return ( - // @container creates containment context -
-
- -
-

- {title} -

-

- {description} -

-
-
-
- ); -} - -// Named containers -function Dashboard() { - return ( -
- -
{/* ... */}
-
- ); -} -``` - -## Fallback Strategies - -```css -/* Provide fallbacks for browsers without support */ -.card { - /* Default (fallback) styles */ - display: flex; - flex-direction: column; -} - -/* Feature query for container support */ -@supports (container-type: inline-size) { - .card-container { - container-type: inline-size; - } - - @container (min-width: 400px) { - .card { - flex-direction: row; - } - } -} - -/* Alternative: media query fallback */ -.card { - display: flex; - flex-direction: column; -} - -/* Viewport-based fallback */ -@media (min-width: 768px) { - .card { - flex-direction: row; - } -} - -/* Enhanced with container queries when supported */ -@supports (container-type: inline-size) { - @media (min-width: 768px) { - .card { - flex-direction: column; /* Reset */ - } - } - - @container (min-width: 400px) { - .card { - flex-direction: row; - } - } -} -``` - -## Performance Considerations - -```css -/* Avoid over-nesting containers */ -/* Bad: Too many nested containers */ -.level-1 { - container-type: inline-size; -} -.level-2 { - container-type: inline-size; -} -.level-3 { - container-type: inline-size; -} -.level-4 { - container-type: inline-size; -} - -/* Good: Strategic container placement */ -.component-wrapper { - container-type: inline-size; -} - -/* Use inline-size instead of size when possible */ -/* size containment is more expensive */ -.container { - container-type: inline-size; /* Preferred */ - /* container-type: size; */ /* Only when needed */ -} -``` - -## Testing Container Queries - -```javascript -// Test container query support -const supportsContainerQueries = CSS.supports("container-type", "inline-size"); - -// Resize observer for testing -const observer = new ResizeObserver((entries) => { - for (const entry of entries) { - console.log("Container width:", entry.contentRect.width); - } -}); - -observer.observe(document.querySelector(".container")); -``` - -## Resources - -- [MDN Container Queries](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_container_queries) -- [CSS Container Queries Spec](https://www.w3.org/TR/css-contain-3/) -- [Una Kravets: Container Queries](https://web.dev/cq-stable/) -- [Ahmad Shadeed: Container Queries Guide](https://ishadeed.com/article/container-queries-are-finally-here/) diff --git a/.agents/skills/responsive-design/references/fluid-layouts.md b/.agents/skills/responsive-design/references/fluid-layouts.md deleted file mode 100644 index 29fde9c..0000000 --- a/.agents/skills/responsive-design/references/fluid-layouts.md +++ /dev/null @@ -1,538 +0,0 @@ -# Fluid Layouts and Typography - -## Overview - -Fluid design creates smooth scaling experiences by using relative units and mathematical functions instead of fixed breakpoints. This approach reduces the need for media queries and creates more natural-feeling interfaces. - -## Fluid Typography - -### The clamp() Function - -```css -/* clamp(minimum, preferred, maximum) */ -.heading { - /* Never smaller than 1.5rem, never larger than 3rem */ - /* Scales at 5vw between those values */ - font-size: clamp(1.5rem, 5vw, 3rem); -} -``` - -### Calculating Fluid Values - -The preferred value in `clamp()` typically combines a base size with a viewport-relative portion: - -```css -/* Formula: clamp(min, base + scale * vw, max) */ - -/* For text that scales from 16px (320px viewport) to 24px (1200px viewport): */ -/* slope = (24 - 16) / (1200 - 320) = 8 / 880 = 0.00909 */ -/* y-intercept = 16 - 0.00909 * 320 = 13.09px = 0.818rem */ - -.text { - font-size: clamp(1rem, 0.818rem + 0.909vw, 1.5rem); -} -``` - -### Type Scale Generator - -```javascript -// Generate a fluid type scale -function fluidType({ - minFontSize, - maxFontSize, - minViewport = 320, - maxViewport = 1200, -}) { - const minFontRem = minFontSize / 16; - const maxFontRem = maxFontSize / 16; - const minViewportRem = minViewport / 16; - const maxViewportRem = maxViewport / 16; - - const slope = (maxFontRem - minFontRem) / (maxViewportRem - minViewportRem); - const yAxisIntersection = minFontRem - slope * minViewportRem; - - return `clamp(${minFontRem}rem, ${yAxisIntersection.toFixed(4)}rem + ${(slope * 100).toFixed(4)}vw, ${maxFontRem}rem)`; -} - -// Usage -const typeScale = { - xs: fluidType({ minFontSize: 12, maxFontSize: 14 }), - sm: fluidType({ minFontSize: 14, maxFontSize: 16 }), - base: fluidType({ minFontSize: 16, maxFontSize: 18 }), - lg: fluidType({ minFontSize: 18, maxFontSize: 20 }), - xl: fluidType({ minFontSize: 20, maxFontSize: 24 }), - "2xl": fluidType({ minFontSize: 24, maxFontSize: 32 }), - "3xl": fluidType({ minFontSize: 30, maxFontSize: 48 }), - "4xl": fluidType({ minFontSize: 36, maxFontSize: 60 }), -}; -``` - -### Complete Type Scale - -```css -:root { - /* Base: 16-18px */ - --text-base: clamp(1rem, 0.9rem + 0.5vw, 1.125rem); - - /* Smaller sizes */ - --text-sm: clamp(0.875rem, 0.8rem + 0.375vw, 1rem); - --text-xs: clamp(0.75rem, 0.7rem + 0.25vw, 0.875rem); - - /* Larger sizes */ - --text-lg: clamp(1.125rem, 1rem + 0.625vw, 1.25rem); - --text-xl: clamp(1.25rem, 1.1rem + 0.75vw, 1.5rem); - --text-2xl: clamp(1.5rem, 1.2rem + 1.5vw, 2rem); - --text-3xl: clamp(1.875rem, 1.4rem + 2.375vw, 2.5rem); - --text-4xl: clamp(2.25rem, 1.5rem + 3.75vw, 3.5rem); - --text-5xl: clamp(3rem, 1.8rem + 6vw, 5rem); - - /* Line heights scale inversely */ - --leading-tight: 1.25; - --leading-normal: 1.5; - --leading-relaxed: 1.75; -} - -/* Apply to elements */ -body { - font-size: var(--text-base); - line-height: var(--leading-normal); -} - -h1 { - font-size: var(--text-4xl); - line-height: var(--leading-tight); -} -h2 { - font-size: var(--text-3xl); - line-height: var(--leading-tight); -} -h3 { - font-size: var(--text-2xl); - line-height: var(--leading-tight); -} -h4 { - font-size: var(--text-xl); - line-height: var(--leading-normal); -} -h5 { - font-size: var(--text-lg); - line-height: var(--leading-normal); -} -h6 { - font-size: var(--text-base); - line-height: var(--leading-normal); -} - -small { - font-size: var(--text-sm); -} -``` - -## Fluid Spacing - -### Spacing Scale - -```css -:root { - /* Spacing tokens that scale with viewport */ - --space-3xs: clamp(0.25rem, 0.2rem + 0.25vw, 0.375rem); - --space-2xs: clamp(0.375rem, 0.3rem + 0.375vw, 0.5rem); - --space-xs: clamp(0.5rem, 0.4rem + 0.5vw, 0.75rem); - --space-sm: clamp(0.75rem, 0.6rem + 0.75vw, 1rem); - --space-md: clamp(1rem, 0.8rem + 1vw, 1.5rem); - --space-lg: clamp(1.5rem, 1.2rem + 1.5vw, 2rem); - --space-xl: clamp(2rem, 1.5rem + 2.5vw, 3rem); - --space-2xl: clamp(3rem, 2rem + 5vw, 5rem); - --space-3xl: clamp(4rem, 2.5rem + 7.5vw, 8rem); - - /* One-up pairs (for asymmetric spacing) */ - --space-xs-sm: clamp(0.5rem, 0.3rem + 1vw, 1rem); - --space-sm-md: clamp(0.75rem, 0.5rem + 1.25vw, 1.5rem); - --space-md-lg: clamp(1rem, 0.6rem + 2vw, 2rem); - --space-lg-xl: clamp(1.5rem, 1rem + 2.5vw, 3rem); -} - -/* Usage examples */ -.section { - padding-block: var(--space-xl); - padding-inline: var(--space-md); -} - -.card { - padding: var(--space-md); - gap: var(--space-sm); -} - -.stack > * + * { - margin-top: var(--space-md); -} -``` - -### Container Widths - -```css -:root { - /* Fluid max-widths */ - --container-xs: min(100% - 2rem, 20rem); - --container-sm: min(100% - 2rem, 30rem); - --container-md: min(100% - 2rem, 45rem); - --container-lg: min(100% - 2rem, 65rem); - --container-xl: min(100% - 3rem, 80rem); - --container-2xl: min(100% - 4rem, 96rem); -} - -.container { - width: var(--container-lg); - margin-inline: auto; -} - -.prose { - max-width: var(--container-md); -} - -.full-bleed { - width: 100vw; - margin-inline: calc(-50vw + 50%); -} -``` - -## CSS Grid Fluid Layouts - -### Auto-fit Grid - -```css -/* Grid that fills available space */ -.auto-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(min(100%, 250px), 1fr)); - gap: var(--space-md); -} - -/* With maximum columns */ -.auto-grid-max-4 { - display: grid; - grid-template-columns: repeat( - auto-fit, - minmax(min(100%, max(200px, calc((100% - 3 * var(--space-md)) / 4))), 1fr) - ); - gap: var(--space-md); -} -``` - -### Responsive Grid Areas - -```css -.page-grid { - display: grid; - grid-template-columns: - 1fr - min(var(--container-lg), 100%) - 1fr; - grid-template-rows: auto 1fr auto; -} - -.page-grid > * { - grid-column: 2; -} - -.full-width { - grid-column: 1 / -1; -} - -/* Content with sidebar */ -.content-grid { - display: grid; - grid-template-columns: 1fr; - gap: var(--space-lg); -} - -@media (min-width: 768px) { - .content-grid { - grid-template-columns: 1fr min(300px, 30%); - } -} -``` - -### Fluid Aspect Ratios - -```css -/* Maintain aspect ratio fluidly */ -.aspect-video { - aspect-ratio: 16 / 9; -} - -.aspect-square { - aspect-ratio: 1; -} - -/* Fluid aspect ratio that changes */ -.hero-image { - aspect-ratio: 1; /* Mobile: square */ -} - -@media (min-width: 640px) { - .hero-image { - aspect-ratio: 4 / 3; - } -} - -@media (min-width: 1024px) { - .hero-image { - aspect-ratio: 16 / 9; - } -} -``` - -## Flexbox Fluid Patterns - -### Flexible Sidebar - -```css -/* Sidebar that collapses when too narrow */ -.with-sidebar { - display: flex; - flex-wrap: wrap; - gap: var(--space-lg); -} - -.with-sidebar > :first-child { - flex-basis: 300px; - flex-grow: 1; -} - -.with-sidebar > :last-child { - flex-basis: 0; - flex-grow: 999; - min-width: 60%; -} -``` - -### Cluster Layout - -```css -/* Items cluster and wrap naturally */ -.cluster { - display: flex; - flex-wrap: wrap; - gap: var(--space-sm); - justify-content: flex-start; - align-items: center; -} - -/* Center-aligned cluster */ -.cluster-center { - display: flex; - flex-wrap: wrap; - gap: var(--space-sm); - justify-content: center; - align-items: center; -} - -/* Space-between cluster */ -.cluster-spread { - display: flex; - flex-wrap: wrap; - gap: var(--space-sm); - justify-content: space-between; - align-items: center; -} -``` - -### Switcher Layout - -```css -/* Switches from horizontal to vertical based on container */ -.switcher { - display: flex; - flex-wrap: wrap; - gap: var(--space-md); -} - -.switcher > * { - /* Items go vertical when container is narrower than threshold */ - flex-grow: 1; - flex-basis: calc((30rem - 100%) * 999); -} - -/* Limit columns */ -.switcher > :nth-last-child(n + 4), -.switcher > :nth-last-child(n + 4) ~ * { - flex-basis: 100%; -} -``` - -## Intrinsic Sizing - -### Content-Based Widths - -```css -/* Size based on content */ -.fit-content { - width: fit-content; - max-width: 100%; -} - -/* Minimum content size */ -.min-content { - width: min-content; -} - -/* Maximum content size */ -.max-content { - width: max-content; -} - -/* Practical examples */ -.button { - width: fit-content; - min-width: 8rem; /* Prevent too-narrow buttons */ - padding-inline: var(--space-md); -} - -.tag { - width: fit-content; - padding: var(--space-2xs) var(--space-xs); -} - -.modal { - width: min(90vw, 600px); - max-height: min(90vh, 800px); -} -``` - -### min() and max() Functions - -```css -/* Responsive sizing without media queries */ -.container { - /* 90% of viewport or 1200px, whichever is smaller */ - width: min(90%, 1200px); - margin-inline: auto; -} - -.hero-text { - /* At least 2rem, at most 4rem */ - font-size: max(2rem, min(5vw, 4rem)); -} - -.sidebar { - /* At least 200px, at most 25% of parent */ - width: max(200px, min(300px, 25%)); -} - -.card-grid { - /* Each card at least 200px, fill available space */ - grid-template-columns: repeat(auto-fit, minmax(max(200px, 100%/4), 1fr)); -} -``` - -## Viewport Units - -### Modern Viewport Units - -```css -/* Dynamic viewport height - accounts for mobile browser UI */ -.full-height { - min-height: 100dvh; -} - -/* Small viewport - minimum size when UI is visible */ -.hero { - min-height: 100svh; -} - -/* Large viewport - maximum size when UI is hidden */ -.backdrop { - height: 100lvh; -} - -/* Viewport-relative positioning */ -.fixed-nav { - position: fixed; - inset-inline: 0; - top: 0; - height: max(60px, 8vh); -} - -/* Safe area insets for notched devices */ -.safe-area { - padding-top: env(safe-area-inset-top); - padding-right: env(safe-area-inset-right); - padding-bottom: env(safe-area-inset-bottom); - padding-left: env(safe-area-inset-left); -} -``` - -### Combining Viewport and Container Units - -```css -/* Responsive based on both viewport and container */ -.component { - container-type: inline-size; -} - -.component-text { - /* Uses viewport when small, container when in container */ - font-size: clamp(1rem, 2vw + 0.5rem, 1.5rem); -} - -@container (min-width: 400px) { - .component-text { - font-size: clamp(1rem, 4cqi, 1.5rem); - } -} -``` - -## Utility Classes - -```css -/* Tailwind-style fluid utilities */ -.text-fluid-sm { - font-size: var(--text-sm); -} -.text-fluid-base { - font-size: var(--text-base); -} -.text-fluid-lg { - font-size: var(--text-lg); -} -.text-fluid-xl { - font-size: var(--text-xl); -} -.text-fluid-2xl { - font-size: var(--text-2xl); -} -.text-fluid-3xl { - font-size: var(--text-3xl); -} -.text-fluid-4xl { - font-size: var(--text-4xl); -} - -.p-fluid-sm { - padding: var(--space-sm); -} -.p-fluid-md { - padding: var(--space-md); -} -.p-fluid-lg { - padding: var(--space-lg); -} - -.gap-fluid-sm { - gap: var(--space-sm); -} -.gap-fluid-md { - gap: var(--space-md); -} -.gap-fluid-lg { - gap: var(--space-lg); -} -``` - -## Resources - -- [Utopia Fluid Type Calculator](https://utopia.fyi/) -- [Modern Fluid Typography](https://www.smashingmagazine.com/2022/01/modern-fluid-typography-css-clamp/) -- [Every Layout](https://every-layout.dev/) -- [CSS min(), max(), and clamp()](https://web.dev/min-max-clamp/) diff --git a/.agents/skills/web-design-guidelines/SKILL.md b/.agents/skills/web-design-guidelines/SKILL.md deleted file mode 100644 index ceae92a..0000000 --- a/.agents/skills/web-design-guidelines/SKILL.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -name: web-design-guidelines -description: Review UI code for Web Interface Guidelines compliance. Use when asked to "review my UI", "check accessibility", "audit design", "review UX", or "check my site against best practices". -metadata: - author: vercel - version: "1.0.0" - argument-hint: ---- - -# Web Interface Guidelines - -Review files for compliance with Web Interface Guidelines. - -## How It Works - -1. Fetch the latest guidelines from the source URL below -2. Read the specified files (or prompt user for files/pattern) -3. Check against all rules in the fetched guidelines -4. Output findings in the terse `file:line` format - -## Guidelines Source - -Fetch fresh guidelines before each review: - -``` -https://raw.githubusercontent.com/vercel-labs/web-interface-guidelines/main/command.md -``` - -Use WebFetch to retrieve the latest rules. The fetched content contains all the rules and output format instructions. - -## Usage - -When a user provides a file or pattern argument: -1. Fetch guidelines from the source URL above -2. Read the specified files -3. Apply all rules from the fetched guidelines -4. Output findings using the format specified in the guidelines - -If no files specified, ask the user which files to review. diff --git a/.gitignore b/.gitignore index bf2fedc..22d282c 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,12 @@ !.yarn/plugins !.yarn/releases !.yarn/versions +package-lock.json + +# agent tooling +skills-lock.json +.agents/ +.interface-design/ # testing /coverage @@ -23,6 +29,7 @@ # misc .DS_Store *.pem +.cache/ # debug npm-debug.log* @@ -42,5 +49,8 @@ next-env.d.ts *.db +dev.db +sqlite.db -dev.db. \ No newline at end of file +# stale migrations +drizzle_migrations/ \ No newline at end of file diff --git a/README.md b/README.md index 9fa339a..6b5a2f9 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Paste any GitHub repo, walk every commit, and understand what changed and why 3. Create a `.env.local` file: ```env - # Required + # Required (For instructions, see: https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens) GITHUB_TOKEN=your_github_personal_access_token # Required — must be stable across restarts (session credentials become unreadable if changed) diff --git a/dev.db b/dev.db deleted file mode 100644 index 15dbe5469825da7c25c03fa116706d0a4a9230b0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8855552 zcmeFa2b2`W7C$_(C-;ySL}V02KxVgRdLk;4k(?1!LQhW*JFv-}S%Lu^fhFghbB-p= zm@xrn%$PA_#`yI6-QEH=v_5>_JOA_j-#2^C-rG~vRdwsuty|S~Z&eNG*E5+`Jc;7c zLMiPLb+vSQy|bOiqtlss>2%go-Pym|cr=~;snb__{)*k_r{|cQRdEnp9HtI`;3}eqd)$x89vP5AlrDdVQp)PjAo2 z%85pLT8vb)8`*j!WjM0sxH^`c%TZ`!wb07%jwmZF7*SS~yrHa0xpi5&gL-%CH|R|9 zf00m|0%Sb0e;h3UZBz{-l!Z}!G)UPDpzU-$CZ>^nk-2ti;F5UqnuM| zsWdgxlPpRrqm)ulr_Np459&G4BT!siNl0Y{=@GF~sYuRe1e|{3(n2X&bSb@7sz`A( zgIXZ{E|5~`5eX$N=PRh!>6|MQE+a`=iI0%dHRJ6q!?n4b9+&8KYHOx$D1a6lA(f?z z)!!BJ#N=m$uQHqKpmSNNgWck`XqImu>-TxWhuy=T17QKszYnA09lWai_41ABU?(mCFe2}udiBsMdC_1y8ohlR*EY4 zm0qtzKugJ`Sp>)*k&IVL!~aDQ6)$Smkwvw<*=fnS3bklfQH!%|Bi^AD)~zZP=3<3a z?NyatsG9O`OP^|RSaO;*)6eLk_N+>oRC;u>0$IPR6a!vC5UE?87Qf0ptNoyXeY*8V z;$EG5qmNbUXQ%n1cE-vP+8yw8OVxOt#1cF@+b2p&i$|3zslV!J>Eg6h0Qo5_Os4

aArZ@xSbg6}|hcXrJNcX(gbli9(6Ae3}&cS$>~> zLp-Aqs?k-o3!TonqQq2Lp)w*-T3pDgS~FWJxJ#e@ox64Itt!hF>JULJZ|UjZxl8B% zoqKoeJfNDcZ9x*zlKR!4zMW{?DJu3zGU-DK>(set=gMLBmLu(!95F|qF`ntjiDZG2 zVnm@Z^{1m|AFK9Sg`}&2)QKyaPo=fgX2}Vo1lGwgTvkb1BBk@y0K0bA(t7_d3u`@+ zvcsU|vs!X;a`aQ$t6fXYUj??R-AN7mPdn1tI-YLXD*a5Qq~-`E7h@QL3OzHFq>?vc zU^(4~Ps@}lbp=wR_;IGiP*sz99XJLe<|vXqjgxMFWP$in4Z)>xl9BM2;; z5vhEMJz8suL8X@}TIF=%G?e5^BU{Ee2w3CF0(LqHQy}c8<*)NTqiJZGR4M<{;Vs9R zF*!mRm#DN?$saT@EzW{DO)>k6G&QPpy2#cQmAkFPWXWmVSU)wa${EXrA#+t(W1;`4 zw4E-wrbH2&Ylap=kDd{oDuRi5Mt*U{MD?r`vOonh9ySdlHAQOktZY^uEQ(9x3N-L( zv8@zURYp&fT~$IUG{q)3BmbG078or#(I)zg`;44QMN+|pRArrYI>b~{rcOtlRd5w; zd^)_E^6G0qr?^Z%RBc{1PfID7Y8PRgs+hK&{%Nez+|?pm(0sKuUTt|>TfHTxNfZ5) zK5Cw)%RP+^RdN}5I`}`8nhK#)hiQ$b8j10CY*KB=8pFFXz69z=Iy;lDB18XX#H}g= zXNjmP1*dz&KNEn;pO-HB;r^j=Q1&^?fh-5I9LRDY%YiHhvK+{AAj^R)2eKTF_IN)+0)*0Oo=-dyu-*kWaKUTSHb+R1Dav;lrEC;e2$Z{adfh-5I9LRDY%YiHh zvK;uop95~au^GHa&v>dfLX~*E>X0?YVK6!k#>NKbATXrVZZzf?)VTgHSFXC9eq`-4=?BG7>{@nes z`#pF9yy8CUe%k$*`$6{s_kQBle*xc+ufscej@P+< zb^YKv>H5_5f$MG8G1m*OXI)RY4!Z7j-Qn8p+Ui>GTIpKsn(LbGy3KX7YpkopHQE(- z4R;N3^>_7jb#b+GwQ&Vpxvmzji(Kcs8n|k?>@Jh@59iO$@0?#cPdMLmzUh3$dDQu| z^D*ax&I8W<&YjN9&b7|v&V|m|&T^bzaHBKrEOO>MW6tZHgPnby-JPABZJiOP-`U#P z-05+i=d9=ComQv8@w?-c;~U54j*lG29j`lHavXLXay;U=-*J~?uVcGoqhqyWsbjul zrelg@qGP*nNKe8UTzHWWVdf0l%`iS*@>s{8p*6r4f*45Uf*7??%)+yGB z*74R-Yk@Uk9cjJRI?&qNdbPEK^$KgqDp*@tFScG_ZD_4yby_*A&ho3}2g^yzr zr_A4&KR175K5l;9{F3>w`H=Y$^Zn+#%zMq-%^S_D%}dSm%`?qY%oEMy&86l7bHY5* ze64w)xwrXha|iPk=8#!1w=!RBzQEkjT*vG*b7r0CSJMxslcrBiADG@Y9W%XPde-!W z>7eOe(;cSWrmd#+rj@3}rn#o+rrS(6o5q?-OruS4({R%eQ-4!WQx{V^QyWvjlxu2X zy2y0Cse!4M$u1SeQ_uic+-?NCeE(R$=>9Zdcz+>aX#X(4;Qro#f&Etkiu+pw=Iw6? zn7iKw*m~atz*hT412*4xE#M{lx&b!Z7Y4j=Uvt2w`x*dVun(t*7@O?H*&@dC_ey|` z_YMJUxECjm7#r+u3s`Tj7qHG=tZ5o+@3jE(dnuJ;56%ZMTK6OXEqew7a(lW18ut)Y zhCMBHI>VoPkjn7;9w*>$yKe&gWjClY{Ji@*z@K*a1U$9-D!?Ci=K=n(y9MC)yUz#w zZZ{T04PWoN5%A=$0>H0!p)Q6mccJcv&v#u8_}MOc|I=OP0e-Rz8>kE?c7X$ik9QdW zKiY|U89vyl0KT`A(!RU1FW}od+X2406ZJB@zVjl$*LH#%hGRQXJHx9xz-`0JJMsZv z+Ciya+yU+xp5H+w9@znI8=l=kB|fu*%6e-1c)&y3sa{WRN9!0K-`)rCvF+fV;nD2^ z;KA)p03X^;HF|K{Er1VfD+0WKTMY2tZT$cbY-CodQtjDB zHQK!uJU8sxO0@0VN<7@LwLjqYt;Cb9TRQ-5-YNoa+Di4>xV0YO`mI*L)mv@?T(zYT zaK#oGaM_k?0GDp*0l0VzwZft;RQkd#RL2EdngY(>QVVe27Ak$tX5!53&BV`Ho6+Ki z>6@lzVE+vy8~rxmq#Z-w_0-~h){g<~wI033&|^KdLihEZ0lTfIcE5Uk5U}fd zYW>dZiJp$@sV{U`Pp#Q*9m&8|>xu!}uA{cNa@`=n%hyp3ZPrm+gx3*Op>>otxQ?h2 z*EIw5t*Z|xtRoKRuBA5hu1x}7w)Q%}R%?l$E!L7GT)fr?*leu_@S?R10WVxjb@8mZ z8L;UZlE4erP}(MIh$oHL5Dksi5Dg925dHPmP`P#1@2Xw2!N&TOzzybZgR)GWh-&c(Q{B;#d z*Z;Dr8{ki?z(f71Rp6ff$5odAe!mK}(0{iICm-p*S)~I!xe_$!zgn3O`1wloQ~jqa zuLeA^67r+}cqOI%XeBg>{==21wf=(@;FbRU6{xlTy%pC3zPkdguRp$m%6fZ+AMni; z;IRIU74-pMUxAj=zp{KH;7iL#1HQO?7~l)bQ9J$f%i94SULF8^W;tr7e|kCOP5;z# z$c+ApK`+fyUEsSwdxP zT0&)QTmnhZZ&;ECxONFqxMm4)W7QJ$X#L8?V*!^hCJL7=9tF5$@j$>ui>Zza7k2`j zznC~PZ}G)|a~3xNoV^&br=PV5Qly``s1R`KB5H-niy(pe+ZIu|w=SY`CoY06(%-y@ z_;ceTNRfWRA~WFlg=xUC3n6>@vW3tC`t(9boxXHo7_ekvE5PD~jR6Z65+}zjptAB8 zP+6lEP+KP!P$}^RR7z|CmC|zomC}6y@&D@i^nTa*#J$e*djocuPp#a3KKi)6-F&LW zRr9HRubg)a;1%O%N0db8QLV%CE&vS7qc?@QByYKMA?U2oH?v?4_w(jNWeF0yZ-VX4^>1c7?3)3Ohy62}~1bAdR<$riOQTyz4$fxd^ z=?1{3ra@Qh4ow4xbx%%%{OBH^20rT^n>G^g(P`kG?vZKGJi3Rc^#gop8d^&C;576# z-2>CWPu=~~+5+A;4gF4c?=(n-?w)DHvAd^%!@9esT>^OLvy#r``lOWh&%fH+d>_g)TFd zIC<;TB;cf}3gE=4BLQ!odOhGxQ-=WFIF;mK!c>e1y75zqtK+5;SI15zI?JYB37DQ5 z1uUH!1T2|~exxg!N)lK&m0Gi4DsghmRN`K8D$$caWg_6HDMU$P3dRndGKDxMPa&C! zO(9-MQw9Q#n9>_?_>@k7!=_Lv*G&lkUOS~Z;Ls`5QrApz0}h@-_3b}}^6xj9`bgi& zRIfghiT}MPUjx`{GUe7|GS#B{Wa4(W$yAH0Clj~3P9|=5nM~a7JejzC(PZNGg||^D z7u-hqH_A|pH$;cl9^!`yS+bi3b?vGA6Dmc{nptRFhNyWVzMtizly zxK`M{cC>X3w(aJ(SkJSUIi7INv97lz?e|(=z6QL0Rw#iFjD{N8|oL%pdkeBqb6GMx}s18j@pvSr)@C$g=*Z zW)@LNQNnW67nUMXIVuJsQb3AD0zOGe_`{sGJe@F@0z2HDk%`AL@uoP7k(H9H^ zeUXUdlY*iY4+WIC&o9N}zK{|PCWL_GKs)J|YGx4@WyKd0C0|JN$HIPD6eU3k#}FvR zd~rbzMFbEciLL~)xJxq&SqLXYB^HY-k+2x@MS`H+ACiM$M>Ld(NI^kJBti+vwhvkC z)XV}UM}odk#ODk8f(bbk7kr`|iv_}QeK^4#n(O@_nib71mnLsq8 z_$3#qU5{oK)CA#JGz=;COEDoLgoPN05fl*(Ed(GDenm(GW!Ime;%m(;q*zcCgYj@g zP-4N5FBXv_N-z|P!~>Cl;0vImkaPuOHkx3})yyKO2;pEz@P#9>P#B^hCcC9_h2jco6pKLQ#h4!zNyxG<=JyB1kmABPZg6O35t8CDzm!mdK}dHf z6c!_5BuWB~Lk@*_49y`0;-c(x^#>K}G_!yhMtunZDg(MJCdo=L5D;WZRQ!H{Dj7`p ze3B3qoET>e^)<8b$ECO&l*6%jLJW!tWEBqhBVp(oh-V-OSq&&c1o{FqGyRjAS;T`v z0@4>#qyQS$2hJ(+gkOxtq%h0%CArMTUp#(7|qvy*K8e4R} zk1Sr+%tC?2^@pKB(a*yEa3m59QlWuFyh4Yd3nlz9DPTPgb{aLaK*eR!IKDtA78N4# zgfA{a)kM(o@>DqIdKvb7`h#iyECK;eeN&@4d` zMSoZje8DL7v$%pG482^4N`9zi#YFRpU7A@$@IoLVg)o)|MH$rz$1yZUl?iUt$~L+GwTARY-OVxsvlvUpB23&|f7 z!=(7Z0ujSd9Ab(AFhCj*`cK3#6_rAgWT0_mh-MZ(D9M-~l@3RtZ$u>&4hb=Ic#Hso zYWe((ZXFCc}(iEt?Hi({mWiWv9}SAmN2HM5`^i#{JQRS1WmMKM4p z!cfOCF@j74jPDZqPRvNt---dTx_}2kJ-TE-3i(l;gb$;bjDa|mz@QZIiP3lh(*y*X zzXKIts8m#+t|k0v1Iz>a;j4i0ncLL z7zzxdvxX#p2!nXUrFXn+RYh$W0HDpXGOEMk~k1bqP!ZHl>GTnWelUm`3+ zoN!QW404H4Nr@#A228H?k|sMb<{=rhM9k-lqf?;6`h6h*0~#h>s6g0<*=WKaF>eJG z(=@XPCNQtS^n%(#NC=`2Wca~9biF7hlA#D>M3f?uX%MLRQk$LeL?Qu28;D>UEJ@)= zP=>aQ$)O;|A{pZ~Mm3*blBs4-uh(l{C-~@VU4D%#e2?S7yfDg?Q zH?}GaQOal@TD84wR2O%jY27+?X54{t|=pKnB{M>v{alK|1{$N~I;sFtg zQo%IM7eEIC`Mw0rEJP)!pqq+`fM}rkMMN`;D3rbrj8kMxQG8-Z79yA|ieU-UdQpx` z!59bwQI;U6sF=D|mkTH-C|DsD#5Y3>6CCu}5DJ%l7*J`X6@q@*FGQnO60JWpsgR-l z!-^#MF*Sh}4Mc-VEQIny)K@SA4u?YcA`oKSt*A&yGmB7|)Hf!qvOg9QLjg=|0+=IW zu7x)B`@g&ynKzLWucfBYpYD zHM8(55R*yE1fiQ_(h!eFeF2oheG4k=npuEgd8p*|rN7_0q30n@Ok5SL=c zIjG1g%`9k=31u4#V=k`v#GrzKUVzq$LgHo2<|97ng0LJkk?yRRKv$OwDh?BrXoLo5 zvy_gRxMMn1B)2htD399UsaSRLDbnbSp{s>DGB@mqF38R2ae#ib*SUWJ}XjbH(jH!kSxu~vw@!8;0s4bvzB=FMb_u-o` zhFMn-jirP`(B}bs@wwgtJJ0;l_kV9IEf$yuyWixCV0mxDdw9+s``oJC?d1aoo&L=Qp~y!bhOgJp}#$7rX7S z{U75Wbi`oG&UMs--TFQIA=s!F+Q-8l-O1j{UdwjM_BQOy+ii1UQ@+;L4)$ZG^*h*# z4_Y_FE?i)x8R;ziJd5L+FIcXkXzS4ZDxwh#y)5oUgP4}5LndX?rnIuyWQ_$4Z zWHWwme8>2d@pj`X<5Xj@aj3DQ(QC{x>J48QUNt;y*kM>?xWzEa(9dwW;Sz&e|BL=Z z{So~=`VIP7`ZC9pj(v_5j>(Qf$2E@jj>{bN?SI-ov%hSA$iB_Kz*Llv)@9a=b&M6tUGuNL(QBwKhx(s1 zLr-aj{-_!HgJ$UWnxWrmhJLFV`i*Aj*P5XxHABDB4E<6w^b5_<&ox6o(+vGoGxQV9 z&=Z=WA8Uqwq#635X6Of+q3>&kzNZ=bu4d?Q&Cqu=L*Ld6eM>X+P0i3ZG(%t441G;A z^q6MotD2#&XokM58TyiD=!=@6FKC87uNit&GxUgN=wZ#!=QKl~)eL<`GxTZA(5Ey* z4{3%zsTul&X6WOZp^s^XKB^h|h-T=+nxO|ZLm$!%eNZ#>0nO0+HAC;y482z~^d8O7 z1Dc_CYlhyX8G5H?=pCA&w`+#(*9_gK8M;?9bdP4}?nT??bD>LSfnlW7w zBWlIu?M6&<%^0kCna|gX$-NCRwY6frUm?b*83T)tNgXz;Q{5WNwkCCWsS$$@ag(ZA ztH)fn0x_xzsu2S#jY;jm)ni&=MrTrmw|Y#=HxZ*YeT^7ca!lK`>eFIBV%BQLz@}rG zqZM;0KHW@{wPKoM@@|@-83TKnDXA56$y10Kt{H>%AkzS?n2XmSri*3_rj@2Ev|^g2 z5aZQ~xd_X6rVBM=x*?{%R?LNWA;zj1(-f2TKeS>zGZCXsf~vPq(;pF|P77+pj6{q& zP*#t*;5o#oioZq-taSPwS`;+dfHb#KOpU4F$B0o|q52%ZaXrNJp)}QF&MQMq8?Bf| z#}Ly(GX|f!#vHAfhRYG7*NVyc1>Z30gs?gVIoBdao#0fDY48AI)IqpL%=w5>2a4)3 z^>0Cps?4g#)T4#om0Gmb!&jbRhE`17^@u6cjL{<|rWI2shM3-3F}0yx460mLr>!=u ztcFXp($t!Y7*)SjPvbrT2~lOVMhrBpUY+7rkKt*VS)KINh=JW&uTBc8$GAo#Mjdvl z$2cLq`psHYJIOk*Kr6<9k8=G@S~2#M_~t3rjOl}zL0U03Xg=dSimBcg3{*}^Gv))t zwAG5Sl07(2E5`CJVpQ!?y&Ow-#MISF!`+P-b^NKGhV!64st&0hW1fYWx3zLJokEP} zM9c*1gKmvh8Y5Q0^{T$EUXGD^a3Q6s-lmNYBDaBBF*dAbpZ%lnf4%!LK%?LNnB^P& z2hI(~WAKfhZQrAxXyolTxx&U$_oK$mw(|{L9JTH5I^Hk`xLm{A+(_%^&MR$q*lu;c zY&JN`oDSPZuB5S@>rdAa%eC%#hMkU%rZ)T%-2q-U6>v`&#&Zju#r!YUdrekrxxE2= zqo39HG%t5-vmCTuGxX)!kc-m{ybZ6dpG_m-KX3s_hr1#JtTKh*Nq}|fL>SBc zFx!UZIM)BM&WzClmYibGs86;pR5@A{P3s}j-!b->o!)ziL3((Rkmae2= z!2UZ@lv9eB0uxA>)}gTiBx7M+PK1;=Rt?24*73=*36pU|k&LgQPMawO)(?FFn1ci? zb>p3=7{pq97)ze8xP?WSUt#}(Sy`yN5cRl^QpB;M3+oF^l326~!&FNqJ$OmrZ3T8e z5jKq&Y|>HlXDDL;rNG*zk`Ukwk%$UX7?uJUEMP7tOE=6wL97qMQV