|
| 1 | +# Design Spec |
| 2 | + |
| 3 | +This is the source of truth for all visual and interaction decisions in Memo. |
| 4 | +Read this before implementing or reviewing any UI. |
| 5 | + |
| 6 | +--- |
| 7 | + |
| 8 | +## Design Philosophy |
| 9 | + |
| 10 | +Memo is a workspace tool. It should feel fast, quiet, and dense — like Notion or Linear, |
| 11 | +not like a marketing site. Every pixel should serve the user's task. |
| 12 | + |
| 13 | +Principles (in priority order): |
| 14 | +1. **Content density** — show more, chrome less. The user's content is the UI. |
| 15 | +2. **Spatial consistency** — identical spacing, alignment, and sizing everywhere. |
| 16 | +3. **Restraint** — fewer colors, fewer font sizes, fewer shadows. When in doubt, remove. |
| 17 | +4. **Speed perception** — instant transitions, skeleton loaders, optimistic updates. |
| 18 | + |
| 19 | +--- |
| 20 | + |
| 21 | +## Color |
| 22 | + |
| 23 | +Dark mode only. Use oklch values via CSS variables — same hue family as software-factory.dev (hue 250–255, cool-blue undertones). No arbitrary hex values. No light mode. |
| 24 | + |
| 25 | +| Token | Value | Usage | |
| 26 | +|---|---|---| |
| 27 | +| `--background` | `oklch(0.13 0.008 255)` | Page background | |
| 28 | +| `--foreground` | `oklch(0.87 0.01 255)` | Primary text | |
| 29 | +| `--muted` | `oklch(0.22 0.012 255)` | Secondary surfaces (sidebar, hover states) | |
| 30 | +| `--muted-foreground` | `oklch(0.55 0.012 255)` | Secondary text, placeholders, timestamps | |
| 31 | +| `--border` | `oklch(0.22 0.012 255)` | All borders and dividers | |
| 32 | +| `--primary` | `oklch(0.74 0.032 248)` | Primary buttons, active states | |
| 33 | +| `--primary-foreground` | `oklch(0.13 0.008 255)` | Text on primary | |
| 34 | +| `--accent` | `oklch(0.60 0.06 248)` | Links, selected items, focus rings | |
| 35 | +| `--destructive` | `oklch(0.55 0.2 25)` | Delete actions, error states | |
| 36 | + |
| 37 | +Rules: |
| 38 | +- No color outside this token set without updating this file first. |
| 39 | +- Accent color is used sparingly — selected sidebar item, focused input, links. Not decorative. |
| 40 | +- Borders are near-invisible: 1px solid `--border` (matches `--muted`, blends with surfaces). No box shadows except for dropdowns and modals. |
| 41 | +- Use `white/` opacity scale for subtle text hierarchy (e.g. `text-white/40`, `text-white/20`) where token classes feel too heavy. |
| 42 | + |
| 43 | +--- |
| 44 | + |
| 45 | +## Typography |
| 46 | + |
| 47 | +One typeface: **JetBrains Mono** (monospace), matching the software-factory.dev landing page. Loaded via `next/font/google` with `--font-jetbrains-mono` CSS variable. Falls back to `"Berkeley Mono", "SFMono-Regular", Menlo, Consolas, monospace`. |
| 48 | + |
| 49 | +| Element | Class | Weight | Color | |
| 50 | +|---|---|---|---| |
| 51 | +| Page title (in editor) | `text-3xl` | `font-bold` | `foreground` | |
| 52 | +| Heading 1 | `text-2xl` | `font-semibold` | `foreground` | |
| 53 | +| Heading 2 | `text-xl` | `font-semibold` | `foreground` | |
| 54 | +| Heading 3 | `text-lg` | `font-medium` | `foreground` | |
| 55 | +| Body text | `text-sm` | `font-normal` | `foreground` | |
| 56 | +| UI labels (sidebar, buttons) | `text-sm` | `font-medium` | `foreground` | |
| 57 | +| Secondary text (timestamps, metadata) | `text-xs` | `font-normal` | `muted-foreground` | |
| 58 | +| Code (inline) | `text-sm font-mono` | `font-normal` | `foreground` on `muted` bg | |
| 59 | + |
| 60 | +Rules: |
| 61 | +- Body text is always `text-sm` (14px). Not `text-base`. |
| 62 | +- No `text-lg` or larger outside of headings in the editor. |
| 63 | +- Line height: use Tailwind defaults. Don't override. |
| 64 | +- No uppercase text except section labels (e.g. sidebar group headings), which use `text-xs tracking-widest uppercase text-white/30` — matching the landing page convention. |
| 65 | +- Since the entire app uses a monospace font, inline code blocks are distinguished by background (`bg-muted`) rather than font change. |
| 66 | + |
| 67 | +--- |
| 68 | + |
| 69 | +## Spacing |
| 70 | + |
| 71 | +Use Tailwind's spacing scale. The base unit is 4px. |
| 72 | + |
| 73 | +| Context | Value | Class | |
| 74 | +|---|---|---| |
| 75 | +| Inline element gap (icon + label) | 8px | `gap-2` | |
| 76 | +| Between form fields | 12px | `gap-3` or `space-y-3` | |
| 77 | +| Section padding (cards, panels) | 16px | `p-4` | |
| 78 | +| Page-level padding | 24px | `p-6` | |
| 79 | +| Between major sections | 32px | `gap-8` or `space-y-8` | |
| 80 | + |
| 81 | +Rules: |
| 82 | +- Sidebar width: 240px (`w-60`), collapsible to 0. |
| 83 | +- Editor max-width: 720px (`max-w-3xl`), centered. |
| 84 | +- Never use arbitrary spacing values (`p-[13px]`). Use the scale. |
| 85 | +- Consistent padding: if a container uses `p-4`, all similar containers use `p-4`. |
| 86 | + |
| 87 | +--- |
| 88 | + |
| 89 | +## Corners |
| 90 | + |
| 91 | +Sharp corners by default. Use `rounded-none` or omit border-radius entirely. |
| 92 | + |
| 93 | +Exceptions (use `rounded-sm` only): |
| 94 | +- Dropdown menus and popovers (floating elements need a slight radius to feel intentional). |
| 95 | +- Toast notifications. |
| 96 | +- Code blocks within the editor. |
| 97 | + |
| 98 | +Everything else — buttons, inputs, cards, sidebar items, dialogs, sheets — uses sharp corners. This matches the industrial aesthetic of the landing page. |
| 99 | + |
| 100 | +Override shadcn defaults: set `--radius: 0` in the theme, then apply `rounded-sm` explicitly on the exceptions listed above. |
| 101 | + |
| 102 | +--- |
| 103 | + |
| 104 | +## Layout |
| 105 | + |
| 106 | +### App Shell |
| 107 | + |
| 108 | +``` |
| 109 | +┌──────────────────────────────────────────────┐ |
| 110 | +│ Sidebar (240px) │ Main Content Area │ |
| 111 | +│ │ │ |
| 112 | +│ [Workspace] │ [Breadcrumb] │ |
| 113 | +│ [Search] │ │ |
| 114 | +│ [Page Tree] │ [Page Title] │ |
| 115 | +│ │ [Editor Content] │ |
| 116 | +│ │ │ |
| 117 | +│ [Settings] │ │ |
| 118 | +│ [User Menu] │ │ |
| 119 | +└──────────────────────────────────────────────┘ |
| 120 | +``` |
| 121 | + |
| 122 | +- Sidebar: fixed left, full height, `bg-muted`, `border-r border-white/[0.06]`. |
| 123 | +- Main content: fills remaining width, scrolls independently. |
| 124 | +- No top navbar. The sidebar handles all navigation. |
| 125 | +- Mobile (<768px): sidebar is a Sheet (slides in from left), triggered by hamburger icon. |
| 126 | + |
| 127 | +### Page Tree (Sidebar) |
| 128 | + |
| 129 | +- Indentation: 12px per level (`pl-3` per depth). |
| 130 | +- Expand/collapse chevron: 16px, left of page icon. |
| 131 | +- Page icon: 16px emoji or default document icon. |
| 132 | +- Hover: `bg-white/[0.04]`. |
| 133 | +- Selected: `bg-white/[0.08]` with `text-white/70` and `font-medium`. |
| 134 | +- Drag handle: visible on hover, left edge. |
| 135 | +- "New Page" button: bottom of tree, full width, `text-muted-foreground`. |
| 136 | + |
| 137 | +--- |
| 138 | + |
| 139 | +## Components |
| 140 | + |
| 141 | +Use shadcn/ui as the base. Override border-radius to `0` globally. |
| 142 | + |
| 143 | +### Buttons |
| 144 | + |
| 145 | +| Variant | When to use | |
| 146 | +|---|---| |
| 147 | +| `default` | Primary actions (Save, Create) | |
| 148 | +| `secondary` | Secondary actions (Cancel, Back) | |
| 149 | +| `ghost` | Toolbar actions, sidebar items | |
| 150 | +| `destructive` | Delete, remove | |
| 151 | +| `outline` | Rarely — form actions that aren't primary | |
| 152 | + |
| 153 | +Rules: |
| 154 | +- Size: `sm` in toolbars and dense UI, `default` in forms and dialogs. |
| 155 | +- Icons in buttons: 16px (`h-4 w-4`), left of label with `gap-2`. |
| 156 | +- Icon-only buttons: use `size="icon"` variant, always include `aria-label`. |
| 157 | +- Sharp corners on all buttons. No border-radius. |
| 158 | + |
| 159 | +### Inputs |
| 160 | + |
| 161 | +- Height: `h-9` (default shadcn). |
| 162 | +- Border: `border-white/[0.06]`. Sharp corners. |
| 163 | +- Focus: `ring-2 ring-ring` (using the `--ring` token). |
| 164 | +- Labels: above the input, `text-sm font-medium`, `mb-1.5`. |
| 165 | +- Error text: below the input, `text-xs text-destructive`, `mt-1`. |
| 166 | + |
| 167 | +### Dialogs and Sheets |
| 168 | + |
| 169 | +- Dialogs: for confirmations and short forms (< 5 fields). |
| 170 | +- Sheets: for longer forms, settings panels, mobile sidebar. |
| 171 | +- Overlay: `bg-black/50`. |
| 172 | +- Max width: `sm:max-w-md` for dialogs, `sm:max-w-sm` for sheets. |
| 173 | +- Sharp corners. No border-radius. |
| 174 | + |
| 175 | +### Dropdowns and Context Menus |
| 176 | + |
| 177 | +- Use shadcn `DropdownMenu` for action menus. |
| 178 | +- Use shadcn `ContextMenu` for right-click menus. |
| 179 | +- Menu items: `text-sm`, icon (16px) + label, `gap-2`. |
| 180 | +- Destructive items: `text-destructive` with destructive icon. |
| 181 | +- Keyboard shortcuts: right-aligned, `text-xs text-muted-foreground`. |
| 182 | +- Exception: dropdowns use `rounded-sm` (see Corners section). |
| 183 | + |
| 184 | +### Toast Notifications |
| 185 | + |
| 186 | +- Use shadcn `Sonner` (not the old Toast). |
| 187 | +- Position: bottom-right. |
| 188 | +- Duration: 4 seconds for success, 8 seconds for errors. |
| 189 | +- Exception: toasts use `rounded-sm` (see Corners section). |
| 190 | +- No toasts for routine actions (save, navigate). Only for: |
| 191 | + - Errors the user needs to know about |
| 192 | + - Async operations completing (page shared, export ready) |
| 193 | + - Destructive actions with undo ("Page deleted" + Undo button) |
| 194 | + |
| 195 | +--- |
| 196 | + |
| 197 | +## Editor |
| 198 | + |
| 199 | +The editor is the core of the product. It must feel native. |
| 200 | + |
| 201 | +### Block Types |
| 202 | + |
| 203 | +Each block is a full-width element with consistent vertical spacing. |
| 204 | + |
| 205 | +| Block | Spacing above | Notes | |
| 206 | +|---|---|---| |
| 207 | +| Paragraph | 2px (`mt-0.5`) | Default block | |
| 208 | +| Heading 1 | 24px (`mt-6`) | `text-2xl font-semibold` | |
| 209 | +| Heading 2 | 20px (`mt-5`) | `text-xl font-semibold` | |
| 210 | +| Heading 3 | 16px (`mt-4`) | `text-lg font-medium` | |
| 211 | +| Bullet list | 2px (`mt-0.5`) | Indented 24px per level | |
| 212 | +| Numbered list | 2px (`mt-0.5`) | Indented 24px per level | |
| 213 | +| To-do list | 2px (`mt-0.5`) | Checkbox + text, indented 24px per level | |
| 214 | +| Code block | 12px (`mt-3`) | `bg-muted rounded-sm p-4 font-mono text-sm` | |
| 215 | +| Divider | 12px (`mt-3 mb-3`) | `border-t border-white/[0.06]` | |
| 216 | +| Callout | 12px (`mt-3`) | `bg-muted p-4`, emoji left, sharp corners | |
| 217 | +| Image | 12px (`mt-3`) | Max width 100%, centered, optional caption | |
| 218 | + |
| 219 | +### Slash Command Menu |
| 220 | + |
| 221 | +- Triggered by `/` at the start of a line or after a space. |
| 222 | +- Appears as a floating menu below the cursor. |
| 223 | +- Filterable by typing after `/`. |
| 224 | +- Items: icon (20px) + name + description, `text-sm`. |
| 225 | +- Max height: 300px, scrollable. |
| 226 | +- Keyboard navigation: arrow keys + Enter. |
| 227 | +- Uses `rounded-sm` (floating element exception). |
| 228 | + |
| 229 | +### Toolbar |
| 230 | + |
| 231 | +- Floating toolbar appears on text selection. |
| 232 | +- Items: bold, italic, underline, strikethrough, code, link, color. |
| 233 | +- Icon-only buttons, 28px square, `gap-0.5`. |
| 234 | +- `bg-popover border border-white/[0.06] shadow-md p-1`. Sharp corners. |
| 235 | + |
| 236 | +### Placeholder Text |
| 237 | + |
| 238 | +- Empty page: "Untitled" in the title, `text-muted-foreground`. |
| 239 | +- Empty block: "Type '/' for commands" in `text-muted-foreground`. |
| 240 | +- Only show placeholder in the focused empty block, not all empty blocks. |
| 241 | + |
| 242 | +--- |
| 243 | + |
| 244 | +## Interaction Patterns |
| 245 | + |
| 246 | +### Loading States |
| 247 | + |
| 248 | +- **Page navigation**: instant (optimistic). Show skeleton of the page layout while content loads. |
| 249 | +- **Data mutations**: optimistic update, revert on error with toast. |
| 250 | +- **Initial app load**: full-page skeleton matching the app shell layout. |
| 251 | +- **Skeleton style**: `bg-muted animate-pulse`, matching the shape of the content. Sharp corners. |
| 252 | + |
| 253 | +### Empty States |
| 254 | + |
| 255 | +- Centered in the content area. |
| 256 | +- Icon (48px, `text-muted-foreground`) + heading (`text-lg font-medium`) + description (`text-sm text-muted-foreground`) + CTA button. |
| 257 | +- No illustrations. Keep it simple. |
| 258 | + |
| 259 | +### Keyboard Shortcuts |
| 260 | + |
| 261 | +- Display in tooltips and menu items. |
| 262 | +- Format: `⌘` for Mac, `Ctrl` for others. Detect OS. |
| 263 | +- Global shortcuts: `⌘+K` (search), `⌘+N` (new page), `⌘+\` (toggle sidebar). |
| 264 | + |
| 265 | +### Drag and Drop |
| 266 | + |
| 267 | +- Drag handle: 6-dot grip icon, `text-muted-foreground`, visible on hover. |
| 268 | +- Drop indicator: 2px `bg-accent` line at the drop position. |
| 269 | +- Dragging: element at 50% opacity, slight scale (`scale-[1.02]`), `shadow-lg`. |
| 270 | + |
| 271 | +### Transitions |
| 272 | + |
| 273 | +- Sidebar collapse: 200ms ease-out. |
| 274 | +- Dropdown/popover open: 150ms ease-out (shadcn default). |
| 275 | +- Page transitions: none (instant navigation). |
| 276 | +- Hover states: no transition (instant). |
| 277 | +- No decorative animations. No bounce, no spring, no parallax. |
| 278 | + |
| 279 | +--- |
| 280 | + |
| 281 | +## Responsive Behavior |
| 282 | + |
| 283 | +| Breakpoint | Behavior | |
| 284 | +|---|---| |
| 285 | +| ≥1024px (lg) | Full layout: sidebar + content | |
| 286 | +| 768–1023px (md) | Sidebar collapsed by default, toggle to overlay | |
| 287 | +| <768px (sm) | Sidebar as Sheet, hamburger in top-left, editor full-width with `px-4` | |
| 288 | + |
| 289 | +- Editor max-width stays `max-w-3xl` on all breakpoints. |
| 290 | +- Touch targets: minimum 44px on mobile. |
| 291 | +- No horizontal scroll on any breakpoint. |
| 292 | + |
| 293 | +--- |
| 294 | + |
| 295 | +## Accessibility |
| 296 | + |
| 297 | +- All interactive elements are keyboard-accessible. |
| 298 | +- Focus rings: `ring-2 ring-ring ring-offset-2`. |
| 299 | +- Color contrast: minimum 4.5:1 for text, 3:1 for UI elements. |
| 300 | +- Images: always have `alt` text. |
| 301 | +- Icon-only buttons: always have `aria-label`. |
| 302 | +- Page tree: uses `role="tree"` and `role="treeitem"` with `aria-expanded`. |
| 303 | +- Editor: uses `role="textbox"` with `aria-multiline="true"`. |
| 304 | + |
| 305 | +--- |
| 306 | + |
| 307 | +## What NOT to Do |
| 308 | + |
| 309 | +- No light mode. Dark only. |
| 310 | +- No gradients on UI surfaces (background gradients on the page shell are acceptable). |
| 311 | +- No custom scrollbars. |
| 312 | +- No loading spinners — use skeletons. |
| 313 | +- No modals for navigation (use routes). |
| 314 | +- No color outside the token set. |
| 315 | +- No font sizes outside the typography scale. |
| 316 | +- No spacing outside the spacing scale. |
| 317 | +- No hover animations or transitions on text. |
| 318 | +- No "empty" pages with just a button — always provide context. |
| 319 | +- No rounded corners except the explicit exceptions in the Corners section. |
| 320 | +- No system font stack — use JetBrains Mono everywhere. |
| 321 | +- No visible borders — borders should blend with surfaces (`border-white/[0.06]` or match `--border` to `--muted`). |
0 commit comments