Initial commit with translated description
This commit is contained in:
301
references/design-philosophy.md
Normal file
301
references/design-philosophy.md
Normal file
@@ -0,0 +1,301 @@
|
||||
# Design Philosophy — Anti-AI-Slop Manifesto
|
||||
|
||||
This reference extends the core SKILL.md with deeper guidance on creating distinctive designs.
|
||||
|
||||
## The Problem: AI Slop
|
||||
|
||||
Generic AI-generated designs share these telltale signs:
|
||||
|
||||
### Typography Sins
|
||||
- Inter, Roboto, Arial everywhere
|
||||
- Timid weight ranges (400-600 only)
|
||||
- Minimal size progression (1.25x-1.5x)
|
||||
- No distinctive pairing strategy
|
||||
|
||||
### Color Crimes
|
||||
- Purple/blue gradient on white (the cardinal sin)
|
||||
- 5+ evenly-distributed colors with no hierarchy
|
||||
- Muted, "safe" palettes that offend no one and delight no one
|
||||
- Gray backgrounds that signal "I gave up"
|
||||
|
||||
### Layout Laziness
|
||||
- Everything centered
|
||||
- Perfectly symmetrical
|
||||
- Predictable card grids
|
||||
- No visual tension or interest
|
||||
|
||||
### Motion Mediocrity
|
||||
- No animations at all, OR
|
||||
- Generic fade-in on every element
|
||||
- No orchestration or timing consideration
|
||||
|
||||
### Background Boredom
|
||||
- Solid white
|
||||
- Solid light gray
|
||||
- Maybe a subtle gradient if feeling "bold"
|
||||
|
||||
---
|
||||
|
||||
## The Solution: Intentional Design
|
||||
|
||||
### Commit to an Extreme
|
||||
|
||||
The middle ground is where designs go to die. Pick a direction and push it:
|
||||
|
||||
**Maximalism Done Right**:
|
||||
- Dense, layered compositions
|
||||
- Overlapping elements with clear hierarchy
|
||||
- Rich textures and patterns
|
||||
- Multiple animations coordinated
|
||||
- Every pixel working
|
||||
|
||||
**Minimalism Done Right**:
|
||||
- Extreme restraint (3 colors max)
|
||||
- Typography as the star
|
||||
- Negative space as intentional element
|
||||
- Single, perfect animation
|
||||
- Nothing extraneous
|
||||
|
||||
Both require courage. Both create memorable designs.
|
||||
|
||||
### Typography as Identity
|
||||
|
||||
Typography isn't decoration — it's the voice of the design.
|
||||
|
||||
**Building a Type Hierarchy**:
|
||||
```css
|
||||
/* Display: Make a statement */
|
||||
.display {
|
||||
font-family: 'Clash Display', sans-serif;
|
||||
font-size: clamp(3rem, 8vw, 6rem);
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.02em;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* Heading: Support the display */
|
||||
.heading {
|
||||
font-family: 'Satoshi', sans-serif;
|
||||
font-size: clamp(1.5rem, 3vw, 2.5rem);
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.01em;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
/* Body: Effortless reading */
|
||||
.body {
|
||||
font-family: 'Plus Jakarta Sans', sans-serif;
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Mono: Technical credibility */
|
||||
.mono {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 0.875rem;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
```
|
||||
|
||||
**Font Pairing Strategies**:
|
||||
|
||||
| Strategy | Display | Body | Effect |
|
||||
|----------|---------|------|--------|
|
||||
| Contrast | Serif (Playfair) | Sans (Inter... no, Plus Jakarta) | Editorial elegance |
|
||||
| Harmony | Geometric (Satoshi) | Geometric (General Sans) | Modern consistency |
|
||||
| Tension | Brutalist (Clash) | Humanist (Source Sans) | Edgy but readable |
|
||||
| Technical | Mono (JetBrains) | Sans (IBM Plex Sans) | Developer-focused |
|
||||
|
||||
### Color as Emotion
|
||||
|
||||
Color isn't about "what looks nice" — it's about what the design FEELS.
|
||||
|
||||
**Building a Palette**:
|
||||
|
||||
```css
|
||||
/* Dark, Confident, Premium */
|
||||
:root {
|
||||
--bg-primary: #0a0a0a;
|
||||
--bg-secondary: #171717;
|
||||
--bg-tertiary: #262626;
|
||||
--text-primary: #fafafa;
|
||||
--text-secondary: #a3a3a3;
|
||||
--accent: #22c55e; /* Confident green */
|
||||
--accent-subtle: rgba(34, 197, 94, 0.1);
|
||||
}
|
||||
|
||||
/* Light, Warm, Approachable */
|
||||
:root {
|
||||
--bg-primary: #fffbf5;
|
||||
--bg-secondary: #fff7ed;
|
||||
--bg-tertiary: #ffedd5;
|
||||
--text-primary: #1c1917;
|
||||
--text-secondary: #78716c;
|
||||
--accent: #ea580c; /* Warm orange */
|
||||
--accent-subtle: rgba(234, 88, 12, 0.1);
|
||||
}
|
||||
|
||||
/* High Contrast, Editorial */
|
||||
:root {
|
||||
--bg-primary: #ffffff;
|
||||
--bg-secondary: #f5f5f5;
|
||||
--text-primary: #000000;
|
||||
--text-secondary: #525252;
|
||||
--accent: #dc2626; /* Bold red */
|
||||
--accent-subtle: rgba(220, 38, 38, 0.05);
|
||||
}
|
||||
```
|
||||
|
||||
**The 60-30-10 Rule**:
|
||||
- 60% dominant (background)
|
||||
- 30% secondary (cards, sections)
|
||||
- 10% accent (CTAs, highlights)
|
||||
|
||||
### Motion as Narrative
|
||||
|
||||
Animation tells a story. What's your story?
|
||||
|
||||
**Page Load Orchestration**:
|
||||
```css
|
||||
/* Hero elements enter in sequence */
|
||||
.hero-badge {
|
||||
animation: fadeSlideUp 0.6s ease-out 0.1s both;
|
||||
}
|
||||
.hero-title {
|
||||
animation: fadeSlideUp 0.6s ease-out 0.2s both;
|
||||
}
|
||||
.hero-subtitle {
|
||||
animation: fadeSlideUp 0.6s ease-out 0.3s both;
|
||||
}
|
||||
.hero-cta {
|
||||
animation: fadeSlideUp 0.6s ease-out 0.4s both;
|
||||
}
|
||||
|
||||
@keyframes fadeSlideUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Scroll-Triggered Reveals**:
|
||||
```javascript
|
||||
// Intersection Observer for scroll animations
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.classList.add('animate-in');
|
||||
}
|
||||
});
|
||||
}, { threshold: 0.1 });
|
||||
|
||||
document.querySelectorAll('.reveal').forEach(el => observer.observe(el));
|
||||
```
|
||||
|
||||
**Hover States That Surprise**:
|
||||
```css
|
||||
.card {
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow:
|
||||
0 20px 40px rgba(0, 0, 0, 0.1),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
/* Or more dramatic */
|
||||
.card:hover {
|
||||
transform: scale(1.02) rotate(-0.5deg);
|
||||
}
|
||||
```
|
||||
|
||||
### Backgrounds as Atmosphere
|
||||
|
||||
The background sets the mood before any content is read.
|
||||
|
||||
**Gradient Mesh**:
|
||||
```css
|
||||
.gradient-mesh {
|
||||
background:
|
||||
radial-gradient(at 40% 20%, hsla(28, 100%, 74%, 0.3) 0px, transparent 50%),
|
||||
radial-gradient(at 80% 0%, hsla(189, 100%, 56%, 0.2) 0px, transparent 50%),
|
||||
radial-gradient(at 0% 50%, hsla(355, 100%, 93%, 0.3) 0px, transparent 50%),
|
||||
radial-gradient(at 80% 50%, hsla(340, 100%, 76%, 0.2) 0px, transparent 50%),
|
||||
radial-gradient(at 0% 100%, hsla(269, 100%, 77%, 0.3) 0px, transparent 50%);
|
||||
}
|
||||
```
|
||||
|
||||
**Noise Texture**:
|
||||
```css
|
||||
.noise::before {
|
||||
content: '';
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 400 400' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)'/%3E%3C/svg%3E");
|
||||
opacity: 0.03;
|
||||
pointer-events: none;
|
||||
z-index: 1000;
|
||||
}
|
||||
```
|
||||
|
||||
**Dot Pattern**:
|
||||
```css
|
||||
.dots {
|
||||
background-image: radial-gradient(circle, #333 1px, transparent 1px);
|
||||
background-size: 20px 20px;
|
||||
}
|
||||
```
|
||||
|
||||
**Glassmorphism**:
|
||||
```css
|
||||
.glass {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Design Decision Framework
|
||||
|
||||
When stuck, ask these questions:
|
||||
|
||||
1. **What's the ONE thing?** — If users remember one element, what is it?
|
||||
2. **Would I screenshot this?** — Is there a moment worth sharing?
|
||||
3. **Does it feel designed?** — Or does it feel generated?
|
||||
4. **What's the emotion?** — Confident? Playful? Serious? Luxurious?
|
||||
5. **Is it brave?** — Did I play it safe or commit to a direction?
|
||||
|
||||
---
|
||||
|
||||
## Anti-Pattern Detection
|
||||
|
||||
Before shipping, scan for these:
|
||||
|
||||
| Anti-Pattern | Fix |
|
||||
|--------------|-----|
|
||||
| Inter font | Replace with distinctive alternative |
|
||||
| Purple gradient | Choose contextual palette |
|
||||
| All centered | Add asymmetry or left-align |
|
||||
| No animations | Add orchestrated page load |
|
||||
| Solid background | Add texture, gradient, or pattern |
|
||||
| Evenly spaced colors | Apply 60-30-10 rule |
|
||||
| Generic cards | Add unique styling treatment |
|
||||
| Default shadows | Use layered, atmospheric shadows |
|
||||
|
||||
---
|
||||
|
||||
*Remember: Claude is capable of extraordinary creative work. Don't hold back.*
|
||||
519
references/mobile-patterns.md
Normal file
519
references/mobile-patterns.md
Normal file
@@ -0,0 +1,519 @@
|
||||
# Mobile-First Patterns
|
||||
|
||||
Comprehensive responsive CSS patterns learned from real-world implementation failures.
|
||||
|
||||
## Hero Sections
|
||||
|
||||
### Problem
|
||||
2-column grid layouts leave empty space when one column is hidden on mobile.
|
||||
|
||||
### Solution
|
||||
Switch from `display: grid` to `display: flex` on mobile.
|
||||
|
||||
```css
|
||||
/* Desktop: 2-column grid */
|
||||
.hero {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 64px;
|
||||
align-items: center;
|
||||
padding: 80px 0;
|
||||
}
|
||||
|
||||
/* Mobile: Centered flex */
|
||||
@media (max-width: 768px) {
|
||||
.hero {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.hero-content {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.hero-badge {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
font-size: 32px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.hero-subtitle {
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.hero-cta {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.hero-cta .btn {
|
||||
width: 100%;
|
||||
max-width: 280px;
|
||||
}
|
||||
|
||||
.hero-visual {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Key Rule**: Grid reserves space for hidden columns. Flex doesn't.
|
||||
|
||||
---
|
||||
|
||||
## Large Selection Lists
|
||||
|
||||
### Problem
|
||||
Horizontal scroll for 20+ items is unusable on mobile — text gets cut off.
|
||||
|
||||
### Solution
|
||||
Collapsible accordion with category headers.
|
||||
|
||||
```tsx
|
||||
function MobileSelector({ categories }) {
|
||||
const [expanded, setExpanded] = useState<string | null>(null);
|
||||
|
||||
return (
|
||||
<div className="selector">
|
||||
{categories.map(cat => (
|
||||
<div
|
||||
key={cat.name}
|
||||
className={cn("category", expanded === cat.name && "expanded")}
|
||||
>
|
||||
<button
|
||||
className="category-header"
|
||||
onClick={() => setExpanded(
|
||||
expanded === cat.name ? null : cat.name
|
||||
)}
|
||||
>
|
||||
<span>{cat.name}</span>
|
||||
<ChevronDown className={cn(
|
||||
"transition-transform",
|
||||
expanded === cat.name && "rotate-180"
|
||||
)} />
|
||||
</button>
|
||||
|
||||
<div className="category-items">
|
||||
{cat.items.map(item => (
|
||||
<button key={item.id} className="item">
|
||||
{item.name}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
```css
|
||||
.category-items {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.category-items {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.category.expanded .category-items {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 12px;
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 8px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Form Layouts
|
||||
|
||||
### Problem
|
||||
Multi-column form layouts get cut off on mobile.
|
||||
|
||||
### Solution
|
||||
Stack vertically with full width.
|
||||
|
||||
```css
|
||||
.form-row {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.form-row {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.form-row.half-width {
|
||||
/* Even "half width" fields go full on mobile */
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Status/Alert Cards
|
||||
|
||||
### Problem
|
||||
Inconsistent text alignment when stacking horizontal elements vertically.
|
||||
|
||||
### Solution
|
||||
Both `align-items: center` AND `text-align: center`.
|
||||
|
||||
```css
|
||||
.alert {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.alert-icon {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.alert-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.alert {
|
||||
flex-direction: column;
|
||||
align-items: center; /* Center flex items */
|
||||
text-align: center; /* Center text within items */
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.alert-content {
|
||||
text-align: center; /* Explicit for nested elements */
|
||||
}
|
||||
|
||||
.alert strong {
|
||||
text-align: center; /* Block elements need explicit */
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Key Rule**: Stacked flex items need BOTH `align-items: center` AND `text-align: center`.
|
||||
|
||||
---
|
||||
|
||||
## Grid Layouts
|
||||
|
||||
### Universal Mobile Collapse
|
||||
|
||||
```css
|
||||
.pricing-grid,
|
||||
.feature-grid,
|
||||
.team-grid,
|
||||
.stats-grid,
|
||||
.testimonial-grid {
|
||||
display: grid;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
/* Desktop configurations */
|
||||
.pricing-grid { grid-template-columns: repeat(3, 1fr); }
|
||||
.feature-grid { grid-template-columns: repeat(3, 1fr); }
|
||||
.team-grid { grid-template-columns: repeat(4, 1fr); }
|
||||
.stats-grid { grid-template-columns: repeat(4, 1fr); }
|
||||
.testimonial-grid { grid-template-columns: repeat(2, 1fr); }
|
||||
|
||||
/* Tablet */
|
||||
@media (max-width: 1024px) {
|
||||
.team-grid { grid-template-columns: repeat(2, 1fr); }
|
||||
.stats-grid { grid-template-columns: repeat(2, 1fr); }
|
||||
}
|
||||
|
||||
/* Mobile: Everything single column */
|
||||
@media (max-width: 768px) {
|
||||
.pricing-grid,
|
||||
.feature-grid,
|
||||
.team-grid,
|
||||
.stats-grid,
|
||||
.testimonial-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Navigation
|
||||
|
||||
### Mobile Menu Pattern
|
||||
|
||||
```tsx
|
||||
function MobileNav() {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Mobile menu button */}
|
||||
<button
|
||||
className="md:hidden"
|
||||
onClick={() => setOpen(!open)}
|
||||
>
|
||||
{open ? <X /> : <Menu />}
|
||||
</button>
|
||||
|
||||
{/* Mobile menu overlay */}
|
||||
<div className={cn(
|
||||
"fixed inset-0 bg-black/50 md:hidden transition-opacity",
|
||||
open ? "opacity-100" : "opacity-0 pointer-events-none"
|
||||
)} onClick={() => setOpen(false)} />
|
||||
|
||||
{/* Mobile menu panel */}
|
||||
<nav className={cn(
|
||||
"fixed top-0 right-0 h-full w-64 bg-background p-6",
|
||||
"transform transition-transform md:hidden",
|
||||
open ? "translate-x-0" : "translate-x-full"
|
||||
)}>
|
||||
{/* Nav items */}
|
||||
</nav>
|
||||
</>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Form Element Consistency
|
||||
|
||||
### Always Style as a Group
|
||||
|
||||
```css
|
||||
/* WRONG - Only targets input */
|
||||
.input {
|
||||
border: 2px solid var(--border);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
/* CORRECT - All form fields */
|
||||
.input,
|
||||
.select,
|
||||
.textarea {
|
||||
border: 2px solid var(--border);
|
||||
border-radius: 8px;
|
||||
padding: 12px 16px;
|
||||
font-size: 16px; /* Prevents iOS zoom */
|
||||
background: var(--bg-secondary);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
```
|
||||
|
||||
### Textarea Border Radius Exception
|
||||
|
||||
Pill-shaped inputs look wrong on textareas:
|
||||
|
||||
```css
|
||||
.input,
|
||||
.select {
|
||||
border-radius: 100px; /* Pill shape */
|
||||
}
|
||||
|
||||
.textarea {
|
||||
border-radius: 16px; /* Softer, but not pill */
|
||||
}
|
||||
```
|
||||
|
||||
### Dropdown Option Styling
|
||||
|
||||
`<option>` elements can't inherit backdrop-filter:
|
||||
|
||||
```css
|
||||
.select {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(10px);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Options need solid backgrounds */
|
||||
.select option {
|
||||
background: #1a1a2e;
|
||||
color: white;
|
||||
}
|
||||
```
|
||||
|
||||
### Prevent iOS Zoom on Focus
|
||||
|
||||
iOS zooms on inputs with font-size < 16px:
|
||||
|
||||
```css
|
||||
input, select, textarea {
|
||||
font-size: 16px; /* Minimum to prevent zoom */
|
||||
}
|
||||
|
||||
/* Or use transform trick */
|
||||
@media (max-width: 768px) {
|
||||
input, select, textarea {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Color Contrast Checklist
|
||||
|
||||
### Badge/Pill Elements
|
||||
|
||||
```css
|
||||
/* WRONG - May be invisible */
|
||||
.badge {
|
||||
background: var(--accent);
|
||||
color: white; /* Might not contrast */
|
||||
}
|
||||
|
||||
/* CORRECT - Ensure contrast */
|
||||
.badge {
|
||||
background: var(--accent);
|
||||
color: var(--accent-foreground); /* Defined to contrast */
|
||||
}
|
||||
```
|
||||
|
||||
### Color Swatches
|
||||
|
||||
Swatches showing colors need visible borders:
|
||||
|
||||
```css
|
||||
.color-swatch {
|
||||
border: 2px solid rgba(255, 255, 255, 0.15);
|
||||
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
```
|
||||
|
||||
### Dark Theme Form Labels
|
||||
|
||||
```css
|
||||
/* WRONG - Hardcoded */
|
||||
.label {
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* CORRECT - Semantic variable */
|
||||
.label {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Breakpoint Reference
|
||||
|
||||
```css
|
||||
/* Large Desktop */
|
||||
@media (min-width: 1440px) {
|
||||
.container { max-width: 1280px; }
|
||||
}
|
||||
|
||||
/* Desktop */
|
||||
@media (max-width: 1200px) {
|
||||
/* Stack sidebars, maintain content width */
|
||||
}
|
||||
|
||||
/* Tablet */
|
||||
@media (max-width: 1024px) {
|
||||
/* Reduce grid columns */
|
||||
}
|
||||
|
||||
/* Mobile */
|
||||
@media (max-width: 768px) {
|
||||
/* Full single-column, centered content */
|
||||
}
|
||||
|
||||
/* Small Mobile */
|
||||
@media (max-width: 480px) {
|
||||
/* Compact spacing, reduced font sizes */
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Mobile Font Scaling
|
||||
|
||||
```css
|
||||
/* Base (desktop) */
|
||||
.display { font-size: 64px; }
|
||||
.h1 { font-size: 48px; }
|
||||
.h2 { font-size: 36px; }
|
||||
.h3 { font-size: 24px; }
|
||||
.body { font-size: 16px; }
|
||||
.small { font-size: 14px; }
|
||||
|
||||
/* Mobile */
|
||||
@media (max-width: 768px) {
|
||||
.display { font-size: 40px; }
|
||||
.h1 { font-size: 32px; }
|
||||
.h2 { font-size: 24px; }
|
||||
.h3 { font-size: 20px; }
|
||||
.body { font-size: 16px; } /* Keep readable */
|
||||
.small { font-size: 13px; }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Touch Target Sizes
|
||||
|
||||
Minimum 44x44px for touch targets (Apple HIG):
|
||||
|
||||
```css
|
||||
.btn,
|
||||
.nav-link,
|
||||
.icon-btn {
|
||||
min-height: 44px;
|
||||
min-width: 44px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.btn {
|
||||
padding: 14px 24px; /* Larger touch area */
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Pre-Implementation Checklist
|
||||
|
||||
Before finalizing any mobile design:
|
||||
|
||||
- [ ] Hero centers on mobile (not left-aligned with empty space)
|
||||
- [ ] All form fields (input, select, textarea) styled consistently
|
||||
- [ ] Radio/checkboxes visible (especially transparent-border styles)
|
||||
- [ ] Dropdown options have readable backgrounds
|
||||
- [ ] Labels use semantic color variables
|
||||
- [ ] Status/alert cards center properly
|
||||
- [ ] Large selection lists use accordion (not horizontal scroll)
|
||||
- [ ] Grid layouts collapse to single column
|
||||
- [ ] Badge/pill text contrasts with background
|
||||
- [ ] Color swatches have visible borders
|
||||
- [ ] Touch targets are 44x44px minimum
|
||||
- [ ] Font sizes are 16px+ to prevent iOS zoom
|
||||
- [ ] Navigation has mobile menu pattern
|
||||
342
references/shadcn-components.md
Normal file
342
references/shadcn-components.md
Normal file
@@ -0,0 +1,342 @@
|
||||
# shadcn/ui Component Reference
|
||||
|
||||
Quick reference for the 40+ pre-installed shadcn/ui components.
|
||||
|
||||
Documentation: https://ui.shadcn.com/docs/components
|
||||
|
||||
## Most Used for Landing Pages
|
||||
|
||||
| Component | Use Case | Example |
|
||||
|-----------|----------|---------|
|
||||
| `Button` | CTAs, actions | Hero buttons, form submits |
|
||||
| `Badge` | Labels, status | "New", "Popular", "Beta" |
|
||||
| `Card` | Content containers | Feature cards, pricing tiers |
|
||||
| `Accordion` | Collapsible content | FAQ sections |
|
||||
| `Dialog` | Modals | Video players, signup forms |
|
||||
| `NavigationMenu` | Header navigation | Main nav with dropdowns |
|
||||
| `Tabs` | Tabbed content | Feature showcases |
|
||||
| `Carousel` | Sliding content | Testimonials, galleries |
|
||||
|
||||
## Full Component List
|
||||
|
||||
### Layout & Navigation
|
||||
- `Accordion` — Collapsible sections
|
||||
- `Breadcrumb` — Navigation trail
|
||||
- `Carousel` — Sliding content
|
||||
- `Collapsible` — Expand/collapse
|
||||
- `NavigationMenu` — Header nav with dropdowns
|
||||
- `Pagination` — Page navigation
|
||||
- `Resizable` — Resizable panels
|
||||
- `Scroll-Area` — Custom scrollbars
|
||||
- `Separator` — Visual divider
|
||||
- `Sheet` — Slide-out panels
|
||||
- `Sidebar` — App sidebars
|
||||
- `Tabs` — Tabbed content
|
||||
|
||||
### Data Display
|
||||
- `Avatar` — User images
|
||||
- `Badge` — Labels and status
|
||||
- `Card` — Content container
|
||||
- `HoverCard` — Hover popups
|
||||
- `Table` — Data tables
|
||||
|
||||
### Forms
|
||||
- `Button` — Actions
|
||||
- `Checkbox` — Multi-select
|
||||
- `Combobox` — Searchable select
|
||||
- `DatePicker` — Date selection
|
||||
- `Form` — Form wrapper with validation
|
||||
- `Input` — Text input
|
||||
- `InputOTP` — One-time password
|
||||
- `Label` — Form labels
|
||||
- `RadioGroup` — Single select
|
||||
- `Select` — Dropdown select
|
||||
- `Slider` — Range selection
|
||||
- `Switch` — Toggle
|
||||
- `Textarea` — Multi-line input
|
||||
- `Toggle` — Toggle button
|
||||
- `ToggleGroup` — Button group
|
||||
|
||||
### Feedback
|
||||
- `Alert` — Info messages
|
||||
- `AlertDialog` — Confirmation dialogs
|
||||
- `Dialog` — Modal windows
|
||||
- `Drawer` — Bottom sheets
|
||||
- `Popover` — Popup content
|
||||
- `Progress` — Loading bars
|
||||
- `Skeleton` — Loading placeholders
|
||||
- `Sonner` — Toast notifications
|
||||
- `Toast` — Notifications
|
||||
- `Tooltip` — Hover hints
|
||||
|
||||
### Utilities
|
||||
- `AspectRatio` — Maintain ratios
|
||||
- `Calendar` — Date display
|
||||
- `Chart` — Data visualization
|
||||
- `Command` — Command palette
|
||||
- `ContextMenu` — Right-click menus
|
||||
- `DropdownMenu` — Dropdown menus
|
||||
- `Menubar` — App menubars
|
||||
|
||||
---
|
||||
|
||||
## Code Examples
|
||||
|
||||
### Hero with Badge and Buttons
|
||||
|
||||
```tsx
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
function Hero() {
|
||||
return (
|
||||
<section className="py-24 text-center">
|
||||
<Badge variant="secondary" className="mb-4">
|
||||
Now in beta
|
||||
</Badge>
|
||||
|
||||
<h1 className="text-5xl font-bold mb-6">
|
||||
Your headline here
|
||||
</h1>
|
||||
|
||||
<p className="text-xl text-muted-foreground mb-8 max-w-2xl mx-auto">
|
||||
Subheadline with more details about your product.
|
||||
</p>
|
||||
|
||||
<div className="flex gap-4 justify-center">
|
||||
<Button size="lg">Get Started</Button>
|
||||
<Button size="lg" variant="outline">Learn More</Button>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Feature Cards
|
||||
|
||||
```tsx
|
||||
import { Card, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
|
||||
import { Zap, Shield, Globe } from "lucide-react"
|
||||
|
||||
const features = [
|
||||
{ icon: Zap, title: "Fast", description: "Lightning quick performance" },
|
||||
{ icon: Shield, title: "Secure", description: "Enterprise-grade security" },
|
||||
{ icon: Globe, title: "Global", description: "CDN in 200+ locations" },
|
||||
]
|
||||
|
||||
function Features() {
|
||||
return (
|
||||
<section className="py-24">
|
||||
<div className="grid md:grid-cols-3 gap-8">
|
||||
{features.map((f) => (
|
||||
<Card key={f.title}>
|
||||
<CardHeader>
|
||||
<f.icon className="h-10 w-10 mb-4 text-primary" />
|
||||
<CardTitle>{f.title}</CardTitle>
|
||||
<CardDescription>{f.description}</CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Pricing Table
|
||||
|
||||
```tsx
|
||||
import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from "@/components/ui/card"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Check } from "lucide-react"
|
||||
|
||||
const plans = [
|
||||
{ name: "Free", price: 0, features: ["5 projects", "Basic support"] },
|
||||
{ name: "Pro", price: 19, features: ["Unlimited projects", "Priority support", "API access"], popular: true },
|
||||
{ name: "Team", price: 49, features: ["Everything in Pro", "Team features", "SSO"] },
|
||||
]
|
||||
|
||||
function Pricing() {
|
||||
return (
|
||||
<section className="py-24">
|
||||
<div className="grid md:grid-cols-3 gap-8">
|
||||
{plans.map((plan) => (
|
||||
<Card key={plan.name} className={plan.popular ? "border-primary" : ""}>
|
||||
<CardHeader>
|
||||
{plan.popular && <Badge className="w-fit mb-2">Most Popular</Badge>}
|
||||
<CardTitle>{plan.name}</CardTitle>
|
||||
<CardDescription>
|
||||
<span className="text-4xl font-bold">${plan.price}</span>
|
||||
<span className="text-muted-foreground">/month</span>
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ul className="space-y-2">
|
||||
{plan.features.map((f) => (
|
||||
<li key={f} className="flex items-center gap-2">
|
||||
<Check className="h-4 w-4 text-primary" />
|
||||
{f}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<Button className="w-full" variant={plan.popular ? "default" : "outline"}>
|
||||
Get Started
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### FAQ Accordion
|
||||
|
||||
```tsx
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from "@/components/ui/accordion"
|
||||
|
||||
const faqs = [
|
||||
{ q: "How does it work?", a: "Our platform uses AI to..." },
|
||||
{ q: "Is there a free trial?", a: "Yes, you get 14 days free..." },
|
||||
{ q: "Can I cancel anytime?", a: "Absolutely, no questions asked..." },
|
||||
]
|
||||
|
||||
function FAQ() {
|
||||
return (
|
||||
<section className="py-24 max-w-3xl mx-auto">
|
||||
<h2 className="text-3xl font-bold text-center mb-12">
|
||||
Frequently Asked Questions
|
||||
</h2>
|
||||
|
||||
<Accordion type="single" collapsible>
|
||||
{faqs.map((faq, i) => (
|
||||
<AccordionItem key={i} value={`item-${i}`}>
|
||||
<AccordionTrigger>{faq.q}</AccordionTrigger>
|
||||
<AccordionContent>{faq.a}</AccordionContent>
|
||||
</AccordionItem>
|
||||
))}
|
||||
</Accordion>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Mobile Navigation with Sheet
|
||||
|
||||
```tsx
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet"
|
||||
import { Menu } from "lucide-react"
|
||||
|
||||
function MobileNav() {
|
||||
return (
|
||||
<Sheet>
|
||||
<SheetTrigger asChild>
|
||||
<Button variant="ghost" size="icon" className="md:hidden">
|
||||
<Menu className="h-6 w-6" />
|
||||
</Button>
|
||||
</SheetTrigger>
|
||||
<SheetContent side="right">
|
||||
<nav className="flex flex-col gap-4 mt-8">
|
||||
<a href="#features">Features</a>
|
||||
<a href="#pricing">Pricing</a>
|
||||
<a href="#faq">FAQ</a>
|
||||
<Button className="mt-4">Get Started</Button>
|
||||
</nav>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Video Modal with Dialog
|
||||
|
||||
```tsx
|
||||
import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Play } from "lucide-react"
|
||||
|
||||
function VideoModal() {
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="outline" size="lg">
|
||||
<Play className="mr-2 h-4 w-4" />
|
||||
Watch Demo
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-w-4xl p-0">
|
||||
<div className="aspect-video">
|
||||
<iframe
|
||||
src="https://www.youtube.com/embed/..."
|
||||
className="w-full h-full"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
/>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Styling Tips
|
||||
|
||||
### Customizing Colors
|
||||
|
||||
shadcn uses CSS variables. Override in your globals.css:
|
||||
|
||||
```css
|
||||
:root {
|
||||
--primary: 220 90% 56%;
|
||||
--primary-foreground: 0 0% 100%;
|
||||
--accent: 25 95% 53%;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--primary: 220 90% 66%;
|
||||
}
|
||||
```
|
||||
|
||||
### Extending Variants
|
||||
|
||||
```tsx
|
||||
// components/ui/button.tsx
|
||||
const buttonVariants = cva(
|
||||
"...",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "...",
|
||||
// Add custom variant
|
||||
gradient: "bg-gradient-to-r from-primary to-accent text-white hover:opacity-90",
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### Using with Tailwind
|
||||
|
||||
All components accept `className` for additional styling:
|
||||
|
||||
```tsx
|
||||
<Button className="rounded-full px-8">
|
||||
Pill Button
|
||||
</Button>
|
||||
|
||||
<Card className="bg-gradient-to-br from-primary/10 to-accent/10">
|
||||
Gradient Card
|
||||
</Card>
|
||||
```
|
||||
Reference in New Issue
Block a user