devtronic 1.2.2 → 1.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +154 -149
- package/package.json +1 -1
- package/templates/addons/auto-devtronic/agents/failure-analyst.md +156 -0
- package/templates/addons/auto-devtronic/agents/issue-parser.md +145 -0
- package/templates/addons/auto-devtronic/agents/quality-runner.md +85 -0
- package/templates/addons/auto-devtronic/manifest.json +16 -0
- package/templates/addons/auto-devtronic/skills/auto-devtronic/SKILL.md +611 -0
- package/templates/addons/design-best-practices/manifest.json +28 -0
- package/templates/addons/design-best-practices/reference/color-and-contrast.md +146 -0
- package/templates/addons/design-best-practices/reference/interaction-design.md +208 -0
- package/templates/addons/design-best-practices/reference/motion-design.md +167 -0
- package/templates/addons/design-best-practices/reference/responsive-design.md +180 -0
- package/templates/addons/design-best-practices/reference/spatial-design.md +161 -0
- package/templates/addons/design-best-practices/reference/typography.md +136 -0
- package/templates/addons/design-best-practices/reference/ux-writing.md +190 -0
- package/templates/addons/design-best-practices/rules/design-quality.md +53 -0
- package/templates/addons/design-best-practices/skills/design-harden/SKILL.md +142 -0
- package/templates/addons/design-best-practices/skills/design-init/SKILL.md +95 -0
- package/templates/addons/design-best-practices/skills/design-refine/SKILL.md +124 -0
- package/templates/addons/design-best-practices/skills/design-review/SKILL.md +107 -0
- package/templates/addons/design-best-practices/skills/design-system/SKILL.md +125 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Color & Contrast Reference
|
|
2
|
+
|
|
3
|
+
Guide to building functional, accessible color palettes for frontend interfaces.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## OKLCH Color Space
|
|
8
|
+
|
|
9
|
+
OKLCH (Oklab Lightness, Chroma, Hue) provides perceptually uniform color manipulation:
|
|
10
|
+
|
|
11
|
+
```css
|
|
12
|
+
/* oklch(lightness chroma hue) */
|
|
13
|
+
--accent: oklch(0.65 0.2 250); /* Vivid blue */
|
|
14
|
+
--accent-light: oklch(0.85 0.1 250); /* Same hue, lighter */
|
|
15
|
+
--accent-dark: oklch(0.45 0.2 250); /* Same hue, darker */
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
**Why OKLCH over HSL:**
|
|
19
|
+
- Perceptually uniform lightness (50% L in HSL varies wildly across hues)
|
|
20
|
+
- Easier to create harmonious palettes
|
|
21
|
+
- Better for dark mode transformations (adjust L only)
|
|
22
|
+
- Chroma channel controls saturation more predictably
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Palette Construction
|
|
27
|
+
|
|
28
|
+
### Step 1: Neutrals First
|
|
29
|
+
|
|
30
|
+
Build the neutral palette before any color:
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
Near-white: oklch(0.98 0.005 [warm/cool hue])
|
|
34
|
+
Light gray: oklch(0.92 0.005 [same hue])
|
|
35
|
+
Mid gray: oklch(0.70 0.005 [same hue])
|
|
36
|
+
Dark gray: oklch(0.40 0.01 [same hue])
|
|
37
|
+
Near-black: oklch(0.15 0.01 [same hue])
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Slightly warm (hue ~80) or cool (hue ~250) — never pure neutral gray.
|
|
41
|
+
|
|
42
|
+
### Step 2: Functional Colors
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
Error: oklch(0.55 0.2 25) /* Red family */
|
|
46
|
+
Warning: oklch(0.70 0.15 80) /* Amber family */
|
|
47
|
+
Success: oklch(0.60 0.15 150) /* Green family */
|
|
48
|
+
Info: oklch(0.60 0.15 240) /* Blue family */
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Step 3: Accent (One Strong Color)
|
|
52
|
+
|
|
53
|
+
- Pick one accent color for primary actions and emphasis
|
|
54
|
+
- Generate 3-5 shades (lighter for backgrounds, darker for text)
|
|
55
|
+
- One intense color moment is stronger than five
|
|
56
|
+
|
|
57
|
+
### The 60-30-10 Rule
|
|
58
|
+
|
|
59
|
+
- **60%** — Dominant (background, surfaces)
|
|
60
|
+
- **30%** — Secondary (cards, text, supporting elements)
|
|
61
|
+
- **10%** — Accent (CTAs, active states, highlights)
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## WCAG Contrast Requirements
|
|
66
|
+
|
|
67
|
+
### Minimum Ratios
|
|
68
|
+
|
|
69
|
+
| Text Size | Level AA | Level AAA |
|
|
70
|
+
|-----------|----------|-----------|
|
|
71
|
+
| Body text (<18px, <14px bold) | 4.5:1 | 7:1 |
|
|
72
|
+
| Large text (≥18px, ≥14px bold) | 3:1 | 4.5:1 |
|
|
73
|
+
| UI components & graphics | 3:1 | — |
|
|
74
|
+
|
|
75
|
+
### Testing
|
|
76
|
+
|
|
77
|
+
```css
|
|
78
|
+
/* Check in DevTools: Elements → Computed → color */
|
|
79
|
+
/* Or use: contrast-ratio.com, WebAIM contrast checker */
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Common traps:**
|
|
83
|
+
- Light gray text on white (#999 on #fff = 2.8:1 — fails)
|
|
84
|
+
- Colored text on colored background (check every combination)
|
|
85
|
+
- Placeholder text (often too light)
|
|
86
|
+
- Disabled states (still need 3:1 for the border/icon)
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Dark Mode
|
|
91
|
+
|
|
92
|
+
### Strategy: Don't Invert — Remap
|
|
93
|
+
|
|
94
|
+
```css
|
|
95
|
+
/* Light mode */
|
|
96
|
+
--bg: oklch(0.98 0.005 80);
|
|
97
|
+
--text: oklch(0.15 0.01 80);
|
|
98
|
+
--surface: oklch(0.95 0.005 80);
|
|
99
|
+
|
|
100
|
+
/* Dark mode — not simple inversion */
|
|
101
|
+
--bg: oklch(0.15 0.01 250); /* Slightly cool */
|
|
102
|
+
--text: oklch(0.92 0.005 250); /* Not pure white */
|
|
103
|
+
--surface: oklch(0.20 0.01 250); /* Subtle elevation */
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**Rules:**
|
|
107
|
+
- Dark mode backgrounds should never be pure black (#000)
|
|
108
|
+
- Reduce chroma slightly for dark mode (vivid colors are harsher on dark)
|
|
109
|
+
- Use elevation (lighter surfaces) instead of shadows for depth
|
|
110
|
+
- Accent colors may need lightness adjustment to maintain contrast
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Color Accessibility
|
|
115
|
+
|
|
116
|
+
- Never use color alone to convey meaning (add icons, text, patterns)
|
|
117
|
+
- Provide sufficient contrast between adjacent colors
|
|
118
|
+
- Test with color blindness simulators (protanopia, deuteranopia, tritanopia)
|
|
119
|
+
- Link text must be distinguishable from surrounding text (not just color — add underline)
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## Practical Palette Template
|
|
124
|
+
|
|
125
|
+
```css
|
|
126
|
+
:root {
|
|
127
|
+
/* Neutrals */
|
|
128
|
+
--gray-50: oklch(0.98 0.005 80);
|
|
129
|
+
--gray-100: oklch(0.95 0.005 80);
|
|
130
|
+
--gray-200: oklch(0.90 0.005 80);
|
|
131
|
+
--gray-400: oklch(0.70 0.005 80);
|
|
132
|
+
--gray-600: oklch(0.50 0.01 80);
|
|
133
|
+
--gray-800: oklch(0.25 0.01 80);
|
|
134
|
+
--gray-950: oklch(0.13 0.01 80);
|
|
135
|
+
|
|
136
|
+
/* Accent */
|
|
137
|
+
--accent-100: oklch(0.92 0.05 250);
|
|
138
|
+
--accent-500: oklch(0.60 0.20 250);
|
|
139
|
+
--accent-900: oklch(0.30 0.10 250);
|
|
140
|
+
|
|
141
|
+
/* Semantic */
|
|
142
|
+
--color-error: oklch(0.55 0.20 25);
|
|
143
|
+
--color-warning: oklch(0.70 0.15 80);
|
|
144
|
+
--color-success: oklch(0.60 0.15 150);
|
|
145
|
+
}
|
|
146
|
+
```
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# Interaction Design Reference
|
|
2
|
+
|
|
3
|
+
Guide to interactive states, focus management, forms, and keyboard navigation.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## The 8 Interactive States
|
|
8
|
+
|
|
9
|
+
Every interactive element should define these states:
|
|
10
|
+
|
|
11
|
+
| State | Description | Visual Treatment |
|
|
12
|
+
|-------|-------------|-----------------|
|
|
13
|
+
| **Default** | Base appearance | Standard styling |
|
|
14
|
+
| **Hover** | Mouse over (desktop) | Subtle background change, cursor: pointer |
|
|
15
|
+
| **Focus** | Keyboard focused | Visible focus ring (2px+ offset) |
|
|
16
|
+
| **Active** | Being pressed | Slight scale-down or color shift |
|
|
17
|
+
| **Disabled** | Not available | Reduced opacity (0.5), cursor: not-allowed |
|
|
18
|
+
| **Loading** | Processing action | Spinner or skeleton, disable re-click |
|
|
19
|
+
| **Error** | Invalid state | Red border/text, error message |
|
|
20
|
+
| **Success** | Completed action | Green check, confirmation message |
|
|
21
|
+
|
|
22
|
+
### Implementation
|
|
23
|
+
|
|
24
|
+
```css
|
|
25
|
+
.button {
|
|
26
|
+
/* Default */
|
|
27
|
+
background: var(--accent-500);
|
|
28
|
+
color: white;
|
|
29
|
+
cursor: pointer;
|
|
30
|
+
transition: background 150ms ease-out;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.button:hover { background: var(--accent-600); }
|
|
34
|
+
|
|
35
|
+
.button:focus-visible {
|
|
36
|
+
outline: 2px solid var(--accent-500);
|
|
37
|
+
outline-offset: 2px;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.button:active { transform: scale(0.98); }
|
|
41
|
+
|
|
42
|
+
.button:disabled {
|
|
43
|
+
opacity: 0.5;
|
|
44
|
+
cursor: not-allowed;
|
|
45
|
+
pointer-events: none;
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Focus Management
|
|
52
|
+
|
|
53
|
+
### Focus Rings
|
|
54
|
+
|
|
55
|
+
```css
|
|
56
|
+
/* Global focus style */
|
|
57
|
+
:focus-visible {
|
|
58
|
+
outline: 2px solid var(--accent-500);
|
|
59
|
+
outline-offset: 2px;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/* Remove default outline only when using focus-visible */
|
|
63
|
+
:focus:not(:focus-visible) {
|
|
64
|
+
outline: none;
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**Rules:**
|
|
69
|
+
- Focus rings must be visible on all backgrounds
|
|
70
|
+
- Use `outline` not `box-shadow` (outlines respect `outline-offset` and don't affect layout)
|
|
71
|
+
- Minimum 2px width, contrasting color
|
|
72
|
+
- `focus-visible` only shows for keyboard navigation, not mouse clicks
|
|
73
|
+
|
|
74
|
+
### Focus Trapping (Modals)
|
|
75
|
+
|
|
76
|
+
```javascript
|
|
77
|
+
// When modal opens:
|
|
78
|
+
// 1. Move focus to first focusable element
|
|
79
|
+
// 2. Trap Tab/Shift+Tab within modal
|
|
80
|
+
// 3. Close on Escape
|
|
81
|
+
// 4. Return focus to trigger element on close
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Focus Restoration
|
|
85
|
+
|
|
86
|
+
After closing a modal, dropdown, or sidebar, return focus to the element that triggered it.
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Form Patterns
|
|
91
|
+
|
|
92
|
+
### Labels
|
|
93
|
+
|
|
94
|
+
- Every input needs a visible `<label>` (not just placeholder)
|
|
95
|
+
- Labels above inputs (not beside — better for mobile and scanning)
|
|
96
|
+
- Required fields: use `*` after label text + `aria-required="true"`
|
|
97
|
+
|
|
98
|
+
### Validation
|
|
99
|
+
|
|
100
|
+
```css
|
|
101
|
+
/* Show errors on blur, not on type */
|
|
102
|
+
.input:not(:focus):invalid {
|
|
103
|
+
border-color: var(--color-error);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.error-message {
|
|
107
|
+
color: var(--color-error);
|
|
108
|
+
font-size: 0.875rem;
|
|
109
|
+
margin-top: 4px;
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**Rules:**
|
|
114
|
+
- Validate on blur (not on every keystroke)
|
|
115
|
+
- Show error messages below the field, not in alerts/toasts
|
|
116
|
+
- Don't clear the input on error — let users correct
|
|
117
|
+
- Use `aria-describedby` to associate error messages with inputs
|
|
118
|
+
- Provide positive feedback for complex fields (password strength)
|
|
119
|
+
|
|
120
|
+
### Input Sizing
|
|
121
|
+
|
|
122
|
+
- Text inputs: minimum width to fit expected content
|
|
123
|
+
- Select/dropdown: wide enough for longest option
|
|
124
|
+
- Textarea: show at least 3 lines by default
|
|
125
|
+
- Touch targets: 44x44px minimum (48px recommended)
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Keyboard Navigation
|
|
130
|
+
|
|
131
|
+
### Essential Patterns
|
|
132
|
+
|
|
133
|
+
| Key | Action |
|
|
134
|
+
|-----|--------|
|
|
135
|
+
| Tab | Move to next focusable element |
|
|
136
|
+
| Shift + Tab | Move to previous focusable element |
|
|
137
|
+
| Enter / Space | Activate focused element |
|
|
138
|
+
| Escape | Close overlay, cancel action |
|
|
139
|
+
| Arrow keys | Navigate within components (tabs, menus, radio groups) |
|
|
140
|
+
|
|
141
|
+
### Tab Order
|
|
142
|
+
|
|
143
|
+
- Follow visual reading order (top-left to bottom-right for LTR)
|
|
144
|
+
- Don't use `tabindex > 0` — rearranging tab order confuses users
|
|
145
|
+
- Use `tabindex="-1"` for programmatically focusable elements
|
|
146
|
+
- Use `tabindex="0"` to make non-interactive elements focusable (sparingly)
|
|
147
|
+
|
|
148
|
+
### Skip Links
|
|
149
|
+
|
|
150
|
+
```html
|
|
151
|
+
<a href="#main-content" class="skip-link">Skip to main content</a>
|
|
152
|
+
|
|
153
|
+
<style>
|
|
154
|
+
.skip-link {
|
|
155
|
+
position: absolute;
|
|
156
|
+
left: -9999px;
|
|
157
|
+
}
|
|
158
|
+
.skip-link:focus {
|
|
159
|
+
position: fixed;
|
|
160
|
+
top: 8px;
|
|
161
|
+
left: 8px;
|
|
162
|
+
z-index: 999;
|
|
163
|
+
}
|
|
164
|
+
</style>
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Loading States
|
|
170
|
+
|
|
171
|
+
### Button Loading
|
|
172
|
+
|
|
173
|
+
```html
|
|
174
|
+
<button disabled aria-busy="true">
|
|
175
|
+
<span class="spinner" aria-hidden="true"></span>
|
|
176
|
+
Saving...
|
|
177
|
+
</button>
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
- Disable the button to prevent double-clicks
|
|
181
|
+
- Show a spinner inside the button (not replacing it)
|
|
182
|
+
- Update button text to reflect action ("Save" → "Saving...")
|
|
183
|
+
- Use `aria-busy="true"` for screen readers
|
|
184
|
+
|
|
185
|
+
### Page/Section Loading
|
|
186
|
+
|
|
187
|
+
1. **Skeleton screen** — preferred for known content layouts
|
|
188
|
+
2. **Spinner** — for unknown content or short waits (<2s)
|
|
189
|
+
3. **Progress bar** — for file uploads, multi-step processes
|
|
190
|
+
4. **Optimistic UI** — show expected result immediately, rollback on failure
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## Click/Touch Targets
|
|
195
|
+
|
|
196
|
+
- Minimum 44x44px (WCAG 2.5.8)
|
|
197
|
+
- Recommended 48x48px for primary actions
|
|
198
|
+
- Minimum 8px gap between adjacent targets
|
|
199
|
+
- Extend hit area with padding, not just visual size:
|
|
200
|
+
|
|
201
|
+
```css
|
|
202
|
+
.icon-button {
|
|
203
|
+
/* Visual size: 24px icon */
|
|
204
|
+
/* Hit area: 44px with padding */
|
|
205
|
+
padding: 10px;
|
|
206
|
+
margin: -10px; /* Prevent layout shift */
|
|
207
|
+
}
|
|
208
|
+
```
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# Motion Design Reference
|
|
2
|
+
|
|
3
|
+
Guide to animation, transitions, and motion in frontend interfaces.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Duration Rules
|
|
8
|
+
|
|
9
|
+
### The 100 / 300 / 500 Rule
|
|
10
|
+
|
|
11
|
+
| Duration | Use Case | Example |
|
|
12
|
+
|----------|----------|---------|
|
|
13
|
+
| 100-150ms | Micro-interactions | Button press, toggle, hover state |
|
|
14
|
+
| 200-300ms | Standard transitions | Panel open, tab switch, fade in |
|
|
15
|
+
| 400-500ms | Complex animations | Page transition, modal entrance, list reorder |
|
|
16
|
+
|
|
17
|
+
**Rules:**
|
|
18
|
+
- Never exceed 500ms for UI transitions (feels sluggish)
|
|
19
|
+
- Entrances can be slightly slower than exits (300ms in, 200ms out)
|
|
20
|
+
- Content-shifting animations need to be fast (≤200ms) to avoid feeling broken
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Easing Curves
|
|
25
|
+
|
|
26
|
+
### Standard Curves
|
|
27
|
+
|
|
28
|
+
```css
|
|
29
|
+
/* Entering the screen — starts fast, decelerates */
|
|
30
|
+
--ease-out: cubic-bezier(0.0, 0.0, 0.2, 1.0);
|
|
31
|
+
|
|
32
|
+
/* Leaving the screen — starts slow, accelerates */
|
|
33
|
+
--ease-in: cubic-bezier(0.4, 0.0, 1.0, 1.0);
|
|
34
|
+
|
|
35
|
+
/* Moving on screen — accelerates then decelerates */
|
|
36
|
+
--ease-in-out: cubic-bezier(0.4, 0.0, 0.2, 1.0);
|
|
37
|
+
|
|
38
|
+
/* Playful spring effect */
|
|
39
|
+
--ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1.0);
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### When to Use Each
|
|
43
|
+
|
|
44
|
+
| Curve | Use When |
|
|
45
|
+
|-------|----------|
|
|
46
|
+
| ease-out | Elements appearing, expanding, entering view |
|
|
47
|
+
| ease-in | Elements disappearing, collapsing, leaving view |
|
|
48
|
+
| ease-in-out | Elements moving position on screen |
|
|
49
|
+
| spring | Toggles, switches, playful micro-interactions |
|
|
50
|
+
| linear | Progress bars, continuous animations, opacity fades |
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Reduced Motion
|
|
55
|
+
|
|
56
|
+
**Always respect `prefers-reduced-motion`:**
|
|
57
|
+
|
|
58
|
+
```css
|
|
59
|
+
/* Default: full animation */
|
|
60
|
+
.element {
|
|
61
|
+
transition: transform 300ms var(--ease-out), opacity 300ms var(--ease-out);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* Reduced motion: instant or minimal */
|
|
65
|
+
@media (prefers-reduced-motion: reduce) {
|
|
66
|
+
.element {
|
|
67
|
+
transition: opacity 100ms linear;
|
|
68
|
+
/* Remove transform animations, keep opacity */
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/* Disable all non-essential animations */
|
|
72
|
+
*,
|
|
73
|
+
*::before,
|
|
74
|
+
*::after {
|
|
75
|
+
animation-duration: 0.01ms !important;
|
|
76
|
+
animation-iteration-count: 1 !important;
|
|
77
|
+
transition-duration: 0.01ms !important;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**What to keep in reduced motion:**
|
|
83
|
+
- Opacity transitions (shorter duration)
|
|
84
|
+
- Color changes
|
|
85
|
+
- Essential state indicators
|
|
86
|
+
|
|
87
|
+
**What to remove:**
|
|
88
|
+
- Transform animations (slide, scale, rotate)
|
|
89
|
+
- Parallax effects
|
|
90
|
+
- Auto-playing animations
|
|
91
|
+
- Decorative motion
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Perceived Performance
|
|
96
|
+
|
|
97
|
+
Use motion to make loading feel faster:
|
|
98
|
+
|
|
99
|
+
### Skeleton Screens
|
|
100
|
+
|
|
101
|
+
```css
|
|
102
|
+
.skeleton {
|
|
103
|
+
background: linear-gradient(
|
|
104
|
+
90deg,
|
|
105
|
+
oklch(0.92 0 0) 0%,
|
|
106
|
+
oklch(0.96 0 0) 50%,
|
|
107
|
+
oklch(0.92 0 0) 100%
|
|
108
|
+
);
|
|
109
|
+
background-size: 200% 100%;
|
|
110
|
+
animation: shimmer 1.5s ease-in-out infinite;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
@keyframes shimmer {
|
|
114
|
+
0% { background-position: 200% 0; }
|
|
115
|
+
100% { background-position: -200% 0; }
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Progressive Loading
|
|
120
|
+
|
|
121
|
+
1. Show skeleton layout immediately
|
|
122
|
+
2. Fade in critical content first (text, primary actions)
|
|
123
|
+
3. Load images/media progressively
|
|
124
|
+
4. Animate list items in with staggered delay (50ms between items, max 5 items)
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Animation Patterns
|
|
129
|
+
|
|
130
|
+
### Enter / Exit
|
|
131
|
+
|
|
132
|
+
```css
|
|
133
|
+
/* Fade up enter */
|
|
134
|
+
@keyframes fadeUp {
|
|
135
|
+
from { opacity: 0; transform: translateY(8px); }
|
|
136
|
+
to { opacity: 1; transform: translateY(0); }
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/* Scale enter (modals, popovers) */
|
|
140
|
+
@keyframes scaleIn {
|
|
141
|
+
from { opacity: 0; transform: scale(0.95); }
|
|
142
|
+
to { opacity: 1; transform: scale(1); }
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Stagger
|
|
147
|
+
|
|
148
|
+
```css
|
|
149
|
+
.list-item {
|
|
150
|
+
animation: fadeUp 300ms var(--ease-out) both;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.list-item:nth-child(1) { animation-delay: 0ms; }
|
|
154
|
+
.list-item:nth-child(2) { animation-delay: 50ms; }
|
|
155
|
+
.list-item:nth-child(3) { animation-delay: 100ms; }
|
|
156
|
+
/* Cap at 5 items — don't stagger infinitely */
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Performance Guidelines
|
|
162
|
+
|
|
163
|
+
- Animate only `transform` and `opacity` (GPU-accelerated, no layout recalc)
|
|
164
|
+
- Avoid animating `width`, `height`, `top`, `left`, `margin`, `padding`
|
|
165
|
+
- Use `will-change` sparingly and only on elements about to animate
|
|
166
|
+
- Test on low-end devices — smooth on MacBook Pro ≠ smooth everywhere
|
|
167
|
+
- Prefer CSS transitions/animations over JavaScript when possible
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# Responsive Design Reference
|
|
2
|
+
|
|
3
|
+
Guide to mobile-first responsive design, breakpoints, and adaptive layouts.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Mobile-First Approach
|
|
8
|
+
|
|
9
|
+
Write base styles for mobile, then add complexity for larger screens:
|
|
10
|
+
|
|
11
|
+
```css
|
|
12
|
+
/* Base: mobile (320px+) */
|
|
13
|
+
.grid { display: flex; flex-direction: column; gap: 16px; }
|
|
14
|
+
|
|
15
|
+
/* Tablet (768px+) */
|
|
16
|
+
@media (min-width: 768px) {
|
|
17
|
+
.grid { flex-direction: row; flex-wrap: wrap; }
|
|
18
|
+
.grid > * { flex: 1 1 calc(50% - 8px); }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/* Desktop (1024px+) */
|
|
22
|
+
@media (min-width: 1024px) {
|
|
23
|
+
.grid > * { flex: 1 1 calc(33.333% - 11px); }
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**Why mobile-first:**
|
|
28
|
+
- Forces you to prioritize content
|
|
29
|
+
- Progressive enhancement is more robust than graceful degradation
|
|
30
|
+
- Mobile CSS is simpler (fewer overrides needed)
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Content-Driven Breakpoints
|
|
35
|
+
|
|
36
|
+
Don't use device-specific breakpoints. Break where the content breaks:
|
|
37
|
+
|
|
38
|
+
```css
|
|
39
|
+
/* Bad: targeting specific devices */
|
|
40
|
+
@media (min-width: 375px) { } /* iPhone */
|
|
41
|
+
@media (min-width: 414px) { } /* iPhone Plus */
|
|
42
|
+
|
|
43
|
+
/* Good: where content needs it */
|
|
44
|
+
@media (min-width: 40em) { } /* ~640px — content gets cramped */
|
|
45
|
+
@media (min-width: 52em) { } /* ~832px — space for 2 columns */
|
|
46
|
+
@media (min-width: 72em) { } /* ~1152px — space for sidebar */
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Recommended Breakpoint Scale
|
|
50
|
+
|
|
51
|
+
| Name | Width | Use Case |
|
|
52
|
+
|------|-------|----------|
|
|
53
|
+
| sm | 640px | Phone landscape, small tablet |
|
|
54
|
+
| md | 768px | Tablet portrait, two-column |
|
|
55
|
+
| lg | 1024px | Tablet landscape, small desktop |
|
|
56
|
+
| xl | 1280px | Desktop |
|
|
57
|
+
| 2xl | 1536px | Large desktop |
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Container Queries
|
|
62
|
+
|
|
63
|
+
Use container queries for component-level responsive design:
|
|
64
|
+
|
|
65
|
+
```css
|
|
66
|
+
/* Define the container */
|
|
67
|
+
.card-wrapper {
|
|
68
|
+
container-type: inline-size;
|
|
69
|
+
container-name: card;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* Respond to container size, not viewport */
|
|
73
|
+
@container card (min-width: 300px) {
|
|
74
|
+
.card { flex-direction: row; }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
@container card (max-width: 299px) {
|
|
78
|
+
.card { flex-direction: column; }
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**When to use container queries vs media queries:**
|
|
83
|
+
- **Container queries**: Reusable components that may be placed in different-width containers
|
|
84
|
+
- **Media queries**: Page-level layout decisions (columns, navigation mode)
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Input Detection
|
|
89
|
+
|
|
90
|
+
Adapt UI based on input method:
|
|
91
|
+
|
|
92
|
+
```css
|
|
93
|
+
/* Fine pointer (mouse) — smaller targets OK */
|
|
94
|
+
@media (pointer: fine) {
|
|
95
|
+
.button { padding: 8px 16px; }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/* Coarse pointer (touch) — larger targets */
|
|
99
|
+
@media (pointer: coarse) {
|
|
100
|
+
.button { padding: 12px 24px; min-height: 48px; }
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/* Hover capability */
|
|
104
|
+
@media (hover: hover) {
|
|
105
|
+
.card:hover { box-shadow: var(--shadow-md); }
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
@media (hover: none) {
|
|
109
|
+
/* Don't rely on hover states for mobile */
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Safe Areas
|
|
116
|
+
|
|
117
|
+
Handle device-specific safe areas (notch, home indicator):
|
|
118
|
+
|
|
119
|
+
```css
|
|
120
|
+
/* iOS safe area insets */
|
|
121
|
+
.app {
|
|
122
|
+
padding-top: env(safe-area-inset-top);
|
|
123
|
+
padding-bottom: env(safe-area-inset-bottom);
|
|
124
|
+
padding-left: env(safe-area-inset-left);
|
|
125
|
+
padding-right: env(safe-area-inset-right);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/* Fixed bottom navigation */
|
|
129
|
+
.bottom-nav {
|
|
130
|
+
padding-bottom: calc(16px + env(safe-area-inset-bottom));
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Responsive Images
|
|
137
|
+
|
|
138
|
+
```html
|
|
139
|
+
<!-- srcset for resolution switching -->
|
|
140
|
+
<img
|
|
141
|
+
src="image-400.jpg"
|
|
142
|
+
srcset="image-400.jpg 400w, image-800.jpg 800w, image-1200.jpg 1200w"
|
|
143
|
+
sizes="(min-width: 1024px) 33vw, (min-width: 768px) 50vw, 100vw"
|
|
144
|
+
alt="Description"
|
|
145
|
+
loading="lazy"
|
|
146
|
+
/>
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
```css
|
|
150
|
+
/* Responsive image defaults */
|
|
151
|
+
img {
|
|
152
|
+
max-width: 100%;
|
|
153
|
+
height: auto;
|
|
154
|
+
display: block;
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Responsive Typography
|
|
161
|
+
|
|
162
|
+
Use fluid type instead of breakpoint-based size changes:
|
|
163
|
+
|
|
164
|
+
```css
|
|
165
|
+
/* Better than @media jumps */
|
|
166
|
+
h1 { font-size: clamp(1.75rem, 4vw + 1rem, 3.5rem); }
|
|
167
|
+
body { font-size: clamp(1rem, 0.5vw + 0.875rem, 1.125rem); }
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Testing Checklist
|
|
173
|
+
|
|
174
|
+
- [ ] No horizontal scroll at any viewport width (320px minimum)
|
|
175
|
+
- [ ] Touch targets ≥ 44x44px on mobile
|
|
176
|
+
- [ ] Text readable without zooming on mobile
|
|
177
|
+
- [ ] Navigation accessible on all screen sizes
|
|
178
|
+
- [ ] Images scale appropriately
|
|
179
|
+
- [ ] Forms usable on mobile (proper input types, no tiny fields)
|
|
180
|
+
- [ ] Content priority matches viewport size (most important content visible first on mobile)
|