File
Blob: .impeccable/design.json
| 1 | { |
| 2 | "schemaVersion": 2, |
| 3 | "generatedAt": "2026-05-21T00:00:00.000Z", |
| 4 | "title": "Design System: dab", |
| 5 | "extensions": { |
| 6 | "colorMeta": { |
| 7 | "canvas": { |
| 8 | "role": "neutral", |
| 9 | "displayName": "Lifted Canvas", |
| 10 | "canonical": "oklch(22.6% 0.005 35)", |
| 11 | "tonalRamp": ["#0c090b", "#1b181a", "#221f21", "#2a2729", "#423f42", "#555259", "#747178", "#a3a1a8"] |
| 12 | }, |
| 13 | "zinc-900": { |
| 14 | "role": "neutral", |
| 15 | "displayName": "Chrome", |
| 16 | "canonical": "oklch(19.4% 0.005 35)", |
| 17 | "tonalRamp": ["#0c090b", "#1b181a", "#221f21", "#2a2729", "#423f42", "#555259", "#747178", "#a3a1a8"] |
| 18 | }, |
| 19 | "zinc-800": { |
| 20 | "role": "neutral", |
| 21 | "displayName": "Elevated", |
| 22 | "canonical": "oklch(25.5% 0.005 35)", |
| 23 | "tonalRamp": ["#0c090b", "#1b181a", "#221f21", "#2a2729", "#423f42", "#555259", "#747178", "#a3a1a8"] |
| 24 | }, |
| 25 | "accent-500": { |
| 26 | "role": "primary", |
| 27 | "displayName": "Bookmark Coral", |
| 28 | "canonical": "oklch(60.3% 0.219 357)", |
| 29 | "tonalRamp": ["#5b143b", "#76174a", "#941b58", "#b8226e", "#db3a8b", "#f06da6", "#f9a8d4", "#fbcfe8"] |
| 30 | }, |
| 31 | "accent-400": { |
| 32 | "role": "primary", |
| 33 | "displayName": "Bookmark Coral (light)", |
| 34 | "canonical": "oklch(68.3% 0.196 357)", |
| 35 | "tonalRamp": ["#5b143b", "#76174a", "#941b58", "#b8226e", "#db3a8b", "#f06da6", "#f9a8d4", "#fbcfe8"] |
| 36 | }, |
| 37 | "accent-600": { |
| 38 | "role": "primary", |
| 39 | "displayName": "Bookmark Coral (deep)", |
| 40 | "canonical": "oklch(52.6% 0.198 357)", |
| 41 | "tonalRamp": ["#5b143b", "#76174a", "#941b58", "#b8226e", "#db3a8b", "#f06da6", "#f9a8d4", "#fbcfe8"] |
| 42 | } |
| 43 | }, |
| 44 | "typographyMeta": { |
| 45 | "display": { |
| 46 | "displayName": "Display (Fraunces)", |
| 47 | "purpose": "Page H1 inside PageHeader, and the brand wordmark in the header. One serif moment per page; never on cards, dialogs, or section titles." |
| 48 | }, |
| 49 | "body": { |
| 50 | "displayName": "Body (Hanken Grotesk 450)", |
| 51 | "purpose": "Default content. The 450 weight is intentional: heavier than 400 to resist halation on dark surfaces for astigmatic readers." |
| 52 | }, |
| 53 | "caption": { |
| 54 | "displayName": "Caption (Hanken uppercase tracking)", |
| 55 | "purpose": "Catalog-card overlines. <dt> content in definition lists, table column headers, meta labels. Always uppercase with 0.1em tracking." |
| 56 | }, |
| 57 | "mono": { |
| 58 | "displayName": "Mono (JetBrains Mono)", |
| 59 | "purpose": "Mono-shaped values only: UUIDs, DAV URLs, host labels, ETags, sync tokens. Never decorative." |
| 60 | } |
| 61 | }, |
| 62 | "shadows": [ |
| 63 | { |
| 64 | "name": "dialog-scrim-backdrop", |
| 65 | "value": "background-color: rgba(12, 9, 11, 0.6); backdrop-filter: blur(4px);", |
| 66 | "purpose": "Native <dialog> backdrop. 4px blur is cosmetic, not structural." |
| 67 | }, |
| 68 | { |
| 69 | "name": "dialog-lift", |
| 70 | "value": "0 25px 50px -12px rgba(0, 0, 0, 0.25)", |
| 71 | "purpose": "Native <dialog> only. The single context where a heavy shadow earns its place." |
| 72 | } |
| 73 | ], |
| 74 | "motion": [ |
| 75 | { |
| 76 | "name": "default-transition-duration", |
| 77 | "value": "75ms", |
| 78 | "purpose": "Tailwind default override via --default-transition-duration. Scoped to specific properties; never transition-all." |
| 79 | }, |
| 80 | { |
| 81 | "name": "fade-in", |
| 82 | "value": "fade-in 0.4s ease-out both", |
| 83 | "purpose": "Main content entrance on page load." |
| 84 | }, |
| 85 | { |
| 86 | "name": "slide-up", |
| 87 | "value": "slide-up 0.35s ease-out both", |
| 88 | "purpose": "Page-level content wrappers, route changes, toast entrance." |
| 89 | }, |
| 90 | { |
| 91 | "name": "scale-fade", |
| 92 | "value": "scale-fade 0.3s cubic-bezier(0.16, 1, 0.3, 1) both", |
| 93 | "purpose": "Dialog open, popover entrance, scale-from-origin reveals." |
| 94 | }, |
| 95 | { |
| 96 | "name": "shimmer", |
| 97 | "value": "shimmer 1.5s ease-in-out infinite", |
| 98 | "purpose": "Loading skeleton background sweep." |
| 99 | }, |
| 100 | { |
| 101 | "name": "stagger-step", |
| 102 | "value": "60ms", |
| 103 | "purpose": "Increment between sibling entrance animations. Capped at 8 items (480ms ceiling)." |
| 104 | }, |
| 105 | { |
| 106 | "name": "brand-glyph-rotate", |
| 107 | "value": "transform: rotate(-6deg); transition: transform 200ms", |
| 108 | "purpose": "Layers brand glyph signature hover. The single 'a person made this' moment in the chrome." |
| 109 | } |
| 110 | ], |
| 111 | "breakpoints": [ |
| 112 | { "name": "sm", "value": "640px" }, |
| 113 | { "name": "md", "value": "768px" }, |
| 114 | { "name": "lg", "value": "1024px" }, |
| 115 | { "name": "xl", "value": "1280px" } |
| 116 | ] |
| 117 | }, |
| 118 | "components": [ |
| 119 | { |
| 120 | "name": "Primary Button", |
| 121 | "kind": "button", |
| 122 | "refersTo": "button-primary", |
| 123 | "description": "The primary CTA. Solid coral-magenta fill, white text, never a gradient. Used at most twice per page.", |
| 124 | "html": "<button class=\"ds-btn-primary\" type=\"button\">Create token</button>", |
| 125 | "css": ".ds-btn-primary { display: inline-flex; align-items: center; justify-content: center; gap: 0.5rem; height: 2.5rem; padding: 0 1rem; border: none; border-radius: 0.75rem; background-color: #b8226e; color: #ffffff; font-family: 'Hanken Grotesk', ui-sans-serif, system-ui, sans-serif; font-size: 0.875rem; font-weight: 500; line-height: 1; cursor: pointer; transition: background-color 75ms, transform 75ms; } .ds-btn-primary:hover { background-color: #db3a8b; } .ds-btn-primary:active { transform: scale(0.98); } .ds-btn-primary:focus-visible { outline: none; box-shadow: 0 0 0 2px #221f21, 0 0 0 4px rgba(219, 58, 139, 0.5); } .ds-btn-primary:disabled { opacity: 0.6; cursor: not-allowed; }" |
| 126 | }, |
| 127 | { |
| 128 | "name": "Secondary Button", |
| 129 | "kind": "button", |
| 130 | "refersTo": "button-secondary", |
| 131 | "description": "Bordered neutral fill on Elevated surfaces. Resting state inside a card; primary nav buttons in the header.", |
| 132 | "html": "<button class=\"ds-btn-secondary\" type=\"button\">Cancel</button>", |
| 133 | "css": ".ds-btn-secondary { display: inline-flex; align-items: center; justify-content: center; gap: 0.5rem; height: 2.5rem; padding: 0 1rem; border: 1px solid rgba(66, 63, 66, 0.6); border-radius: 0.75rem; background-color: rgba(42, 39, 41, 0.6); color: #e5e4e5; font-family: 'Hanken Grotesk', ui-sans-serif, system-ui, sans-serif; font-size: 0.875rem; font-weight: 500; line-height: 1; cursor: pointer; transition: background-color 75ms, transform 75ms; } .ds-btn-secondary:hover { background-color: rgba(66, 63, 66, 0.6); } .ds-btn-secondary:active { transform: scale(0.98); }" |
| 134 | }, |
| 135 | { |
| 136 | "name": "Danger Button", |
| 137 | "kind": "button", |
| 138 | "refersTo": "button-danger", |
| 139 | "description": "Confirm-shaped destructive actions (Revoke, Delete). Red tint with red text, never a solid red fill.", |
| 140 | "html": "<button class=\"ds-btn-danger\" type=\"button\">Revoke</button>", |
| 141 | "css": ".ds-btn-danger { display: inline-flex; align-items: center; justify-content: center; gap: 0.5rem; height: 2rem; padding: 0 0.75rem; border: 1px solid rgba(248, 113, 113, 0.2); border-radius: 0.75rem; background-color: rgba(248, 113, 113, 0.1); color: #fca5a5; font-family: 'Hanken Grotesk', ui-sans-serif, system-ui, sans-serif; font-size: 0.75rem; font-weight: 500; line-height: 1; cursor: pointer; transition: background-color 75ms, transform 75ms; } .ds-btn-danger:hover { background-color: rgba(248, 113, 113, 0.2); } .ds-btn-danger:active { transform: scale(0.98); }" |
| 142 | }, |
| 143 | { |
| 144 | "name": "Ghost Button", |
| 145 | "kind": "button", |
| 146 | "refersTo": "button-ghost", |
| 147 | "description": "Inline row actions, icon-only buttons, dialog 'Cancel' on light contexts.", |
| 148 | "html": "<button class=\"ds-btn-ghost\" type=\"button\">Edit</button>", |
| 149 | "css": ".ds-btn-ghost { display: inline-flex; align-items: center; justify-content: center; gap: 0.5rem; height: 2.5rem; padding: 0 1rem; border: none; border-radius: 0.75rem; background-color: transparent; color: #a3a1a8; font-family: 'Hanken Grotesk', ui-sans-serif, system-ui, sans-serif; font-size: 0.875rem; font-weight: 500; line-height: 1; cursor: pointer; transition: background-color 75ms, color 75ms, transform 75ms; } .ds-btn-ghost:hover { background-color: rgba(42, 39, 41, 0.7); color: #f5f4f4; } .ds-btn-ghost:active { transform: scale(0.97); }" |
| 150 | }, |
| 151 | { |
| 152 | "name": "Card (Default)", |
| 153 | "kind": "card", |
| 154 | "refersTo": "card-default", |
| 155 | "description": "The labeled shelf. One per record (calendar, address book, PAT, file row). Tonal layering on canvas, no shadow.", |
| 156 | "html": "<article class=\"ds-card\"><div class=\"ds-card-header\"><h3 class=\"ds-card-title\">Personal calendar</h3><span class=\"ds-badge ds-badge-success\">Active</span></div><p class=\"ds-card-desc\">Family events and birthdays.</p><p class=\"ds-card-meta\">Modified 2 days ago</p></article>", |
| 157 | "css": ".ds-card { border: 1px solid rgba(66, 63, 66, 0.6); border-radius: 1rem; background-color: rgba(27, 24, 26, 0.5); padding: 1.25rem; font-family: 'Hanken Grotesk', ui-sans-serif, system-ui, sans-serif; color: #f5f4f4; } @media (min-width: 640px) { .ds-card { padding: 1.5rem; } } .ds-card-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 0.75rem; margin-bottom: 0.5rem; } .ds-card-title { font-size: 1rem; font-weight: 600; color: #f5f4f4; margin: 0; line-height: 1.4; } .ds-card-desc { font-size: 0.875rem; color: #a3a1a8; margin: 0; line-height: 1.55; font-weight: 450; } .ds-card-meta { font-size: 0.75rem; color: #747178; margin: 0.5rem 0 0; } .ds-badge { display: inline-flex; align-items: center; gap: 0.25rem; border: 1px solid; border-radius: 9999px; padding: 0.125rem 0.5rem; font-size: 0.75rem; font-weight: 500; } .ds-badge-success { border-color: rgba(52, 211, 153, 0.2); background-color: rgba(52, 211, 153, 0.1); color: #6ee7b7; }" |
| 158 | }, |
| 159 | { |
| 160 | "name": "Input", |
| 161 | "kind": "input", |
| 162 | "refersTo": "input", |
| 163 | "description": "Single-line text input. Soft-cornered, zinc-800 fill on canvas, focus ring uses the coral-magenta at 30% opacity.", |
| 164 | "html": "<input class=\"ds-input\" type=\"text\" placeholder=\"e.g. Thunderbird on laptop\" />", |
| 165 | "css": ".ds-input { width: 100%; border: 1px solid rgba(66, 63, 66, 0.6); border-radius: 0.75rem; background-color: rgba(42, 39, 41, 0.8); padding: 0.625rem 1rem; color: #f5f4f4; font-family: 'Hanken Grotesk', ui-sans-serif, system-ui, sans-serif; font-size: 0.875rem; font-weight: 450; line-height: 1.55; outline: none; transition: border-color 75ms, box-shadow 75ms; } .ds-input::placeholder { color: #747178; } .ds-input:focus { border-color: rgba(219, 58, 139, 0.5); box-shadow: 0 0 0 1px rgba(219, 58, 139, 0.3); } .ds-input:focus-visible { box-shadow: 0 0 0 2px #221f21, 0 0 0 4px rgba(219, 58, 139, 0.5); }" |
| 166 | }, |
| 167 | { |
| 168 | "name": "Badge (Accent)", |
| 169 | "kind": "chip", |
| 170 | "refersTo": "badge-accent", |
| 171 | "description": "Pill-shaped chip in the bookmark-coral tone. Used for 'Active' nav states and accent-toned tags. Rare.", |
| 172 | "html": "<span class=\"ds-badge ds-badge-accent\">Active</span>", |
| 173 | "css": ".ds-badge { display: inline-flex; align-items: center; gap: 0.25rem; border: 1px solid; border-radius: 9999px; padding: 0.125rem 0.5rem; font-size: 0.75rem; font-weight: 500; font-family: 'Hanken Grotesk', ui-sans-serif, system-ui, sans-serif; } .ds-badge-accent { border-color: rgba(219, 58, 139, 0.2); background-color: rgba(219, 58, 139, 0.1); color: #f9a8d4; }" |
| 174 | }, |
| 175 | { |
| 176 | "name": "Header (Brand)", |
| 177 | "kind": "nav", |
| 178 | "refersTo": "header", |
| 179 | "description": "Sticky chrome at the top of every authed page. Layers glyph rotates -6deg on hover. Nav pills with active state in accent tone.", |
| 180 | "html": "<header class=\"ds-header\"><div class=\"ds-header-inner\"><a class=\"ds-brand\" href=\"#\"><span class=\"ds-brand-icon\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" width=\"24\" height=\"24\" aria-hidden=\"true\"><path d=\"M12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83Z\"/><path d=\"M2 12a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 12\"/><path d=\"M2 17a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 17\"/></svg></span><span class=\"ds-brand-text\"><strong>dab</strong><small>your DAV-native data home</small></span></a><nav class=\"ds-nav\"><a class=\"ds-nav-link ds-nav-link-active\" href=\"#\">Home</a><a class=\"ds-nav-link\" href=\"#\">Profile</a><a class=\"ds-nav-link\" href=\"#\">Tokens</a></nav></div></header>", |
| 181 | "css": ".ds-header { position: sticky; top: 0; z-index: 50; border-bottom: 1px solid rgba(42, 39, 41, 0.6); background-color: rgba(27, 24, 26, 0.95); backdrop-filter: blur(4px); font-family: 'Hanken Grotesk', ui-sans-serif, system-ui, sans-serif; } .ds-header-inner { max-width: 80rem; margin: 0 auto; display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding: 0.75rem 1rem; } @media (min-width: 640px) { .ds-header-inner { padding: 0.75rem 1.5rem; } } .ds-brand { display: inline-flex; align-items: center; gap: 0.75rem; color: inherit; text-decoration: none; transition: opacity 75ms; } .ds-brand:hover { opacity: 0.8; } .ds-brand-icon { display: inline-grid; place-items: center; width: 2.25rem; height: 2.25rem; color: #f06da6; transition: transform 200ms; } .ds-brand:hover .ds-brand-icon { transform: rotate(-6deg); } .ds-brand-text { display: none; } @media (min-width: 640px) { .ds-brand-text { display: block; } } .ds-brand-text strong { display: block; font-size: 0.875rem; font-weight: 600; color: #f5f4f4; } .ds-brand-text small { display: block; font-size: 0.75rem; color: #a3a1a8; } .ds-nav { display: flex; align-items: center; gap: 0.25rem; } .ds-nav-link { padding: 0.375rem 0.75rem; border-radius: 0.5rem; font-size: 0.875rem; color: #a3a1a8; text-decoration: none; transition: background-color 75ms, color 75ms; } .ds-nav-link:hover { background-color: rgba(42, 39, 41, 0.7); color: #f5f4f4; } .ds-nav-link-active { background-color: rgba(219, 58, 139, 0.1); color: #f9a8d4; }" |
| 182 | }, |
| 183 | { |
| 184 | "name": "Copyable", |
| 185 | "kind": "custom", |
| 186 | "refersTo": "copyable", |
| 187 | "description": "Mono value + Copy button. The receipt for any operation that produces a URL or token. Toast confirms on success; no celebratory animation.", |
| 188 | "html": "<div class=\"ds-copyable\"><div class=\"ds-copyable-value\">dab_pat_4f3a8b2c1e9d7f6a5b4c3d2e1f0a9b8c</div><button class=\"ds-copyable-btn\" type=\"button\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" width=\"14\" height=\"14\" aria-hidden=\"true\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg>Copy</button></div>", |
| 189 | "css": ".ds-copyable { display: flex; align-items: stretch; gap: 0.5rem; border: 1px solid rgba(66, 63, 66, 0.6); border-radius: 0.75rem; background-color: rgba(27, 24, 26, 0.6); padding: 0.5rem; font-family: 'Hanken Grotesk', ui-sans-serif, system-ui, sans-serif; } .ds-copyable-value { flex: 1; min-width: 0; overflow-x: auto; padding: 0.375rem 0.5rem; font-family: 'JetBrains Mono', 'SF Mono', monospace; font-size: 0.875rem; color: #e5e4e5; white-space: nowrap; } .ds-copyable-btn { display: inline-flex; align-items: center; gap: 0.375rem; flex-shrink: 0; padding: 0 0.625rem; border: 1px solid rgba(66, 63, 66, 0.6); border-radius: 0.5rem; background-color: rgba(42, 39, 41, 0.6); color: #e5e4e5; font-size: 0.75rem; font-weight: 500; cursor: pointer; transition: background-color 75ms; } .ds-copyable-btn:hover { background-color: rgba(66, 63, 66, 0.6); }" |
| 190 | }, |
| 191 | { |
| 192 | "name": "Page Header", |
| 193 | "kind": "custom", |
| 194 | "refersTo": "page-header", |
| 195 | "description": "The placard at the top of each wing. Layers-tinted icon, Fraunces display H1, Hanken body description. One per page.", |
| 196 | "html": "<header class=\"ds-page-header\"><div class=\"ds-page-header-row\"><div class=\"ds-page-header-icon\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" width=\"28\" height=\"28\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"12\" r=\"4\"/><path d=\"M16 8v5a3 3 0 0 0 6 0v-1a10 10 0 1 0-4 8\"/></svg></div><div><h1 class=\"ds-page-header-title\">Your profile</h1><p class=\"ds-page-header-desc\">The identifiers and discovery URLs your DAV clients use to reach this account.</p></div></div></header>", |
| 197 | "css": ".ds-page-header { margin-bottom: 1.5rem; padding-bottom: 1.5rem; border-bottom: 1px solid rgba(42, 39, 41, 0.6); display: flex; flex-direction: column; gap: 1rem; } @media (min-width: 640px) { .ds-page-header { flex-direction: row; align-items: flex-end; justify-content: space-between; } } .ds-page-header-row { display: flex; align-items: flex-start; gap: 1rem; font-family: 'Hanken Grotesk', ui-sans-serif, system-ui, sans-serif; } .ds-page-header-icon { margin-top: 0.25rem; color: #f06da6; } .ds-page-header-title { margin: 0; font-family: 'Fraunces', ui-serif, Georgia, serif; font-size: 1.875rem; font-weight: 800; line-height: 1.05; letter-spacing: -0.025em; color: #fafaf9; font-variation-settings: 'opsz' 32, 'SOFT' 60; } @media (min-width: 640px) { .ds-page-header-title { font-size: 2.25rem; } } .ds-page-header-desc { margin: 0.25rem 0 0; max-width: 42rem; font-size: 0.875rem; color: #a3a1a8; line-height: 1.55; font-weight: 450; } @media (min-width: 640px) { .ds-page-header-desc { font-size: 1rem; } }" |
| 198 | } |
| 199 | ], |
| 200 | "narrative": { |
| 201 | "northStar": "The private wing of the library.", |
| 202 | "overview": "dab is the small reading room behind tessera's front desk. Each subject's opaque five-word host is the placard at the entrance to a wing of stacks; inside, three labeled shelves hold calendars, contacts, and files. The control-plane UI is a place to organize what is on those shelves, hand visitors (DAV clients) a shelfmark, and leave. Nothing on this surface wants your attention longer than necessary.\n\nThe aesthetic is institutional-print restraint with a single warm bookmark slip. Fraunces (warm variable serif) does the ceremonial work on page H1s and the brand mark, the way a typeset placard sits above a row of stacks. Hanken Grotesk does the steady labor everywhere else. Coral-magenta (#db3a8b) appears rarely, never as decoration. Its power is rarity.\n\nThe whole system explicitly rejects the share-link product (Dropbox / iCloud / Drive), the enterprise NAS appliance (Nextcloud / ownCloud / Synology), the AI-template dark-mode default, and the cheerful-Apple pastel calendar aesthetic. dab is not a daily driver; the design is sized for the four-times-a-year visit and the sixty-second task, and every surface should read like a labeled shelf rather than a marketing tile.", |
| 203 | "keyCharacteristics": [ |
| 204 | "Lifted-canvas dark only (#221f21); no light-mode toggle.", |
| 205 | "One coral-magenta accent, one warm serif, one warm sans, one mono. Nothing more.", |
| 206 | "Catalog-card density: each row carries display name, internal name (mono), one timestamp, the URL in a collapsed disclosure.", |
| 207 | "Personality budget spent on the Layers glyph -rotate-6 on hover, the serif H1, the gold-magenta CTA, and the footer heart. Nowhere else.", |
| 208 | "75ms default transition, scoped to specific properties. Never transition-all." |
| 209 | ], |
| 210 | "rules": [ |
| 211 | { |
| 212 | "name": "The One Bookmark Rule", |
| 213 | "body": "Coral-magenta is the primary CTA, the active nav state, the footer heart, and nothing else. Never a background fill. Never a gradient. Never a decorative shadow. Never as a row stripe. If a third coral-magenta element appears on a page, one of the three is wrong.", |
| 214 | "section": "colors" |
| 215 | }, |
| 216 | { |
| 217 | "name": "The Lifted Canvas Rule", |
| 218 | "body": "Body background is #221f21, not zinc-950, not #000. Lifting prevents halation for astigmatic readers and gives the surface hierarchy room to express depth without shadows. Don't push canvas darker for 'drama'; it makes the wing feel like a basement.", |
| 219 | "section": "colors" |
| 220 | }, |
| 221 | { |
| 222 | "name": "The Warm-Shifted Zinc Rule", |
| 223 | "body": "Override stock zinc in @theme with the warm-shifted values. Stock zinc leans cool/clinical; the warm shift reads as 'neutral' rather than 'cool gray' and rhymes with cream paper. The warmth should be felt, not seen.", |
| 224 | "section": "colors" |
| 225 | }, |
| 226 | { |
| 227 | "name": "The Serif Once Rule", |
| 228 | "body": "Fraunces appears on the page H1 inside PageHeader and on the brand mark in the header — nowhere else. Section titles, card titles, dialog titles all use Hanken. Spreading the serif across cards turns the wing into a museum gift shop. One serif moment per page.", |
| 229 | "section": "typography" |
| 230 | }, |
| 231 | { |
| 232 | "name": "The 450 Floor Rule", |
| 233 | "body": "Body text never drops below font-weight 450. No font-light, no font-thin. Light strokes halate on dark surfaces for ~33% of users. The 450 floor is set on <body> and cascades; only override upward.", |
| 234 | "section": "typography" |
| 235 | }, |
| 236 | { |
| 237 | "name": "The Mono For Mono-Shaped Values Rule", |
| 238 | "body": "JetBrains Mono is reserved for values that are themselves machine-shaped: UUIDs, DAV URLs, host labels, ETags, sync tokens. Never use mono for 'tech feel' — that's the AI-template reflex. A user-facing display name is not mono.", |
| 239 | "section": "typography" |
| 240 | }, |
| 241 | { |
| 242 | "name": "The Tonal-First Rule", |
| 243 | "body": "Depth comes from tone, not shadow. If you need something to feel raised, give it a lighter zinc fill — don't reach for box-shadow. Shadows on cards or buttons read as decoration; they earn their place only on <dialog>.", |
| 244 | "section": "elevation" |
| 245 | }, |
| 246 | { |
| 247 | "name": "The Sticky Header Blur Rule", |
| 248 | "body": "Header is bg-zinc-900/95 backdrop-blur-sm (4px). Never backdrop-blur-xl (24px) — the larger radius is ~6x more expensive per frame and barely distinguishable at high background opacity.", |
| 249 | "section": "elevation" |
| 250 | }, |
| 251 | { |
| 252 | "name": "The One-H1-Per-Page Rule", |
| 253 | "body": "Every authed page has exactly one display H1 inside PageHeader. The dashboard's tile titles are H2 / CardTitle, not H1. The serif appears once.", |
| 254 | "section": "components" |
| 255 | }, |
| 256 | { |
| 257 | "name": "The Borderless Hover Rule", |
| 258 | "body": "Hover transitions stay within the same hue family. Borders never cross-fade between zinc and accent; doing so produces a muddy intermediate even at 75ms. Use background tint shifts (hover:bg-zinc-700/60) for hover state, not border-color transitions.", |
| 259 | "section": "components" |
| 260 | }, |
| 261 | { |
| 262 | "name": "The 75ms Default Rule", |
| 263 | "body": "--default-transition-duration: 75ms overrides Tailwind's 150ms default. Anything that feels sluggish at 75ms is either animating a layout property (don't) or has too many properties in scope (also don't). Scope transitions explicitly. Never transition-all.", |
| 264 | "section": "components" |
| 265 | } |
| 266 | ], |
| 267 | "dos": [ |
| 268 | "Do use accent-600 for the primary CTA fill, hovering to accent-500. Solid, never a gradient.", |
| 269 | "Do keep the page H1 in Fraunces via font-display. One serif H1 per page. Section titles and card titles are Hanken.", |
| 270 | "Do treat each list row as a labeled shelf: display name, internal name (mono), one timestamp, the URL in a collapsed <details>. No charts, no sparklines, no KPI tiles.", |
| 271 | "Do copy tessera's footer spacing (py-3 mobile, py-2 sm+). The suite reads consistent because the footer is.", |
| 272 | "Do make the brand glyph rotate -rotate-6 on hover. That single micro-interaction carries the 'a person made this' message; everything else stays still.", |
| 273 | "Do render calendar color as a h-3 w-3 rounded-full dot beside the display name. Solid fill, 1px border in zinc-700/60. No tinted background, no soft-pastel pill.", |
| 274 | "Do return the user to the URL as the receipt. After PAT or collection creation, the page shows the URL inside Copyable. No 'Successfully created!' banner.", |
| 275 | "Do respect prefers-reduced-motion via the global rule. No per-element guards.", |
| 276 | "Do use tonal layering for depth (Chrome / Canvas / Elevated / Hover). Reach for shadow-2xl only on <dialog>." |
| 277 | ], |
| 278 | "donts": [ |
| 279 | "Don't make dab feel like Dropbox, iCloud, or Google Drive. No tile-grid file browser. No 'Upgrade to Pro' banner. No share-link modal with social buttons. No 'Recents' carousel. No '+ New' floating action button.", |
| 280 | "Don't make dab feel like Nextcloud, ownCloud, or Synology DSM. No Bootstrap-styled 'Files / Contacts / Calendar' tab bar. No left-rail accordion. No cyan-on-gray palette. No 'App Center' landing page.", |
| 281 | "Don't ship any AI-template dark-mode signal: no neon-on-near-black, no gradient meshes, no glassmorphism (backdrop-blur-xl is forbidden), no gradient text (background-clip: text + gradient is banned), no shield-plus-checkmark hero icon, no 'Your data, secure' hero copy.", |
| 282 | "Don't drift into the iCloud-style 'soft pastel' calendar aesthetic. Calendar color is one 12px dot, not a tinted pill. No pastel event-pill mocks anywhere in the UI.", |
| 283 | "Don't use transition-all. Always scope to the properties that change.", |
| 284 | "Don't animate border hue shifts (zinc → accent on hover). The intermediate hues read as muddy even at 75ms. Use background tint shifts instead.", |
| 285 | "Don't use shadow-lg or shadow-xl on interactive elements. Reserve shadow-2xl for <dialog>. No colored accent shadows (shadow-accent-500/10) anywhere.", |
| 286 | "Don't use font-light or font-thin. Body weight floor is 450.", |
| 287 | "Don't use side-stripe borders (left/right border-l >1px as a colored accent on cards or rows). Universal ban; rewrite with leading icons or full borders instead.", |
| 288 | "Don't add a 'Welcome back, {name}!' or 'Successfully created!' banner. The user knows their name. The row appearing is the success.", |
| 289 | "Don't put Fraunces on more than one element per page. Cards, dialogs, badges, and buttons all use Hanken. The serif is the signature, not decoration.", |
| 290 | "Don't use mono for 'tech feel.' JetBrains Mono is for mono-shaped values (URLs, IDs, ETags, host labels) only." |
| 291 | ] |
| 292 | } |
| 293 | } |