File
Blob: .impeccable/design.json
| 1 | { |
| 2 | "schemaVersion": 2, |
| 3 | "generatedAt": "2026-05-17T00:00:00Z", |
| 4 | "title": "Design System: bland", |
| 5 | "extensions": { |
| 6 | "colorMeta": { |
| 7 | "accent-500": { |
| 8 | "role": "primary", |
| 9 | "displayName": "Warm Amethyst", |
| 10 | "canonical": "#9d6ee8", |
| 11 | "hue": "263°", |
| 12 | "saturation": "~73%", |
| 13 | "lightness": "~67%", |
| 14 | "tonalRamp": [ |
| 15 | "#4d2a7c", |
| 16 | "#5e3497", |
| 17 | "#7241b8", |
| 18 | "#8854d4", |
| 19 | "#9d6ee8", |
| 20 | "#b48ef5", |
| 21 | "#ccb4fb", |
| 22 | "#e2d7fe", |
| 23 | "#f1ecfe", |
| 24 | "#faf7ff" |
| 25 | ] |
| 26 | }, |
| 27 | "canvas": { |
| 28 | "role": "neutral", |
| 29 | "displayName": "Lifted Warm Canvas", |
| 30 | "canonical": "#221f21", |
| 31 | "tonalRamp": ["#0c090b", "#1b181a", "#221f21", "#2a2729", "#423f42", "#555259", "#747178", "#a3a1a8"] |
| 32 | }, |
| 33 | "zinc-900": { |
| 34 | "role": "neutral", |
| 35 | "displayName": "Warm Chrome", |
| 36 | "canonical": "#1b181a", |
| 37 | "note": "Sidebar, header, recessed containers. Darker than canvas — chrome frames the layout." |
| 38 | }, |
| 39 | "zinc-800": { |
| 40 | "role": "neutral", |
| 41 | "displayName": "Warm Elevated", |
| 42 | "canonical": "#2a2729", |
| 43 | "note": "Menus, dialogs, dropdowns, popovers, inputs. Lighter than canvas — floating feel." |
| 44 | }, |
| 45 | "zinc-700": { |
| 46 | "role": "neutral", |
| 47 | "displayName": "Warm Overlay Hover", |
| 48 | "canonical": "#423f42", |
| 49 | "note": "Hover states inside overlays, borders, scrollbar thumb, overlay-surface for floating toolbars." |
| 50 | }, |
| 51 | "zinc-500": { |
| 52 | "role": "neutral", |
| 53 | "displayName": "Warm Muted", |
| 54 | "canonical": "#747178", |
| 55 | "note": "Placeholder text, blockquote refs. Passes WCAG AA on canvas (~5.5:1)." |
| 56 | }, |
| 57 | "zinc-300": { |
| 58 | "role": "neutral", |
| 59 | "displayName": "Editor Body", |
| 60 | "canonical": "#d6d4d7", |
| 61 | "note": "Default editor body text color." |
| 62 | }, |
| 63 | "zinc-100": { |
| 64 | "role": "neutral", |
| 65 | "displayName": "Primary Body", |
| 66 | "canonical": "#f5f4f4", |
| 67 | "note": "Primary body text, heading color in editor." |
| 68 | }, |
| 69 | "callout-info": { |
| 70 | "role": "tertiary", |
| 71 | "displayName": "Slate Info", |
| 72 | "canonical": "oklch(0.82 0.06 265)", |
| 73 | "hex": "#b6c8e6", |
| 74 | "note": "Low-chroma cool slate for :::info callouts. Tuned for astigmatism comfort." |
| 75 | }, |
| 76 | "callout-tip": { |
| 77 | "role": "tertiary", |
| 78 | "displayName": "Pale Amethyst Tip", |
| 79 | "canonical": "oklch(0.89 0.05 295)", |
| 80 | "hex": "#d8c8eb", |
| 81 | "note": "Low-chroma amethyst-adjacent for :::tip callouts." |
| 82 | }, |
| 83 | "callout-warning": { |
| 84 | "role": "tertiary", |
| 85 | "displayName": "Muted Warm Gold", |
| 86 | "canonical": "oklch(0.82 0.07 62)", |
| 87 | "hex": "#e2c598", |
| 88 | "note": "Muted gold for :::warning callouts. Deliberately not a fire alarm." |
| 89 | } |
| 90 | }, |
| 91 | "typographyMeta": { |
| 92 | "display": { |
| 93 | "displayName": "Display (Bricolage)", |
| 94 | "purpose": "Editor H1 / page title only. Reserved for hero sizes — overuse dilutes its character." |
| 95 | }, |
| 96 | "headline": { |
| 97 | "displayName": "Headline (Outfit 600)", |
| 98 | "purpose": "Editor H2. Tighter tracking for visual weight at 1.5rem." |
| 99 | }, |
| 100 | "title": { |
| 101 | "displayName": "Title (Outfit 600)", |
| 102 | "purpose": "Editor H3, card titles." |
| 103 | }, |
| 104 | "body": { |
| 105 | "displayName": "Body (Outfit 450)", |
| 106 | "purpose": "Editor prose. The 450 weight and 1.7 line-height are non-negotiable — primary halation defense for astigmatic readers." |
| 107 | }, |
| 108 | "label": { |
| 109 | "displayName": "Label (Outfit 500, wide tracking)", |
| 110 | "purpose": "Overlines, meta labels, caption text. Often uppercase." |
| 111 | }, |
| 112 | "mono": { |
| 113 | "displayName": "Mono (JetBrains Mono)", |
| 114 | "purpose": "Code only — inline or block. Never extend to nav, headings, or body." |
| 115 | } |
| 116 | }, |
| 117 | "shadows": [ |
| 118 | { |
| 119 | "name": "overlay-sm", |
| 120 | "value": "0 1px 2px 0 rgb(0 0 0 / 0.20)", |
| 121 | "purpose": "Subtle drop under floating toolbars, dropdowns, drag handles. Tailwind shadow-sm class." |
| 122 | }, |
| 123 | { |
| 124 | "name": "dialog-2xl", |
| 125 | "value": "0 25px 50px -12px rgb(0 0 0 / 0.50)", |
| 126 | "purpose": "Reserved for modals/dialogs only. Tailwind shadow-2xl class." |
| 127 | } |
| 128 | ], |
| 129 | "motion": [ |
| 130 | { |
| 131 | "name": "default-transition", |
| 132 | "value": "75ms", |
| 133 | "purpose": "Default transition-duration override. Scope to specific properties — never transition-all." |
| 134 | }, |
| 135 | { |
| 136 | "name": "ease-snappy", |
| 137 | "value": "cubic-bezier(0.16, 1, 0.3, 1)", |
| 138 | "purpose": "Out-quart curve for scale-fade entrances. Strong initial motion, soft landing." |
| 139 | }, |
| 140 | { |
| 141 | "name": "fade-in", |
| 142 | "value": "fade-in 0.4s ease-out both", |
| 143 | "purpose": "Main content areas on page load. Opacity 0 → 1." |
| 144 | }, |
| 145 | { |
| 146 | "name": "slide-up", |
| 147 | "value": "slide-up 0.35s ease-out both", |
| 148 | "purpose": "Page wrappers, modal/dialog entrances, toast entrance. Opacity 0 + translateY(12px) → visible." |
| 149 | }, |
| 150 | { |
| 151 | "name": "scale-fade", |
| 152 | "value": "scale-fade 0.3s cubic-bezier(0.16, 1, 0.3, 1) both", |
| 153 | "purpose": "Cards, popovers, dropdown menus. Opacity 0 + scale(0.96) → visible — scale implies origin." |
| 154 | }, |
| 155 | { |
| 156 | "name": "shimmer", |
| 157 | "value": "shimmer 1.5s ease-in-out infinite", |
| 158 | "purpose": "Loading skeleton placeholder." |
| 159 | }, |
| 160 | { |
| 161 | "name": "caret-blink", |
| 162 | "value": "caret-blink 1.1s steps(2, start) infinite", |
| 163 | "purpose": "Editor caret. Slower than OS default for calmer rhythm." |
| 164 | }, |
| 165 | { |
| 166 | "name": "stagger-step", |
| 167 | "value": "60ms", |
| 168 | "purpose": "Staggered reveal delay per sibling. Cap at 8 items (480ms total)." |
| 169 | } |
| 170 | ], |
| 171 | "breakpoints": [ |
| 172 | { "name": "sm", "value": "640px" }, |
| 173 | { "name": "md", "value": "768px" }, |
| 174 | { "name": "lg", "value": "1024px" }, |
| 175 | { "name": "xl", "value": "1280px" } |
| 176 | ] |
| 177 | }, |
| 178 | "components": [ |
| 179 | { |
| 180 | "name": "Primary Button", |
| 181 | "kind": "button", |
| 182 | "refersTo": "button-primary", |
| 183 | "description": "Solid accent CTA. Never gradient.", |
| 184 | "html": "<button class=\"ds-btn-primary\">Get started</button>", |
| 185 | "css": ".ds-btn-primary { background: #8854d4; color: #fff; font-family: 'Outfit Variable', ui-sans-serif, system-ui, sans-serif; font-weight: 500; font-size: 0.875rem; padding: 0.5rem 1rem; border: none; border-radius: 0.5rem; cursor: pointer; transition: background-color 75ms, transform 75ms; } .ds-btn-primary:hover { background: #9d6ee8; } .ds-btn-primary:active { transform: scale(0.98); } .ds-btn-primary:focus-visible { outline: 2px solid rgba(157, 110, 232, 0.5); outline-offset: 2px; }" |
| 186 | }, |
| 187 | { |
| 188 | "name": "Secondary Button", |
| 189 | "kind": "button", |
| 190 | "refersTo": "button-secondary", |
| 191 | "description": "Subtle elevated surface, neutral border. Never accent.", |
| 192 | "html": "<button class=\"ds-btn-secondary\">Cancel</button>", |
| 193 | "css": ".ds-btn-secondary { background: rgba(42, 39, 41, 0.6); color: #d6d4d7; font-family: 'Outfit Variable', ui-sans-serif, system-ui, sans-serif; font-weight: 500; font-size: 0.875rem; padding: 0.5rem 1rem; border: 1px solid rgba(66, 63, 66, 0.6); border-radius: 0.5rem; cursor: pointer; transition: background-color 75ms, transform 75ms; } .ds-btn-secondary:hover { background: rgba(66, 63, 66, 0.6); color: #f5f4f4; } .ds-btn-secondary:active { transform: scale(0.98); }" |
| 194 | }, |
| 195 | { |
| 196 | "name": "Ghost Button", |
| 197 | "kind": "button", |
| 198 | "refersTo": "button-ghost", |
| 199 | "description": "No border, no rest background. Used for editor toolbar utility actions.", |
| 200 | "html": "<button class=\"ds-btn-ghost\" aria-label=\"More\"><svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"1\"></circle><circle cx=\"19\" cy=\"12\" r=\"1\"></circle><circle cx=\"5\" cy=\"12\" r=\"1\"></circle></svg></button>", |
| 201 | "css": ".ds-btn-ghost { background: transparent; color: #a3a1a8; padding: 0.375rem 0.75rem; border: none; border-radius: 0.5rem; cursor: pointer; transition: background-color 75ms, color 75ms, transform 75ms; display: inline-flex; align-items: center; justify-content: center; } .ds-btn-ghost:hover { background: rgba(42, 39, 41, 0.7); color: #f5f4f4; } .ds-btn-ghost:active { transform: scale(0.97); }" |
| 202 | }, |
| 203 | { |
| 204 | "name": "Card (default)", |
| 205 | "kind": "card", |
| 206 | "refersTo": "card-default", |
| 207 | "description": "Surface for actual content (pages, attachments). Not for marketing copy.", |
| 208 | "html": "<article class=\"ds-card\"><h3 class=\"ds-card-title\">Q4 planning notes</h3><p class=\"ds-card-body\">Edited 12 minutes ago by Rachel.</p></article>", |
| 209 | "css": ".ds-card { background: rgba(27, 24, 26, 0.5); border: 1px solid rgba(42, 39, 41, 0.6); border-radius: 1rem; padding: 1.25rem; color: #d6d4d7; font-family: 'Outfit Variable', ui-sans-serif, system-ui, sans-serif; font-weight: 450; line-height: 1.6; transition: border-color 75ms, transform 75ms; } .ds-card:hover { border-color: rgba(66, 63, 66, 0.6); transform: translateY(-2px); } .ds-card-title { margin: 0 0 0.25rem; font-size: 1rem; font-weight: 600; color: #f5f4f4; } .ds-card-body { margin: 0; font-size: 0.875rem; color: #a3a1a8; }" |
| 210 | }, |
| 211 | { |
| 212 | "name": "Text Input", |
| 213 | "kind": "input", |
| 214 | "refersTo": "input", |
| 215 | "description": "Elevated tier background, soft border, pill-ish radius.", |
| 216 | "html": "<input class=\"ds-input\" type=\"text\" placeholder=\"Search pages…\" />", |
| 217 | "css": ".ds-input { width: 100%; background: rgba(42, 39, 41, 0.8); color: #f5f4f4; font-family: 'Outfit Variable', ui-sans-serif, system-ui, sans-serif; font-weight: 450; font-size: 0.875rem; padding: 0.625rem 1rem; border: 1px solid rgba(66, 63, 66, 0.6); border-radius: 0.875rem; outline: none; transition: border-color 75ms, box-shadow 75ms; } .ds-input::placeholder { color: #747178; } .ds-input:focus { border-color: rgba(157, 110, 232, 0.5); box-shadow: 0 0 0 1px rgba(157, 110, 232, 0.3); } .ds-input:focus-visible { box-shadow: 0 0 0 2px rgba(157, 110, 232, 0.5); }" |
| 218 | }, |
| 219 | { |
| 220 | "name": "Sidebar Nav Item", |
| 221 | "kind": "nav", |
| 222 | "refersTo": "sidebar", |
| 223 | "description": "Chrome-tier navigation item. Active uses accent tint.", |
| 224 | "html": "<nav class=\"ds-sidebar\"><a class=\"ds-sidebar-link is-active\" href=\"#\">Q4 planning notes</a><a class=\"ds-sidebar-link\" href=\"#\">Onboarding draft</a><a class=\"ds-sidebar-link\" href=\"#\">Reading log</a></nav>", |
| 225 | "css": ".ds-sidebar { background: #1b181a; padding: 0.5rem; border-right: 1px solid rgba(42, 39, 41, 0.6); display: flex; flex-direction: column; gap: 0.125rem; font-family: 'Outfit Variable', ui-sans-serif, system-ui, sans-serif; min-width: 220px; } .ds-sidebar-link { display: block; padding: 0.375rem 0.625rem; color: #d6d4d7; font-size: 0.875rem; font-weight: 450; line-height: 1.4; text-decoration: none; border-radius: 0.375rem; transition: background-color 75ms, color 75ms; } .ds-sidebar-link:hover { background: rgba(42, 39, 41, 0.6); color: #f5f4f4; } .ds-sidebar-link.is-active { background: rgba(157, 110, 232, 0.1); color: #b48ef5; }" |
| 226 | }, |
| 227 | { |
| 228 | "name": "Editor Surface", |
| 229 | "kind": "custom", |
| 230 | "refersTo": "canvas", |
| 231 | "description": "The signature surface. Canvas background, 450 weight body at 1.7 line-height, accent links, JetBrains Mono inline code.", |
| 232 | "html": "<div class=\"ds-editor\"><h1>The Quiet Studio</h1><p>bland is a dark notes editor for developers who live in dark IDEs all day. The canvas is <code>#221f21</code> — lifted, never near-black. <a href=\"#\">Halation</a> is the reason.</p><blockquote>Comfort over contrast. The interface should be comfortable for hours, not minutes.</blockquote></div>", |
| 233 | "css": ".ds-editor { background: #221f21; padding: 2rem; color: #d6d4d7; font-family: 'Outfit Variable', ui-sans-serif, system-ui, sans-serif; font-weight: 450; font-size: 1rem; line-height: 1.7; font-feature-settings: 'ss01' on, 'cv01' on; border-radius: 0.5rem; } .ds-editor > * + * { margin-top: 0.75em; } .ds-editor h1 { font-family: 'Bricolage Grotesque Variable', 'Outfit Variable', ui-sans-serif, sans-serif; font-size: 1.875rem; font-weight: 700; line-height: 1.15; letter-spacing: -0.025em; color: #f5f4f4; margin: 0 0 0.5rem; } .ds-editor p { margin: 0; } .ds-editor a { color: #b48ef5; text-decoration: underline; text-underline-offset: 2px; transition: color 75ms; } .ds-editor a:hover { color: #ccb4fb; } .ds-editor code { font-family: 'JetBrains Mono', 'SF Mono', monospace; font-size: 0.875em; background: #2a2729; color: #e5e4e5; padding: 0.15em 0.35em; border-radius: 0.25rem; } .ds-editor blockquote { border-left: 3px solid #423f42; padding-left: 1rem; color: #a3a1a8; margin: 0; }" |
| 234 | }, |
| 235 | { |
| 236 | "name": "Callout Block", |
| 237 | "kind": "custom", |
| 238 | "refersTo": "callout-info", |
| 239 | "description": "Editor callout — info/tip/warning. Low-chroma OKLCH hues tuned for astigmatism comfort.", |
| 240 | "html": "<aside class=\"ds-callout ds-callout-info\"><div class=\"ds-callout-rule\"></div><div class=\"ds-callout-body\"><strong>Note.</strong> The lifted canvas is the primary halation defense — do not regress it.</div></aside>", |
| 241 | "css": ".ds-callout { display: flex; gap: 0.875rem; background: rgba(182, 200, 230, 0.08); border-radius: 0.5rem; padding: 0.75rem 1rem; font-family: 'Outfit Variable', ui-sans-serif, sans-serif; font-weight: 450; font-size: 0.9375rem; line-height: 1.6; color: #d6d4d7; } .ds-callout-rule { flex: 0 0 3px; border-radius: 1.5px; background: oklch(0.82 0.06 265); } .ds-callout-body { flex: 1; } .ds-callout-body strong { color: #f5f4f4; font-weight: 600; }" |
| 242 | }, |
| 243 | { |
| 244 | "name": "Focus Ring", |
| 245 | "kind": "custom", |
| 246 | "refersTo": "focus-ring", |
| 247 | "description": "Global *:focus-visible ring. Canvas offset creates a gap between element and ring.", |
| 248 | "html": "<button class=\"ds-focus-demo\">Tab here</button>", |
| 249 | "css": ".ds-focus-demo { background: #2a2729; color: #f5f4f4; font-family: 'Outfit Variable', sans-serif; font-weight: 500; padding: 0.5rem 1rem; border: 1px solid rgba(66, 63, 66, 0.6); border-radius: 0.5rem; cursor: pointer; outline: none; } .ds-focus-demo:focus-visible { box-shadow: 0 0 0 2px #221f21, 0 0 0 4px rgba(157, 110, 232, 0.5); }" |
| 250 | } |
| 251 | ], |
| 252 | "narrative": { |
| 253 | "northStar": "The Quiet Studio", |
| 254 | "overview": "bland is a dark notes editor for developers who live in dark IDEs and want a writing surface that disappears. The aesthetic is sophisticated restraint: a warm, lifted dark palette; a clean studio desk with good lighting; calm focus over visual noise. Typography does the heavy lifting. Color is rationed. Motion is fast — 75ms — and unobtrusive. The product is named bland on purpose; the design is anything but. This system explicitly rejects generic SaaS surfaces, the lazy 'dev tool = monospace + neon + near-black' shorthand, and Notion's exact pixel grammar. It rejects pure dark backgrounds because they cause halation for astigmatic readers — the lifted canvas is an accessibility requirement, not an aesthetic preference. The voice is deadpan, precise, warm.", |
| 255 | "keyCharacteristics": [ |
| 256 | "Lifted warm dark — canvas at #221f21, never near-black", |
| 257 | "Inverted surface hierarchy — sidebar (zinc-900) is darker than the canvas", |
| 258 | "Body weight 450, not 400 — thicker strokes resist halation", |
| 259 | "Body line-height 1.7 — letters need room to breathe", |
| 260 | "Default transition 75ms, not 150ms or 300ms", |
| 261 | "One accent — warm-shifted amethyst #9d6ee8, used on ≤10% of any screen", |
| 262 | "Bricolage Grotesque + Outfit + JetBrains Mono (no Hanken Grotesk — bland picks its own display face)" |
| 263 | ], |
| 264 | "rules": [ |
| 265 | { |
| 266 | "name": "The One Voice Rule", |
| 267 | "body": "The accent is used sparingly — primary CTA, active nav, focus rings, selection, links inside body content. If accent is on more than ~10% of any given screen, something is wrong.", |
| 268 | "section": "colors" |
| 269 | }, |
| 270 | { |
| 271 | "name": "The Lifted Canvas Rule", |
| 272 | "body": "Canvas is #221f21, period. Never use stock zinc-950 (#09090b) or any pure-black-class background for content surfaces. The halation justification (astigmatism comfort) ranks above raw contrast scores.", |
| 273 | "section": "colors" |
| 274 | }, |
| 275 | { |
| 276 | "name": "The Inverted Chrome Rule", |
| 277 | "body": "The sidebar is darker than the canvas. Chrome (zinc-900) frames from the edges; canvas (#221f21) is where the eye rests. Do not 'fix' this by lifting the sidebar — the inversion is the design.", |
| 278 | "section": "colors" |
| 279 | }, |
| 280 | { |
| 281 | "name": "The Warm Shift Rule", |
| 282 | "body": "Every neutral stop is warm-shifted off stock Tailwind zinc (+R, -B). When adding new neutrals or hardcoded values in plain CSS, use the warm values — #1b181a not #18181b, #2a2729 not #27272a. Cool casts on dark surfaces register as clinical even when the user can't name them.", |
| 283 | "section": "colors" |
| 284 | }, |
| 285 | { |
| 286 | "name": "The No Gradient Accent Rule", |
| 287 | "body": "The accent is a solid color, always. bg-accent-600 hover:bg-accent-500. No gradients, no accent-colored shadows. Gradients on the accent read as the AI-generated default.", |
| 288 | "section": "colors" |
| 289 | }, |
| 290 | { |
| 291 | "name": "The Body Weight Rule", |
| 292 | "body": "Body text is 450, not 400. The 50-unit lift is non-negotiable — it is the primary halation defense. Never set body or paragraph weight below 450. Never use font-light or font-thin anywhere.", |
| 293 | "section": "typography" |
| 294 | }, |
| 295 | { |
| 296 | "name": "The 1.7 Line-Height Rule", |
| 297 | "body": "Body text in content surfaces (editor, long-form reading) uses line-height 1.7. Tightening is allowed for chrome/UI text where space is the constraint, but the editor surface is sacred.", |
| 298 | "section": "typography" |
| 299 | }, |
| 300 | { |
| 301 | "name": "The Display-for-Heroes Rule", |
| 302 | "body": "Bricolage is reserved for editor H1. Do not extend it to dialog titles, sidebar labels, or marketing copy — its character only carries weight at hero size, and overuse dilutes it.", |
| 303 | "section": "typography" |
| 304 | }, |
| 305 | { |
| 306 | "name": "The Mono-Is-Code Rule", |
| 307 | "body": "JetBrains Mono appears only on code (inline or block) and the rare technical label. Using it anywhere else is the lazy dev-tool shorthand that PRODUCT.md explicitly rejects.", |
| 308 | "section": "typography" |
| 309 | }, |
| 310 | { |
| 311 | "name": "The Tonal-First Rule", |
| 312 | "body": "Depth comes from value first (chrome darker than canvas, elevated lighter than canvas), borders second, shadows third. If you reach for a shadow before exhausting the surface stack, you're solving the wrong problem.", |
| 313 | "section": "elevation" |
| 314 | }, |
| 315 | { |
| 316 | "name": "The Subtle Shadow Rule", |
| 317 | "body": "Any shadow used is at shadow-sm weight unless the element is a modal. Colored shadows are prohibited — they identify the design as AI-generated and add noise without affordance.", |
| 318 | "section": "elevation" |
| 319 | } |
| 320 | ], |
| 321 | "dos": [ |
| 322 | "Do use the lifted canvas (#221f21) as the body background. Body text weight 450, line-height 1.7 on content surfaces.", |
| 323 | "Do keep the sidebar darker than the canvas (zinc-900). The inverted chrome is the design.", |
| 324 | "Do use the warm-shifted zinc values (#1b181a, #2a2729, #423f42, …) — never stock zinc — when hardcoding hex in plain CSS.", |
| 325 | "Do use the solid accent for CTAs (bg-accent-600 hover:bg-accent-500) and ration it — ~10% of any screen, max.", |
| 326 | "Do override transition duration to 75ms via --default-transition-duration. Scope transitions to the exact properties that change (transition-colors, transition-transform, transition-[border-color,box-shadow]).", |
| 327 | "Do prefer tonal layering (surface stack) over shadows for depth. Reserve shadow-sm for floating UI, shadow-2xl for modals.", |
| 328 | "Do stagger entrance animations at 60ms increments, capped at 8 items.", |
| 329 | "Do keep wit in micro-copy — empty states, loading lines, error messages — never in decoration.", |
| 330 | "Do respect prefers-reduced-motion via the global rule in app.css." |
| 331 | ], |
| 332 | "donts": [ |
| 333 | "Don't use pure dark backgrounds (#09090b / stock zinc-950) for content surfaces. Causes halation. PRODUCT.md anti-reference.", |
| 334 | "Don't ship 'generic SaaS' surfaces — Intercom-style dashboards, cookie-cutter cards, stock illustrations. PRODUCT.md anti-reference.", |
| 335 | "Don't lean on 'monospace-everything-dark-mode-neon' as a shorthand for 'dev tool.' PRODUCT.md anti-reference. Mono is for code only.", |
| 336 | "Don't try to be a pixel-perfect Notion clone. bland is self-aware about the resemblance and owns its own grammar. PRODUCT.md anti-reference.", |
| 337 | "Don't use violet-500 (#8b5cf6) or any stock Tailwind purple as the accent — it is the most-recognizable AI-generated color choice. The amethyst accent is hue-shifted warmer and de-saturated for a reason.", |
| 338 | "Don't use gradient buttons (from-accent-500 to-accent-600) or accent-colored shadows (shadow-accent-500/10). Solid color, subtle shadow only.", |
| 339 | "Don't animate border hue shifts (zinc → accent via transition-colors). The intermediate tones look unnatural even at 75ms. Lighten within the same hue family instead.", |
| 340 | "Don't use transition-all ever. Scope to actual properties.", |
| 341 | "Don't use font-light or font-thin anywhere. Insufficient contrast on dark surfaces; halation makes thin strokes unreadable for astigmatic users.", |
| 342 | "Don't use hover:brightness-* on gradient buttons. Forces per-frame GPU gradient recompute.", |
| 343 | "Don't use backdrop-blur-xl (24px). backdrop-blur-sm (4px) is plenty and ~6x cheaper per frame.", |
| 344 | "Don't add position: fixed pseudo-element background layers (body::before glow). Apply ambient gradients directly to body's background-image.", |
| 345 | "Don't wrap marketing copy in <Card> primitives. Cards are for real content (pages, attachments), not decoration.", |
| 346 | "Don't use zinc-600 for semantically meaningful text — insufficient contrast against any dark background.", |
| 347 | "Don't rely on color alone for state — pair with icon or label." |
| 348 | ] |
| 349 | } |
| 350 | } |