omgkit 2.2.0 → 2.3.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.
Files changed (55) hide show
  1. package/package.json +1 -1
  2. package/plugin/skills/databases/mongodb/SKILL.md +60 -776
  3. package/plugin/skills/databases/prisma/SKILL.md +53 -744
  4. package/plugin/skills/databases/redis/SKILL.md +53 -860
  5. package/plugin/skills/devops/aws/SKILL.md +68 -672
  6. package/plugin/skills/devops/github-actions/SKILL.md +54 -657
  7. package/plugin/skills/devops/kubernetes/SKILL.md +67 -602
  8. package/plugin/skills/devops/performance-profiling/SKILL.md +59 -863
  9. package/plugin/skills/frameworks/django/SKILL.md +87 -853
  10. package/plugin/skills/frameworks/express/SKILL.md +95 -1301
  11. package/plugin/skills/frameworks/fastapi/SKILL.md +90 -1198
  12. package/plugin/skills/frameworks/laravel/SKILL.md +87 -1187
  13. package/plugin/skills/frameworks/nestjs/SKILL.md +106 -973
  14. package/plugin/skills/frameworks/react/SKILL.md +94 -962
  15. package/plugin/skills/frameworks/vue/SKILL.md +95 -1242
  16. package/plugin/skills/frontend/accessibility/SKILL.md +91 -1056
  17. package/plugin/skills/frontend/frontend-design/SKILL.md +69 -1262
  18. package/plugin/skills/frontend/responsive/SKILL.md +76 -799
  19. package/plugin/skills/frontend/shadcn-ui/SKILL.md +73 -921
  20. package/plugin/skills/frontend/tailwindcss/SKILL.md +60 -788
  21. package/plugin/skills/frontend/threejs/SKILL.md +72 -1266
  22. package/plugin/skills/languages/javascript/SKILL.md +106 -849
  23. package/plugin/skills/methodology/brainstorming/SKILL.md +70 -576
  24. package/plugin/skills/methodology/defense-in-depth/SKILL.md +79 -831
  25. package/plugin/skills/methodology/dispatching-parallel-agents/SKILL.md +81 -654
  26. package/plugin/skills/methodology/executing-plans/SKILL.md +86 -529
  27. package/plugin/skills/methodology/finishing-development-branch/SKILL.md +95 -586
  28. package/plugin/skills/methodology/problem-solving/SKILL.md +67 -681
  29. package/plugin/skills/methodology/receiving-code-review/SKILL.md +70 -533
  30. package/plugin/skills/methodology/requesting-code-review/SKILL.md +70 -610
  31. package/plugin/skills/methodology/root-cause-tracing/SKILL.md +70 -646
  32. package/plugin/skills/methodology/sequential-thinking/SKILL.md +70 -478
  33. package/plugin/skills/methodology/systematic-debugging/SKILL.md +66 -559
  34. package/plugin/skills/methodology/test-driven-development/SKILL.md +91 -752
  35. package/plugin/skills/methodology/testing-anti-patterns/SKILL.md +78 -687
  36. package/plugin/skills/methodology/token-optimization/SKILL.md +72 -602
  37. package/plugin/skills/methodology/verification-before-completion/SKILL.md +108 -529
  38. package/plugin/skills/methodology/writing-plans/SKILL.md +79 -566
  39. package/plugin/skills/omega/omega-architecture/SKILL.md +91 -752
  40. package/plugin/skills/omega/omega-coding/SKILL.md +161 -552
  41. package/plugin/skills/omega/omega-sprint/SKILL.md +132 -777
  42. package/plugin/skills/omega/omega-testing/SKILL.md +157 -845
  43. package/plugin/skills/omega/omega-thinking/SKILL.md +165 -606
  44. package/plugin/skills/security/better-auth/SKILL.md +46 -1034
  45. package/plugin/skills/security/oauth/SKILL.md +80 -934
  46. package/plugin/skills/security/owasp/SKILL.md +78 -862
  47. package/plugin/skills/testing/playwright/SKILL.md +77 -700
  48. package/plugin/skills/testing/pytest/SKILL.md +73 -811
  49. package/plugin/skills/testing/vitest/SKILL.md +60 -920
  50. package/plugin/skills/tools/document-processing/SKILL.md +111 -838
  51. package/plugin/skills/tools/image-processing/SKILL.md +126 -659
  52. package/plugin/skills/tools/mcp-development/SKILL.md +85 -758
  53. package/plugin/skills/tools/media-processing/SKILL.md +118 -735
  54. package/plugin/stdrules/SKILL_STANDARDS.md +490 -0
  55. package/plugin/skills/SKILL_STANDARDS.md +0 -743
@@ -1,872 +1,149 @@
1
1
  ---
2
- name: responsive
3
- description: Responsive web design with mobile-first approach, fluid layouts, container queries, and cross-device testing
4
- category: frontend
5
- triggers:
6
- - responsive
7
- - responsive design
8
- - mobile first
9
- - adaptive layout
10
- - breakpoints
11
- - media queries
2
+ name: building-responsive-layouts
3
+ description: Claude builds responsive web layouts with mobile-first CSS, fluid typography, and container queries. Use when creating adaptive UIs that work across all device sizes.
12
4
  ---
13
5
 
14
- # Responsive Design
6
+ # Building Responsive Layouts
15
7
 
16
- Enterprise-grade **responsive web design** following industry best practices. This skill covers mobile-first development, fluid layouts, container queries, responsive images, and cross-device optimization patterns used by top engineering teams.
8
+ ## Quick Start
17
9
 
18
- ## Purpose
19
-
20
- Build seamless cross-device experiences:
21
-
22
- - Implement mobile-first design approach
23
- - Create fluid, flexible layouts
24
- - Use modern CSS layout techniques
25
- - Optimize images for all devices
26
- - Implement container queries
27
- - Test across devices and viewports
28
- - Handle touch and mouse interactions
29
-
30
- ## Features
31
-
32
- ### 1. Mobile-First Breakpoints
33
-
34
- ```css
35
- /* Base styles (mobile-first) */
36
- :root {
37
- /* Breakpoint values */
38
- --breakpoint-sm: 640px;
39
- --breakpoint-md: 768px;
40
- --breakpoint-lg: 1024px;
41
- --breakpoint-xl: 1280px;
42
- --breakpoint-2xl: 1536px;
43
- }
44
-
45
- /* Mobile base styles */
46
- .container {
47
- width: 100%;
48
- padding-left: 1rem;
49
- padding-right: 1rem;
50
- margin-left: auto;
51
- margin-right: auto;
52
- }
53
-
54
- /* Small devices (640px and up) */
55
- @media (min-width: 640px) {
56
- .container {
57
- max-width: 640px;
58
- padding-left: 1.5rem;
59
- padding-right: 1.5rem;
60
- }
61
- }
62
-
63
- /* Medium devices (768px and up) */
64
- @media (min-width: 768px) {
65
- .container {
66
- max-width: 768px;
67
- }
68
- }
69
-
70
- /* Large devices (1024px and up) */
71
- @media (min-width: 1024px) {
72
- .container {
73
- max-width: 1024px;
74
- padding-left: 2rem;
75
- padding-right: 2rem;
76
- }
77
- }
78
-
79
- /* Extra large devices (1280px and up) */
80
- @media (min-width: 1280px) {
81
- .container {
82
- max-width: 1280px;
83
- }
10
+ ```tsx
11
+ // Responsive grid with auto-fit
12
+ export function ResponsiveGrid({ children, minWidth = '300px' }: GridProps) {
13
+ return (
14
+ <div style={{
15
+ display: 'grid',
16
+ gridTemplateColumns: `repeat(auto-fit, minmax(min(${minWidth}, 100%), 1fr))`,
17
+ gap: '1.5rem'
18
+ }}>
19
+ {children}
20
+ </div>
21
+ );
84
22
  }
85
23
 
86
- /* 2XL devices (1536px and up) */
87
- @media (min-width: 1536px) {
88
- .container {
89
- max-width: 1536px;
90
- }
91
- }
24
+ // With Tailwind
25
+ <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 md:gap-6">
26
+ {items.map(item => <Card key={item.id} {...item} />)}
27
+ </div>
92
28
  ```
93
29
 
94
- ```typescript
95
- // hooks/useBreakpoint.ts
96
- import { useState, useEffect } from "react";
97
-
98
- const breakpoints = {
99
- sm: 640,
100
- md: 768,
101
- lg: 1024,
102
- xl: 1280,
103
- "2xl": 1536,
104
- } as const;
105
-
106
- type Breakpoint = keyof typeof breakpoints;
107
-
108
- export function useBreakpoint() {
109
- const [breakpoint, setBreakpoint] = useState<Breakpoint | null>(null);
110
- const [width, setWidth] = useState(0);
111
-
112
- useEffect(() => {
113
- const handleResize = () => {
114
- const w = window.innerWidth;
115
- setWidth(w);
116
-
117
- if (w >= breakpoints["2xl"]) setBreakpoint("2xl");
118
- else if (w >= breakpoints.xl) setBreakpoint("xl");
119
- else if (w >= breakpoints.lg) setBreakpoint("lg");
120
- else if (w >= breakpoints.md) setBreakpoint("md");
121
- else if (w >= breakpoints.sm) setBreakpoint("sm");
122
- else setBreakpoint(null);
123
- };
124
-
125
- handleResize();
126
- window.addEventListener("resize", handleResize);
127
- return () => window.removeEventListener("resize", handleResize);
128
- }, []);
30
+ ## Features
129
31
 
130
- const isAbove = (bp: Breakpoint) => width >= breakpoints[bp];
131
- const isBelow = (bp: Breakpoint) => width < breakpoints[bp];
32
+ | Feature | Description | Guide |
33
+ |---------|-------------|-------|
34
+ | Mobile-First Breakpoints | sm(640), md(768), lg(1024), xl(1280), 2xl(1536) | `ref/breakpoints.md` |
35
+ | Fluid Typography | `clamp()` for responsive font sizes | `ref/fluid-type.md` |
36
+ | Container Queries | Component-level responsive design | `ref/container-queries.md` |
37
+ | Responsive Images | srcset, sizes, and art direction | `ref/images.md` |
38
+ | Touch-Friendly | 44px minimum targets, hover vs touch handling | `ref/touch.md` |
39
+ | Safe Areas | Handle notched devices and dynamic viewports | `ref/safe-areas.md` |
132
40
 
133
- return { breakpoint, width, isAbove, isBelow };
134
- }
41
+ ## Common Patterns
135
42
 
136
- // hooks/useMediaQuery.ts
137
- import { useState, useEffect } from "react";
43
+ ### useMediaQuery Hook
138
44
 
45
+ ```tsx
139
46
  export function useMediaQuery(query: string): boolean {
140
47
  const [matches, setMatches] = useState(false);
141
48
 
142
49
  useEffect(() => {
143
50
  const media = window.matchMedia(query);
144
- if (media.matches !== matches) {
145
- setMatches(media.matches);
146
- }
147
-
51
+ setMatches(media.matches);
148
52
  const listener = (e: MediaQueryListEvent) => setMatches(e.matches);
149
- media.addEventListener("change", listener);
150
- return () => media.removeEventListener("change", listener);
151
- }, [matches, query]);
53
+ media.addEventListener('change', listener);
54
+ return () => media.removeEventListener('change', listener);
55
+ }, [query]);
152
56
 
153
57
  return matches;
154
58
  }
155
59
 
156
60
  // Usage
157
- function Component() {
158
- const isMobile = useMediaQuery("(max-width: 639px)");
159
- const isTablet = useMediaQuery("(min-width: 640px) and (max-width: 1023px)");
160
- const isDesktop = useMediaQuery("(min-width: 1024px)");
161
- const prefersDark = useMediaQuery("(prefers-color-scheme: dark)");
162
- const prefersReducedMotion = useMediaQuery("(prefers-reduced-motion: reduce)");
163
-
164
- return (
165
- <div>
166
- {isMobile && <MobileLayout />}
167
- {isTablet && <TabletLayout />}
168
- {isDesktop && <DesktopLayout />}
169
- </div>
170
- );
171
- }
172
- ```
173
-
174
- ### 2. Fluid Typography
175
-
176
- ```css
177
- /* Fluid typography using clamp() */
178
- :root {
179
- /* Base font sizes */
180
- --font-size-xs: clamp(0.75rem, 0.7rem + 0.25vw, 0.875rem);
181
- --font-size-sm: clamp(0.875rem, 0.8rem + 0.35vw, 1rem);
182
- --font-size-base: clamp(1rem, 0.9rem + 0.5vw, 1.125rem);
183
- --font-size-lg: clamp(1.125rem, 1rem + 0.6vw, 1.25rem);
184
- --font-size-xl: clamp(1.25rem, 1.1rem + 0.75vw, 1.5rem);
185
- --font-size-2xl: clamp(1.5rem, 1.25rem + 1.25vw, 2rem);
186
- --font-size-3xl: clamp(1.875rem, 1.5rem + 1.875vw, 2.5rem);
187
- --font-size-4xl: clamp(2.25rem, 1.75rem + 2.5vw, 3rem);
188
- --font-size-5xl: clamp(3rem, 2rem + 5vw, 4rem);
189
-
190
- /* Fluid spacing */
191
- --space-xs: clamp(0.25rem, 0.2rem + 0.25vw, 0.5rem);
192
- --space-sm: clamp(0.5rem, 0.4rem + 0.5vw, 0.75rem);
193
- --space-md: clamp(1rem, 0.8rem + 1vw, 1.5rem);
194
- --space-lg: clamp(1.5rem, 1.2rem + 1.5vw, 2.5rem);
195
- --space-xl: clamp(2rem, 1.5rem + 2.5vw, 4rem);
196
- --space-2xl: clamp(3rem, 2rem + 5vw, 6rem);
197
- }
198
-
199
- /* Typography scale */
200
- h1 {
201
- font-size: var(--font-size-4xl);
202
- line-height: 1.1;
203
- letter-spacing: -0.02em;
204
- }
205
-
206
- h2 {
207
- font-size: var(--font-size-3xl);
208
- line-height: 1.2;
209
- letter-spacing: -0.01em;
210
- }
211
-
212
- h3 {
213
- font-size: var(--font-size-2xl);
214
- line-height: 1.3;
215
- }
216
-
217
- h4 {
218
- font-size: var(--font-size-xl);
219
- line-height: 1.4;
220
- }
221
-
222
- p {
223
- font-size: var(--font-size-base);
224
- line-height: 1.6;
225
- }
226
-
227
- /* Responsive text utilities */
228
- .text-balance {
229
- text-wrap: balance;
230
- }
231
-
232
- .text-pretty {
233
- text-wrap: pretty;
234
- }
235
- ```
236
-
237
- ### 3. Flexible Grid Layouts
238
-
239
- ```css
240
- /* Auto-fit responsive grid */
241
- .auto-grid {
242
- display: grid;
243
- grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
244
- gap: var(--space-md);
245
- }
246
-
247
- /* Auto-fill variant */
248
- .auto-fill-grid {
249
- display: grid;
250
- grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
251
- gap: var(--space-md);
252
- }
253
-
254
- /* Sidebar layout */
255
- .sidebar-layout {
256
- display: grid;
257
- grid-template-columns: 1fr;
258
- gap: var(--space-lg);
259
- }
260
-
261
- @media (min-width: 768px) {
262
- .sidebar-layout {
263
- grid-template-columns: minmax(200px, 25%) 1fr;
264
- }
265
- }
266
-
267
- /* Holy grail layout */
268
- .holy-grail {
269
- display: grid;
270
- grid-template-rows: auto 1fr auto;
271
- min-height: 100vh;
272
- }
273
-
274
- .holy-grail-main {
275
- display: grid;
276
- grid-template-columns: 1fr;
277
- gap: var(--space-md);
278
- }
279
-
280
- @media (min-width: 768px) {
281
- .holy-grail-main {
282
- grid-template-columns: minmax(150px, 20%) 1fr;
283
- }
284
- }
285
-
286
- @media (min-width: 1024px) {
287
- .holy-grail-main {
288
- grid-template-columns: minmax(200px, 20%) 1fr minmax(200px, 20%);
289
- }
290
- }
291
-
292
- /* Masonry-like layout with CSS Grid */
293
- .masonry-grid {
294
- display: grid;
295
- grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
296
- grid-auto-rows: 10px;
297
- gap: var(--space-md);
298
- }
299
-
300
- /* Card spans based on content */
301
- .masonry-item--small {
302
- grid-row: span 20;
303
- }
304
-
305
- .masonry-item--medium {
306
- grid-row: span 30;
307
- }
308
-
309
- .masonry-item--large {
310
- grid-row: span 40;
311
- }
312
- ```
313
-
314
- ```tsx
315
- // Responsive grid component
316
- interface ResponsiveGridProps {
317
- children: React.ReactNode;
318
- minWidth?: string;
319
- gap?: string;
320
- className?: string;
321
- }
322
-
323
- export function ResponsiveGrid({
324
- children,
325
- minWidth = "300px",
326
- gap = "1rem",
327
- className = "",
328
- }: ResponsiveGridProps) {
329
- return (
330
- <div
331
- className={className}
332
- style={{
333
- display: "grid",
334
- gridTemplateColumns: `repeat(auto-fit, minmax(min(${minWidth}, 100%), 1fr))`,
335
- gap,
336
- }}
337
- >
338
- {children}
339
- </div>
340
- );
341
- }
342
-
343
- // Usage
344
- function ProductGrid() {
345
- return (
346
- <ResponsiveGrid minWidth="280px" gap="1.5rem">
347
- {products.map((product) => (
348
- <ProductCard key={product.id} product={product} />
349
- ))}
350
- </ResponsiveGrid>
351
- );
352
- }
61
+ const isMobile = useMediaQuery('(max-width: 639px)');
62
+ const prefersReducedMotion = useMediaQuery('(prefers-reduced-motion: reduce)');
353
63
  ```
354
64
 
355
- ### 4. Container Queries
65
+ ### Container Query Component
356
66
 
357
67
  ```css
358
- /* Container query setup */
359
68
  .card-container {
360
69
  container-type: inline-size;
361
- container-name: card;
362
70
  }
363
71
 
364
- /* Base card styles (smallest size) */
365
72
  .card {
366
73
  display: flex;
367
74
  flex-direction: column;
368
- padding: 1rem;
369
75
  }
370
76
 
371
- .card-image {
372
- width: 100%;
373
- aspect-ratio: 16/9;
374
- object-fit: cover;
375
- }
376
-
377
- .card-content {
378
- padding: 1rem 0;
379
- }
380
-
381
- /* Card layout changes based on container width */
382
- @container card (min-width: 400px) {
77
+ @container (min-width: 400px) {
383
78
  .card {
384
79
  flex-direction: row;
385
80
  gap: 1rem;
386
81
  }
387
-
388
- .card-image {
389
- width: 40%;
390
- aspect-ratio: 1;
391
- }
392
-
393
- .card-content {
394
- flex: 1;
395
- display: flex;
396
- flex-direction: column;
397
- justify-content: center;
398
- }
399
- }
400
-
401
- @container card (min-width: 600px) {
402
- .card {
403
- padding: 1.5rem;
404
- }
405
-
406
- .card-image {
407
- width: 35%;
408
- }
409
-
410
- .card-title {
411
- font-size: 1.5rem;
412
- }
413
- }
414
-
415
- /* Container query units */
416
- .responsive-text {
417
- font-size: clamp(1rem, 3cqi, 1.5rem);
418
- padding: 2cqi;
419
- }
420
-
421
- /* Style queries (experimental) */
422
- @container style(--theme: dark) {
423
- .card {
424
- background: #1a1a1a;
425
- color: white;
426
- }
82
+ .card-image { width: 40%; }
427
83
  }
428
84
  ```
429
85
 
430
86
  ```tsx
431
- // React component with container queries
432
- export function ContainerQueryCard({ title, description, image }: CardProps) {
87
+ export function ResponsiveCard({ image, title, content }: CardProps) {
433
88
  return (
434
89
  <div className="card-container">
435
90
  <article className="card">
436
91
  <img src={image} alt="" className="card-image" />
437
92
  <div className="card-content">
438
- <h3 className="card-title">{title}</h3>
439
- <p className="card-description">{description}</p>
93
+ <h3>{title}</h3>
94
+ <p>{content}</p>
440
95
  </div>
441
96
  </article>
442
97
  </div>
443
98
  );
444
99
  }
445
-
446
- // CSS Module with container queries
447
- // Card.module.css
448
- /*
449
- .container {
450
- container-type: inline-size;
451
- }
452
-
453
- .card {
454
- display: grid;
455
- gap: 1rem;
456
- }
457
-
458
- @container (min-width: 30rem) {
459
- .card {
460
- grid-template-columns: 1fr 2fr;
461
- }
462
- }
463
- */
464
- ```
465
-
466
- ### 5. Responsive Images
467
-
468
- ```tsx
469
- // Responsive image component
470
- interface ResponsiveImageProps {
471
- src: string;
472
- alt: string;
473
- sizes?: string;
474
- className?: string;
475
- priority?: boolean;
476
- }
477
-
478
- export function ResponsiveImage({
479
- src,
480
- alt,
481
- sizes = "100vw",
482
- className,
483
- priority = false,
484
- }: ResponsiveImageProps) {
485
- // Generate srcset for different sizes
486
- const widths = [320, 640, 768, 1024, 1280, 1536, 1920];
487
- const srcSet = widths
488
- .map((w) => `${src}?w=${w} ${w}w`)
489
- .join(", ");
490
-
491
- return (
492
- <img
493
- src={src}
494
- srcSet={srcSet}
495
- sizes={sizes}
496
- alt={alt}
497
- className={className}
498
- loading={priority ? "eager" : "lazy"}
499
- decoding={priority ? "sync" : "async"}
500
- />
501
- );
502
- }
503
-
504
- // Picture element for art direction
505
- export function ArtDirectedImage({
506
- mobileSrc,
507
- tabletSrc,
508
- desktopSrc,
509
- alt,
510
- }: {
511
- mobileSrc: string;
512
- tabletSrc: string;
513
- desktopSrc: string;
514
- alt: string;
515
- }) {
516
- return (
517
- <picture>
518
- {/* Desktop - landscape image */}
519
- <source media="(min-width: 1024px)" srcSet={desktopSrc} />
520
- {/* Tablet - square image */}
521
- <source media="(min-width: 640px)" srcSet={tabletSrc} />
522
- {/* Mobile - portrait image (default) */}
523
- <img src={mobileSrc} alt={alt} loading="lazy" />
524
- </picture>
525
- );
526
- }
527
-
528
- // Background image with responsive behavior
529
- export function ResponsiveHero({ children }: { children: React.ReactNode }) {
530
- return (
531
- <section
532
- className="
533
- relative min-h-[50vh] md:min-h-[60vh] lg:min-h-[70vh]
534
- bg-cover bg-center bg-no-repeat
535
- bg-[image:var(--mobile-bg)]
536
- md:bg-[image:var(--tablet-bg)]
537
- lg:bg-[image:var(--desktop-bg)]
538
- "
539
- style={{
540
- "--mobile-bg": "url('/hero-mobile.jpg')",
541
- "--tablet-bg": "url('/hero-tablet.jpg')",
542
- "--desktop-bg": "url('/hero-desktop.jpg')",
543
- } as React.CSSProperties}
544
- >
545
- <div className="absolute inset-0 bg-black/50" />
546
- <div className="relative z-10 flex items-center justify-center h-full">
547
- {children}
548
- </div>
549
- </section>
550
- );
551
- }
552
- ```
553
-
554
- ### 6. Touch-Friendly Design
555
-
556
- ```css
557
- /* Touch target sizes (minimum 44x44px) */
558
- .touch-target {
559
- min-width: 44px;
560
- min-height: 44px;
561
- display: flex;
562
- align-items: center;
563
- justify-content: center;
564
- }
565
-
566
- /* Larger touch targets on mobile */
567
- @media (pointer: coarse) {
568
- .button,
569
- .link,
570
- .interactive {
571
- min-height: 48px;
572
- padding: 12px 16px;
573
- }
574
-
575
- /* Increase spacing between interactive elements */
576
- .button-group {
577
- gap: 12px;
578
- }
579
-
580
- /* Larger form inputs */
581
- input,
582
- select,
583
- textarea {
584
- min-height: 48px;
585
- font-size: 16px; /* Prevents iOS zoom on focus */
586
- }
587
- }
588
-
589
- /* Hover only on devices that support it */
590
- @media (hover: hover) {
591
- .card:hover {
592
- transform: translateY(-4px);
593
- box-shadow: 0 12px 24px rgba(0, 0, 0, 0.15);
594
- }
595
-
596
- .button:hover {
597
- background-color: var(--color-primary-dark);
598
- }
599
- }
600
-
601
- /* Active states for touch devices */
602
- @media (hover: none) {
603
- .card:active {
604
- transform: scale(0.98);
605
- }
606
-
607
- .button:active {
608
- background-color: var(--color-primary-dark);
609
- }
610
- }
611
-
612
- /* Disable hover effects on touch */
613
- @media (hover: none) and (pointer: coarse) {
614
- .hover-effect {
615
- transform: none !important;
616
- }
617
- }
618
-
619
- /* Safe area insets for notched devices */
620
- .safe-area-padding {
621
- padding-left: env(safe-area-inset-left);
622
- padding-right: env(safe-area-inset-right);
623
- padding-bottom: env(safe-area-inset-bottom);
624
- }
625
-
626
- .fixed-bottom-nav {
627
- position: fixed;
628
- bottom: 0;
629
- left: 0;
630
- right: 0;
631
- padding-bottom: calc(1rem + env(safe-area-inset-bottom));
632
- }
633
- ```
634
-
635
- ```tsx
636
- // Touch-friendly button component
637
- export function TouchButton({
638
- children,
639
- onClick,
640
- ...props
641
- }: React.ButtonHTMLAttributes<HTMLButtonElement>) {
642
- return (
643
- <button
644
- onClick={onClick}
645
- className="
646
- min-h-[44px] min-w-[44px]
647
- px-4 py-2
648
- touch-manipulation
649
- select-none
650
- active:scale-95
651
- transition-transform duration-150
652
- "
653
- {...props}
654
- >
655
- {children}
656
- </button>
657
- );
658
- }
659
-
660
- // Swipe gesture hook
661
- import { useState, useRef } from "react";
662
-
663
- interface SwipeHandlers {
664
- onSwipeLeft?: () => void;
665
- onSwipeRight?: () => void;
666
- onSwipeUp?: () => void;
667
- onSwipeDown?: () => void;
668
- }
669
-
670
- export function useSwipe(handlers: SwipeHandlers, threshold = 50) {
671
- const touchStart = useRef<{ x: number; y: number } | null>(null);
672
-
673
- const onTouchStart = (e: React.TouchEvent) => {
674
- touchStart.current = {
675
- x: e.touches[0].clientX,
676
- y: e.touches[0].clientY,
677
- };
678
- };
679
-
680
- const onTouchEnd = (e: React.TouchEvent) => {
681
- if (!touchStart.current) return;
682
-
683
- const deltaX = e.changedTouches[0].clientX - touchStart.current.x;
684
- const deltaY = e.changedTouches[0].clientY - touchStart.current.y;
685
-
686
- if (Math.abs(deltaX) > Math.abs(deltaY)) {
687
- if (deltaX > threshold) handlers.onSwipeRight?.();
688
- if (deltaX < -threshold) handlers.onSwipeLeft?.();
689
- } else {
690
- if (deltaY > threshold) handlers.onSwipeDown?.();
691
- if (deltaY < -threshold) handlers.onSwipeUp?.();
692
- }
693
-
694
- touchStart.current = null;
695
- };
696
-
697
- return { onTouchStart, onTouchEnd };
698
- }
699
100
  ```
700
101
 
701
- ### 7. Responsive Navigation
102
+ ### Responsive Navigation
702
103
 
703
104
  ```tsx
704
- // Responsive navigation component
705
- import { useState } from "react";
706
-
707
105
  export function ResponsiveNav() {
708
106
  const [isOpen, setIsOpen] = useState(false);
709
107
 
710
108
  return (
711
- <nav className="relative bg-white shadow-sm">
712
- <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
713
- <div className="flex justify-between h-16">
714
- {/* Logo */}
715
- <div className="flex-shrink-0 flex items-center">
716
- <Logo />
717
- </div>
718
-
719
- {/* Desktop navigation */}
720
- <div className="hidden md:flex md:items-center md:space-x-8">
721
- <NavLink href="/">Home</NavLink>
722
- <NavLink href="/products">Products</NavLink>
723
- <NavLink href="/about">About</NavLink>
724
- <NavLink href="/contact">Contact</NavLink>
725
- </div>
726
-
727
- {/* Mobile menu button */}
728
- <div className="flex items-center md:hidden">
729
- <button
730
- onClick={() => setIsOpen(!isOpen)}
731
- className="
732
- inline-flex items-center justify-center
733
- p-2 rounded-md
734
- text-gray-600 hover:text-gray-900 hover:bg-gray-100
735
- focus:outline-none focus:ring-2 focus:ring-inset focus:ring-blue-500
736
- "
737
- aria-expanded={isOpen}
738
- aria-controls="mobile-menu"
739
- >
740
- <span className="sr-only">
741
- {isOpen ? "Close menu" : "Open menu"}
742
- </span>
743
- {isOpen ? (
744
- <XIcon className="h-6 w-6" />
745
- ) : (
746
- <MenuIcon className="h-6 w-6" />
747
- )}
748
- </button>
749
- </div>
109
+ <nav className="relative">
110
+ <div className="flex justify-between items-center h-16 px-4">
111
+ <Logo />
112
+ {/* Desktop nav */}
113
+ <div className="hidden md:flex items-center space-x-8">
114
+ <NavLink href="/">Home</NavLink>
115
+ <NavLink href="/about">About</NavLink>
750
116
  </div>
117
+ {/* Mobile menu button */}
118
+ <button
119
+ className="md:hidden p-2 min-h-[44px] min-w-[44px]"
120
+ onClick={() => setIsOpen(!isOpen)}
121
+ aria-expanded={isOpen}
122
+ >
123
+ <span className="sr-only">{isOpen ? 'Close' : 'Open'} menu</span>
124
+ {isOpen ? <XIcon /> : <MenuIcon />}
125
+ </button>
751
126
  </div>
752
-
753
- {/* Mobile menu */}
754
- <div
755
- id="mobile-menu"
756
- className={`
757
- md:hidden
758
- ${isOpen ? "block" : "hidden"}
759
- `}
760
- >
761
- <div className="px-2 pt-2 pb-3 space-y-1 bg-white border-t">
762
- <MobileNavLink href="/" onClick={() => setIsOpen(false)}>
763
- Home
764
- </MobileNavLink>
765
- <MobileNavLink href="/products" onClick={() => setIsOpen(false)}>
766
- Products
767
- </MobileNavLink>
768
- <MobileNavLink href="/about" onClick={() => setIsOpen(false)}>
769
- About
770
- </MobileNavLink>
771
- <MobileNavLink href="/contact" onClick={() => setIsOpen(false)}>
772
- Contact
773
- </MobileNavLink>
127
+ {/* Mobile nav */}
128
+ {isOpen && (
129
+ <div className="md:hidden px-2 pb-3 space-y-1">
130
+ <MobileNavLink href="/" onClick={() => setIsOpen(false)}>Home</MobileNavLink>
131
+ <MobileNavLink href="/about" onClick={() => setIsOpen(false)}>About</MobileNavLink>
774
132
  </div>
775
- </div>
133
+ )}
776
134
  </nav>
777
135
  );
778
136
  }
779
-
780
- function MobileNavLink({
781
- href,
782
- children,
783
- onClick,
784
- }: {
785
- href: string;
786
- children: React.ReactNode;
787
- onClick: () => void;
788
- }) {
789
- return (
790
- <a
791
- href={href}
792
- onClick={onClick}
793
- className="
794
- block px-3 py-2 rounded-md
795
- text-base font-medium
796
- text-gray-700 hover:text-gray-900 hover:bg-gray-50
797
- min-h-[44px] flex items-center
798
- "
799
- >
800
- {children}
801
- </a>
802
- );
803
- }
804
- ```
805
-
806
- ## Use Cases
807
-
808
- ### Responsive Card Layout
809
-
810
- ```tsx
811
- export function ResponsiveCardGrid() {
812
- return (
813
- <div
814
- className="
815
- grid gap-4 sm:gap-6
816
- grid-cols-1
817
- sm:grid-cols-2
818
- lg:grid-cols-3
819
- xl:grid-cols-4
820
- "
821
- >
822
- {cards.map((card) => (
823
- <Card key={card.id} className="h-full">
824
- <CardImage src={card.image} alt={card.title} />
825
- <CardContent>
826
- <CardTitle className="line-clamp-2">{card.title}</CardTitle>
827
- <CardDescription className="line-clamp-3">
828
- {card.description}
829
- </CardDescription>
830
- </CardContent>
831
- </Card>
832
- ))}
833
- </div>
834
- );
835
- }
836
137
  ```
837
138
 
838
139
  ## Best Practices
839
140
 
840
- ### Do's
841
-
842
- - Start with mobile-first CSS
843
- - Use relative units (rem, em, %)
844
- - Test on real devices
845
- - Use semantic HTML
846
- - Implement touch-friendly targets
847
- - Consider reduced motion preferences
848
- - Use container queries for components
849
- - Optimize images for different sizes
850
- - Test across browsers
851
- - Use CSS logical properties
852
-
853
- ### Don'ts
854
-
855
- - Don't hide content on mobile unnecessarily
856
- - Don't use fixed widths
857
- - Don't rely only on hover states
858
- - Don't use small touch targets
859
- - Don't ignore landscape orientation
860
- - Don't skip accessibility testing
861
- - Don't use device-specific breakpoints
862
- - Don't forget keyboard navigation
863
- - Don't ignore safe area insets
864
- - Don't assume mouse input
865
-
866
- ## References
867
-
868
- - [CSS Media Queries](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries)
869
- - [Container Queries](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Container_Queries)
870
- - [Responsive Images](https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images)
871
- - [Touch Events](https://developer.mozilla.org/en-US/docs/Web/API/Touch_events)
872
- - [Viewport Concepts](https://developer.mozilla.org/en-US/docs/Web/CSS/Viewport_concepts)
141
+ | Do | Avoid |
142
+ |----|-------|
143
+ | Start with mobile-first CSS | Hiding essential content on mobile |
144
+ | Use relative units (rem, %, vw) | Fixed pixel widths |
145
+ | Test on real devices | Relying only on hover states |
146
+ | Use 44px minimum touch targets | Small touch targets on mobile |
147
+ | Consider reduced motion preferences | Ignoring landscape orientation |
148
+ | Use CSS logical properties (inline, block) | Device-specific breakpoints |
149
+ | Handle safe-area-inset for notched devices | Assuming mouse input |