picasso-skill 1.0.0
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/LICENSE +21 -0
- package/README.md +124 -0
- package/SKILL.md +202 -0
- package/bin/install.mjs +81 -0
- package/package.json +36 -0
- package/references/anti-patterns.md +95 -0
- package/references/color-and-contrast.md +174 -0
- package/references/component-patterns.md +113 -0
- package/references/design-system.md +176 -0
- package/references/generative-art.md +54 -0
- package/references/interaction-design.md +162 -0
- package/references/motion-and-animation.md +260 -0
- package/references/react-patterns.md +216 -0
- package/references/responsive-design.md +118 -0
- package/references/sensory-design.md +125 -0
- package/references/spatial-design.md +176 -0
- package/references/typography.md +168 -0
- package/skills/picasso/SKILL.md +202 -0
- package/skills/picasso/references/anti-patterns.md +95 -0
- package/skills/picasso/references/color-and-contrast.md +174 -0
- package/skills/picasso/references/component-patterns.md +113 -0
- package/skills/picasso/references/design-system.md +176 -0
- package/skills/picasso/references/generative-art.md +54 -0
- package/skills/picasso/references/interaction-design.md +162 -0
- package/skills/picasso/references/motion-and-animation.md +260 -0
- package/skills/picasso/references/react-patterns.md +216 -0
- package/skills/picasso/references/responsive-design.md +118 -0
- package/skills/picasso/references/sensory-design.md +125 -0
- package/skills/picasso/references/spatial-design.md +176 -0
- package/skills/picasso/references/typography.md +168 -0
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
# Motion and Animation Reference
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
1. Principles
|
|
5
|
+
2. Easing Functions
|
|
6
|
+
3. Duration Guidelines
|
|
7
|
+
4. Staggered Reveals
|
|
8
|
+
5. Scroll-Triggered Animation
|
|
9
|
+
6. Text Morphing
|
|
10
|
+
7. Micro-Interactions
|
|
11
|
+
8. Reduced Motion
|
|
12
|
+
9. Common Mistakes
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 1. Principles
|
|
17
|
+
|
|
18
|
+
Motion serves three purposes: feedback (confirming an action), orientation (showing where something went), and delight (making the interface feel alive). If an animation does not serve one of these, remove it.
|
|
19
|
+
|
|
20
|
+
### Priority of Animation Investment
|
|
21
|
+
1. Page load choreography (highest impact, seen by everyone)
|
|
22
|
+
2. State transitions (tabs, modals, accordions)
|
|
23
|
+
3. Hover/focus states
|
|
24
|
+
4. Scroll-triggered reveals
|
|
25
|
+
5. Loading states and skeletons
|
|
26
|
+
6. Micro-interactions (button press effects, toggle animations)
|
|
27
|
+
|
|
28
|
+
Invest time in this order. A well-choreographed page load does more than fifty micro-interactions.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 2. Easing Functions
|
|
33
|
+
|
|
34
|
+
### Use These
|
|
35
|
+
```css
|
|
36
|
+
/* Standard ease-out: elements arriving */
|
|
37
|
+
--ease-out: cubic-bezier(0.16, 1, 0.3, 1);
|
|
38
|
+
|
|
39
|
+
/* Standard ease-in: elements departing */
|
|
40
|
+
--ease-in: cubic-bezier(0.55, 0.085, 0.68, 0.53);
|
|
41
|
+
|
|
42
|
+
/* Standard ease-in-out: elements transforming in place */
|
|
43
|
+
--ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
|
|
44
|
+
|
|
45
|
+
/* Spring-like (subtle): natural deceleration */
|
|
46
|
+
--ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Never Use
|
|
50
|
+
- `linear` for UI animations (looks mechanical)
|
|
51
|
+
- `ease` (the CSS default is mediocre)
|
|
52
|
+
- `bounce` / elastic easing (looks dated and gimmicky)
|
|
53
|
+
- Spring animations with visible oscillation (too playful for most UIs)
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## 3. Duration Guidelines
|
|
58
|
+
|
|
59
|
+
| Type | Duration | Why |
|
|
60
|
+
|---|---|---|
|
|
61
|
+
| Hover state change | 100-150ms | Must feel instant |
|
|
62
|
+
| Button press | 80-120ms | Tactile response |
|
|
63
|
+
| Tooltip appear | 150-200ms | Quick but not jarring |
|
|
64
|
+
| Fade in/out | 150-250ms | Smooth perception |
|
|
65
|
+
| Slide/expand | 200-350ms | Visible movement |
|
|
66
|
+
| Page transition | 300-500ms | Substantial change |
|
|
67
|
+
| Complex choreography | 400-800ms total | Entrance sequence |
|
|
68
|
+
|
|
69
|
+
Rule of thumb: if the user is waiting for it, it should be fast (under 200ms). If the user is watching it, it can be slower (200-500ms).
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## 4. Staggered Reveals
|
|
74
|
+
|
|
75
|
+
The most impactful animation pattern. Elements enter one after another with increasing delay.
|
|
76
|
+
|
|
77
|
+
### CSS-Only Pattern
|
|
78
|
+
```css
|
|
79
|
+
.reveal-item {
|
|
80
|
+
opacity: 0;
|
|
81
|
+
transform: translateY(12px);
|
|
82
|
+
animation: reveal 0.5s var(--ease-out) forwards;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
@keyframes reveal {
|
|
86
|
+
to {
|
|
87
|
+
opacity: 1;
|
|
88
|
+
transform: translateY(0);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.reveal-item:nth-child(1) { animation-delay: 0ms; }
|
|
93
|
+
.reveal-item:nth-child(2) { animation-delay: 60ms; }
|
|
94
|
+
.reveal-item:nth-child(3) { animation-delay: 120ms; }
|
|
95
|
+
.reveal-item:nth-child(4) { animation-delay: 180ms; }
|
|
96
|
+
.reveal-item:nth-child(5) { animation-delay: 240ms; }
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Key Parameters
|
|
100
|
+
- **Stagger interval**: 40-80ms between items (shorter for many items, longer for few)
|
|
101
|
+
- **Translate distance**: 8-16px (subtle is better)
|
|
102
|
+
- **Do not stagger more than 6-8 items**. After that, group them.
|
|
103
|
+
|
|
104
|
+
### React with Motion Library
|
|
105
|
+
```jsx
|
|
106
|
+
import { motion } from "framer-motion";
|
|
107
|
+
|
|
108
|
+
const container = {
|
|
109
|
+
hidden: {},
|
|
110
|
+
show: { transition: { staggerChildren: 0.06 } }
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const item = {
|
|
114
|
+
hidden: { opacity: 0, y: 12 },
|
|
115
|
+
show: { opacity: 1, y: 0, transition: { duration: 0.5, ease: [0.16, 1, 0.3, 1] } }
|
|
116
|
+
};
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## 5. Scroll-Triggered Animation
|
|
122
|
+
|
|
123
|
+
### CSS-Only with Scroll-Driven Animations
|
|
124
|
+
```css
|
|
125
|
+
@keyframes fade-in {
|
|
126
|
+
from { opacity: 0; transform: translateY(20px); }
|
|
127
|
+
to { opacity: 1; transform: translateY(0); }
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.scroll-reveal {
|
|
131
|
+
animation: fade-in linear both;
|
|
132
|
+
animation-timeline: view();
|
|
133
|
+
animation-range: entry 0% entry 30%;
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Intersection Observer Pattern
|
|
138
|
+
```js
|
|
139
|
+
const observer = new IntersectionObserver(
|
|
140
|
+
(entries) => {
|
|
141
|
+
entries.forEach(entry => {
|
|
142
|
+
if (entry.isIntersecting) {
|
|
143
|
+
entry.target.classList.add('visible');
|
|
144
|
+
observer.unobserve(entry.target);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
},
|
|
148
|
+
{ threshold: 0.15, rootMargin: '0px 0px -50px 0px' }
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
document.querySelectorAll('.animate-on-scroll').forEach(el => observer.observe(el));
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## 6. Text Morphing
|
|
157
|
+
|
|
158
|
+
For animated text transitions (changing labels, counters, status updates), use **Torph** (dependency-free).
|
|
159
|
+
|
|
160
|
+
### Installation
|
|
161
|
+
```
|
|
162
|
+
npm i torph
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Usage
|
|
166
|
+
```jsx
|
|
167
|
+
import { TextMorph } from 'torph/react';
|
|
168
|
+
|
|
169
|
+
// Automatically animates between text values
|
|
170
|
+
<TextMorph>{status}</TextMorph>
|
|
171
|
+
|
|
172
|
+
// Works with any dynamic text
|
|
173
|
+
<button>
|
|
174
|
+
<TextMorph>{isLoading ? "Processing..." : `Buy for $${price}`}</TextMorph>
|
|
175
|
+
</button>
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Torph morphs individual characters with smooth enter/exit animations. It works with React, Vue, Svelte, and vanilla TypeScript.
|
|
179
|
+
|
|
180
|
+
### When to Use
|
|
181
|
+
- Tab labels that change on selection
|
|
182
|
+
- Button text that updates (Add to Cart -> Added!)
|
|
183
|
+
- Counter values that increment
|
|
184
|
+
- Status indicators that cycle through states
|
|
185
|
+
- Any text that changes in response to user action
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## 7. Micro-Interactions
|
|
190
|
+
|
|
191
|
+
### Button Press
|
|
192
|
+
```css
|
|
193
|
+
button:active {
|
|
194
|
+
transform: scale(0.97);
|
|
195
|
+
transition: transform 80ms var(--ease-in);
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Toggle Switch
|
|
200
|
+
Animate the knob position and background color simultaneously. The knob should arrive slightly before the color finishes changing.
|
|
201
|
+
|
|
202
|
+
### Checkbox
|
|
203
|
+
Scale the checkmark from 0 to 1 with a slight overshoot:
|
|
204
|
+
```css
|
|
205
|
+
.checkbox-mark {
|
|
206
|
+
transform: scale(0);
|
|
207
|
+
transition: transform 200ms cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
208
|
+
}
|
|
209
|
+
.checkbox:checked .checkbox-mark {
|
|
210
|
+
transform: scale(1);
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Skeleton Loading
|
|
215
|
+
Shimmer from left to right using a gradient animation:
|
|
216
|
+
```css
|
|
217
|
+
.skeleton {
|
|
218
|
+
background: linear-gradient(90deg,
|
|
219
|
+
var(--surface-2) 25%,
|
|
220
|
+
var(--surface-3) 50%,
|
|
221
|
+
var(--surface-2) 75%
|
|
222
|
+
);
|
|
223
|
+
background-size: 200% 100%;
|
|
224
|
+
animation: shimmer 1.5s infinite;
|
|
225
|
+
}
|
|
226
|
+
@keyframes shimmer {
|
|
227
|
+
0% { background-position: 200% 0; }
|
|
228
|
+
100% { background-position: -200% 0; }
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## 8. Reduced Motion
|
|
235
|
+
|
|
236
|
+
Always respect the user's motion preference:
|
|
237
|
+
```css
|
|
238
|
+
@media (prefers-reduced-motion: reduce) {
|
|
239
|
+
*, *::before, *::after {
|
|
240
|
+
animation-duration: 0.01ms !important;
|
|
241
|
+
animation-iteration-count: 1 !important;
|
|
242
|
+
transition-duration: 0.01ms !important;
|
|
243
|
+
scroll-behavior: auto !important;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
This does not mean removing all visual feedback. Opacity changes (fades) are still acceptable. Remove translation, scaling, and rotation animations.
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## 9. Common Mistakes
|
|
253
|
+
|
|
254
|
+
- Animating everything on the page (creates visual noise, reduces perceived performance)
|
|
255
|
+
- Using `animation-duration: 0` for reduced motion (some browsers behave unexpectedly; use 0.01ms)
|
|
256
|
+
- Bounce/elastic easing on UI elements (acceptable only in game-like or toy-like contexts)
|
|
257
|
+
- Animating layout properties (width, height, top, left) instead of transforms (causes layout thrashing)
|
|
258
|
+
- Forgetting `will-change` on frequently animated elements (or overusing it on everything)
|
|
259
|
+
- Staggering 20+ items with visible delays (group them or animate the container)
|
|
260
|
+
- Using `transition: all 0.3s` (animates properties you did not intend; be explicit about which properties to transition)
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# React Patterns Reference
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
1. Component Architecture
|
|
5
|
+
2. State Management
|
|
6
|
+
3. Performance
|
|
7
|
+
4. Composition Patterns
|
|
8
|
+
5. Data Fetching
|
|
9
|
+
6. Styling
|
|
10
|
+
7. Common Mistakes
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 1. Component Architecture
|
|
15
|
+
|
|
16
|
+
### Server vs. Client Components
|
|
17
|
+
Default to Server Components. Add `'use client'` only when the component needs:
|
|
18
|
+
- Event handlers (onClick, onChange, etc.)
|
|
19
|
+
- useState, useEffect, useRef, or other hooks
|
|
20
|
+
- Browser APIs (window, document, navigator)
|
|
21
|
+
- Third-party libraries that use hooks or browser APIs
|
|
22
|
+
|
|
23
|
+
### File Organization
|
|
24
|
+
Colocate related files. Keep components, styles, tests, and types in the same directory:
|
|
25
|
+
```
|
|
26
|
+
components/
|
|
27
|
+
user-card/
|
|
28
|
+
user-card.tsx
|
|
29
|
+
user-card.test.tsx
|
|
30
|
+
user-card.module.css
|
|
31
|
+
types.ts
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Naming
|
|
35
|
+
- Components: PascalCase (`UserCard`, `DashboardHeader`)
|
|
36
|
+
- Files: kebab-case (`user-card.tsx`, `dashboard-header.tsx`)
|
|
37
|
+
- Hooks: camelCase with `use` prefix (`useAuth`, `useMediaQuery`)
|
|
38
|
+
- Event handlers: `handle` + event (`handleClick`, `handleSubmit`)
|
|
39
|
+
- Boolean props: `is`/`has`/`should` prefix (`isLoading`, `hasError`)
|
|
40
|
+
|
|
41
|
+
### Export Patterns
|
|
42
|
+
- **Default export**: page/route components and layout components
|
|
43
|
+
- **Named export**: everything else (utilities, hooks, shared components)
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## 2. State Management
|
|
48
|
+
|
|
49
|
+
### Where State Lives
|
|
50
|
+
1. **URL state**: filters, pagination, search queries (use `searchParams`)
|
|
51
|
+
2. **Server state**: data from APIs (use React Query, SWR, or server components)
|
|
52
|
+
3. **Local state**: form inputs, UI toggles, hover/focus state (use `useState`)
|
|
53
|
+
4. **Shared local state**: state needed by siblings (lift to parent, or use context)
|
|
54
|
+
5. **Global state**: rarely needed (auth user, theme preference, feature flags)
|
|
55
|
+
|
|
56
|
+
### Rules
|
|
57
|
+
- Do not store derived state. Compute it during render.
|
|
58
|
+
- Do not sync state between sources. Pick one source of truth.
|
|
59
|
+
- Prefer `useReducer` over `useState` when the next state depends on the previous state or when managing more than 3 related state variables.
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
// Bad: storing derived state
|
|
63
|
+
const [items, setItems] = useState(data);
|
|
64
|
+
const [filteredItems, setFilteredItems] = useState([]);
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
setFilteredItems(items.filter(i => i.active));
|
|
67
|
+
}, [items]);
|
|
68
|
+
|
|
69
|
+
// Good: compute during render
|
|
70
|
+
const [items, setItems] = useState(data);
|
|
71
|
+
const filteredItems = items.filter(i => i.active);
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## 3. Performance
|
|
77
|
+
|
|
78
|
+
### Rendering
|
|
79
|
+
- Use `React.memo` only for components that re-render often with the same props
|
|
80
|
+
- Use `useMemo` for expensive computations, not for every variable
|
|
81
|
+
- Use `useCallback` for callbacks passed to memoized children
|
|
82
|
+
- Use `key` props correctly (stable, unique identifiers, never array indices for reorderable lists)
|
|
83
|
+
|
|
84
|
+
### Code Splitting
|
|
85
|
+
```tsx
|
|
86
|
+
import dynamic from 'next/dynamic';
|
|
87
|
+
|
|
88
|
+
const Chart = dynamic(() => import('./chart'), {
|
|
89
|
+
loading: () => <ChartSkeleton />,
|
|
90
|
+
ssr: false,
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Virtualization
|
|
95
|
+
For lists with 100+ items, use `react-window` or `@tanstack/virtual`:
|
|
96
|
+
```tsx
|
|
97
|
+
import { FixedSizeList } from 'react-window';
|
|
98
|
+
|
|
99
|
+
<FixedSizeList height={600} itemCount={items.length} itemSize={48} width="100%">
|
|
100
|
+
{({ index, style }) => <Row style={style} item={items[index]} />}
|
|
101
|
+
</FixedSizeList>
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Image Optimization
|
|
105
|
+
Use `next/image` in Next.js or `loading="lazy"` with explicit `width`/`height` attributes. Always set `aspect-ratio` to prevent layout shift.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## 4. Composition Patterns
|
|
110
|
+
|
|
111
|
+
### Compound Components
|
|
112
|
+
Components that share implicit state through context:
|
|
113
|
+
```tsx
|
|
114
|
+
<Select value={value} onChange={setValue}>
|
|
115
|
+
<Select.Trigger>Choose a fruit</Select.Trigger>
|
|
116
|
+
<Select.Content>
|
|
117
|
+
<Select.Item value="apple">Apple</Select.Item>
|
|
118
|
+
<Select.Item value="banana">Banana</Select.Item>
|
|
119
|
+
</Select.Content>
|
|
120
|
+
</Select>
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Slot Pattern
|
|
124
|
+
Flexible component composition through named children:
|
|
125
|
+
```tsx
|
|
126
|
+
function Card({ header, children, footer }) {
|
|
127
|
+
return (
|
|
128
|
+
<div className="card">
|
|
129
|
+
{header && <div className="card-header">{header}</div>}
|
|
130
|
+
<div className="card-body">{children}</div>
|
|
131
|
+
{footer && <div className="card-footer">{footer}</div>}
|
|
132
|
+
</div>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Render Props (when needed)
|
|
138
|
+
For components that need to share behavior, not UI:
|
|
139
|
+
```tsx
|
|
140
|
+
<DataFetcher url="/api/users">
|
|
141
|
+
{({ data, isLoading }) => isLoading ? <Skeleton /> : <UserList users={data} />}
|
|
142
|
+
</DataFetcher>
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## 5. Data Fetching
|
|
148
|
+
|
|
149
|
+
### Server Components (preferred)
|
|
150
|
+
```tsx
|
|
151
|
+
async function UserList() {
|
|
152
|
+
const users = await fetch('/api/users').then(r => r.json());
|
|
153
|
+
return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Client-Side with Suspense
|
|
158
|
+
```tsx
|
|
159
|
+
function Dashboard() {
|
|
160
|
+
return (
|
|
161
|
+
<Suspense fallback={<DashboardSkeleton />}>
|
|
162
|
+
<DashboardContent />
|
|
163
|
+
</Suspense>
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Error Boundaries
|
|
169
|
+
Wrap data-fetching components in error boundaries:
|
|
170
|
+
```tsx
|
|
171
|
+
<ErrorBoundary fallback={<ErrorMessage />}>
|
|
172
|
+
<Suspense fallback={<Skeleton />}>
|
|
173
|
+
<DataComponent />
|
|
174
|
+
</Suspense>
|
|
175
|
+
</ErrorBoundary>
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## 6. Styling
|
|
181
|
+
|
|
182
|
+
### Tailwind Best Practices
|
|
183
|
+
- Use Tailwind's core utility classes (pre-defined classes only in Claude artifacts)
|
|
184
|
+
- Extract repeated patterns into component variants, not `@apply` rules
|
|
185
|
+
- Use CSS variables for theme values, Tailwind utilities for everything else
|
|
186
|
+
- Never use more than ~10 utility classes on a single element; extract a component instead
|
|
187
|
+
|
|
188
|
+
### CSS Modules
|
|
189
|
+
For non-Tailwind projects:
|
|
190
|
+
```tsx
|
|
191
|
+
import styles from './button.module.css';
|
|
192
|
+
<button className={styles.primary}>Click</button>
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Semantic HTML
|
|
196
|
+
Use the right element, not just `div` with classes:
|
|
197
|
+
- `<nav>` for navigation
|
|
198
|
+
- `<main>` for primary content
|
|
199
|
+
- `<section>` for thematic grouping
|
|
200
|
+
- `<article>` for self-contained content
|
|
201
|
+
- `<aside>` for tangentially related content
|
|
202
|
+
- `<header>` and `<footer>` for their semantic purpose
|
|
203
|
+
- `<button>` for clickable actions, `<a>` for navigation
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## 7. Common Mistakes
|
|
208
|
+
|
|
209
|
+
- Using `useEffect` for derived state (compute during render instead)
|
|
210
|
+
- Putting everything in global state (most state should be local or server-derived)
|
|
211
|
+
- Using `index` as `key` for dynamic lists
|
|
212
|
+
- Wrapping every component in `React.memo`
|
|
213
|
+
- Using `any` in TypeScript (defeats the purpose of type safety)
|
|
214
|
+
- Fetching data in `useEffect` when a server component would suffice
|
|
215
|
+
- Not using Suspense boundaries (the whole page flashes instead of parts loading independently)
|
|
216
|
+
- Prop drilling through 5+ levels (use composition or context)
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# Responsive Design Reference
|
|
2
|
+
|
|
3
|
+
## 1. Breakpoints
|
|
4
|
+
|
|
5
|
+
Use content-driven breakpoints, not device-driven. These are sensible defaults:
|
|
6
|
+
|
|
7
|
+
```css
|
|
8
|
+
/* Mobile first: no media query = mobile */
|
|
9
|
+
/* Small tablets / large phones */
|
|
10
|
+
@media (min-width: 640px) { }
|
|
11
|
+
/* Tablets / small laptops */
|
|
12
|
+
@media (min-width: 768px) { }
|
|
13
|
+
/* Laptops / desktops */
|
|
14
|
+
@media (min-width: 1024px) { }
|
|
15
|
+
/* Large desktops */
|
|
16
|
+
@media (min-width: 1280px) { }
|
|
17
|
+
/* Ultrawide */
|
|
18
|
+
@media (min-width: 1536px) { }
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## 2. Mobile-First Approach
|
|
22
|
+
|
|
23
|
+
Start with the mobile layout (single column, stacked). Add complexity at wider breakpoints. This ensures the core experience works everywhere before enhancements.
|
|
24
|
+
|
|
25
|
+
```css
|
|
26
|
+
/* Base: single column */
|
|
27
|
+
.grid { display: grid; gap: 1rem; }
|
|
28
|
+
|
|
29
|
+
/* Tablet: two columns */
|
|
30
|
+
@media (min-width: 768px) {
|
|
31
|
+
.grid { grid-template-columns: repeat(2, 1fr); }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* Desktop: three columns */
|
|
35
|
+
@media (min-width: 1024px) {
|
|
36
|
+
.grid { grid-template-columns: repeat(3, 1fr); }
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## 3. Fluid Design
|
|
41
|
+
|
|
42
|
+
Use `clamp()` for font sizes, padding, and gaps that scale smoothly:
|
|
43
|
+
|
|
44
|
+
```css
|
|
45
|
+
.container {
|
|
46
|
+
padding: clamp(1rem, 4vw, 3rem);
|
|
47
|
+
max-width: 1200px;
|
|
48
|
+
margin: 0 auto;
|
|
49
|
+
}
|
|
50
|
+
h1 {
|
|
51
|
+
font-size: clamp(1.75rem, 4vw + 0.5rem, 3.5rem);
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## 4. Container Queries
|
|
56
|
+
|
|
57
|
+
For component-level responsiveness (when the component's width, not the viewport, should determine layout):
|
|
58
|
+
|
|
59
|
+
```css
|
|
60
|
+
.card-container {
|
|
61
|
+
container-type: inline-size;
|
|
62
|
+
}
|
|
63
|
+
@container (min-width: 400px) {
|
|
64
|
+
.card { display: grid; grid-template-columns: 1fr 2fr; }
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## 5. Touch Targets
|
|
69
|
+
|
|
70
|
+
Minimum touch target: 44x44px (WCAG) or 48x48px (Material). Apply to all interactive elements on mobile. If the visual element is smaller, use padding or `::before` pseudo-element to extend the hit area.
|
|
71
|
+
|
|
72
|
+
```css
|
|
73
|
+
.icon-button {
|
|
74
|
+
position: relative;
|
|
75
|
+
width: 24px;
|
|
76
|
+
height: 24px;
|
|
77
|
+
}
|
|
78
|
+
.icon-button::before {
|
|
79
|
+
content: '';
|
|
80
|
+
position: absolute;
|
|
81
|
+
inset: -12px; /* extends tap area to 48x48 */
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## 6. Responsive Typography
|
|
86
|
+
|
|
87
|
+
Do not just shrink everything on mobile. Adjust the type scale:
|
|
88
|
+
- Mobile: use a smaller ratio (1.2) with a 15-16px base
|
|
89
|
+
- Desktop: use a larger ratio (1.25-1.333) with a 16-18px base
|
|
90
|
+
- Headings should scale more aggressively than body text
|
|
91
|
+
|
|
92
|
+
## 7. Navigation Patterns
|
|
93
|
+
|
|
94
|
+
- **Mobile (< 768px)**: Hamburger menu, bottom tab bar, or slide-out drawer
|
|
95
|
+
- **Tablet (768-1024px)**: Collapsed sidebar or icon-only navigation
|
|
96
|
+
- **Desktop (> 1024px)**: Full sidebar, top navigation bar, or mega-menu
|
|
97
|
+
|
|
98
|
+
## 8. Images
|
|
99
|
+
|
|
100
|
+
Use `srcset` and `sizes` for responsive images. Use `loading="lazy"` for below-the-fold images. Set `aspect-ratio` to prevent layout shift:
|
|
101
|
+
|
|
102
|
+
```css
|
|
103
|
+
img {
|
|
104
|
+
width: 100%;
|
|
105
|
+
height: auto;
|
|
106
|
+
aspect-ratio: 16 / 9;
|
|
107
|
+
object-fit: cover;
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## 9. Common Mistakes
|
|
112
|
+
|
|
113
|
+
- Hiding too much content on mobile (users expect the same information, just reorganized)
|
|
114
|
+
- Using `vw` units for text without a `clamp()` minimum (becomes unreadable on small screens)
|
|
115
|
+
- Horizontal scrolling on mobile (almost never acceptable)
|
|
116
|
+
- Fixed-position elements that cover too much of the mobile viewport
|
|
117
|
+
- Not testing at actual device widths (375px for iPhone SE, 390px for modern iPhones, 360px for Android)
|
|
118
|
+
- Tables that overflow on mobile (use horizontal scroll wrapper or reformat as stacked cards)
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Sensory Design Reference
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
1. UI Sound Design
|
|
5
|
+
2. Haptic Feedback
|
|
6
|
+
3. Multi-Sensory Integration
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 1. UI Sound Design
|
|
11
|
+
|
|
12
|
+
### Why Sound
|
|
13
|
+
Sound provides confirmation that an action occurred, draws attention to important state changes, and adds personality to the interface. It is underused on the web but highly effective when applied with restraint.
|
|
14
|
+
|
|
15
|
+
### The Soundcn Pattern
|
|
16
|
+
Use inline base64 audio data URIs with the Web Audio API. This avoids external file loading, CORS issues, and runtime fetching. Each sound is a self-contained TypeScript module.
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
// sounds/click-soft.ts
|
|
20
|
+
export const clickSoftSound = "data:audio/wav;base64,UklGR..."; // base64 WAV
|
|
21
|
+
|
|
22
|
+
// hooks/use-sound.ts
|
|
23
|
+
import { useCallback, useRef } from 'react';
|
|
24
|
+
|
|
25
|
+
export function useSound(src: string) {
|
|
26
|
+
const audioContextRef = useRef<AudioContext | null>(null);
|
|
27
|
+
|
|
28
|
+
const play = useCallback(() => {
|
|
29
|
+
if (!audioContextRef.current) {
|
|
30
|
+
audioContextRef.current = new AudioContext();
|
|
31
|
+
}
|
|
32
|
+
const ctx = audioContextRef.current;
|
|
33
|
+
fetch(src)
|
|
34
|
+
.then(r => r.arrayBuffer())
|
|
35
|
+
.then(buf => ctx.decodeAudioData(buf))
|
|
36
|
+
.then(decoded => {
|
|
37
|
+
const source = ctx.createBufferSource();
|
|
38
|
+
source.buffer = decoded;
|
|
39
|
+
source.connect(ctx.destination);
|
|
40
|
+
source.start(0);
|
|
41
|
+
});
|
|
42
|
+
}, [src]);
|
|
43
|
+
|
|
44
|
+
return [play] as const;
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Usage
|
|
49
|
+
```tsx
|
|
50
|
+
import { useSound } from "@/hooks/use-sound";
|
|
51
|
+
import { clickSoftSound } from "@/sounds/click-soft";
|
|
52
|
+
|
|
53
|
+
function Button() {
|
|
54
|
+
const [play] = useSound(clickSoftSound);
|
|
55
|
+
return <button onClick={() => { play(); handleAction(); }}>Save</button>;
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### When to Use Sound
|
|
60
|
+
- **Button clicks**: soft, short click sound (50-100ms)
|
|
61
|
+
- **Success actions**: pleasant confirmation tone
|
|
62
|
+
- **Notifications**: attention-getting but not alarming chime
|
|
63
|
+
- **Errors**: subtle alert, not a harsh buzz
|
|
64
|
+
- **Toggle switches**: satisfying mechanical click
|
|
65
|
+
- **Transitions**: whoosh or swipe sound for page changes
|
|
66
|
+
|
|
67
|
+
### Rules
|
|
68
|
+
- Always provide a sound toggle in the UI (respect user preference)
|
|
69
|
+
- Keep sounds under 200ms for UI interactions
|
|
70
|
+
- Use the Web Audio API, not `<audio>` elements (lower latency)
|
|
71
|
+
- Sound volume should be subtle by default (0.3-0.5 of max)
|
|
72
|
+
- Never auto-play sounds on page load
|
|
73
|
+
- Source sounds from CC0 collections (Kenney, Freesound)
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## 2. Haptic Feedback
|
|
78
|
+
|
|
79
|
+
### The Vibration API
|
|
80
|
+
```javascript
|
|
81
|
+
// Check support
|
|
82
|
+
if ('vibrate' in navigator) {
|
|
83
|
+
// Simple tap (10ms pulse)
|
|
84
|
+
navigator.vibrate(10);
|
|
85
|
+
|
|
86
|
+
// Success pattern (two short pulses)
|
|
87
|
+
navigator.vibrate([10, 50, 10]);
|
|
88
|
+
|
|
89
|
+
// Error pattern (one longer pulse)
|
|
90
|
+
navigator.vibrate(30);
|
|
91
|
+
|
|
92
|
+
// Warning (three quick pulses)
|
|
93
|
+
navigator.vibrate([10, 30, 10, 30, 10]);
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### When to Use Haptics
|
|
98
|
+
- **Button press confirmation**: 10ms pulse on touch
|
|
99
|
+
- **Toggle switch**: 10ms pulse on state change
|
|
100
|
+
- **Destructive action confirmation**: 30ms pulse before confirmation dialog
|
|
101
|
+
- **Pull-to-refresh threshold**: 10ms pulse when the threshold is reached
|
|
102
|
+
- **Drag and drop**: 10ms pulse on pickup and drop
|
|
103
|
+
|
|
104
|
+
### Rules
|
|
105
|
+
- Gate behind feature detection (`'vibrate' in navigator`)
|
|
106
|
+
- Respect `prefers-reduced-motion` by disabling haptics when motion is reduced
|
|
107
|
+
- Keep durations very short (10-30ms for taps, never longer than 100ms)
|
|
108
|
+
- Do not use haptics for every interaction, only pivotal moments
|
|
109
|
+
- Mobile only: haptics have no effect on desktop
|
|
110
|
+
|
|
111
|
+
### iOS Considerations
|
|
112
|
+
The Vibration API has limited support on iOS Safari. For broader iOS support, use the experimental Haptic Feedback API or accept that haptics are Android-primary.
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## 3. Multi-Sensory Integration
|
|
117
|
+
|
|
118
|
+
The strongest UI moments combine visual, auditory, and haptic feedback simultaneously:
|
|
119
|
+
|
|
120
|
+
1. User taps "Complete" button
|
|
121
|
+
2. **Visual**: checkmark animation scales in with a satisfying bounce
|
|
122
|
+
3. **Sound**: soft success chime (100ms)
|
|
123
|
+
4. **Haptic**: double-pulse pattern (10ms, 50ms gap, 10ms)
|
|
124
|
+
|
|
125
|
+
This triple feedback creates a moment of certainty that a single visual change cannot match. Use it sparingly, for milestone moments (order placed, task completed, level achieved).
|