# DESIGN SYSTEM — BellSystems Control Panel v2

> Single source of truth for all UI/UX decisions.
> Read before writing any page, component, or modal.
> Never override these rules inline. Change the rule here first, then propagate.
>
> Live reference: `/dev/styleguide` — every component, every variant, every state.

---

## 1. Core Philosophy

- **Consistency over creativity.** Every page must feel like it belongs to the same product.
- **Tokens over hardcoded values.** Never write a raw color, spacing value, or font size. Always `var(--token)`.
- **Components over repetition.** If you write the same pattern twice, it becomes a shared component.
- **Page layout is global.** All pages share the same padding and spacing anchors. Content always starts at the same position.
- **Accessible by default.** ARIA labels, keyboard navigation, visible focus states on all interactive elements.

---

## 2. Page Layout Anatomy

Every authenticated page lives inside `MainLayout`, which provides:

```
┌─────────────┬────────────────────────────────────────────┐
│             │  HEADER  (height: 56px / --header-height)  │
│             │────────────────────────────────────────────┤
│             │                                            │
│   SIDEBAR   │          CONTENT AREA                      │
│  (224px /   │  ┌──────────────────────────────────────┐  │
│ --sidebar-  │  │  .page-wrapper                       │  │
│   width)    │  │  padding: 48px (--space-12)          │  │
│             │  │  gap: 24px between sections          │  │
│             │  │                                      │  │
│             │  │  <PageHeader>   ← always first       │  │
│             │  │  <content...>                        │  │
│             │  └──────────────────────────────────────┘  │
└─────────────┴────────────────────────────────────────────┘
```

### The consistency guarantee

`.page-wrapper` is defined once in `global.css`. It is the only wrapper used on every page:

```css
.page-wrapper {
  flex: 1;
  display: flex;
  flex-direction: column;
  padding: var(--space-12);  /* 48px — desktop */
  gap: var(--space-6);       /* 24px between direct children */
  min-width: 0;
}
/* Mobile: padding drops to --space-8 (32px), gap to --space-4 (16px) */
```

**Rules:**
- Every page's root element is `<div className="page-wrapper">` — no exceptions
- Never add extra padding, margin, or wrapper divs that shift content relative to `.page-wrapper`
- Never override `.page-wrapper` padding per-page
- The `<PageHeader>` is always the first child inside `.page-wrapper`
- This ensures that on every page, the title starts at exactly 48px from the top-left corner of the content area

### Content width modes

Every page falls into one of two modes:

#### 1. Full-width (default)
Content expands to fill the entire available area. Use this for all data-heavy pages: lists, tables, dashboards, detail views.

```jsx
<div className="page-wrapper">
  {/* content fills the full content area */}
</div>
```

#### 2. Centered (narrow content)
For pages with a small number of elements that would look lost spanning the full viewport — e.g. settings forms, auth pages, single-entity configuration screens.

```jsx
<div className="page-wrapper page-wrapper--centered">
  {/* every direct child is capped at --content-max-width-sm (640px) and centered */}
</div>
```

Default max-width is `--content-max-width-sm` (640px). Override per-page only when necessary:

```jsx
<div
  className="page-wrapper page-wrapper--centered"
  style={{ '--page-content-max-width': 'var(--content-max-width-md)' }}
>
```

Available width tokens:

| Token                       | Value  | Use                                     |
|-----------------------------|--------|-----------------------------------------|
| `--content-max-width-xs`    | 480px  | Tiny forms, login, auth                 |
| `--content-max-width-sm`    | 640px  | Small forms, simple settings (default)  |
| `--content-max-width-md`    | 800px  | Medium forms, detail-light pages        |
| `--content-max-width-lg`    | 1024px | Moderate-width constrained pages        |

**Rules:**
- Never use `page-wrapper--centered` on a list page, data table page, or any page where content should grow with the viewport
- Never hardcode a pixel `max-width` in a page file — always use a token

---

## 3. Color Tokens

All colors are CSS custom properties defined in `frontend/src/styles/tokens.css`.
The system is **dark-first**: `:root` = dark theme. `[data-theme="light"]` overrides exist as a placeholder.

### Rule: never write a raw color value in any component or page file. Always `var(--token)`.

### Background Surfaces (7-step tonal ladder)

| Token                  | Value        | Use                                                    |
|------------------------|--------------|--------------------------------------------------------|
| `--color-bg-abyss`     | `#0a0e14`    | Deepest well: code blocks, input backgrounds           |
| `--color-bg-base`      | `#10141a`    | Page background (viewport fill)                        |
| `--color-bg-void`      | `#181c22`    | Sidebar, header                                        |
| `--color-bg-surface`   | `#1c2026`    | Default card / panel background                        |
| `--color-bg-elevated`  | `#262a31`    | Raised cards, hovered rows, dropdowns                  |
| `--color-bg-island`    | `#31353c`    | Active states, selected rows, pressed buttons          |
| `--color-bg-float`     | `rgba(53,57,64,0.80)` | Glassmorphism: modals, floating panels      |

### Brand / Primary (Indigo Glow)

| Token                      | Value                        | Use                                  |
|----------------------------|------------------------------|--------------------------------------|
| `--color-primary`          | `#c0c1ff`                    | CTAs, active nav, key accent          |
| `--color-primary-hover`    | `#d2bbff`                    | Hover, gradient endpoint              |
| `--color-primary-container`| `#8083ff`                    | Container fills                       |
| `--color-primary-subtle`   | `rgba(128,131,255,0.12)`     | Hover backgrounds, tinted areas       |
| `--gradient-primary`       | `linear-gradient(135deg, #c0c1ff, #d2bbff)` | Primary button fill   |

### Semantic / State Colors

| Token                  | Value                     | Use                                            |
|------------------------|---------------------------|------------------------------------------------|
| `--color-success`      | `#4ade80`                 | Online, active, confirmed                      |
| `--color-success-bg`   | `rgba(74,222,128,0.12)`   | Success badge / button resting background      |
| `--color-warning`      | `#fbbf24`                 | Pending, needs attention                       |
| `--color-warning-bg`   | `rgba(251,191,36,0.12)`   | Warning badge / button resting background      |
| `--color-danger`       | `#ff5c5c`                 | Error text, destructive actions                |
| `--color-danger-bg`    | `rgba(255,92,92,0.12)`    | Danger badge / button resting background       |
| `--color-info`         | `#7bd0ff`                 | Informational, aqua-sky accent                 |
| `--color-info-bg`      | `rgba(123,208,255,0.12)`  | Info badge background                          |

### Text Colors (4-step hierarchy)

| Token                     | Value       | Use                                               |
|---------------------------|-------------|---------------------------------------------------|
| `--color-text-primary`    | `#dfe2eb`   | Body copy, data values, headings                  |
| `--color-text-secondary`  | `#c7c4d7`   | Labels, metadata, inactive nav                    |
| `--color-text-muted`      | `#908fa0`   | Placeholders, disabled, category headers          |
| `--color-text-inverse`    | `#10141a`   | Text on primary/accent backgrounds (dark on light)|
| `--color-text-accent`     | `#c0c1ff`   | Active nav items, links                           |

### Borders

| Token                   | Value                      | Use                                        |
|-------------------------|----------------------------|--------------------------------------------|
| `--color-border`        | `rgba(70,69,84,0.20)`      | Resting inputs, card outlines              |
| `--color-border-strong` | `rgba(70,69,84,0.45)`      | Secondary buttons, stronger dividers       |
| `--color-border-focus`  | `rgba(192,193,255,0.40)`   | Focus ring halo on inputs                  |

---

## 4. Typography

Two-font system. Three families total.

### Font Families

| Token                    | Font                | Role                                              |
|--------------------------|---------------------|---------------------------------------------------|
| `--font-family-display`  | `Barlow Condensed`  | H1, H2, page titles, modal titles                 |
| `--font-family-base`     | `Onest`             | All UI text, body, labels, buttons, table rows    |
| `--font-family-mono`     | `JetBrains Mono`    | Serial numbers, IDs, code, API keys, terminal     |

**Why this pairing:**
- `Barlow Condensed` has an industrial/engineering quality — feels like instrument panel labelling. Makes page titles immediately distinctive.
- `Onest` is a Ukrainian geometric grotesque with slightly unusual proportions and excellent numerics. Clean at 14px. Not the overused Inter/Space Grotesk.
- `JetBrains Mono` is the standard for developer-facing data.

### Font Sizes

| Token              | Value      | Use                                          |
|--------------------|------------|----------------------------------------------|
| `--font-size-xs`   | `0.6875rem` (11px) | Labels, sidebar category headers, chips |
| `--font-size-sm`   | `0.75rem` (12px)   | Captions, helper text, table headers    |
| `--font-size-base` | `0.875rem` (14px)  | Body text, table rows (default)         |
| `--font-size-md`   | `1rem` (16px)      | Card titles, module headers             |
| `--font-size-lg`   | `1.125rem` (18px)  | Section subheadings                     |
| `--font-size-xl`   | `1.5rem` (24px)    | Page headings (h1/h2)                   |
| `--font-size-2xl`  | `3.5rem` (56px)    | Hero KPI numbers, dashboard metrics     |

### Font Weights

| Token                     | Value | Use                                    |
|---------------------------|-------|----------------------------------------|
| `--font-weight-normal`    | 400   | Body copy                              |
| `--font-weight-medium`    | 500   | Emphasized body, table values          |
| `--font-weight-semibold`  | 600   | Headings, button labels, field labels  |
| `--font-weight-bold`      | 700   | Strong emphasis, hero metrics          |

### Typography Usage Rules

- **Page titles (`<PageHeader>`):** `Barlow Condensed`, `1.75rem`, weight 600 (handled by `.v2-page-header-title`)
- **Modal titles:** `Barlow Condensed`, `1.125rem`, weight 600 (handled by `.v2-modal-title`)
- **H1, H2 globally:** `Barlow Condensed`, `var(--font-size-xl)`, weight 600 — set in `global.css`
- **H3–H6:** `Onest` (body font), normal heading weights
- **Card titles:** `Onest`, `--font-size-base`, weight 600
- **Table headers:** `Onest`, `--font-size-sm`, weight 600, uppercase, `--tracking-wide`
- **Body / cell text:** `Onest`, `--font-size-base`, weight 400
- **Muted / helper text:** `Onest`, `--font-size-sm`, `--color-text-muted`
- **Serials, IDs, codes:** `JetBrains Mono`, `--font-size-sm`

### Letter Spacing

| Token               | Value     | Use                                        |
|---------------------|-----------|--------------------------------------------|
| `--tracking-normal` | `0em`     | Default                                    |
| `--tracking-tight`  | `-0.01em` | Barlow Condensed headings                  |
| `--tracking-wide`   | `0.08em`  | Uppercase labels, sidebar category headers |
| `--tracking-display`| `-0.02em` | Hero KPI numbers at 56px                   |

---

## 4b. Date, Time & Currency Formatting

All dates use **Greek/European style** (day-first). Never use US-style MM/DD/YYYY anywhere in the app.

All formatting is centralized in `frontend/src/lib/formatters.js`. Never use raw `toLocaleDateString()`, `Intl.DateTimeFormat`, `toLocaleString()`, or `toISOString().slice()` in pages or modals — always import from `@/lib/formatters`.

### Available formatters

| Function            | Output example                     | Use for                              |
|---------------------|------------------------------------|--------------------------------------|
| `fmtDate`           | `05/03/2026`                       | Short numeric dates (tables, lists)  |
| `fmtDateMedium`     | `5 Mar 2026`                       | Medium dates (cards, details)        |
| `fmtDateLong`       | `5 March 2026`                     | Long dates (headings, summaries)     |
| `fmtDateFull`       | `Wednesday, 5 March 2026`          | Dashboard, full context              |
| `fmtDateTime`       | `5 March 2026, 2:30 pm`            | Date + 12h time                      |
| `fmtDateTimeMedium` | `5 Mar 2026, 14:30`                | Date + 24h time (compact)            |
| `fmtDateTimeFull`   | `Wed, 5 Mar 2026, 2:30 pm`         | Emails, comms                        |
| `fmtRelative`       | `5 minutes ago`                    | Relative timestamps                  |
| `fmtEuro`           | `1.250,00 €`                       | Euro currency (Greek locale)         |

### Form input helpers

| Function            | Output example          | Use for                                          |
|---------------------|-------------------------|--------------------------------------------------|
| `toDatetimeLocal`   | `2026-03-05T14:30`      | Populating `datetime-local` inputs (local time)  |
| `nowLocal`          | `2026-03-05T14:30`      | Current time for form defaults                   |
| `toDateInput`       | `2026-03-05`            | Populating `date` inputs                         |

### Critical rule: no `toISOString().slice()` for form inputs

`toISOString()` converts to **UTC**, which shifts the time by the user's timezone offset (e.g. 3 hours for Greece). Always use `toDatetimeLocal()` or `nowLocal()` instead.

---

## 5. Spacing System

4px base unit. All spacing must use tokens — no arbitrary pixel values.

| Token        | Value  | Common use                                       |
|--------------|--------|--------------------------------------------------|
| `--space-1`  | 4px    | Tight gaps, icon padding                         |
| `--space-2`  | 8px    | Between label and input, inline gaps             |
| `--space-3`  | 12px   | Table cell padding, compact button padding       |
| `--space-4`  | 16px   | Between form fields, mobile page padding         |
| `--space-5`  | 20px   | Tab item spacing                                 |
| `--space-6`  | 24px   | **Page padding**, card padding, section gap      |
| `--space-8`  | 32px   | Between major sections                           |
| `--space-10` | 40px   | Large section gap                                |
| `--space-12` | 48px   | Extra large spacing                              |
| `--space-16` | 64px   | Maximum spacing, hero sections                   |

---

## 6. Border Radius & Shadows

### Border Radius

| Token          | Value    | Use                                       |
|----------------|----------|-------------------------------------------|
| `--radius-sm`  | 4px      | Tags, small chips, select option rows     |
| `--radius-md`  | 6px      | Buttons, inputs, table badges             |
| `--radius-lg`  | 8px      | Cards, panels, dropdown menus             |
| `--radius-xl`  | 12px     | Modals, large containers                  |
| `--radius-full`| 9999px   | Status badge pills, avatars               |

### Shadows

| Token                   | Value                                    | Use                                |
|-------------------------|------------------------------------------|------------------------------------|
| `--shadow-card`         | `inset 0 1px 0 rgba(192,193,255,0.05)`  | Card top-edge glass reflection     |
| `--shadow-sm`           | `0 2px 8px rgba(10,14,20,0.40)`         | Subtle lift                        |
| `--shadow-md`           | `0 4px 16px rgba(10,14,20,0.50)`        | Elevated cards                     |
| `--shadow-lg`           | `0 8px 24px rgba(13,17,23,0.60)`        | Modals, dropdowns                  |
| `--shadow-focus`        | `0 0 0 3px rgba(192,193,255,0.20)`      | Focus ring glow                    |
| `--shadow-primary-glow` | `0 4px 16px rgba(192,193,255,0.28)`     | Primary button hover halo          |
| `--shadow-danger-glow`  | `0 4px 16px rgba(255,92,92,0.40)`       | Danger button hover halo           |
| `--shadow-success-glow` | `0 4px 16px rgba(74,222,128,0.35)`      | Success button hover halo          |

---

## 7. Component Rules

### Button

Import: `@/components/ui/Button`

**Variants:**

| Variant          | Resting state                         | Hover state                                              |
|------------------|---------------------------------------|----------------------------------------------------------|
| `primary`        | Indigo→violet gradient, dark text     | `+brightness(1.06)` + `--shadow-primary-glow` halo      |
| `secondary`      | Island bg, ghost border               | Elevated bg + focus border + subtle indigo glow          |
| `ghost`          | Transparent                           | Elevated bg + whisper indigo glow                        |
| `danger`         | Coral tint bg, coral text             | **Solid coral fill**, dark text + `--shadow-danger-glow` |
| `success`        | Emerald tint bg, emerald text         | **Solid emerald fill**, dark text + `--shadow-success-glow` |
| `table-actions`  | Fully transparent, muted text         | Island bg + strong border (identical to `secondary`) — also activates on `tr:hover` |

**Sizes:** `sm`, `md` (default), `lg`

**Rules:**
- Never use a raw `<button>` element for a styled action
- Always pass `loading` prop for async actions (shows spinner, disables interaction)
- Icon-only buttons must have `aria-label`
- Active/press state: `filter: brightness(0.94)`, glow removed

---

### FormField

Import: `@/components/ui/FormField`

Wraps every form control: label + input/textarea/select + hint + error message.
Never place a raw `<input>` on a page.

**Types:** `text`, `email`, `password`, `number`, `tel`, `url`, `textarea`, `select`

**For `type="select"`**: pass `<option>` elements as children. FormField uses the custom `Select` component internally — the native `<select>` is never rendered.

```jsx
<FormField label="Status" name="status" type="select" value={val} onChange={handleChange}>
  <option value="">Choose…</option>
  <option value="active">Active</option>
  <option value="inactive">Inactive</option>
</FormField>
```

**Input appearance:** "cutout" inset-shadow treatment — the field appears recessed into the surface. Background: `--color-bg-abyss`. Focus: `--color-border-focus` ring.

---

### Select (standalone)

Import: `@/components/ui/Select`

Fully custom dropdown replacing native `<select>`. Floating menu via portal, keyboard navigation, checkmark on selected item. Usually consumed via `FormField type="select"`. Use directly when you need a select outside a form label context.

---

### DataTable

Import: `@/components/ui/DataTable`

**Always include:** column headers, loading skeleton, empty state, pagination.
**Rows:** alternate tint via `--color-tint-row` (`rgba(192,193,255,0.015)`). Hover: `--color-bg-island`.
**Status columns:** always `<StatusBadge>` — never raw text.
**Row actions:** last column, right-aligned, use portal-based action menu.

---

### Modal

Import: `@/components/ui/Modal`

Sizes: `sm` (480px), `md` (640px — default), `lg` (800px), `xl` (60vw/60vh), `xxl` (85vw/85vh), `full` (calc(100vw/100vh − 64px)).

**Rules:**
- Always has: title, close (×) button, footer action buttons
- Closes on Escape + backdrop click unless `persistent={true}`
- Destructive prompts use `<ConfirmDialog>` instead
- Modal JSX never lives inside a page file — always in `frontend/src/modals/[domain]/`

---

### ConfirmDialog

Import: `@/components/ui/ConfirmDialog`

Wraps `<Modal size="sm">` with a centred icon + message. Use for any action that is destructive or hard to reverse.

Variants: `danger` (coral circle + triangle icon), `primary` (indigo circle + info icon).

---

### PageHeader

Import: `@/components/ui/PageHeader`

**Always the first element inside `.page-wrapper`.** Creates the page title block.

Props: `title` (required), `subtitle`, `breadcrumbs`, `children` (action buttons slot).

The title renders as `<h1>` with class `.v2-page-header-title` — uses `Barlow Condensed` at `1.75rem` / weight 600.

```jsx
<PageHeader title="Device Inventory" subtitle="All registered Bell units">
  <Button variant="primary">Add Device</Button>
</PageHeader>
```

---

### Card

Import: `@/components/ui/Card`

Variants: `flat` (default), `elevated`, `outlined`.

Props: `title`, `subtitle`, `footer`, `padding` (bool, default true), `children`.

Card header has a faint indigo gradient ceiling (`linear-gradient` from top).

---

### Tabs

Import: `@/components/ui/Tabs`

Variants: `line` (default — underline indicator), `pill` (filled background).

Props: `tabs` (array of `{key, label, icon?, count?}`), `active`, `onChange`, `variant`.

Line variant uses a sliding indicator measured with `useLayoutEffect`. Pill variant uses filled backgrounds.

Spacing: line tabs have `gap: --space-5` between items, pill tabs `gap: --space-4`.

---

### Toast

Import: `@/components/ui/Toast` → `{ ToastProvider, useToast }`

Setup: wrap the app (or router) with `<ToastProvider>`. Then in any component:

```jsx
const toast = useToast()
toast.success('Saved', 'Device updated successfully.')
toast.danger('Error', 'Failed to connect.')
toast.warning('Warning', 'Firmware is outdated.')
toast.info('Info', 'Sync in progress.')
```

Toasts auto-dismiss after 4000ms. Hover pauses the timer. Stack appears in the bottom-right corner.

---

### SearchBar

Import: `@/components/ui/SearchBar`

Supports controlled (`value` + `onChange`) or uncontrolled mode.
Debounced by default (300ms). Clear button appears when text is present.
Appearance matches the `FormField` cutout treatment.

---

### Breadcrumbs

Import: `@/components/ui/Breadcrumbs`

Use on detail pages only (not list pages). Items: array of `{ label, href? }`. Last item has no href — it is the current page.

---

### Spinner

Import: `@/components/ui/Spinner`

Props: `size` (`sm`, `md`, `lg`), `color` (defaults to `--color-primary`).

Use inside loading states. Buttons show their own spinner via `loading` prop — do not add a separate `<Spinner>` inside buttons.

---

### StatusBadge

Import: `@/components/ui/StatusBadge`

Never use a raw `<span>` with a background color for status. Always `<StatusBadge>`.

Variants: `success`, `warning`, `danger`, `info`, `neutral`.

---

### Icon

Import: `@/components/ui/Icon`

Renders an inline SVG by name. 35 named icons available (see Style Guide `/dev/styleguide` → Icon section for the full list).

```jsx
<Icon name="edit" size={16} />
<Icon name="delete" size={20} color="var(--color-danger)" />
```

**Asset SVGs** (from `/assets/` folders) are displayed via `<img>` tags in the Style Guide, not via `<Icon>`. These are pre-rendered SVG files used for sidebar icons, comms icons, customer status icons, etc. Use them as image sources, not as Icon component names.

---

## 8. Icons

Three sources:

| Source                              | Use case                              | How to render               |
|-------------------------------------|---------------------------------------|-----------------------------|
| `<Icon name="..." />`               | Action icons, UI chrome               | `@/components/ui/Icon`      |
| `assets/side-menu-icons/*.svg`      | Sidebar navigation                    | `<img src={...} />`         |
| `assets/comms/*.svg`                | Communication type indicators         | `<img src={...} />`         |
| `assets/customer-status/*.svg`      | CRM status icons                      | `<img src={...} />`         |
| `assets/global-icons/*.svg`         | Legacy action icons (prefer `<Icon>`) | `<img src={...} />`         |
| `assets/other-icons/*.svg`          | Misc UI icons                         | `<img src={...} />`         |

Never add a new icon library (e.g. heroicons, lucide). Use the existing sources.

---

## 9. Theming Rules

- Theme is controlled by `data-theme` attribute on `<html>`
- Default is dark (`:root` = dark theme)
- **Never** use Tailwind's `dark:` prefix — theming is handled entirely via CSS tokens
- `[data-theme="light"]` overrides exist in `tokens.css` as a future placeholder

---

## 10. Responsive Breakpoints

| Token              | Value  | Behaviour                                              |
|--------------------|--------|--------------------------------------------------------|
| `--breakpoint-sm`  | 640px  |                                                        |
| `--breakpoint-md`  | 768px  | Sidebar collapses; page padding drops to `--space-4`  |
| `--breakpoint-lg`  | 1024px | Full sidebar shown                                     |
| `--breakpoint-xl`  | 1280px |                                                        |

Mobile (`< 768px`): single column, sidebar hidden (drawer), tables may become card lists.

---

## 11. Section Layout — Masonry Grid

**Default layout for ALL content pages with multiple variable-height sections.**

Sections on a content page must flow like physical objects stacked in columns — the next section always drops into the shortest column. This is CSS column masonry.

### How it works

```
Column 1    | Column 2    | Column 3
────────────┼─────────────┼────────────
Section A   | Section B   | Section C
(300px)     | (250px)     | (350px)
            │             │
Section E   | Section D   |
(200px)     | (200px)     |
```

Sections fill left-to-right across the top, then each new section drops into whichever column is currently shortest. This is automatic — the browser handles placement via CSS `columns`.

### Usage

```jsx
{/* 2 columns */}
<div className="masonry-grid masonry-grid--2">
  <Card title="Account Info">…</Card>
  <Card title="Profile">…</Card>
  <Card title="Security">…</Card>   {/* auto-drops into shortest column */}
</div>

{/* 3 columns */}
<div className="masonry-grid masonry-grid--3">
  {sections.map(s => <Card key={s.id}>…</Card>)}
</div>
```

Available variants: `masonry-grid--2`, `masonry-grid--3`, `masonry-grid--4`

Responsive behaviour:
- `--4` collapses to 3 cols at 1024px, 1 col at 768px
- `--3` collapses to 2 cols at 1024px, 1 col at 768px
- `--2` collapses to 1 col at 768px

### Rules

- **Use `.masonry-grid` by default** on all content pages with 2+ variable-height sections
- **Do NOT** use `display: grid` with `gridTemplateColumns` for variable-height card layouts — this creates uneven whitespace when cards differ in height
- **Do NOT** use `.masonry-grid` for DataTable pages — tables span full width on their own
- **Do NOT** use `.masonry-grid` when sections must align horizontally (e.g. two fields that are semantically paired side-by-side within a card) — that's an internal card layout, not page-level masonry
- The `Card` component already has `break-inside: avoid` so it will never be split across columns

---

## 12. What Claude Code Must NEVER Do

- ❌ Write a hex color, `rgb()`, or `hsl()` value directly in any component or page file
- ❌ Write a pixel spacing or size value that isn't a `--space-*` token
- ❌ Use a raw `<button>`, `<input>`, or `<select>` for anything styled — always use the wrapper component
- ❌ Create a `.module.css` or any per-page CSS file
- ❌ Use Tailwind's `dark:` prefix — theming is via CSS tokens only
- ❌ Place modal JSX inside a page file — modals live in `frontend/src/modals/`
- ❌ Wrap `.page-wrapper` in additional divs that shift content alignment
- ❌ Override `.page-wrapper`'s padding to make a single page "different"
- ❌ Skip loading, error, and empty states on any data-fetching component
- ❌ Import from `_archive/` anywhere except `@/lib/api.js`, `@/hooks/useAuth.js`, and `@/providers/AuthProvider.jsx`
- ❌ Install a new icon library or introduce new SVG icons outside of `assets/`
- ❌ Invent new color values not in `tokens.css`
