Skip to content
File

Blob: .impeccable/design.json

json351 lines
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}