# LinkBreeze Design System This document defines the visual language for LinkBreeze's admin interface. Every component, page, and contribution must follow these rules. **Public pages** (the user-facing `/{slug}` route) are user-themed and override these tokens via CSS variables — they are NOT bound by this system. --- ## 1. Design Philosophy LinkBreeze's admin is a **dark aurora glass UI**. The visual identity is: - **Aurora background**: animated violet/lavender gradient blobs behind every screen - **Glass panels**: semi-transparent cards with backdrop-blur and subtle lavender borders - **Calm, confident spacing**: generous whitespace, no cramped layouts - **Two fonts**: Clash Display for headings (geometric, modern), Satoshi for body (warm, readable) - **Motion is rare and purposeful**: aurora drift (ambient), card hover (interactive), aurora-rise (entrance) The admin is always dark. There is no light mode and no plan for one. --- ## 2. Color Tokens ### Brand Palette (CSS variables in `globals.css`) | Token | Value | Used for | |---|---|---| | `--background` | `#0a0820` | App background (behind aurora) | | `--foreground` | `#eceafe` | Default text | | `--card` | `rgba(20, 17, 46, 0.6)` | Glass card background | | `--popover` | `#1b1738` | Dropdowns, menus | | `--primary` | `#533fd6` | Primary actions, buttons | | `--primary-foreground` | `#ffffff` | Text on primary | | `--secondary` | `#1b1738` | Secondary surfaces | | `--muted` | `#1b1738` | Muted backgrounds | | `--muted-foreground` | `#b7b1e6` | Labels, hints, secondary text | | `--accent` | `#261f4d` | Hover states, active nav | | `--border` | `rgba(167, 139, 250, 0.12)` | All borders | | `--input` | `rgba(167, 139, 250, 0.16)` | Input borders | | `--ring` | `#a78bfa` | Focus rings | ### Semantic Feedback Tokens | Token | Value | Used for | |---|---|---| | `--destructive` | `oklch(0.62 0.22 25)` | Errors, delete actions | | `--success` | `oklch(0.65 0.17 150)` | Success messages, confirmations | | `--warning` | `oklch(0.72 0.15 75)` | Warnings, default-page star | ### Brand Accent Scale (use via Tailwind classes) | Class | Value | Usage | |---|---|---| | `bg-violet / text-violet` | `#533fd6` | Same as `--primary` — use for brand fills | | `bg-violet-bright` | `#7c3aed` | Hover accents, gradients | | `text-lavender` | `#a78bfa` | Brand accent text (links, icons, active states) | | `text-lavender-soft` | `#c4b5fd` | Lighter accent variant | ### Rules 1. **Never use raw Tailwind colors** (`text-green-500`, `text-red-400`, `text-amber-400`). Use the semantic tokens: `text-success`, `text-destructive`, `text-warning`. 2. **Lavender is brand accent, not success.** Use `text-success` for confirmations. 3. **Border opacity is locked**: use `border-border` (the token). For accent borders, use `border-lavender/12` (subtle) or `border-violet/30` (emphasis). Do not invent new opacity values. 4. **Background tints**: `bg-violet/15` for active/hover, `bg-violet/10` for subtle hints. Two values, no others. --- ## 3. Spacing Scale All spacing must use one of these 5 steps. No exceptions. | Token | Tailwind | Pixels | Use | |---|---|---|---| | **xs** | `gap-1` / `p-1` | 4px | Icon gaps, tight clusters | | **sm** | `gap-2` / `p-2` | 8px | Label → input, button icon gaps | | **md** | `gap-3` / `p-3` | 12px | Between form fields, card sections | | **lg** | `gap-4` / `p-4` | 16px | Between cards, main content padding | | **xl** | `gap-6` / `p-6` | 24px | Card header ↔ content, major sections | ### Rules 1. **Form fields use `gap-2` (8px)** between label and input. This is enforced by the `FormField` component — do not hand-roll `
`. 2. **Card content uses `gap-4` (16px)** between fields (the `CardContent` default). 3. **Main content uses `p-4` (mobile) / `p-8` (desktop)** — set in the admin layout. 4. **Never use `gap-5`, `gap-7`, `mb-3`, `mb-5`** or other off-scale values. If you need more space, jump to the next token up. --- ## 4. Typography ### Font Families | Token | Family | Weights | Used for | |---|---|---|---| | `--font-heading` | Clash Display | 500, 600, 700 | Card titles, page headings, logo | | `--font-sans` | Satoshi | 400, 500, 700 | All body text, labels, buttons | | `--font-mono` | Geist Mono | 400 | Code, URLs in hints | ### Type Scale | Class | Size | Used for | |---|---|---| | `text-xs` | 12px | Labels, hints, badges, timestamps | | `text-sm` | 14px | Body text, input values, button labels | | `text-base` | 16px | Rare — only when `text-sm` feels too small | | `text-lg` | 18px | Card titles (via `CardTitle` component) | | `text-xl` | 20px | Section headings in dashboards | | `text-2xl` | 24px | Page titles | ### Rules 1. **Body text is always `text-sm`** unless there's a specific reason otherwise. 2. **Labels and hints are always `text-xs`** with `text-muted-foreground` for hints. 3. **Headings use `font-heading`** — this is set by `CardTitle` automatically. For standalone headings, add `className="font-heading"`. 4. **Public page fonts** are separate — 9 families loaded via `next/font/google` and applied via `--lb-font-*` variables by the theme system. --- ## 5. Component Conventions ### FormField (required for all admin forms) ```tsx import { FormField } from "@/components/ui/form-field"; ``` - Label, spacing, and hint are handled by the component. - Do not wrap Label + Input in a raw `
` — use `FormField`. - For fields with a prefix (like the `/` before a slug), use `FormField` with a flex row inside: `
...
` ### Cards - `CardContent` uses `gap-4` between fields by default. - Card titles: `CardTitle` renders at `text-base font-heading` automatically. - Card descriptions: `CardDescription` renders at `text-sm text-muted-foreground`. - Use `size="sm"` for compact cards (settings sub-sections). ### Buttons - Default size (`h-8`) for form submit buttons and primary actions. - `size="sm"` (`h-7`) for secondary actions inside cards. - `size="icon"` / `size="icon-sm"` for icon-only buttons. - Form submit buttons go in `CardFooter` or at the end of the form, right-aligned. - Destructive actions use `variant="destructive"`. ### Inputs - Height: `h-8` (locked in the Input component). - Border radius: `rounded-lg` (locked in the Input component). - Focus state: `focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50`. --- ## 6. Border Radius | Token | Value | Tailwind | Used for | |---|---|---|---| | `--radius-sm` | `0.525rem` | `rounded-md` | Small elements (badges, chips) | | `--radius-md` | `0.7rem` | `rounded-lg` | Inputs, buttons (the default) | | `--radius-lg` | `0.875rem` | `rounded-xl` | Cards, dialogs | | `--radius-full` | `9999px` | `rounded-full` | Avatars, pills | Base `--radius: 0.875rem`. All other radii are derived via multipliers. --- ## 7. Motion | Element | Animation | Duration | Easing | |---|---|---|---| | Aurora blobs | `aurora-drift` | 24s / 30s | ease-in-out, infinite | | Entrances | `aurora-rise` | 0.5s | `cubic-bezier(0.16, 1, 0.3, 1)` | | Card hover | border/shadow transition | 0.2s | default | | Link card hover | lift/scale/glow | 0.18s | ease | | Nav hover | `translate-x-0.5` | default | default | ### Rules 1. **`prefers-reduced-motion: reduce`** disables ALL animations. Test with this. 2. No spring physics, no bounce, no stagger beyond what exists. 3. Transitions are for hover/focus state changes only — no decorative animation. 4. The aurora background is the only ambient animation. --- ## 8. Accessibility 1. **Focus rings**: every interactive element shows `focus-visible:ring-3` with `ring-ring/50`. Never remove `outline-none` without replacing it. 2. **Contrast**: `--foreground` (#eceafe) on `--background` (#0a0820) = 14.5:1 (AAA). `--muted-foreground` (#b7b1e6) on `--card` = 7.2:1 (AAA). 3. **Touch targets**: minimum 32px (`size-8`). Mobile tab bar items are 48px tall. 4. **Labels**: every input has a `