chore: install openagent opencode
Signed-off-by: Dmytro Stanchiev <git@dmytros.dev>
This commit is contained in:
82
.opencode/context/ui/navigation.md
Normal file
82
.opencode/context/ui/navigation.md
Normal file
@@ -0,0 +1,82 @@
|
||||
<!-- Context: ui/navigation | Priority: critical | Version: 1.0 | Updated: 2026-02-15 -->
|
||||
|
||||
# UI Context
|
||||
|
||||
**Purpose**: User interface design patterns, standards, and best practices across all platforms
|
||||
|
||||
**Last Updated**: 2026-01-07
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
This category contains UI/UX patterns, design systems, and interface standards for building user-facing applications across different platforms.
|
||||
|
||||
---
|
||||
|
||||
## Directory Structure
|
||||
|
||||
### 📁 Subcategories
|
||||
|
||||
| Subcategory | Description | Path |
|
||||
|-------------|-------------|------|
|
||||
| **web/** | Web UI patterns, animations, styling, React components | [web/navigation.md](web/navigation.md) |
|
||||
| **terminal/** | Terminal UI (TUI) patterns for CLI applications | [terminal/navigation.md](terminal/navigation.md) |
|
||||
|
||||
### Future Subcategories
|
||||
|
||||
- **mobile/** - React Native, Flutter patterns (planned)
|
||||
- **desktop/** - Electron, Tauri patterns (planned)
|
||||
|
||||
---
|
||||
|
||||
## Quick Navigation
|
||||
|
||||
### Web UI
|
||||
- Animation patterns and micro-interactions
|
||||
- CSS styling standards and design systems
|
||||
- React component patterns
|
||||
- Design assets and resources
|
||||
- Scroll-linked animations (scrollytelling)
|
||||
|
||||
### Terminal UI
|
||||
- CLI/TUI patterns (planned)
|
||||
- Ink, Blessed component patterns (planned)
|
||||
- Terminal animations and progress indicators (planned)
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
**Web development**: Navigate to `web/` for browser-based UI patterns
|
||||
|
||||
**CLI/TUI development**: Navigate to `terminal/` for terminal interface patterns
|
||||
|
||||
**Cross-platform**: Consider patterns from multiple subcategories
|
||||
|
||||
---
|
||||
|
||||
## Scope
|
||||
|
||||
This category covers:
|
||||
- ✅ Visual design patterns
|
||||
- ✅ Animation and transitions
|
||||
- ✅ Component architecture
|
||||
- ✅ Styling standards
|
||||
- ✅ Design systems
|
||||
- ✅ Accessibility patterns
|
||||
- ⏳ Platform-specific patterns (mobile, desktop)
|
||||
|
||||
---
|
||||
|
||||
## Related Categories
|
||||
|
||||
- `development/` - General development patterns (backend, APIs, clean code)
|
||||
- `product/` - Product design and UX strategy
|
||||
- `content/` - Content design and copywriting
|
||||
|
||||
---
|
||||
|
||||
## Used By
|
||||
|
||||
**Agents**: frontend-specialist, design-specialist, ui-developer, animation-expert
|
||||
95
.opencode/context/ui/terminal/navigation.md
Normal file
95
.opencode/context/ui/terminal/navigation.md
Normal file
@@ -0,0 +1,95 @@
|
||||
<!-- Context: ui/navigation | Priority: critical | Version: 1.0 | Updated: 2026-02-15 -->
|
||||
|
||||
# Terminal UI Context
|
||||
|
||||
**Purpose**: Terminal UI (TUI) patterns, CLI animations, and command-line interface design
|
||||
|
||||
**Last Updated**: 2026-01-07
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
This subcategory will contain patterns and best practices for building terminal user interfaces using libraries like Ink, Blessed, and native terminal capabilities.
|
||||
|
||||
---
|
||||
|
||||
## Planned Content
|
||||
|
||||
### Core Files (Future)
|
||||
|
||||
| File | Description | Priority |
|
||||
|------|-------------|----------|
|
||||
| `tui-patterns.md` | Terminal UI component patterns and layouts | high |
|
||||
| `cli-animations.md` | Terminal animations, spinners, progress bars | high |
|
||||
| `ink-components.md` | React Ink component patterns | medium |
|
||||
| `blessed-patterns.md` | Blessed.js patterns and widgets | medium |
|
||||
| `terminal-styling.md` | ANSI colors, chalk, terminal theming | medium |
|
||||
|
||||
### Planned Topics
|
||||
|
||||
- **Layout patterns**: Boxes, borders, flexbox-like layouts
|
||||
- **Interactive components**: Menus, forms, selects, inputs
|
||||
- **Progress indicators**: Spinners, progress bars, loading states
|
||||
- **Terminal animations**: Frame-based animations, smooth transitions
|
||||
- **Color and styling**: ANSI escape codes, chalk, gradient text
|
||||
- **Keyboard handling**: Key bindings, shortcuts, navigation
|
||||
- **Terminal detection**: Capability detection, fallbacks
|
||||
|
||||
---
|
||||
|
||||
## Example Libraries
|
||||
|
||||
### React-based TUI
|
||||
- **Ink** - React for CLIs
|
||||
- **Pastel** - React-like TUI framework
|
||||
|
||||
### Traditional TUI
|
||||
- **Blessed** - High-level terminal interface library
|
||||
- **Blessed-contrib** - Widgets for blessed (charts, gauges)
|
||||
- **Terminal-kit** - Comprehensive terminal manipulation
|
||||
|
||||
### Styling
|
||||
- **Chalk** - Terminal string styling
|
||||
- **Gradient-string** - Gradient colors in terminal
|
||||
- **Boxen** - Create boxes in terminal
|
||||
|
||||
### Progress/Animation
|
||||
- **Ora** - Elegant terminal spinners
|
||||
- **CLI-progress** - Progress bars
|
||||
- **Listr** - Terminal task lists
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
**When this subcategory is populated**, use it for:
|
||||
- Building CLI tools with rich interfaces
|
||||
- Creating terminal-based dashboards
|
||||
- Implementing interactive command-line applications
|
||||
- Adding animations and progress indicators to CLI tools
|
||||
|
||||
---
|
||||
|
||||
## Related Categories
|
||||
|
||||
- `ui/web/` - Web UI patterns
|
||||
- `development/` - General development patterns
|
||||
|
||||
---
|
||||
|
||||
## Status
|
||||
|
||||
⏳ **Placeholder** - This subcategory is planned but not yet populated.
|
||||
|
||||
To contribute content here, follow the MVI principles:
|
||||
1. Extract core concepts (1-3 sentences)
|
||||
2. List key points (3-5 bullets)
|
||||
3. Provide minimal example
|
||||
4. Link to full documentation
|
||||
|
||||
---
|
||||
|
||||
## Used By
|
||||
|
||||
**Agents**: cli-developer, terminal-specialist, devops-specialist (future)
|
||||
200
.opencode/context/ui/web/animation-advanced.md
Normal file
200
.opencode/context/ui/web/animation-advanced.md
Normal file
@@ -0,0 +1,200 @@
|
||||
<!-- Context: ui/web/animation-advanced | Priority: medium | Version: 1.0 | Updated: 2025-12-09 -->
|
||||
# Advanced Animation Patterns
|
||||
|
||||
Recipes, best practices, micro-interactions, and accessibility considerations.
|
||||
|
||||
---
|
||||
|
||||
## Page Transitions
|
||||
|
||||
### Route Changes
|
||||
|
||||
```css
|
||||
/* Page fade out */
|
||||
.page-exit {
|
||||
animation: fadeOut 200ms ease-in;
|
||||
}
|
||||
@keyframes fadeOut {
|
||||
from { opacity: 1; }
|
||||
to { opacity: 0; }
|
||||
}
|
||||
|
||||
/* Page fade in */
|
||||
.page-enter {
|
||||
animation: fadeIn 300ms ease-out;
|
||||
}
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
```
|
||||
|
||||
**Micro-syntax**:
|
||||
```
|
||||
pageExit: 200ms ease-in [α1→0]
|
||||
pageEnter: 300ms ease-out [α0→1]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Micro-Interactions
|
||||
|
||||
### Hover Effects
|
||||
|
||||
```css
|
||||
/* Link underline slide */
|
||||
.link {
|
||||
position: relative;
|
||||
}
|
||||
.link::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 0;
|
||||
height: 2px;
|
||||
background: currentColor;
|
||||
transition: width 250ms ease-out;
|
||||
}
|
||||
.link:hover::after {
|
||||
width: 100%;
|
||||
}
|
||||
```
|
||||
|
||||
**Micro-syntax**:
|
||||
```
|
||||
linkHover: 250ms ease-out [width0→100%]
|
||||
```
|
||||
|
||||
### Toggle Switches
|
||||
|
||||
```css
|
||||
/* Toggle slide */
|
||||
.toggle-switch {
|
||||
transition: background-color 200ms ease-out;
|
||||
}
|
||||
.toggle-switch .thumb {
|
||||
transition: transform 200ms ease-out;
|
||||
}
|
||||
.toggle-switch.on .thumb {
|
||||
transform: translateX(20px);
|
||||
}
|
||||
```
|
||||
|
||||
**Micro-syntax**:
|
||||
```
|
||||
toggle: 200ms ease-out [X0→20, bg→accent]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Animation Recipes
|
||||
|
||||
### Chat UI Complete Animation System
|
||||
|
||||
```
|
||||
## Core Message Flow
|
||||
userMsg: 400ms ease-out [Y+20→0, X+10→0, S0.9→1]
|
||||
aiMsg: 600ms bounce [Y+15→0, S0.95→1] +200ms
|
||||
typing: 1400ms ∞ [Y±8, α0.4→1] stagger+200ms
|
||||
status: 300ms ease-out [α0.6→1, S1→1.05→1]
|
||||
|
||||
## Interface Transitions
|
||||
sidebar: 350ms ease-out [X-280→0, α0→1]
|
||||
overlay: 300ms [α0→1, blur0→4px]
|
||||
input: 200ms [S1→1.01, shadow+ring] focus
|
||||
input: 150ms [S1.01→1, shadow-ring] blur
|
||||
|
||||
## Button Interactions
|
||||
sendBtn: 150ms [S1→0.95→1, R±2°] press
|
||||
sendBtn: 200ms [S1→1.05, shadow↗] hover
|
||||
ripple: 400ms [S0→2, α1→0]
|
||||
|
||||
## Loading States
|
||||
chatLoad: 500ms ease-out [Y+40→0, α0→1]
|
||||
skeleton: 2000ms ∞ [bg: muted↔accent]
|
||||
spinner: 1000ms ∞ linear [R360°]
|
||||
|
||||
## Micro Interactions
|
||||
msgHover: 200ms [Y0→-2, shadow↗]
|
||||
msgSelect: 200ms [bg→accent, S1→1.02]
|
||||
error: 400ms [X±5] shake
|
||||
success: 600ms bounce [S0→1.2→1, R360°]
|
||||
|
||||
## Scroll & Navigation
|
||||
autoScroll: 400ms smooth
|
||||
scrollHint: 800ms ∞×3 [Y±5]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Do's ✅
|
||||
|
||||
- Keep animations under 400ms for most interactions
|
||||
- Use `transform` and `opacity` for 60fps performance
|
||||
- Provide purpose for every animation
|
||||
- Use ease-out for entrances, ease-in for exits
|
||||
- Test on low-end devices
|
||||
- Respect `prefers-reduced-motion`
|
||||
- Stagger animations for lists (50-100ms delay)
|
||||
- Use consistent timing across similar interactions
|
||||
|
||||
### Don'ts ❌
|
||||
|
||||
- Don't animate width/height (use scale instead)
|
||||
- Don't use animations longer than 800ms
|
||||
- Don't animate too many elements at once
|
||||
- Don't use animations without purpose
|
||||
- Don't ignore accessibility preferences
|
||||
- Don't use jarring/distracting animations
|
||||
- Don't animate on every interaction
|
||||
- Don't use complex easing for simple interactions
|
||||
|
||||
---
|
||||
|
||||
## Accessibility
|
||||
|
||||
### Reduced Motion
|
||||
|
||||
```css
|
||||
/* Respect user preferences */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Focus Indicators
|
||||
|
||||
```css
|
||||
/* Always animate focus states */
|
||||
:focus-visible {
|
||||
outline: 2px solid var(--ring);
|
||||
outline-offset: 2px;
|
||||
transition: outline-offset 150ms ease-out;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- [Web Animation API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API)
|
||||
- [CSS Easing Functions](https://easings.net/)
|
||||
- [Animation Performance](https://web.dev/animations-guide/)
|
||||
- [Reduced Motion](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion)
|
||||
|
||||
---
|
||||
|
||||
## Related Files
|
||||
|
||||
- [Animation Basics](./animation-basics.md) - Fundamentals
|
||||
- [Animation Components](./animation-components.md) - Common UI patterns
|
||||
- [Loading Animations](./animation-loading.md) - Loading states
|
||||
94
.opencode/context/ui/web/animation-basics.md
Normal file
94
.opencode/context/ui/web/animation-basics.md
Normal file
@@ -0,0 +1,94 @@
|
||||
<!-- Context: ui/web/animation-basics | Priority: high | Version: 1.0 | Updated: 2025-12-09 -->
|
||||
# Animation Basics
|
||||
|
||||
## Overview
|
||||
|
||||
Standards and patterns for UI animations, micro-interactions, and transitions. Animations should feel natural, purposeful, and enhance user experience without causing distraction.
|
||||
|
||||
## Quick Reference
|
||||
|
||||
**Timing**: 150-400ms for most interactions
|
||||
**Easing**: ease-out for entrances, ease-in for exits
|
||||
**Purpose**: Every animation should have a clear purpose
|
||||
**Performance**: Use transform and opacity for 60fps
|
||||
|
||||
---
|
||||
|
||||
## Animation Micro-Syntax
|
||||
|
||||
### Notation Guide
|
||||
|
||||
**Format**: `element: duration easing [properties] modifiers`
|
||||
|
||||
**Symbols**:
|
||||
- `→` = transition from → to
|
||||
- `±` = oscillate/shake
|
||||
- `↗` = increase
|
||||
- `↘` = decrease
|
||||
- `∞` = infinite loop
|
||||
- `×N` = repeat N times
|
||||
- `+Nms` = delay N milliseconds
|
||||
|
||||
**Properties**:
|
||||
- `Y` = translateY
|
||||
- `X` = translateX
|
||||
- `S` = scale
|
||||
- `R` = rotate
|
||||
- `α` = opacity
|
||||
- `bg` = background
|
||||
|
||||
**Example**: `button: 200ms ease-out [S1→1.05, α0.8→1]`
|
||||
- Button scales from 1 to 1.05 and fades from 0.8 to 1 over 200ms with ease-out
|
||||
|
||||
---
|
||||
|
||||
## Core Animation Principles
|
||||
|
||||
### Timing Standards
|
||||
|
||||
```
|
||||
Ultra-fast: 100-150ms (micro-feedback, hover states)
|
||||
Fast: 150-250ms (button clicks, toggles)
|
||||
Standard: 250-350ms (modals, dropdowns, navigation)
|
||||
Moderate: 350-500ms (page transitions, complex animations)
|
||||
Slow: 500-800ms (dramatic reveals, storytelling)
|
||||
```
|
||||
|
||||
### Easing Functions
|
||||
|
||||
```css
|
||||
/* Entrances - start slow, end fast */
|
||||
ease-out: cubic-bezier(0, 0, 0.2, 1);
|
||||
|
||||
/* Exits - start fast, end slow */
|
||||
ease-in: cubic-bezier(0.4, 0, 1, 1);
|
||||
|
||||
/* Both - smooth throughout */
|
||||
ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
|
||||
/* Bounce - playful, attention-grabbing */
|
||||
bounce: cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
||||
|
||||
/* Elastic - spring-like */
|
||||
elastic: cubic-bezier(0.68, -0.6, 0.32, 1.6);
|
||||
```
|
||||
|
||||
### Performance Guidelines
|
||||
|
||||
**60fps Animations** (GPU-accelerated):
|
||||
- ✅ `transform` (translate, scale, rotate)
|
||||
- ✅ `opacity`
|
||||
- ✅ `filter` (with caution)
|
||||
|
||||
**Avoid** (causes reflow/repaint):
|
||||
- ❌ `width`, `height`
|
||||
- ❌ `top`, `left`, `right`, `bottom`
|
||||
- ❌ `margin`, `padding`
|
||||
|
||||
---
|
||||
|
||||
## Related Files
|
||||
|
||||
- [Animation Components](./animation-components.md) - Common UI patterns
|
||||
- [Loading Animations](./animation-loading.md) - Loading states
|
||||
- [Advanced Animations](./animation-advanced.md) - Recipes & best practices
|
||||
113
.opencode/context/ui/web/animation-chat.md
Normal file
113
.opencode/context/ui/web/animation-chat.md
Normal file
@@ -0,0 +1,113 @@
|
||||
<!-- Context: ui/web/animation-chat | Priority: medium | Version: 1.0 | Updated: 2025-12-09 -->
|
||||
# Chat UI Animation Patterns
|
||||
|
||||
Animation patterns for message entrances, typing indicators, and chat interactions.
|
||||
|
||||
---
|
||||
|
||||
## Message Entrance
|
||||
|
||||
```css
|
||||
/* User message - slide from right */
|
||||
.message-user {
|
||||
animation: slideInRight 400ms ease-out;
|
||||
}
|
||||
@keyframes slideInRight {
|
||||
from {
|
||||
transform: translateX(10px) translateY(20px);
|
||||
opacity: 0;
|
||||
scale: 0.9;
|
||||
}
|
||||
to {
|
||||
transform: translateX(0) translateY(0);
|
||||
opacity: 1;
|
||||
scale: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* AI message - slide from left with bounce */
|
||||
.message-ai {
|
||||
animation: slideInLeft 600ms cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
||||
animation-delay: 200ms;
|
||||
}
|
||||
@keyframes slideInLeft {
|
||||
from {
|
||||
transform: translateY(15px);
|
||||
opacity: 0;
|
||||
scale: 0.95;
|
||||
}
|
||||
to {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
scale: 1;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Micro-syntax**:
|
||||
```
|
||||
userMsg: 400ms ease-out [Y+20→0, X+10→0, S0.9→1]
|
||||
aiMsg: 600ms bounce [Y+15→0, S0.95→1] +200ms
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Typing Indicator
|
||||
|
||||
```css
|
||||
.typing-indicator span {
|
||||
animation: typingDot 1400ms infinite;
|
||||
}
|
||||
.typing-indicator span:nth-child(2) { animation-delay: 200ms; }
|
||||
.typing-indicator span:nth-child(3) { animation-delay: 400ms; }
|
||||
|
||||
@keyframes typingDot {
|
||||
0%, 60%, 100% {
|
||||
transform: translateY(0);
|
||||
opacity: 0.4;
|
||||
}
|
||||
30% {
|
||||
transform: translateY(-8px);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Micro-syntax**:
|
||||
```
|
||||
typing: 1400ms ∞ [Y±8, α0.4→1] stagger+200ms
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Chat Message Micro-Interactions
|
||||
|
||||
```css
|
||||
/* Message hover */
|
||||
.message:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
transition: all 200ms ease-out;
|
||||
}
|
||||
|
||||
/* Message selection */
|
||||
.message.selected {
|
||||
background-color: var(--accent);
|
||||
transform: scale(1.02);
|
||||
transition: all 200ms ease-out;
|
||||
}
|
||||
```
|
||||
|
||||
**Micro-syntax**:
|
||||
```
|
||||
msgHover: 200ms [Y0→-2, shadow↗]
|
||||
msgSelect: 200ms [bg→accent, S1→1.02]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Related Files
|
||||
|
||||
- [Animation Basics](./animation-basics.md) - Fundamentals
|
||||
- [Component Animations](./animation-components.md) - UI components
|
||||
- [Loading Animations](./animation-loading.md) - Loading states
|
||||
137
.opencode/context/ui/web/animation-components.md
Normal file
137
.opencode/context/ui/web/animation-components.md
Normal file
@@ -0,0 +1,137 @@
|
||||
<!-- Context: ui/web/animation-components | Priority: high | Version: 1.0 | Updated: 2025-12-09 -->
|
||||
# Component Animation Patterns
|
||||
|
||||
Animation patterns for buttons, cards, modals, dropdowns, and sidebars.
|
||||
|
||||
---
|
||||
|
||||
## Button Interactions
|
||||
|
||||
```css
|
||||
.button {
|
||||
transition: transform 200ms ease-out, box-shadow 200ms ease-out;
|
||||
}
|
||||
.button:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
.button:active {
|
||||
transform: scale(0.95);
|
||||
transition: transform 100ms ease-in;
|
||||
}
|
||||
|
||||
@keyframes ripple {
|
||||
from { transform: scale(0); opacity: 1; }
|
||||
to { transform: scale(2); opacity: 0; }
|
||||
}
|
||||
.button::after { animation: ripple 400ms ease-out; }
|
||||
```
|
||||
|
||||
**Micro-syntax**:
|
||||
```
|
||||
buttonHover: 200ms ease-out [Y0→-2, shadow↗]
|
||||
buttonPress: 100ms ease-in [S1→0.95]
|
||||
ripple: 400ms ease-out [S0→2, α1→0]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Card Interactions
|
||||
|
||||
```css
|
||||
.card {
|
||||
transition: transform 300ms ease-out, box-shadow 300ms ease-out;
|
||||
}
|
||||
.card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
.card.selected {
|
||||
transform: scale(1.02);
|
||||
background-color: var(--accent);
|
||||
transition: all 200ms ease-out;
|
||||
}
|
||||
```
|
||||
|
||||
**Micro-syntax**:
|
||||
```
|
||||
cardHover: 300ms ease-out [Y0→-4, shadow↗]
|
||||
cardSelect: 200ms ease-out [S1→1.02, bg→accent]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Modal/Dialog Animations
|
||||
|
||||
```css
|
||||
.modal-backdrop { animation: fadeIn 300ms ease-out; }
|
||||
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
||||
|
||||
.modal { animation: slideUp 350ms ease-out; }
|
||||
@keyframes slideUp {
|
||||
from { transform: translateY(40px); opacity: 0; }
|
||||
to { transform: translateY(0); opacity: 1; }
|
||||
}
|
||||
|
||||
.modal.closing { animation: slideDown 250ms ease-in; }
|
||||
@keyframes slideDown {
|
||||
from { transform: translateY(0); opacity: 1; }
|
||||
to { transform: translateY(40px); opacity: 0; }
|
||||
}
|
||||
```
|
||||
|
||||
**Micro-syntax**:
|
||||
```
|
||||
backdrop: 300ms ease-out [α0→1]
|
||||
modalEnter: 350ms ease-out [Y+40→0, α0→1]
|
||||
modalExit: 250ms ease-in [Y0→+40, α1→0]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Dropdown/Menu Animations
|
||||
|
||||
```css
|
||||
.dropdown {
|
||||
animation: dropdownOpen 200ms ease-out;
|
||||
transform-origin: top;
|
||||
}
|
||||
@keyframes dropdownOpen {
|
||||
from { transform: scaleY(0.95); opacity: 0; }
|
||||
to { transform: scaleY(1); opacity: 1; }
|
||||
}
|
||||
```
|
||||
|
||||
**Micro-syntax**: `dropdown: 200ms ease-out [scaleY0.95→1, α0→1]`
|
||||
|
||||
---
|
||||
|
||||
## Sidebar/Drawer Animations
|
||||
|
||||
```css
|
||||
.sidebar { animation: slideInLeft 350ms ease-out; }
|
||||
@keyframes slideInLeft {
|
||||
from { transform: translateX(-280px); opacity: 0; }
|
||||
to { transform: translateX(0); opacity: 1; }
|
||||
}
|
||||
|
||||
.overlay { animation: overlayFade 300ms ease-out; }
|
||||
@keyframes overlayFade {
|
||||
from { opacity: 0; backdrop-filter: blur(0); }
|
||||
to { opacity: 1; backdrop-filter: blur(4px); }
|
||||
}
|
||||
```
|
||||
|
||||
**Micro-syntax**:
|
||||
```
|
||||
sidebar: 350ms ease-out [X-280→0, α0→1]
|
||||
overlay: 300ms ease-out [α0→1, blur0→4px]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Related Files
|
||||
|
||||
- [Animation Basics](./animation-basics.md) - Fundamentals
|
||||
- [Chat Animations](./animation-chat.md) - Message patterns
|
||||
- [Loading Animations](./animation-loading.md) - Loading states
|
||||
121
.opencode/context/ui/web/animation-forms.md
Normal file
121
.opencode/context/ui/web/animation-forms.md
Normal file
@@ -0,0 +1,121 @@
|
||||
<!-- Context: ui/web/animation-forms | Priority: medium | Version: 1.0 | Updated: 2025-12-09 -->
|
||||
# Form Animation Patterns
|
||||
|
||||
Animation patterns for form inputs, validation states, and scroll animations.
|
||||
|
||||
---
|
||||
|
||||
## Focus States
|
||||
|
||||
```css
|
||||
/* Input focus - ring and scale */
|
||||
.input {
|
||||
transition: all 200ms ease-out;
|
||||
}
|
||||
.input:focus {
|
||||
transform: scale(1.01);
|
||||
box-shadow: 0 0 0 3px var(--ring);
|
||||
}
|
||||
|
||||
/* Input blur - return to normal */
|
||||
.input:not(:focus) {
|
||||
transition: all 150ms ease-in;
|
||||
}
|
||||
```
|
||||
|
||||
**Micro-syntax**:
|
||||
```
|
||||
inputFocus: 200ms ease-out [S1→1.01, shadow+ring]
|
||||
inputBlur: 150ms ease-in [S1.01→1, shadow-ring]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Validation States
|
||||
|
||||
```css
|
||||
/* Error shake */
|
||||
.input-error {
|
||||
animation: shake 400ms ease-in-out;
|
||||
}
|
||||
@keyframes shake {
|
||||
0%, 100% { transform: translateX(0); }
|
||||
25% { transform: translateX(-5px); }
|
||||
75% { transform: translateX(5px); }
|
||||
}
|
||||
|
||||
/* Success checkmark */
|
||||
.input-success::after {
|
||||
animation: checkmark 600ms cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
||||
}
|
||||
@keyframes checkmark {
|
||||
from {
|
||||
transform: scale(0) rotate(0deg);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: scale(1.2) rotate(360deg);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Micro-syntax**:
|
||||
```
|
||||
error: 400ms ease-in-out [X±5] shake
|
||||
success: 600ms bounce [S0→1.2, R0→360°, α0→1]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Scroll Animations
|
||||
|
||||
### Scroll-Triggered Fade In
|
||||
|
||||
```css
|
||||
.fade-in-on-scroll {
|
||||
opacity: 0;
|
||||
transform: translateY(40px);
|
||||
transition: opacity 500ms ease-out, transform 500ms ease-out;
|
||||
}
|
||||
.fade-in-on-scroll.visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
```
|
||||
|
||||
**Micro-syntax**:
|
||||
```
|
||||
scrollFadeIn: 500ms ease-out [Y+40→0, α0→1]
|
||||
```
|
||||
|
||||
### Auto-Scroll
|
||||
|
||||
```css
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
.scroll-hint {
|
||||
animation: scrollHint 800ms infinite;
|
||||
animation-iteration-count: 3;
|
||||
}
|
||||
@keyframes scrollHint {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
50% { transform: translateY(5px); }
|
||||
}
|
||||
```
|
||||
|
||||
**Micro-syntax**:
|
||||
```
|
||||
autoScroll: 400ms smooth
|
||||
scrollHint: 800ms ∞×3 [Y±5]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Related Files
|
||||
|
||||
- [Animation Basics](./animation-basics.md) - Fundamentals
|
||||
- [Animation Components](./animation-components.md) - Common UI patterns
|
||||
- [Loading Animations](./animation-loading.md) - Loading states
|
||||
118
.opencode/context/ui/web/animation-loading.md
Normal file
118
.opencode/context/ui/web/animation-loading.md
Normal file
@@ -0,0 +1,118 @@
|
||||
<!-- Context: ui/web/animation-loading | Priority: medium | Version: 1.0 | Updated: 2025-12-09 -->
|
||||
# Loading State Animations
|
||||
|
||||
Animation patterns for skeleton screens, spinners, progress bars, and status indicators.
|
||||
|
||||
---
|
||||
|
||||
## Skeleton Screens
|
||||
|
||||
```css
|
||||
/* Skeleton shimmer */
|
||||
.skeleton {
|
||||
animation: shimmer 2000ms infinite;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
var(--muted) 0%,
|
||||
var(--accent) 50%,
|
||||
var(--muted) 100%
|
||||
);
|
||||
background-size: 200% 100%;
|
||||
}
|
||||
@keyframes shimmer {
|
||||
0% { background-position: 200% 0; }
|
||||
100% { background-position: -200% 0; }
|
||||
}
|
||||
```
|
||||
|
||||
**Micro-syntax**:
|
||||
```
|
||||
skeleton: 2000ms ∞ [bg: muted↔accent]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Spinners
|
||||
|
||||
```css
|
||||
/* Circular spinner */
|
||||
.spinner {
|
||||
animation: spin 1000ms linear infinite;
|
||||
}
|
||||
@keyframes spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* Pulsing dots */
|
||||
.loading-dots span {
|
||||
animation: dotPulse 1500ms infinite;
|
||||
}
|
||||
.loading-dots span:nth-child(2) { animation-delay: 200ms; }
|
||||
.loading-dots span:nth-child(3) { animation-delay: 400ms; }
|
||||
@keyframes dotPulse {
|
||||
0%, 80%, 100% { opacity: 0.3; scale: 0.8; }
|
||||
40% { opacity: 1; scale: 1; }
|
||||
}
|
||||
```
|
||||
|
||||
**Micro-syntax**:
|
||||
```
|
||||
spinner: 1000ms ∞ linear [R360°]
|
||||
dotPulse: 1500ms ∞ [α0.3→1→0.3, S0.8→1→0.8] stagger+200ms
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Progress Bars
|
||||
|
||||
```css
|
||||
/* Indeterminate progress */
|
||||
.progress-bar {
|
||||
animation: progress 2000ms ease-in-out infinite;
|
||||
}
|
||||
@keyframes progress {
|
||||
0% { transform: translateX(-100%); }
|
||||
50% { transform: translateX(0); }
|
||||
100% { transform: translateX(100%); }
|
||||
}
|
||||
```
|
||||
|
||||
**Micro-syntax**:
|
||||
```
|
||||
progress: 2000ms ∞ ease-in-out [X-100%→0→100%]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Status Indicators
|
||||
|
||||
```css
|
||||
/* Online status pulse */
|
||||
.status-online {
|
||||
animation: pulse 2000ms infinite;
|
||||
}
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
opacity: 1;
|
||||
scale: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.6;
|
||||
scale: 1.05;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Micro-syntax**:
|
||||
```
|
||||
status: 2000ms ∞ [α1→0.6→1, S1→1.05→1]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Related Files
|
||||
|
||||
- [Animation Basics](./animation-basics.md) - Fundamentals
|
||||
- [Form Animations](./animation-forms.md) - Form patterns
|
||||
- [Advanced Animations](./animation-advanced.md) - Recipes & best practices
|
||||
381
.opencode/context/ui/web/design-systems.md
Normal file
381
.opencode/context/ui/web/design-systems.md
Normal file
@@ -0,0 +1,381 @@
|
||||
<!-- Context: development/design-systems | Priority: high | Version: 1.0 | Updated: 2025-12-09 -->
|
||||
# Design Systems
|
||||
|
||||
## Overview
|
||||
|
||||
This context file provides reusable design system patterns, theme templates, and color systems for frontend design work. Use these as starting points for creating cohesive, professional UI designs.
|
||||
|
||||
## Quick Reference
|
||||
|
||||
**Color Format**: OKLCH (perceptually uniform color space)
|
||||
**Theme Variables**: CSS custom properties (--variable-name)
|
||||
**Font Sources**: Google Fonts
|
||||
**Responsive**: All designs must be mobile-first responsive
|
||||
|
||||
---
|
||||
|
||||
## Theme Patterns
|
||||
|
||||
### Neo-Brutalism Style
|
||||
|
||||
**Characteristics**: 90s web design aesthetic, bold borders, flat shadows, high contrast
|
||||
|
||||
**Use Cases**:
|
||||
- Retro/vintage applications
|
||||
- Bold, statement-making interfaces
|
||||
- Art/creative portfolios
|
||||
- Playful consumer apps
|
||||
|
||||
**Theme Template**:
|
||||
|
||||
```css
|
||||
:root {
|
||||
/* Colors - High contrast, bold */
|
||||
--background: oklch(1.0000 0 0);
|
||||
--foreground: oklch(0 0 0);
|
||||
--card: oklch(1.0000 0 0);
|
||||
--card-foreground: oklch(0 0 0);
|
||||
--popover: oklch(1.0000 0 0);
|
||||
--popover-foreground: oklch(0 0 0);
|
||||
--primary: oklch(0.6489 0.2370 26.9728);
|
||||
--primary-foreground: oklch(1.0000 0 0);
|
||||
--secondary: oklch(0.9680 0.2110 109.7692);
|
||||
--secondary-foreground: oklch(0 0 0);
|
||||
--muted: oklch(0.9551 0 0);
|
||||
--muted-foreground: oklch(0.3211 0 0);
|
||||
--accent: oklch(0.5635 0.2408 260.8178);
|
||||
--accent-foreground: oklch(1.0000 0 0);
|
||||
--destructive: oklch(0 0 0);
|
||||
--destructive-foreground: oklch(1.0000 0 0);
|
||||
--border: oklch(0 0 0);
|
||||
--input: oklch(0 0 0);
|
||||
--ring: oklch(0.6489 0.2370 26.9728);
|
||||
|
||||
/* Chart colors */
|
||||
--chart-1: oklch(0.6489 0.2370 26.9728);
|
||||
--chart-2: oklch(0.9680 0.2110 109.7692);
|
||||
--chart-3: oklch(0.5635 0.2408 260.8178);
|
||||
--chart-4: oklch(0.7323 0.2492 142.4953);
|
||||
--chart-5: oklch(0.5931 0.2726 328.3634);
|
||||
|
||||
/* Sidebar */
|
||||
--sidebar: oklch(0.9551 0 0);
|
||||
--sidebar-foreground: oklch(0 0 0);
|
||||
--sidebar-primary: oklch(0.6489 0.2370 26.9728);
|
||||
--sidebar-primary-foreground: oklch(1.0000 0 0);
|
||||
--sidebar-accent: oklch(0.5635 0.2408 260.8178);
|
||||
--sidebar-accent-foreground: oklch(1.0000 0 0);
|
||||
--sidebar-border: oklch(0 0 0);
|
||||
--sidebar-ring: oklch(0.6489 0.2370 26.9728);
|
||||
|
||||
/* Typography */
|
||||
--font-sans: DM Sans, sans-serif;
|
||||
--font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
|
||||
--font-mono: Space Mono, monospace;
|
||||
|
||||
/* Border radius - Sharp corners */
|
||||
--radius: 0px;
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
|
||||
/* Shadows - Bold, offset shadows */
|
||||
--shadow-2xs: 4px 4px 0px 0px hsl(0 0% 0% / 0.50);
|
||||
--shadow-xs: 4px 4px 0px 0px hsl(0 0% 0% / 0.50);
|
||||
--shadow-sm: 4px 4px 0px 0px hsl(0 0% 0% / 1.00), 4px 1px 2px -1px hsl(0 0% 0% / 1.00);
|
||||
--shadow: 4px 4px 0px 0px hsl(0 0% 0% / 1.00), 4px 1px 2px -1px hsl(0 0% 0% / 1.00);
|
||||
--shadow-md: 4px 4px 0px 0px hsl(0 0% 0% / 1.00), 4px 2px 4px -1px hsl(0 0% 0% / 1.00);
|
||||
--shadow-lg: 4px 4px 0px 0px hsl(0 0% 0% / 1.00), 4px 4px 6px -1px hsl(0 0% 0% / 1.00);
|
||||
--shadow-xl: 4px 4px 0px 0px hsl(0 0% 0% / 1.00), 4px 8px 10px -1px hsl(0 0% 0% / 1.00);
|
||||
--shadow-2xl: 4px 4px 0px 0px hsl(0 0% 0% / 2.50);
|
||||
|
||||
/* Spacing */
|
||||
--tracking-normal: 0em;
|
||||
--spacing: 0.25rem;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Modern Dark Mode Style
|
||||
|
||||
**Characteristics**: Clean, minimal, professional (Vercel/Linear aesthetic)
|
||||
|
||||
**Use Cases**:
|
||||
- SaaS applications
|
||||
- Developer tools
|
||||
- Professional dashboards
|
||||
- Enterprise applications
|
||||
- Modern web apps
|
||||
|
||||
**Theme Template**:
|
||||
|
||||
```css
|
||||
:root {
|
||||
/* Colors - Subtle, professional */
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.1450 0 0);
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.1450 0 0);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.1450 0 0);
|
||||
--primary: oklch(0.2050 0 0);
|
||||
--primary-foreground: oklch(0.9850 0 0);
|
||||
--secondary: oklch(0.9700 0 0);
|
||||
--secondary-foreground: oklch(0.2050 0 0);
|
||||
--muted: oklch(0.9700 0 0);
|
||||
--muted-foreground: oklch(0.5560 0 0);
|
||||
--accent: oklch(0.9700 0 0);
|
||||
--accent-foreground: oklch(0.2050 0 0);
|
||||
--destructive: oklch(0.5770 0.2450 27.3250);
|
||||
--destructive-foreground: oklch(1 0 0);
|
||||
--border: oklch(0.9220 0 0);
|
||||
--input: oklch(0.9220 0 0);
|
||||
--ring: oklch(0.7080 0 0);
|
||||
|
||||
/* Chart colors - Monochromatic blues */
|
||||
--chart-1: oklch(0.8100 0.1000 252);
|
||||
--chart-2: oklch(0.6200 0.1900 260);
|
||||
--chart-3: oklch(0.5500 0.2200 263);
|
||||
--chart-4: oklch(0.4900 0.2200 264);
|
||||
--chart-5: oklch(0.4200 0.1800 266);
|
||||
|
||||
/* Sidebar */
|
||||
--sidebar: oklch(0.9850 0 0);
|
||||
--sidebar-foreground: oklch(0.1450 0 0);
|
||||
--sidebar-primary: oklch(0.2050 0 0);
|
||||
--sidebar-primary-foreground: oklch(0.9850 0 0);
|
||||
--sidebar-accent: oklch(0.9700 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.2050 0 0);
|
||||
--sidebar-border: oklch(0.9220 0 0);
|
||||
--sidebar-ring: oklch(0.7080 0 0);
|
||||
|
||||
/* Typography - System fonts */
|
||||
--font-sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
|
||||
--font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
|
||||
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
|
||||
/* Border radius - Rounded */
|
||||
--radius: 0.625rem;
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
|
||||
/* Shadows - Subtle, soft */
|
||||
--shadow-2xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05);
|
||||
--shadow-xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05);
|
||||
--shadow-sm: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10);
|
||||
--shadow: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10);
|
||||
--shadow-md: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 2px 4px -1px hsl(0 0% 0% / 0.10);
|
||||
--shadow-lg: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 4px 6px -1px hsl(0 0% 0% / 0.10);
|
||||
--shadow-xl: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 8px 10px -1px hsl(0 0% 0% / 0.10);
|
||||
--shadow-2xl: 0 1px 3px 0px hsl(0 0% 0% / 0.25);
|
||||
|
||||
/* Spacing */
|
||||
--tracking-normal: 0em;
|
||||
--spacing: 0.25rem;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Typography System
|
||||
|
||||
### Recommended Font Families
|
||||
|
||||
**Monospace Fonts** (Code, technical interfaces):
|
||||
- JetBrains Mono
|
||||
- Fira Code
|
||||
- Source Code Pro
|
||||
- IBM Plex Mono
|
||||
- Roboto Mono
|
||||
- Space Mono
|
||||
- Geist Mono
|
||||
|
||||
**Sans-Serif Fonts** (UI, body text):
|
||||
- Inter
|
||||
- Roboto
|
||||
- Open Sans
|
||||
- Poppins
|
||||
- Montserrat
|
||||
- Outfit
|
||||
- Plus Jakarta Sans
|
||||
- DM Sans
|
||||
- Geist
|
||||
- Space Grotesk
|
||||
|
||||
**Display/Decorative Fonts**:
|
||||
- Oxanium
|
||||
- Architects Daughter
|
||||
|
||||
**Serif Fonts** (Editorial, formal):
|
||||
- Merriweather
|
||||
- Playfair Display
|
||||
- Lora
|
||||
- Source Serif Pro
|
||||
- Libre Baskerville
|
||||
|
||||
### Font Loading
|
||||
|
||||
Always use Google Fonts for consistency and reliability:
|
||||
|
||||
```html
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Color System Guidelines
|
||||
|
||||
### OKLCH Color Space
|
||||
|
||||
Use OKLCH for perceptually uniform colors:
|
||||
- **L** (Lightness): 0-1 (0 = black, 1 = white)
|
||||
- **C** (Chroma): 0-0.4 (saturation)
|
||||
- **H** (Hue): 0-360 (color angle)
|
||||
|
||||
**Format**: `oklch(L C H)`
|
||||
|
||||
**Example**: `oklch(0.6489 0.2370 26.9728)` = vibrant orange
|
||||
|
||||
### Color Palette Rules
|
||||
|
||||
1. **Avoid Bootstrap Blue**: Unless explicitly requested, avoid generic blue (#007bff)
|
||||
2. **Semantic Colors**: Use meaningful color names (--primary, --destructive, --success)
|
||||
3. **Contrast**: Ensure WCAG AA compliance (4.5:1 for text)
|
||||
4. **Consistency**: Use theme variables, not hardcoded colors
|
||||
|
||||
### Background/Foreground Pairing
|
||||
|
||||
**Rule**: Background should contrast with content
|
||||
|
||||
- Light component → Dark background
|
||||
- Dark component → Light background
|
||||
- Ensures visibility and visual hierarchy
|
||||
|
||||
---
|
||||
|
||||
## Shadow System
|
||||
|
||||
### Shadow Scales
|
||||
|
||||
Shadows create depth and hierarchy:
|
||||
|
||||
- `--shadow-2xs`: Minimal elevation (1-2px)
|
||||
- `--shadow-xs`: Subtle lift (2-3px)
|
||||
- `--shadow-sm`: Small cards (3-4px)
|
||||
- `--shadow`: Default elevation (4-6px)
|
||||
- `--shadow-md`: Medium cards (6-8px)
|
||||
- `--shadow-lg`: Modals, dropdowns (8-12px)
|
||||
- `--shadow-xl`: Floating panels (12-16px)
|
||||
- `--shadow-2xl`: Maximum elevation (16-24px)
|
||||
|
||||
### Shadow Styles
|
||||
|
||||
**Soft Shadows** (Modern):
|
||||
```css
|
||||
box-shadow: 0 1px 3px 0px hsl(0 0% 0% / 0.10);
|
||||
```
|
||||
|
||||
**Hard Shadows** (Neo-brutalism):
|
||||
```css
|
||||
box-shadow: 4px 4px 0px 0px hsl(0 0% 0% / 1.00);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Spacing System
|
||||
|
||||
### Base Unit
|
||||
|
||||
Use `--spacing: 0.25rem` (4px) as base unit
|
||||
|
||||
### Scale
|
||||
|
||||
- 1x = 0.25rem (4px)
|
||||
- 2x = 0.5rem (8px)
|
||||
- 3x = 0.75rem (12px)
|
||||
- 4x = 1rem (16px)
|
||||
- 6x = 1.5rem (24px)
|
||||
- 8x = 2rem (32px)
|
||||
- 12x = 3rem (48px)
|
||||
- 16x = 4rem (64px)
|
||||
|
||||
---
|
||||
|
||||
## Border Radius System
|
||||
|
||||
### Radius Scales
|
||||
|
||||
```css
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
```
|
||||
|
||||
### Common Values
|
||||
|
||||
- **Sharp** (Neo-brutalism): `--radius: 0px`
|
||||
- **Subtle** (Modern): `--radius: 0.375rem` (6px)
|
||||
- **Rounded** (Friendly): `--radius: 0.625rem` (10px)
|
||||
- **Pill** (Buttons): `--radius: 9999px`
|
||||
|
||||
---
|
||||
|
||||
## Usage Guidelines
|
||||
|
||||
### When to Use Each Theme
|
||||
|
||||
**Neo-Brutalism**:
|
||||
- ✅ Creative/artistic projects
|
||||
- ✅ Retro/vintage aesthetics
|
||||
- ✅ Bold, statement-making designs
|
||||
- ❌ Enterprise/corporate applications
|
||||
- ❌ Accessibility-critical interfaces
|
||||
|
||||
**Modern Dark Mode**:
|
||||
- ✅ SaaS applications
|
||||
- ✅ Developer tools
|
||||
- ✅ Professional dashboards
|
||||
- ✅ Enterprise applications
|
||||
- ✅ Accessibility-critical interfaces
|
||||
|
||||
### Customization
|
||||
|
||||
1. Start with a base theme template
|
||||
2. Adjust primary/accent colors for brand
|
||||
3. Modify radius for desired feel
|
||||
4. Adjust shadows for depth preference
|
||||
5. Test contrast ratios for accessibility
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
✅ **Use CSS custom properties** for all theme values
|
||||
✅ **Test in light and dark modes** if applicable
|
||||
✅ **Validate color contrast** (WCAG AA minimum)
|
||||
✅ **Use semantic color names** (--primary, not --blue)
|
||||
✅ **Load fonts from Google Fonts** for reliability
|
||||
✅ **Apply consistent spacing** using the spacing scale
|
||||
✅ **Test responsive behavior** at all breakpoints
|
||||
|
||||
❌ **Don't hardcode colors** in components
|
||||
❌ **Don't use generic blue** (#007bff) without reason
|
||||
❌ **Don't mix color formats** (stick to OKLCH)
|
||||
❌ **Don't skip contrast testing**
|
||||
❌ **Don't use too many font families** (2-3 max)
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- [OKLCH Color Picker](https://oklch.com/)
|
||||
- [Google Fonts](https://fonts.google.com/)
|
||||
- [WCAG Contrast Checker](https://webaim.org/resources/contrastchecker/)
|
||||
- [Tailwind CSS Colors](https://tailwindcss.com/docs/customizing-colors)
|
||||
@@ -0,0 +1,54 @@
|
||||
<!-- Context: ui/scroll-linked-animations | Priority: critical | Version: 1.0 | Updated: 2026-02-15 -->
|
||||
|
||||
# Concept: Scroll-Linked Animations
|
||||
|
||||
**Purpose**: Sync image sequences to scroll position for cinematic product reveals
|
||||
|
||||
**Last Updated**: 2026-01-07
|
||||
|
||||
---
|
||||
|
||||
## Core Idea
|
||||
|
||||
Map scroll position to video frames. As user scrolls, play through image sequence like a scrubbing timeline. Creates illusion of 3D animation controlled by scroll.
|
||||
|
||||
**Formula**: `scrollProgress (0→1) → frameIndex (0→N) → canvas.drawImage()`
|
||||
|
||||
---
|
||||
|
||||
## Essential Parts
|
||||
|
||||
1. **Image sequence** - 60-150 WebP frames from video/3D render
|
||||
2. **Sticky canvas** - Fixed HTML5 canvas, always visible while scrolling
|
||||
3. **Scroll tracker** - Framer Motion `useScroll` hook
|
||||
4. **Preloader** - Load all frames upfront (prevents flicker)
|
||||
5. **Background match** - Page BG = image BG (hides edges)
|
||||
|
||||
---
|
||||
|
||||
## Minimal Example
|
||||
|
||||
```tsx
|
||||
const { scrollYProgress } = useScroll({ target: containerRef })
|
||||
const frameIndex = useTransform(scrollYProgress, [0, 1], [0, 119])
|
||||
|
||||
useEffect(() => {
|
||||
ctx.drawImage(images[Math.round(frameIndex)], 0, 0)
|
||||
}, [frameIndex])
|
||||
```
|
||||
|
||||
**Why canvas?** 10x faster than swapping `<img src>`. DOM updates are slow.
|
||||
|
||||
---
|
||||
|
||||
## Related
|
||||
|
||||
- examples/scrollytelling-headphone.md - Full code
|
||||
- guides/building-scrollytelling-pages.md - Implementation
|
||||
- lookup/scroll-animation-prompts.md - Generate sequences
|
||||
|
||||
---
|
||||
|
||||
## Reference
|
||||
|
||||
[Apple AirPods Pro](https://www.apple.com/airpods-pro/) - Production example
|
||||
@@ -0,0 +1,265 @@
|
||||
<!-- Context: ui/scrollytelling-headphone | Priority: high | Version: 1.0 | Updated: 2026-02-15 -->
|
||||
|
||||
---
|
||||
description: "Full Next.js implementation of scroll-linked image sequence animation"
|
||||
---
|
||||
|
||||
# Example: Scrollytelling Headphone Animation
|
||||
|
||||
**Purpose**: Full Next.js implementation of scroll-linked image sequence animation
|
||||
|
||||
**Last Updated**: 2026-01-07
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Complete working example of "Zenith X" headphone scrollytelling page using Next.js 14, Framer Motion, and Canvas.
|
||||
|
||||
**Tech Stack**: Next.js 14 (App Router) + Framer Motion + Canvas + Tailwind CSS
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
app/
|
||||
├── page.tsx
|
||||
├── components/
|
||||
│ └── HeadphoneScroll.tsx
|
||||
└── globals.css
|
||||
public/
|
||||
└── frames/
|
||||
└── frame_0001.webp through frame_0120.webp
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 1. globals.css
|
||||
|
||||
```css
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
body {
|
||||
@apply bg-[#050505] text-white antialiased;
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. app/page.tsx
|
||||
|
||||
```tsx
|
||||
import HeadphoneScroll from './components/HeadphoneScroll'
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<main className="bg-[#050505]">
|
||||
<HeadphoneScroll />
|
||||
</main>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. components/HeadphoneScroll.tsx
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { motion, useScroll, useTransform } from 'framer-motion'
|
||||
|
||||
const FRAME_COUNT = 120
|
||||
|
||||
export default function HeadphoneScroll() {
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null)
|
||||
const [images, setImages] = useState<HTMLImageElement[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
// Track scroll progress (0 to 1)
|
||||
const { scrollYProgress } = useScroll({
|
||||
target: containerRef,
|
||||
offset: ['start start', 'end end']
|
||||
})
|
||||
|
||||
// Map scroll progress to frame index
|
||||
const frameIndex = useTransform(scrollYProgress, [0, 1], [0, FRAME_COUNT - 1])
|
||||
const [currentFrame, setCurrentFrame] = useState(0)
|
||||
|
||||
// Update current frame
|
||||
useEffect(() => {
|
||||
return frameIndex.on('change', (latest) => {
|
||||
setCurrentFrame(Math.round(latest))
|
||||
})
|
||||
}, [frameIndex])
|
||||
|
||||
// Preload all images
|
||||
useEffect(() => {
|
||||
const loadImages = async () => {
|
||||
const promises = Array.from({ length: FRAME_COUNT }, (_, i) => {
|
||||
return new Promise<HTMLImageElement>((resolve) => {
|
||||
const img = new Image()
|
||||
const frameNum = String(i + 1).padStart(4, '0')
|
||||
img.src = `/frames/frame_${frameNum}.webp`
|
||||
img.onload = () => resolve(img)
|
||||
})
|
||||
})
|
||||
|
||||
const loaded = await Promise.all(promises)
|
||||
setImages(loaded)
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
loadImages()
|
||||
}, [])
|
||||
|
||||
// Render current frame to canvas
|
||||
useEffect(() => {
|
||||
if (!canvasRef.current || !images.length) return
|
||||
|
||||
const canvas = canvasRef.current
|
||||
const ctx = canvas.getContext('2d')
|
||||
if (!ctx) return
|
||||
|
||||
const img = images[currentFrame]
|
||||
|
||||
// Set canvas size
|
||||
canvas.width = window.innerWidth
|
||||
canvas.height = window.innerHeight
|
||||
|
||||
// Clear and draw centered
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
||||
|
||||
const scale = Math.min(
|
||||
canvas.width / img.width,
|
||||
canvas.height / img.height
|
||||
)
|
||||
|
||||
const x = (canvas.width - img.width * scale) / 2
|
||||
const y = (canvas.height - img.height * scale) / 2
|
||||
|
||||
ctx.drawImage(img, x, y, img.width * scale, img.height * scale)
|
||||
}, [currentFrame, images])
|
||||
|
||||
// Text overlay opacity transforms
|
||||
const title = useTransform(scrollYProgress, [0, 0.1, 0.2], [1, 1, 0])
|
||||
const text1 = useTransform(scrollYProgress, [0.25, 0.3, 0.4], [0, 1, 0])
|
||||
const text2 = useTransform(scrollYProgress, [0.55, 0.6, 0.7], [0, 1, 0])
|
||||
const cta = useTransform(scrollYProgress, [0.85, 0.9, 1], [0, 1, 1])
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="fixed inset-0 flex items-center justify-center bg-[#050505]">
|
||||
<div className="h-12 w-12 animate-spin rounded-full border-4 border-white/20 border-t-white" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={containerRef} className="relative h-[400vh]">
|
||||
{/* Sticky Canvas */}
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
className="sticky top-0 h-screen w-full"
|
||||
style={{ willChange: 'transform' }}
|
||||
/>
|
||||
|
||||
{/* Text Overlays */}
|
||||
<motion.div
|
||||
style={{ opacity: title }}
|
||||
className="pointer-events-none fixed inset-0 flex items-center justify-center"
|
||||
>
|
||||
<div className="text-center">
|
||||
<h1 className="text-7xl font-bold tracking-tight text-white/90">
|
||||
Zenith X
|
||||
</h1>
|
||||
<p className="mt-4 text-xl text-white/60">Pure Sound.</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
style={{ opacity: text1 }}
|
||||
className="pointer-events-none fixed inset-y-0 left-20 flex items-center"
|
||||
>
|
||||
<p className="text-4xl font-bold tracking-tight text-white/90">
|
||||
Precision Engineering.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
style={{ opacity: text2 }}
|
||||
className="pointer-events-none fixed inset-y-0 right-20 flex items-center"
|
||||
>
|
||||
<p className="text-4xl font-bold tracking-tight text-white/90">
|
||||
Titanium Drivers.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
style={{ opacity: cta }}
|
||||
className="pointer-events-none fixed inset-0 flex items-center justify-center"
|
||||
>
|
||||
<div className="text-center">
|
||||
<h2 className="text-6xl font-bold tracking-tight text-white/90">
|
||||
Hear Everything.
|
||||
</h2>
|
||||
<button className="pointer-events-auto mt-8 rounded-full bg-white px-8 py-3 text-lg font-semibold text-black transition hover:bg-white/90">
|
||||
Pre-Order Now
|
||||
</button>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Key Implementation Details
|
||||
|
||||
**Line 15-18**: `useScroll` tracks scroll progress from container start to end
|
||||
**Line 21**: `useTransform` maps 0-1 scroll to 0-119 frame index
|
||||
**Line 32-46**: Preload all 120 frames using Promise.all
|
||||
**Line 49-70**: Draw current frame to canvas, scaled and centered
|
||||
**Line 73-76**: Text opacity transforms for fade in/out at specific scroll positions
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
1. Install dependencies: `bun --bun install framer-motion`
|
||||
2. Place 120 WebP frames in `/public/frames/`
|
||||
3. Copy code into respective files
|
||||
4. Run: `bun --bun run dev`
|
||||
|
||||
---
|
||||
|
||||
## Customization
|
||||
|
||||
**Change frame count**: Update `FRAME_COUNT` constant (line 7)
|
||||
**Adjust scroll length**: Change `h-[400vh]` to `h-[300vh]` or `h-[500vh]` (line 120)
|
||||
**Modify text timing**: Update transform ranges in lines 73-76
|
||||
**Change colors**: Update `bg-[#050505]` to match your image background
|
||||
|
||||
---
|
||||
|
||||
## Related
|
||||
|
||||
- concepts/scroll-linked-animations.md - Understanding the technique
|
||||
- guides/scrollytelling-setup.md - Getting started
|
||||
- lookup/scroll-animation-prompts.md - Generating image sequences
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- [Framer Motion Docs](https://www.framer.com/motion/)
|
||||
- [Next.js App Router](https://nextjs.org/docs/app)
|
||||
@@ -0,0 +1,273 @@
|
||||
<!-- Context: ui/building-scrollytelling-pages | Priority: high | Version: 1.0 | Updated: 2026-02-15 -->
|
||||
|
||||
---
|
||||
description: "Step-by-step implementation of scroll-linked image sequence animations"
|
||||
---
|
||||
|
||||
# Guide: Building Scrollytelling Pages
|
||||
|
||||
**Purpose**: Step-by-step implementation of scroll-linked image sequence animations
|
||||
|
||||
**Last Updated**: 2026-01-07
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Next.js 14+ project with App Router
|
||||
- Framer Motion installed (`bun --bun i framer-motion`)
|
||||
- Tailwind CSS configured
|
||||
- Image sequence ready (60-240 WebP frames)
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Generate Image Sequences
|
||||
|
||||
Use nano banana or AI image tools to create start/end frames, then generate interpolation:
|
||||
|
||||
**Start frame prompt**:
|
||||
```
|
||||
Ultra-premium product photography of [product] on matte black surface,
|
||||
minimalistic studio shoot, deep black background with subtle gradient,
|
||||
soft rim lighting, cinematic, high contrast, luxury aesthetic, sharp focus,
|
||||
no clutter, DSLR 85mm f/1.8, photorealistic
|
||||
```
|
||||
|
||||
**End frame prompt**:
|
||||
```
|
||||
Exploded technical diagram of same [product], every component separated
|
||||
and floating in alignment, against deep black studio background, visible
|
||||
internal structure, hyper-realistic, studio rim lighting, cinematic,
|
||||
high contrast, no labels, photorealistic
|
||||
```
|
||||
|
||||
**Generate video**: Use AI video tools (Runway, Pika) to interpolate between frames.
|
||||
|
||||
**Export frames**: Use ffmpeg or ezgif to split video into 120+ WebP images.
|
||||
|
||||
```bash
|
||||
ffmpeg -i animation.mp4 -vf fps=30 frame_%04d.webp
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Project Structure
|
||||
|
||||
```
|
||||
app/
|
||||
├── page.tsx # Main landing page
|
||||
├── components/
|
||||
│ └── HeadphoneScroll.tsx # Scroll animation component
|
||||
└── globals.css # Dark theme, Inter font
|
||||
public/
|
||||
└── frames/
|
||||
├── frame_0001.webp # 120+ frames
|
||||
├── frame_0002.webp
|
||||
└── ...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Setup globals.css
|
||||
|
||||
```css
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
body {
|
||||
@apply bg-[#050505] text-white antialiased;
|
||||
font-family: 'Inter', -apple-system, sans-serif;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Create Scroll Component
|
||||
|
||||
**Key patterns**:
|
||||
- Container with `h-[400vh]` for long scroll
|
||||
- Canvas with `sticky top-0` stays fixed
|
||||
- `useScroll` tracks scroll progress (0-1)
|
||||
- `useTransform` maps progress to frame index
|
||||
- `useEffect` preloads all images
|
||||
|
||||
**Core logic**:
|
||||
```tsx
|
||||
const { scrollYProgress } = useScroll({ target: containerRef })
|
||||
const frameIndex = useTransform(scrollYProgress, [0, 1], [0, 119])
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 5: Implement Preloader
|
||||
|
||||
Always preload images before starting animation:
|
||||
|
||||
```tsx
|
||||
useEffect(() => {
|
||||
const loadImages = async () => {
|
||||
const promises = Array.from({ length: 120 }, (_, i) => {
|
||||
return new Promise((resolve) => {
|
||||
const img = new Image()
|
||||
img.src = `/frames/frame_${String(i + 1).padStart(4, '0')}.webp`
|
||||
img.onload = () => resolve(img)
|
||||
})
|
||||
})
|
||||
|
||||
const loaded = await Promise.all(promises)
|
||||
setImages(loaded)
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
loadImages()
|
||||
}, [])
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 6: Canvas Rendering
|
||||
|
||||
Draw current frame to canvas on every scroll update:
|
||||
|
||||
```tsx
|
||||
useEffect(() => {
|
||||
if (!canvasRef.current || !images.length) return
|
||||
|
||||
const canvas = canvasRef.current
|
||||
const ctx = canvas.getContext('2d')
|
||||
const img = images[Math.round(currentFrame)]
|
||||
|
||||
// Scale canvas to window
|
||||
canvas.width = window.innerWidth
|
||||
canvas.height = window.innerHeight
|
||||
|
||||
// Draw centered
|
||||
ctx.drawImage(img,
|
||||
(canvas.width - img.width) / 2,
|
||||
(canvas.height - img.height) / 2
|
||||
)
|
||||
}, [currentFrame, images])
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 7: Add Text Overlays
|
||||
|
||||
Fade text in/out at specific scroll positions:
|
||||
|
||||
```tsx
|
||||
<motion.div
|
||||
style={{
|
||||
opacity: useTransform(scrollYProgress,
|
||||
[0.25, 0.30, 0.35], // Fade in 25-30%, out 35%
|
||||
[0, 1, 0]
|
||||
)
|
||||
}}
|
||||
className="absolute left-20 text-4xl font-bold"
|
||||
>
|
||||
Precision Engineering.
|
||||
</motion.div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 8: Match Backgrounds
|
||||
|
||||
**CRITICAL**: Page background MUST match image background exactly.
|
||||
|
||||
1. Open first frame in image editor
|
||||
2. Use eyedropper tool on background (e.g., `#050505`)
|
||||
3. Set page background to exact same color in globals.css
|
||||
4. Test: Image edges should be invisible
|
||||
|
||||
---
|
||||
|
||||
## Step 9: Optimize Performance
|
||||
|
||||
```tsx
|
||||
// Add GPU hint
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
className="sticky top-0 h-screen w-full"
|
||||
style={{ willChange: 'transform' }}
|
||||
/>
|
||||
|
||||
// Throttle redraws on mobile
|
||||
useEffect(() => {
|
||||
let rafId
|
||||
const render = () => {
|
||||
// Draw logic here
|
||||
rafId = requestAnimationFrame(render)
|
||||
}
|
||||
render()
|
||||
return () => cancelAnimationFrame(rafId)
|
||||
}, [])
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 10: Add Loading State
|
||||
|
||||
Show spinner while frames load:
|
||||
|
||||
```tsx
|
||||
{loading && (
|
||||
<div className="fixed inset-0 flex items-center justify-center bg-[#050505]">
|
||||
<div className="animate-spin h-12 w-12 border-4 border-white/20 border-t-white rounded-full" />
|
||||
</div>
|
||||
)}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Common Issues & Fixes
|
||||
|
||||
### Images not loading
|
||||
- Check file paths match exactly (case-sensitive)
|
||||
- Verify all frames exist in `/public/frames/`
|
||||
- Open browser console for 404 errors
|
||||
|
||||
### Stuttering animation
|
||||
- Ensure all images preloaded before starting
|
||||
- Use WebP (not PNG/JPEG)
|
||||
- Check canvas size isn't too large
|
||||
|
||||
### Visible image edges
|
||||
- Background colors don't match exactly
|
||||
- Use eyedropper tool, not guessing
|
||||
- Check for gradients in image background
|
||||
|
||||
### Mobile performance
|
||||
- Reduce frame count (use every 2nd frame)
|
||||
- Debounce with requestAnimationFrame
|
||||
- Consider disabling on small screens
|
||||
|
||||
---
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] All frames load without 404s
|
||||
- [ ] Animation smooth from 0-100% scroll
|
||||
- [ ] Text fades in/out at correct positions
|
||||
- [ ] Background seamlessly blends with images
|
||||
- [ ] Loading spinner shows before animation
|
||||
- [ ] Works on mobile (or gracefully disabled)
|
||||
- [ ] No console errors
|
||||
|
||||
---
|
||||
|
||||
## Related
|
||||
|
||||
- concepts/scroll-linked-animations.md - Understanding the technique
|
||||
- examples/headphone-scrollytelling.md - Full code example
|
||||
- lookup/animation-image-prompts.md - Prompts for frame generation
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- [Next.js Image Optimization](https://nextjs.org/docs/app/building-your-application/optimizing/images)
|
||||
- [Framer Motion useScroll](https://www.framer.com/motion/use-scroll/)
|
||||
@@ -0,0 +1,215 @@
|
||||
<!-- Context: ui/scroll-animation-prompts | Priority: high | Version: 1.0 | Updated: 2026-02-15 -->
|
||||
|
||||
---
|
||||
description: "AI prompts for generating start/end frames and video sequences for scrollytelling"
|
||||
---
|
||||
|
||||
# Lookup: Scroll Animation Image Generation Prompts
|
||||
|
||||
**Purpose**: AI prompts for generating start/end frames and video sequences for scrollytelling
|
||||
|
||||
**Last Updated**: 2026-01-07
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Use these prompts with nano banana, Runway, Pika, or other AI tools to create image sequences for scroll animations.
|
||||
|
||||
**Workflow**: Start frame → End frame → Video interpolation → Frame extraction
|
||||
|
||||
---
|
||||
|
||||
## Start Frame Prompts
|
||||
|
||||
### Product Hero Shot
|
||||
|
||||
```
|
||||
Ultra-premium product photography of [PRODUCT NAME] placed on a matte black
|
||||
surface, minimalistic studio photoshoot. Deep black background (#050505) with
|
||||
subtle gradient falloff, soft rim lighting outlining edges, controlled
|
||||
reflections on smooth textures. Cinematic lighting, high contrast, luxury tech
|
||||
aesthetic, sharp focus, shallow depth of field. No clutter, no text, no logos
|
||||
emphasized. Shot with professional DSLR, 85mm lens, f/1.8, ultra-high
|
||||
resolution, photorealistic, Apple-level product shoot, dramatic mood, modern
|
||||
and elegant.
|
||||
```
|
||||
|
||||
**Variables**: Replace [PRODUCT NAME] with your product
|
||||
**Output**: Starting position, fully assembled, hero angle
|
||||
|
||||
---
|
||||
|
||||
### Variations by Product Type
|
||||
|
||||
| Product Type | Additional Details |
|
||||
|--------------|-------------------|
|
||||
| **Headphones** | "over-ear headphones with leather cushions and metal headband" |
|
||||
| **Smartphone** | "smartphone with edge-to-edge OLED display, aluminum frame" |
|
||||
| **Watch** | "luxury smartwatch with titanium case and sapphire crystal" |
|
||||
| **Laptop** | "thin laptop with aluminum unibody, open at 45 degrees" |
|
||||
| **Camera** | "mirrorless camera with prime lens attached, side profile" |
|
||||
|
||||
---
|
||||
|
||||
## End Frame Prompts
|
||||
|
||||
### Exploded Technical View
|
||||
|
||||
```
|
||||
Exploded technical diagram view of [PRODUCT NAME], every component precisely
|
||||
separated and floating in perfect alignment, suspended in mid-air against deep
|
||||
black studio background (#050505). Visible internal structure including
|
||||
[INTERNAL COMPONENTS], all parts evenly spaced showing assembly order.
|
||||
Hyper-realistic product visualization, ultra-sharp focus, studio rim lighting
|
||||
identical to hero shot, soft highlights tracing each component, controlled
|
||||
reflections on matte and metal surfaces. Cinematic lighting, high contrast,
|
||||
luxury engineering aesthetic, no labels, no annotations, no text.
|
||||
Photorealistic, ultra-high resolution, Apple-style industrial design render,
|
||||
dramatic and clean.
|
||||
```
|
||||
|
||||
**Variables**:
|
||||
- [PRODUCT NAME]: Your product
|
||||
- [INTERNAL COMPONENTS]: Specific parts to show
|
||||
|
||||
---
|
||||
|
||||
### Internal Components by Product
|
||||
|
||||
| Product | Internal Components Examples |
|
||||
|---------|------------------------------|
|
||||
| **Headphones** | "copper wiring, titanium drivers, magnets, circuit boards, padding layers, metal frame" |
|
||||
| **Smartphone** | "battery, logic board, cameras, OLED panel, antenna bands, frame" |
|
||||
| **Watch** | "watch movement, gears, battery, sensors, display, crown mechanism" |
|
||||
| **Laptop** | "keyboard assembly, trackpad, battery cells, logic board, cooling fans, display panel" |
|
||||
| **Camera** | "sensor, shutter mechanism, lens elements, mirror assembly, circuit boards" |
|
||||
|
||||
---
|
||||
|
||||
## Video Interpolation Prompts
|
||||
|
||||
### For Runway/Pika
|
||||
|
||||
```
|
||||
Smoothly transition from fully assembled [PRODUCT] to exploded view.
|
||||
Components separate slowly and precisely, maintaining perfect alignment.
|
||||
Camera stays locked, product rotates slightly clockwise. Cinematic motion,
|
||||
professional product animation, 4-5 seconds duration, 30fps.
|
||||
```
|
||||
|
||||
**Settings**:
|
||||
- Duration: 4-5 seconds
|
||||
- FPS: 30 (yields 120-150 frames)
|
||||
- Camera: Static or slow orbit
|
||||
- Motion: Smooth, controlled separation
|
||||
|
||||
---
|
||||
|
||||
## Frame Extraction
|
||||
|
||||
### Using ffmpeg
|
||||
|
||||
```bash
|
||||
# Extract as WebP (best for web)
|
||||
ffmpeg -i animation.mp4 -vf fps=30 frame_%04d.webp
|
||||
|
||||
# Extract as PNG (higher quality, larger)
|
||||
ffmpeg -i animation.mp4 -vf fps=30 frame_%04d.png
|
||||
|
||||
# Extract with quality control
|
||||
ffmpeg -i animation.mp4 -vf fps=30 -quality 90 frame_%04d.webp
|
||||
```
|
||||
|
||||
### Using ezgif.com
|
||||
|
||||
1. Upload MP4 video
|
||||
2. Choose "Video to GIF" → "Split to frames"
|
||||
3. Select WebP format
|
||||
4. Download all frames as ZIP
|
||||
5. Rename: `frame_0001.webp`, `frame_0002.webp`, etc.
|
||||
|
||||
---
|
||||
|
||||
## Background Color Matching
|
||||
|
||||
**CRITICAL**: Page background MUST match image background exactly
|
||||
|
||||
### Recommended Dark Backgrounds
|
||||
|
||||
| Color Code | Usage |
|
||||
|------------|-------|
|
||||
| `#050505` | Pure black with slight lift (recommended) |
|
||||
| `#0a0a0a` | Slightly lighter, less harsh |
|
||||
| `#000000` | True black (only if images are true black) |
|
||||
| `#1a1a1a` | Dark gray (for lighter renders) |
|
||||
|
||||
**Pro tip**: Use eyedropper tool on first frame background, use exact hex in CSS
|
||||
|
||||
---
|
||||
|
||||
## Alternative Animation Styles
|
||||
|
||||
### Rotation (360° spin)
|
||||
|
||||
**Start**: Front view
|
||||
**End**: Front view (after 360° rotation)
|
||||
**Prompt**: "Rotate product 360 degrees on turntable, maintain lighting"
|
||||
|
||||
### Zoom In (Feature reveal)
|
||||
|
||||
**Start**: Full product view
|
||||
**End**: Close-up of key feature
|
||||
**Prompt**: "Smooth camera push-in focusing on [FEATURE], maintain focus"
|
||||
|
||||
### Morph (Color/style change)
|
||||
|
||||
**Start**: Product in color A
|
||||
**End**: Product in color B
|
||||
**Prompt**: "Seamlessly morph product color from [A] to [B], maintain form"
|
||||
|
||||
---
|
||||
|
||||
## Quality Settings
|
||||
|
||||
### For High-End Results
|
||||
|
||||
- **Resolution**: 1920x1080 minimum (4K for high-DPI)
|
||||
- **Format**: WebP (compression) or PNG (quality)
|
||||
- **Frame count**: 90-150 frames (3-5 seconds at 30fps)
|
||||
- **Total size**: Target <50MB for all frames combined
|
||||
|
||||
### Optimization Tips
|
||||
|
||||
1. Use WebP format (70% smaller than PNG)
|
||||
2. Compress with quality 85-90
|
||||
3. Resize images to max 2000px width
|
||||
4. Use consistent aspect ratio (16:9 or 1:1)
|
||||
|
||||
---
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] Background color matches exactly (no visible edges)
|
||||
- [ ] All frames same dimensions
|
||||
- [ ] Smooth motion (no jumps between frames)
|
||||
- [ ] Consistent lighting across sequence
|
||||
- [ ] File names sequential (`frame_0001` to `frame_0120`)
|
||||
- [ ] Total file size reasonable (<50MB)
|
||||
|
||||
---
|
||||
|
||||
## Related
|
||||
|
||||
- concepts/scroll-linked-animations.md - Understanding the technique
|
||||
- examples/scrollytelling-headphone.md - Full implementation
|
||||
- guides/scrollytelling-setup.md - Setup instructions
|
||||
|
||||
---
|
||||
|
||||
## Tool References
|
||||
|
||||
- [Runway ML](https://runwayml.com) - AI video generation
|
||||
- [Pika Labs](https://pika.art) - AI video interpolation
|
||||
- [ezgif](https://ezgif.com/split-video) - Frame extraction
|
||||
- [FFmpeg](https://ffmpeg.org) - Video processing
|
||||
95
.opencode/context/ui/web/design/navigation.md
Normal file
95
.opencode/context/ui/web/design/navigation.md
Normal file
@@ -0,0 +1,95 @@
|
||||
<!-- Context: ui/navigation | Priority: critical | Version: 1.0 | Updated: 2026-02-15 -->
|
||||
|
||||
---
|
||||
description: "Advanced web UI patterns - scroll animations, visual effects, and interactive design"
|
||||
---
|
||||
|
||||
# Web Design Patterns
|
||||
|
||||
**Purpose**: Advanced web UI patterns - scroll animations, visual effects, and interactive design
|
||||
|
||||
**Last Updated**: 2026-01-31
|
||||
|
||||
---
|
||||
|
||||
## Quick Navigation
|
||||
|
||||
### Concepts
|
||||
| File | Description | Priority |
|
||||
|------|-------------|----------|
|
||||
| [navigation.md](concepts/navigation.md) | Concepts navigation | high |
|
||||
| [scroll-linked-animations.md](concepts/scroll-linked-animations.md) | Scroll-synced image sequences (scrollytelling) | high |
|
||||
|
||||
### Examples
|
||||
| File | Description | Priority |
|
||||
|------|-------------|----------|
|
||||
| [navigation.md](examples/navigation.md) | Examples navigation | high |
|
||||
| [scrollytelling-headphone.md](examples/scrollytelling-headphone.md) | Full Next.js scroll animation example | high |
|
||||
|
||||
### Guides
|
||||
| File | Description | Priority |
|
||||
|------|-------------|----------|
|
||||
| [navigation.md](guides/navigation.md) | Guides navigation | high |
|
||||
| [building-scrollytelling-pages.md](guides/building-scrollytelling-pages.md) | Complete implementation guide | high |
|
||||
| [premium-dark-ui-visual-reference.md](guides/premium-dark-ui-visual-reference.md) | Visual reference for premium dark UI | medium |
|
||||
|
||||
### Lookup
|
||||
| File | Description | Priority |
|
||||
|------|-------------|----------|
|
||||
| [navigation.md](lookup/navigation.md) | Lookup navigation | high |
|
||||
| [scroll-animation-prompts.md](lookup/scroll-animation-prompts.md) | AI prompts for generating animation sequences | medium |
|
||||
|
||||
### Errors
|
||||
| File | Description | Priority |
|
||||
|------|-------------|----------|
|
||||
| _(No error files yet)_ | | |
|
||||
|
||||
---
|
||||
|
||||
## Loading Strategy
|
||||
|
||||
**For scroll animation work**:
|
||||
1. Load `concepts/scroll-linked-animations.md` (understand technique)
|
||||
2. Load `lookup/scroll-animation-prompts.md` (generate image sequences)
|
||||
3. Load `examples/scrollytelling-headphone.md` (see full code)
|
||||
4. Reference `guides/building-scrollytelling-pages.md` (step-by-step)
|
||||
|
||||
**For premium dark UI design**:
|
||||
1. Load `guides/premium-dark-ui-visual-reference.md` (visual patterns and implementation)
|
||||
|
||||
---
|
||||
|
||||
## Scope
|
||||
|
||||
This subcategory covers:
|
||||
- ✅ Scroll-linked animations (scrollytelling)
|
||||
- ✅ Canvas-based rendering
|
||||
- ✅ Framer Motion patterns
|
||||
- ✅ Image sequence generation
|
||||
- ✅ Premium dark UI design system
|
||||
- ✅ Glassmorphism patterns
|
||||
- ⏳ CSS animations (future)
|
||||
- ⏳ SVG animations (future)
|
||||
- ⏳ WebGL effects (future)
|
||||
|
||||
---
|
||||
|
||||
## Related Categories
|
||||
|
||||
- `ui/web/` - Core web UI patterns (parent directory)
|
||||
- `ui/web/animation-patterns.md` - CSS animations and transitions
|
||||
- `development/` - General development patterns
|
||||
|
||||
---
|
||||
|
||||
## Used By
|
||||
|
||||
**Agents**: frontend-specialist, design-specialist, animation-expert
|
||||
|
||||
## Statistics
|
||||
- Concepts: 1 + navigation
|
||||
- Examples: 1 + navigation
|
||||
- Guides: 6 + navigation
|
||||
- Lookup: 1 + navigation
|
||||
- Errors: 0
|
||||
- **Total**: 13 files
|
||||
118
.opencode/context/ui/web/navigation.md
Normal file
118
.opencode/context/ui/web/navigation.md
Normal file
@@ -0,0 +1,118 @@
|
||||
<!-- Context: ui/navigation | Priority: critical | Version: 1.0 | Updated: 2026-02-15 -->
|
||||
|
||||
# Web UI Context
|
||||
|
||||
**Purpose**: Web-based UI patterns, animations, styling standards, and React component design
|
||||
|
||||
**Last Updated**: 2026-01-07
|
||||
|
||||
---
|
||||
|
||||
## Quick Navigation
|
||||
|
||||
### Core Files
|
||||
|
||||
| File | Description | Priority |
|
||||
|------|-------------|----------|
|
||||
| [animation-basics.md](animation-basics.md) | Animation fundamentals, timing, easing | high |
|
||||
| [animation-components.md](animation-components.md) | Button, card, modal, dropdown animations | high |
|
||||
| [animation-chat.md](animation-chat.md) | Chat UI and message animations | medium |
|
||||
| [animation-loading.md](animation-loading.md) | Skeleton, spinner, progress animations | medium |
|
||||
| [animation-forms.md](animation-forms.md) | Form input and validation animations | medium |
|
||||
| [animation-advanced.md](animation-advanced.md) | Recipes, best practices, accessibility | medium |
|
||||
| [ui-styling-standards.md](ui-styling-standards.md) | CSS frameworks, Tailwind patterns, styling best practices | high |
|
||||
| [react-patterns.md](react-patterns.md) | Modern React patterns, hooks, component design | high |
|
||||
| [design-systems.md](design-systems.md) | Design system principles and component libraries | medium |
|
||||
| [images-guide.md](images-guide.md) | Placeholder and responsive images | medium |
|
||||
| [icons-guide.md](icons-guide.md) | Icon systems (Lucide, Heroicons, FA) | medium |
|
||||
| [fonts-guide.md](fonts-guide.md) | Font loading and optimization | medium |
|
||||
| [cdn-resources.md](cdn-resources.md) | CDN libraries and resources | medium |
|
||||
|
||||
### Subcategories
|
||||
|
||||
| Subcategory | Description | Path |
|
||||
|-------------|-------------|------|
|
||||
| **design/** | Advanced design patterns (scrollytelling, effects) | [design/navigation.md](design/navigation.md) |
|
||||
|
||||
---
|
||||
|
||||
## Loading Strategy
|
||||
|
||||
### For general web UI work:
|
||||
1. Load `ui-styling-standards.md` (CSS frameworks, Tailwind)
|
||||
2. Load `react-patterns.md` (component patterns)
|
||||
3. Reference `animation-patterns.md` (if animations needed)
|
||||
|
||||
### For animation work:
|
||||
1. Load `animation-basics.md` (fundamentals, timing, easing)
|
||||
2. Load `animation-components.md` (UI component animations)
|
||||
3. Reference `animation-chat.md` for chat UI patterns
|
||||
4. Reference `animation-advanced.md` for recipes and accessibility
|
||||
|
||||
### For scroll animations:
|
||||
1. Navigate to `design/` subcategory
|
||||
2. Load scroll-linked animation guides
|
||||
|
||||
---
|
||||
|
||||
## Scope
|
||||
|
||||
This subcategory covers:
|
||||
- ✅ CSS animations and transitions
|
||||
- ✅ Tailwind CSS and utility-first styling
|
||||
- ✅ React component patterns and hooks
|
||||
- ✅ Design systems and component libraries
|
||||
- ✅ Icon libraries and web fonts
|
||||
- ✅ Scroll-linked animations (scrollytelling)
|
||||
- ✅ Canvas-based rendering
|
||||
- ✅ Framer Motion patterns
|
||||
|
||||
---
|
||||
|
||||
## File Summaries
|
||||
|
||||
### animation-basics.md, animation-components.md, animation-chat.md, animation-loading.md, animation-forms.md, animation-advanced.md
|
||||
CSS animations, micro-interactions, and UI transitions split into focused modules.
|
||||
|
||||
**Key topics**: Animation micro-syntax, 60fps performance, reduced motion, chat UI animations, component patterns
|
||||
|
||||
### ui-styling-standards.md
|
||||
CSS framework usage, Tailwind CSS patterns, responsive design, and styling best practices.
|
||||
|
||||
**Key topics**: Utility-first CSS, component styling, responsive breakpoints, dark mode
|
||||
|
||||
### react-patterns.md
|
||||
Modern React patterns including functional components, hooks, state management, and performance optimization.
|
||||
|
||||
**Key topics**: Custom hooks, context API, code splitting, memoization
|
||||
|
||||
### design-systems.md
|
||||
Design system principles, component libraries, and maintaining consistency across applications.
|
||||
|
||||
**Key topics**: Design tokens, component APIs, documentation, versioning
|
||||
|
||||
### images-guide.md, icons-guide.md, fonts-guide.md, cdn-resources.md
|
||||
Managing design assets in web applications - split into focused guides.
|
||||
|
||||
**Key topics**: Placeholder images, icon libraries (Lucide, Heroicons), web fonts, CDN resources
|
||||
|
||||
---
|
||||
|
||||
## Related Categories
|
||||
|
||||
- `ui/terminal/` - Terminal UI patterns
|
||||
- `development/` - General development patterns
|
||||
- `product/` - Product design and UX strategy
|
||||
|
||||
---
|
||||
|
||||
## Used By
|
||||
|
||||
**Agents**: frontend-specialist, design-specialist, ui-developer, react-developer, animation-expert
|
||||
|
||||
---
|
||||
|
||||
## Statistics
|
||||
- Core files: 8
|
||||
- Subcategories: 1 (design/)
|
||||
- **Total context files**: 8 + design subcategory
|
||||
330
.opencode/context/ui/web/react-patterns.md
Normal file
330
.opencode/context/ui/web/react-patterns.md
Normal file
@@ -0,0 +1,330 @@
|
||||
<!-- Context: ui/react-patterns | Priority: low | Version: 1.0 | Updated: 2026-02-15 -->
|
||||
|
||||
# React Patterns & Best Practices
|
||||
|
||||
**Category**: development
|
||||
**Purpose**: Modern React patterns, hooks usage, and component design principles
|
||||
**Used by**: frontend-specialist
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
This guide covers modern React patterns using functional components, hooks, and best practices for building scalable React applications.
|
||||
|
||||
## Component Patterns
|
||||
|
||||
### 1. Functional Components with Hooks
|
||||
|
||||
**Always use functional components**:
|
||||
```jsx
|
||||
// Good
|
||||
function UserProfile({ userId }) {
|
||||
const [user, setUser] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetchUser(userId).then(setUser);
|
||||
}, [userId]);
|
||||
|
||||
return <div>{user?.name}</div>;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Custom Hooks for Reusable Logic
|
||||
|
||||
**Extract common logic into custom hooks**:
|
||||
```jsx
|
||||
// Custom hook
|
||||
function useUser(userId) {
|
||||
const [user, setUser] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
fetchUser(userId)
|
||||
.then(setUser)
|
||||
.catch(setError)
|
||||
.finally(() => setLoading(false));
|
||||
}, [userId]);
|
||||
|
||||
return { user, loading, error };
|
||||
}
|
||||
|
||||
// Usage
|
||||
function UserProfile({ userId }) {
|
||||
const { user, loading, error } = useUser(userId);
|
||||
|
||||
if (loading) return <Spinner />;
|
||||
if (error) return <Error message={error.message} />;
|
||||
return <div>{user.name}</div>;
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Composition Over Props Drilling
|
||||
|
||||
**Use composition to avoid prop drilling**:
|
||||
```jsx
|
||||
// Bad - Props drilling
|
||||
function App() {
|
||||
const [theme, setTheme] = useState('light');
|
||||
return <Layout theme={theme} setTheme={setTheme} />;
|
||||
}
|
||||
|
||||
// Good - Composition with Context
|
||||
const ThemeContext = createContext();
|
||||
|
||||
function App() {
|
||||
const [theme, setTheme] = useState('light');
|
||||
return (
|
||||
<ThemeContext.Provider value={{ theme, setTheme }}>
|
||||
<Layout />
|
||||
</ThemeContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
function Layout() {
|
||||
const { theme } = useContext(ThemeContext);
|
||||
return <div className={theme}>...</div>;
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Compound Components
|
||||
|
||||
**For complex, related components**:
|
||||
```jsx
|
||||
function Tabs({ children }) {
|
||||
const [activeTab, setActiveTab] = useState(0);
|
||||
|
||||
return (
|
||||
<TabsContext.Provider value={{ activeTab, setActiveTab }}>
|
||||
{children}
|
||||
</TabsContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
Tabs.List = function TabsList({ children }) {
|
||||
return <div className="tabs-list">{children}</div>;
|
||||
};
|
||||
|
||||
Tabs.Tab = function Tab({ index, children }) {
|
||||
const { activeTab, setActiveTab } = useContext(TabsContext);
|
||||
return (
|
||||
<button
|
||||
className={activeTab === index ? 'active' : ''}
|
||||
onClick={() => setActiveTab(index)}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
Tabs.Panel = function TabPanel({ index, children }) {
|
||||
const { activeTab } = useContext(TabsContext);
|
||||
return activeTab === index ? <div>{children}</div> : null;
|
||||
};
|
||||
|
||||
// Usage
|
||||
<Tabs>
|
||||
<Tabs.List>
|
||||
<Tabs.Tab index={0}>Tab 1</Tabs.Tab>
|
||||
<Tabs.Tab index={1}>Tab 2</Tabs.Tab>
|
||||
</Tabs.List>
|
||||
<Tabs.Panel index={0}>Content 1</Tabs.Panel>
|
||||
<Tabs.Panel index={1}>Content 2</Tabs.Panel>
|
||||
</Tabs>
|
||||
```
|
||||
|
||||
## Hooks Best Practices
|
||||
|
||||
### 1. useEffect Dependencies
|
||||
|
||||
**Always specify dependencies correctly**:
|
||||
```jsx
|
||||
// Bad - Missing dependencies
|
||||
useEffect(() => {
|
||||
fetchData(userId);
|
||||
}, []);
|
||||
|
||||
// Good - Correct dependencies
|
||||
useEffect(() => {
|
||||
fetchData(userId);
|
||||
}, [userId]);
|
||||
|
||||
// Good - Stable function reference
|
||||
const fetchData = useCallback((id) => {
|
||||
api.getUser(id).then(setUser);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
fetchData(userId);
|
||||
}, [userId, fetchData]);
|
||||
```
|
||||
|
||||
### 2. useMemo for Expensive Calculations
|
||||
|
||||
**Memoize expensive computations**:
|
||||
```jsx
|
||||
function DataTable({ data, filters }) {
|
||||
const filteredData = useMemo(() => {
|
||||
return data.filter(item =>
|
||||
filters.every(filter => filter(item))
|
||||
);
|
||||
}, [data, filters]);
|
||||
|
||||
return <Table data={filteredData} />;
|
||||
}
|
||||
```
|
||||
|
||||
### 3. useCallback for Stable References
|
||||
|
||||
**Prevent unnecessary re-renders**:
|
||||
```jsx
|
||||
function Parent() {
|
||||
const [count, setCount] = useState(0);
|
||||
|
||||
// Bad - New function on every render
|
||||
const handleClick = () => setCount(c => c + 1);
|
||||
|
||||
// Good - Stable function reference
|
||||
const handleClick = useCallback(() => {
|
||||
setCount(c => c + 1);
|
||||
}, []);
|
||||
|
||||
return <Child onClick={handleClick} />;
|
||||
}
|
||||
|
||||
const Child = memo(function Child({ onClick }) {
|
||||
return <button onClick={onClick}>Click</button>;
|
||||
});
|
||||
```
|
||||
|
||||
## State Management Patterns
|
||||
|
||||
### 1. Local State First
|
||||
|
||||
**Start with local state, lift when needed**:
|
||||
```jsx
|
||||
// Local state
|
||||
function Counter() {
|
||||
const [count, setCount] = useState(0);
|
||||
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
|
||||
}
|
||||
|
||||
// Lifted state when shared
|
||||
function App() {
|
||||
const [count, setCount] = useState(0);
|
||||
return (
|
||||
<>
|
||||
<Counter count={count} setCount={setCount} />
|
||||
<Display count={count} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 2. useReducer for Complex State
|
||||
|
||||
**Use reducer for related state updates**:
|
||||
```jsx
|
||||
const initialState = { count: 0, step: 1 };
|
||||
|
||||
function reducer(state, action) {
|
||||
switch (action.type) {
|
||||
case 'increment':
|
||||
return { ...state, count: state.count + state.step };
|
||||
case 'decrement':
|
||||
return { ...state, count: state.count - state.step };
|
||||
case 'setStep':
|
||||
return { ...state, step: action.payload };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
function Counter() {
|
||||
const [state, dispatch] = useReducer(reducer, initialState);
|
||||
|
||||
return (
|
||||
<>
|
||||
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
|
||||
<span>{state.count}</span>
|
||||
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Performance Optimization
|
||||
|
||||
### 1. Code Splitting
|
||||
|
||||
**Lazy load routes and heavy components**:
|
||||
```jsx
|
||||
import { lazy, Suspense } from 'react';
|
||||
|
||||
const Dashboard = lazy(() => import('./Dashboard'));
|
||||
const Settings = lazy(() => import('./Settings'));
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<Suspense fallback={<Loading />}>
|
||||
<Routes>
|
||||
<Route path="/dashboard" element={<Dashboard />} />
|
||||
<Route path="/settings" element={<Settings />} />
|
||||
</Routes>
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Virtualization for Long Lists
|
||||
|
||||
**Use virtualization for large datasets**:
|
||||
```jsx
|
||||
import { FixedSizeList } from 'react-window';
|
||||
|
||||
function VirtualList({ items }) {
|
||||
const Row = ({ index, style }) => (
|
||||
<div style={style}>{items[index].name}</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<FixedSizeList
|
||||
height={600}
|
||||
itemCount={items.length}
|
||||
itemSize={50}
|
||||
width="100%"
|
||||
>
|
||||
{Row}
|
||||
</FixedSizeList>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Keep components small and focused** - Single responsibility principle
|
||||
2. **Use TypeScript** - Type safety prevents bugs and improves DX
|
||||
3. **Colocate related code** - Keep components, styles, and tests together
|
||||
4. **Use meaningful prop names** - Clear, descriptive names improve readability
|
||||
5. **Avoid inline functions in JSX** - Extract to named functions or useCallback
|
||||
6. **Use fragments** - Avoid unnecessary wrapper divs
|
||||
7. **Handle loading and error states** - Always show feedback to users
|
||||
8. **Test components** - Use React Testing Library for user-centric tests
|
||||
|
||||
## Anti-Patterns
|
||||
|
||||
- ❌ **Prop drilling** - Use context or composition instead
|
||||
- ❌ **Massive components** - Break down into smaller, focused components
|
||||
- ❌ **Mutating state directly** - Always use setState or dispatch
|
||||
- ❌ **Using index as key** - Use stable, unique identifiers
|
||||
- ❌ **Unnecessary useEffect** - Derive state when possible
|
||||
- ❌ **Ignoring ESLint warnings** - React hooks rules prevent bugs
|
||||
- ❌ **Not memoizing context values** - Causes unnecessary re-renders
|
||||
|
||||
## References
|
||||
|
||||
- React Documentation (react.dev)
|
||||
- React Patterns by Kent C. Dodds
|
||||
- Epic React by Kent C. Dodds
|
||||
552
.opencode/context/ui/web/ui-styling-standards.md
Normal file
552
.opencode/context/ui/web/ui-styling-standards.md
Normal file
@@ -0,0 +1,552 @@
|
||||
<!-- Context: development/ui-styling-standards | Priority: high | Version: 1.0 | Updated: 2025-12-09 -->
|
||||
# UI Styling Standards
|
||||
|
||||
## Overview
|
||||
|
||||
Standards and conventions for CSS frameworks, responsive design, and styling best practices in frontend development.
|
||||
|
||||
## Quick Reference
|
||||
|
||||
**Framework**: Tailwind CSS + Flowbite (default)
|
||||
**Approach**: Mobile-first responsive
|
||||
**Format**: Utility-first CSS
|
||||
**Specificity**: Use `!important` for overrides when needed
|
||||
|
||||
---
|
||||
|
||||
## CSS Framework Conventions
|
||||
|
||||
### Tailwind CSS
|
||||
|
||||
**Loading Method** (Preferred):
|
||||
|
||||
```html
|
||||
<!-- ✅ Use CDN script tag -->
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
```
|
||||
|
||||
**Avoid**:
|
||||
|
||||
```html
|
||||
<!-- ❌ Don't use stylesheet link -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
|
||||
```
|
||||
|
||||
**Why**: Script tag allows for JIT compilation and configuration
|
||||
|
||||
### Flowbite
|
||||
|
||||
**Loading Method**:
|
||||
|
||||
```html
|
||||
<!-- Flowbite CSS -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/flowbite@2.0.0/dist/flowbite.min.css" rel="stylesheet">
|
||||
|
||||
<!-- Flowbite JS -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/flowbite@2.0.0/dist/flowbite.min.js"></script>
|
||||
```
|
||||
|
||||
**Usage**: Flowbite is the default component library unless user specifies otherwise
|
||||
|
||||
**Components Available**:
|
||||
- Buttons, forms, modals
|
||||
- Navigation, dropdowns, tabs
|
||||
- Cards, alerts, badges
|
||||
- Tables, pagination
|
||||
- Tooltips, popovers
|
||||
|
||||
---
|
||||
|
||||
## Responsive Design Requirements
|
||||
|
||||
### Mobile-First Approach
|
||||
|
||||
**Rule**: ALL designs MUST be responsive
|
||||
|
||||
**Breakpoints** (Tailwind defaults):
|
||||
|
||||
```css
|
||||
/* Mobile first - base styles apply to mobile */
|
||||
.element { }
|
||||
|
||||
/* Small devices (640px and up) */
|
||||
@media (min-width: 640px) { } /* sm: */
|
||||
|
||||
/* Medium devices (768px and up) */
|
||||
@media (min-width: 768px) { } /* md: */
|
||||
|
||||
/* Large devices (1024px and up) */
|
||||
@media (min-width: 1024px) { } /* lg: */
|
||||
|
||||
/* Extra large devices (1280px and up) */
|
||||
@media (min-width: 1280px) { } /* xl: */
|
||||
|
||||
/* 2XL devices (1536px and up) */
|
||||
@media (min-width: 1536px) { } /* 2xl: */
|
||||
```
|
||||
|
||||
**Tailwind Syntax**:
|
||||
|
||||
```html
|
||||
<!-- Mobile: stack, Desktop: side-by-side -->
|
||||
<div class="flex flex-col md:flex-row">
|
||||
<div class="w-full md:w-1/2">Left</div>
|
||||
<div class="w-full md:w-1/2">Right</div>
|
||||
</div>
|
||||
|
||||
<!-- Mobile: full width, Desktop: constrained -->
|
||||
<div class="w-full lg:w-3/4 xl:w-1/2 mx-auto">
|
||||
Content
|
||||
</div>
|
||||
```
|
||||
|
||||
### Testing Requirements
|
||||
|
||||
✅ Test at minimum breakpoints: 375px, 768px, 1024px, 1440px
|
||||
✅ Verify touch targets (min 44x44px)
|
||||
✅ Check text readability at all sizes
|
||||
✅ Ensure images scale properly
|
||||
✅ Test navigation on mobile
|
||||
|
||||
---
|
||||
|
||||
## Color Palette Guidelines
|
||||
|
||||
### Avoid Bootstrap Blue
|
||||
|
||||
**Rule**: NEVER use generic Bootstrap blue (#007bff) unless explicitly requested
|
||||
|
||||
**Why**: Overused, lacks personality, feels dated
|
||||
|
||||
**Alternatives**:
|
||||
|
||||
```css
|
||||
/* Instead of Bootstrap blue */
|
||||
--bootstrap-blue: #007bff; /* ❌ Avoid */
|
||||
|
||||
/* Use contextual colors */
|
||||
--primary: oklch(0.6489 0.2370 26.9728); /* Vibrant orange */
|
||||
--accent: oklch(0.5635 0.2408 260.8178); /* Rich purple */
|
||||
--info: oklch(0.6200 0.1900 260); /* Modern blue */
|
||||
--success: oklch(0.7323 0.2492 142.4953); /* Fresh green */
|
||||
```
|
||||
|
||||
### Color Usage Rules
|
||||
|
||||
1. **Semantic naming**: Use `--primary`, `--accent`, not `--blue`, `--red`
|
||||
2. **Brand alignment**: Choose colors that match project personality
|
||||
3. **Contrast testing**: Ensure WCAG AA compliance (4.5:1 minimum)
|
||||
4. **Consistency**: Use theme variables throughout
|
||||
|
||||
---
|
||||
|
||||
## Background/Foreground Contrast
|
||||
|
||||
### Contrast Rule
|
||||
|
||||
**When designing components or posters**:
|
||||
|
||||
- **Light component** → Dark background
|
||||
- **Dark component** → Light background
|
||||
|
||||
**Why**: Ensures visibility and creates visual hierarchy
|
||||
|
||||
**Examples**:
|
||||
|
||||
```html
|
||||
<!-- Light card on dark background -->
|
||||
<div class="bg-gray-900 p-8">
|
||||
<div class="bg-white text-gray-900 p-6 rounded-lg">
|
||||
Light card content
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Dark card on light background -->
|
||||
<div class="bg-gray-50 p-8">
|
||||
<div class="bg-gray-900 text-white p-6 rounded-lg">
|
||||
Dark card content
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Component-Specific Rules
|
||||
|
||||
**Posters/Hero Sections**:
|
||||
- Use high contrast for readability
|
||||
- Consider overlay gradients for text on images
|
||||
- Test with actual content
|
||||
|
||||
**Cards/Panels**:
|
||||
- Subtle elevation with shadows
|
||||
- Clear boundary between card and background
|
||||
- Consistent padding
|
||||
|
||||
---
|
||||
|
||||
## CSS Specificity & Overrides
|
||||
|
||||
### Using !important
|
||||
|
||||
**Rule**: Use `!important` for properties that might be overwritten by Tailwind or Flowbite
|
||||
|
||||
**Common Cases**:
|
||||
|
||||
```css
|
||||
/* Typography overrides */
|
||||
h1 {
|
||||
font-size: 2.5rem !important;
|
||||
font-weight: 700 !important;
|
||||
line-height: 1.2 !important;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Inter', sans-serif !important;
|
||||
color: var(--foreground) !important;
|
||||
}
|
||||
|
||||
/* Component overrides */
|
||||
.custom-button {
|
||||
background-color: var(--primary) !important;
|
||||
border-radius: var(--radius) !important;
|
||||
}
|
||||
```
|
||||
|
||||
**When NOT to use**:
|
||||
|
||||
```css
|
||||
/* ❌ Don't use for everything */
|
||||
.element {
|
||||
margin: 1rem !important;
|
||||
padding: 1rem !important;
|
||||
display: flex !important;
|
||||
}
|
||||
|
||||
/* ✅ Use Tailwind utilities instead */
|
||||
<div class="m-4 p-4 flex">
|
||||
```
|
||||
|
||||
### Specificity Best Practices
|
||||
|
||||
1. **Prefer utility classes** over custom CSS
|
||||
2. **Use !important sparingly** - only for framework overrides
|
||||
3. **Scope custom styles** to avoid conflicts
|
||||
4. **Use CSS custom properties** for theming
|
||||
|
||||
---
|
||||
|
||||
## Layout Patterns
|
||||
|
||||
### Flexbox (Preferred for 1D layouts)
|
||||
|
||||
```html
|
||||
<!-- Horizontal layout -->
|
||||
<div class="flex items-center gap-4">
|
||||
<div>Item 1</div>
|
||||
<div>Item 2</div>
|
||||
</div>
|
||||
|
||||
<!-- Vertical layout -->
|
||||
<div class="flex flex-col gap-4">
|
||||
<div>Item 1</div>
|
||||
<div>Item 2</div>
|
||||
</div>
|
||||
|
||||
<!-- Centered content -->
|
||||
<div class="flex items-center justify-center min-h-screen">
|
||||
<div>Centered content</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Grid (Preferred for 2D layouts)
|
||||
|
||||
```html
|
||||
<!-- Responsive grid -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<div>Card 1</div>
|
||||
<div>Card 2</div>
|
||||
<div>Card 3</div>
|
||||
</div>
|
||||
|
||||
<!-- Dashboard layout -->
|
||||
<div class="grid grid-cols-12 gap-4">
|
||||
<aside class="col-span-12 lg:col-span-3">Sidebar</aside>
|
||||
<main class="col-span-12 lg:col-span-9">Content</main>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Container Patterns
|
||||
|
||||
```html
|
||||
<!-- Centered container with max width -->
|
||||
<div class="container mx-auto px-4 max-w-7xl">
|
||||
Content
|
||||
</div>
|
||||
|
||||
<!-- Full-width section with contained content -->
|
||||
<section class="w-full bg-gray-50">
|
||||
<div class="container mx-auto px-4 py-12 max-w-6xl">
|
||||
Content
|
||||
</div>
|
||||
</section>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Typography Standards
|
||||
|
||||
### Hierarchy
|
||||
|
||||
```html
|
||||
<!-- Heading scale -->
|
||||
<h1 class="text-4xl md:text-5xl lg:text-6xl font-bold">Main Heading</h1>
|
||||
<h2 class="text-3xl md:text-4xl font-semibold">Section Heading</h2>
|
||||
<h3 class="text-2xl md:text-3xl font-semibold">Subsection</h3>
|
||||
<h4 class="text-xl md:text-2xl font-medium">Minor Heading</h4>
|
||||
|
||||
<!-- Body text -->
|
||||
<p class="text-base md:text-lg leading-relaxed">Body text</p>
|
||||
<p class="text-sm text-gray-600">Secondary text</p>
|
||||
<p class="text-xs text-gray-500">Caption text</p>
|
||||
```
|
||||
|
||||
### Font Loading
|
||||
|
||||
**Always use Google Fonts**:
|
||||
|
||||
```html
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
```
|
||||
|
||||
**Apply in CSS**:
|
||||
|
||||
```css
|
||||
body {
|
||||
font-family: 'Inter', sans-serif !important;
|
||||
}
|
||||
```
|
||||
|
||||
### Readability
|
||||
|
||||
- **Line length**: 60-80 characters optimal
|
||||
- **Line height**: 1.5-1.75 for body text
|
||||
- **Font size**: Minimum 16px for body text
|
||||
- **Contrast**: 4.5:1 minimum for normal text
|
||||
|
||||
---
|
||||
|
||||
## Component Styling Patterns
|
||||
|
||||
### Buttons
|
||||
|
||||
```html
|
||||
<!-- Primary button -->
|
||||
<button class="bg-primary text-primary-foreground px-6 py-3 rounded-lg font-medium hover:opacity-90 transition-opacity">
|
||||
Primary Action
|
||||
</button>
|
||||
|
||||
<!-- Secondary button -->
|
||||
<button class="bg-secondary text-secondary-foreground px-6 py-3 rounded-lg font-medium hover:bg-secondary/80 transition-colors">
|
||||
Secondary Action
|
||||
</button>
|
||||
|
||||
<!-- Outline button -->
|
||||
<button class="border-2 border-primary text-primary px-6 py-3 rounded-lg font-medium hover:bg-primary hover:text-primary-foreground transition-all">
|
||||
Outline Action
|
||||
</button>
|
||||
```
|
||||
|
||||
### Cards
|
||||
|
||||
```html
|
||||
<!-- Basic card -->
|
||||
<div class="bg-card text-card-foreground rounded-lg shadow-md p-6">
|
||||
<h3 class="text-xl font-semibold mb-2">Card Title</h3>
|
||||
<p class="text-muted-foreground">Card content</p>
|
||||
</div>
|
||||
|
||||
<!-- Interactive card -->
|
||||
<div class="bg-card text-card-foreground rounded-lg shadow-md p-6 hover:shadow-lg transition-shadow cursor-pointer">
|
||||
<h3 class="text-xl font-semibold mb-2">Interactive Card</h3>
|
||||
<p class="text-muted-foreground">Hover for effect</p>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Forms
|
||||
|
||||
```html
|
||||
<!-- Input field -->
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-medium">Email</label>
|
||||
<input
|
||||
type="email"
|
||||
class="w-full px-4 py-2 border border-input rounded-lg focus:ring-2 focus:ring-ring focus:border-transparent transition-all"
|
||||
placeholder="you@example.com"
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- Textarea -->
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-medium">Message</label>
|
||||
<textarea
|
||||
class="w-full px-4 py-2 border border-input rounded-lg focus:ring-2 focus:ring-ring focus:border-transparent transition-all resize-none"
|
||||
rows="4"
|
||||
placeholder="Your message..."
|
||||
></textarea>
|
||||
</div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Accessibility Standards
|
||||
|
||||
### ARIA Labels
|
||||
|
||||
```html
|
||||
<!-- Button with icon -->
|
||||
<button aria-label="Close dialog">
|
||||
<svg>...</svg>
|
||||
</button>
|
||||
|
||||
<!-- Navigation -->
|
||||
<nav aria-label="Main navigation">
|
||||
<ul>...</ul>
|
||||
</nav>
|
||||
```
|
||||
|
||||
### Semantic HTML
|
||||
|
||||
```html
|
||||
<!-- ✅ Use semantic elements -->
|
||||
<header>...</header>
|
||||
<nav>...</nav>
|
||||
<main>...</main>
|
||||
<article>...</article>
|
||||
<aside>...</aside>
|
||||
<footer>...</footer>
|
||||
|
||||
<!-- ❌ Avoid div soup -->
|
||||
<div class="header">...</div>
|
||||
<div class="nav">...</div>
|
||||
<div class="main">...</div>
|
||||
```
|
||||
|
||||
### Focus States
|
||||
|
||||
```css
|
||||
/* Always provide visible focus states */
|
||||
button:focus-visible {
|
||||
outline: 2px solid var(--ring);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* Tailwind utility */
|
||||
<button class="focus:ring-2 focus:ring-ring focus:ring-offset-2">
|
||||
Button
|
||||
</button>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Performance Optimization
|
||||
|
||||
### CSS Loading
|
||||
|
||||
```html
|
||||
<!-- Preconnect to font sources -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
|
||||
<!-- Preload critical fonts -->
|
||||
<link rel="preload" href="/fonts/inter.woff2" as="font" type="font/woff2" crossorigin>
|
||||
```
|
||||
|
||||
### Image Optimization
|
||||
|
||||
```html
|
||||
<!-- Responsive images -->
|
||||
<img
|
||||
src="image-800.jpg"
|
||||
srcset="image-400.jpg 400w, image-800.jpg 800w, image-1200.jpg 1200w"
|
||||
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
|
||||
alt="Description"
|
||||
loading="lazy"
|
||||
>
|
||||
```
|
||||
|
||||
### Critical CSS
|
||||
|
||||
```html
|
||||
<!-- Inline critical CSS -->
|
||||
<style>
|
||||
/* Above-the-fold styles */
|
||||
body { margin: 0; font-family: system-ui; }
|
||||
.hero { min-height: 100vh; }
|
||||
</style>
|
||||
|
||||
<!-- Load full CSS async -->
|
||||
<link rel="stylesheet" href="styles.css" media="print" onload="this.media='all'">
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Do's ✅
|
||||
|
||||
- Use Tailwind utility classes for rapid development
|
||||
- Load Tailwind via script tag for JIT compilation
|
||||
- Use Flowbite as default component library
|
||||
- Ensure all designs are mobile-first responsive
|
||||
- Test at multiple breakpoints
|
||||
- Use semantic HTML elements
|
||||
- Provide ARIA labels for interactive elements
|
||||
- Use CSS custom properties for theming
|
||||
- Apply `!important` for framework overrides
|
||||
- Ensure proper color contrast (WCAG AA)
|
||||
|
||||
### Don'ts ❌
|
||||
|
||||
- Don't use Bootstrap blue without explicit request
|
||||
- Don't load Tailwind as a stylesheet
|
||||
- Don't skip responsive design
|
||||
- Don't use div soup (use semantic HTML)
|
||||
- Don't forget focus states
|
||||
- Don't hardcode colors (use theme variables)
|
||||
- Don't skip accessibility testing
|
||||
- Don't use tiny touch targets (<44px)
|
||||
- Don't mix color formats
|
||||
- Don't over-use `!important`
|
||||
|
||||
---
|
||||
|
||||
## Framework Alternatives
|
||||
|
||||
If user requests a different framework:
|
||||
|
||||
**Bootstrap**:
|
||||
```html
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
```
|
||||
|
||||
**Bulma**:
|
||||
```html
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">
|
||||
```
|
||||
|
||||
**Foundation**:
|
||||
```html
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/foundation-sites@6.7.5/dist/css/foundation.min.css">
|
||||
<script src="https://cdn.jsdelivr.net/npm/foundation-sites@6.7.5/dist/js/foundation.min.js"></script>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- [Tailwind CSS Documentation](https://tailwindcss.com/docs)
|
||||
- [Flowbite Components](https://flowbite.com/docs/getting-started/introduction/)
|
||||
- [WCAG Guidelines](https://www.w3.org/WAI/WCAG21/quickref/)
|
||||
- [MDN Web Accessibility](https://developer.mozilla.org/en-US/docs/Web/Accessibility)
|
||||
Reference in New Issue
Block a user