- Custom SQLite task manager replacing TickTick wrapper - 73 tasks migrated from TickTick across 15 projects - RRULE recurrence engine with lazy materialization - Dashboard tasks widget (desktop sidebar + mobile card) - Tasks page with project tabs, add/edit/complete/delete - Security: locked ports to localhost, removed old containers - Gitea Actions runner configured and all 3 CI jobs passing - Fixed mobile overflow on dashboard cards - iOS Capacitor app shell (Second Brain) - Frontend/backend guide docs for adding new services - TickTick Google Calendar sync re-authorized Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
468 lines
19 KiB
Markdown
468 lines
19 KiB
Markdown
# Design System — Token Reference
|
||
|
||
> Source of truth: `src/app.css`
|
||
> Last updated: 2026-03-27
|
||
|
||
All UI values must come from these tokens unless listed under [Intentional Raw Values](#intentional-raw-values).
|
||
|
||
---
|
||
|
||
## Spacing
|
||
|
||
4px grid. Token name = multiplier (`--sp-3` = 3 × 4px = 12px).
|
||
|
||
| Token | Value | Use for |
|
||
|-------|-------|---------|
|
||
| `--sp-0` | 0px | Explicit zero |
|
||
| `--sp-px` | 1px | Borders, hairlines |
|
||
| `--sp-0.5` | 2px | Micro-nudge (margin-top on meta text) |
|
||
| `--sp-1` | 4px | Tight gap, field label gap, small padding |
|
||
| `--sp-1.5` | 6px | Badge gap, icon gap, footer gap |
|
||
| `--sp-2` | 8px | Compact gap, button group gap, inner padding |
|
||
| `--sp-3` | 12px | Standard gap, row padding, list item gap |
|
||
| `--sp-4` | 16px | Card padding (mobile), section margin, tab margin |
|
||
| `--sp-5` | 20px | Card padding (desktop), module gap, sidebar gap |
|
||
| `--sp-6` | 24px | Large padding, overlay padding, page container |
|
||
| `--sp-7` | 28px | Primary card padding, section group margin |
|
||
| `--sp-8` | 32px | Page top padding, empty state, desktop grid gap |
|
||
| `--sp-10` | 40px | Large elements (avatar width), empty list padding |
|
||
| `--sp-12` | 48px | Empty state padding, large spacing |
|
||
| `--sp-16` | 64px | Reserved |
|
||
| `--sp-20` | 80px | Page bottom padding (scroll clearance) |
|
||
|
||
### Semantic spacing aliases
|
||
|
||
| Token | Resolves to | Use for |
|
||
|-------|-------------|---------|
|
||
| `--section-gap` | `--sp-7` (28px) | Gap between major page sections |
|
||
| `--card-pad` | `--sp-5` (20px) | Default module/card padding |
|
||
| `--card-pad-primary` | `--sp-7` (28px) | Hero/primary module padding |
|
||
| `--card-pad-secondary` | `--sp-4` (16px) | Compact card padding |
|
||
| `--row-gap` | `--sp-3` (12px) | Gap between list rows |
|
||
| `--module-gap` | `--sp-5` (20px) | Gap between dashboard modules |
|
||
| `--row-pad-y` | 14px | Vertical padding inside data rows (off-grid, intentional) |
|
||
| `--row-pad-x` | `--sp-4` (16px) | Horizontal padding inside data rows |
|
||
| `--inner-gap` | `--sp-3` (12px) | Gap between items within a row |
|
||
|
||
### Mobile overrides (≤768px)
|
||
|
||
| Token | Desktop | Mobile |
|
||
|-------|---------|--------|
|
||
| `--card-pad` | 20px | 16px |
|
||
| `--card-pad-primary` | 28px | 20px |
|
||
| `--row-pad-y` | 14px | 16px |
|
||
| `--section-gap` | 28px | 20px |
|
||
|
||
---
|
||
|
||
## Radius
|
||
|
||
| Token | Value | Use for |
|
||
|-------|-------|---------|
|
||
| `--radius-xs` | 4px | Tiny pills, skeleton placeholders, kbd hints |
|
||
| `--radius-sm` | 6px | Badges, chips, nav links, danger buttons |
|
||
| `--radius-md` | 8px | Buttons, inputs, tabs, entry rows, icon containers |
|
||
| `--radius` | 12px | Cards, modals, panels, main containers |
|
||
| `--radius-lg` | 16px | Hero cards, action cards, pill chips, ImmichPicker modal |
|
||
| `--radius-full` | 9999px | Circles, toggles, avatars |
|
||
|
||
---
|
||
|
||
## Elevation (Shadows)
|
||
|
||
Light and dark mode have separate values. Dark mode uses higher opacity.
|
||
|
||
| Token | Light | Use for |
|
||
|-------|-------|---------|
|
||
| `--shadow-xs` | `0 1px 2px rgba(0,0,0,0.03)` | Row hover, active tabs, inner elements |
|
||
| `--shadow-sm` | `0 1px 3px rgba(0,0,0,0.04), 0 4px 12px rgba(0,0,0,0.04)` | Secondary cards, inputs, budget tables |
|
||
| `--shadow-md` | `0 2px 6px rgba(0,0,0,0.04), 0 8px 24px rgba(0,0,0,0.06)` | Standard card elevation (default `.module`) |
|
||
| `--shadow-lg` | `0 4px 12px rgba(0,0,0,0.06), 0 16px 40px rgba(0,0,0,0.1)` | Hero cards, primary modules, dropdowns |
|
||
| `--shadow-xl` | `0 8px 24px rgba(0,0,0,0.08), 0 24px 60px rgba(0,0,0,0.15)` | Modals, overlay panels |
|
||
|
||
**Legacy aliases**: `--card-shadow` → `--shadow-md`, `--card-shadow-sm` → `--shadow-sm`
|
||
|
||
---
|
||
|
||
## Colors
|
||
|
||
### Surfaces (3-layer depth)
|
||
|
||
| Token | Light | Dark | Layer |
|
||
|-------|-------|------|-------|
|
||
| `--canvas` | `#F5F6F8` | `#09090b` | Page background — everything sits on this |
|
||
| `--surface` | `#FFFFFF` | `#0f0f12` | Sidebars, panels, slide-out sheets |
|
||
| `--surface-secondary` | `#FAFAFB` | `#111114` | Input backgrounds, secondary panels |
|
||
| `--card` | `#FFFFFF` | `#161619` | Content containers, elevated with shadow |
|
||
| `--card-secondary` | `#FAFAFB` | `#111114` | Secondary cards, button backgrounds |
|
||
| `--card-hover` | `#f0f0f3` | `#1c1c20` | Row hover, interactive feedback |
|
||
|
||
### Borders
|
||
|
||
| Token | Light | Dark |
|
||
|-------|-------|------|
|
||
| `--border` | `rgba(0,0,0,0.06)` | `rgba(255,255,255,0.06)` |
|
||
| `--border-strong` | `rgba(0,0,0,0.1)` | `rgba(255,255,255,0.1)` |
|
||
|
||
### Text hierarchy
|
||
|
||
| Token | Light | Dark | Use for |
|
||
|-------|-------|------|---------|
|
||
| `--text-1` | `#1a1a1f` | `#fafafa` | Headings, names, amounts — read first |
|
||
| `--text-2` | `#4a4a55` | `#a1a1aa` | Body text, descriptions — read second |
|
||
| `--text-3` | `#6b6b76` | `#71717a` | Labels, metadata, captions — supporting |
|
||
| `--text-4` | `#b4b4bd` | `#3f3f46` | Placeholders, disabled, timestamps — background |
|
||
|
||
### Accent (indigo / blue)
|
||
|
||
| Token | Light | Dark | Use for |
|
||
|-------|-------|------|---------|
|
||
| `--accent` | `#4F46E5` | `#3b82f6` | Primary actions, links, active states |
|
||
| `--accent-bg` | `#EEF2FF` | `rgba(59,130,246,0.1)` | Icon wells, strong highlight backgrounds |
|
||
| `--accent-dim` | `rgba(79,70,229,0.06)` | `rgba(59,130,246,0.08)` | Subtle hover, selection backgrounds, focus rings |
|
||
| `--accent-border` | `rgba(79,70,229,0.10)` | `rgba(59,130,246,0.12)` | Accent-tinted borders |
|
||
| `--accent-focus` | `rgba(79,70,229,0.12)` | `rgba(59,130,246,0.15)` | Active states, selection bars |
|
||
|
||
### Semantic colors
|
||
|
||
| Token | Light | Dark | Use for |
|
||
|-------|-------|------|---------|
|
||
| `--success` | `#16A34A` | `#22c55e` | Positive values, income, completed |
|
||
| `--success-bg` | `#F0FDF4` | `rgba(34,197,94,0.1)` | Icon wells |
|
||
| `--success-dim` | `rgba(34,197,94,0.08)` | `rgba(34,197,94,0.08)` | Badge backgrounds |
|
||
| `--error` | `#DC2626` | `#ef4444` | Errors, issues, delete actions |
|
||
| `--error-bg` | `#FEF2F2` | `rgba(239,68,68,0.1)` | Icon wells |
|
||
| `--error-dim` | `rgba(239,68,68,0.08)` | `rgba(239,68,68,0.08)` | Badge backgrounds |
|
||
| `--warning` | `#d97706` | `#f59e0b` | Warnings, pending states |
|
||
| `--warning-bg` | `rgba(245,158,11,0.08)` | `rgba(245,158,11,0.1)` | Badge backgrounds |
|
||
|
||
### Overlay
|
||
|
||
| Token | Light | Dark | Use for |
|
||
|-------|-------|------|---------|
|
||
| `--overlay` | `rgba(0,0,0,0.3)` | `rgba(0,0,0,0.6)` | Modal backdrop, reading pane overlay |
|
||
| `--overlay-strong` | `rgba(0,0,0,0.5)` | `rgba(0,0,0,0.75)` | Heavy overlays |
|
||
| `--nav-bg` | `rgba(255,255,255,0.9)` | `rgba(15,15,18,0.9)` | Navbar blur background |
|
||
|
||
---
|
||
|
||
## Typography
|
||
|
||
| Token | Desktop | Mobile (≤768px) | Use for |
|
||
|-------|---------|-----------------|---------|
|
||
| `--text-xs` | 11px | 12px | Badges, pills, tiny counters |
|
||
| `--text-sm` | 13px | 15px | Labels, meta, captions, button text |
|
||
| `--text-base` | 14px | 16px | Body text, list items, inputs |
|
||
| `--text-md` | 15px | 17px | Card titles, important rows (16px+ avoids iOS zoom) |
|
||
| `--text-lg` | 17px | 18px | Section headers, modal titles |
|
||
| `--text-xl` | 22px | 22px | Page titles |
|
||
| `--text-2xl` | 28px | 26px | Hero headings |
|
||
| `--text-3xl` | 36px | 32px | Large hero numbers |
|
||
|
||
### Line heights
|
||
|
||
| Token | Value | Use for |
|
||
|-------|-------|---------|
|
||
| `--leading-tight` | 1.2 | Headings, hero numbers |
|
||
| `--leading-snug` | 1.35 | Card titles, compact text |
|
||
| `--leading-normal` | 1.5 | Body text |
|
||
| `--leading-relaxed` | 1.65 | Article content |
|
||
| `--leading-loose` | 1.8 | Long-form reading |
|
||
|
||
### Fonts
|
||
|
||
| Token | Value |
|
||
|-------|-------|
|
||
| `--font` | `'Inter', -apple-system, system-ui, sans-serif` |
|
||
| `--mono` | `'JetBrains Mono', ui-monospace, monospace` |
|
||
|
||
---
|
||
|
||
## Global Component Classes
|
||
|
||
Defined in `app.css`, usable in any component without local `<style>` duplication.
|
||
|
||
| Class | Description |
|
||
|-------|-------------|
|
||
| `.module` | Card container (bg, border, shadow, padding) |
|
||
| `.module.primary` | Hero card (more padding + elevation) |
|
||
| `.module.flush` | No padding (for flush content) |
|
||
| `.module-header` | Flex header row (title left, action right) |
|
||
| `.module-title` | Uppercase label |
|
||
| `.module-action` | Accent link → "View all →" |
|
||
| `.data-row` | Standard list item with hover + zebra striping |
|
||
| `.badge` + `.error/.success/.warning/.accent/.muted` | Semantic status badges |
|
||
| `.tab-bar` + `.tab` + `.tab-badge` | Pill-style tab navigation |
|
||
| `.section-label` | Uppercase group header |
|
||
| `.btn-primary` / `.btn-primary.full` | Primary action buttons |
|
||
| `.btn-secondary` | Secondary action buttons |
|
||
| `.btn-icon` | Square icon button (36×36) |
|
||
| `.input` | Standard text input |
|
||
| `.skeleton` | Shimmer loading placeholder |
|
||
| `.page` / `.page-header` / `.page-title` / `.page-greeting` | Page-level layout |
|
||
| `.app-surface` | Centered max-width container |
|
||
|
||
---
|
||
|
||
## Intentional Raw Values
|
||
|
||
These values exist outside the token system by design. Do not convert them.
|
||
|
||
### Non-scale spacing
|
||
|
||
Values that don't land on the 4px grid. Used for optical tuning where grid steps are too coarse.
|
||
|
||
| Value | Where | Why |
|
||
|-------|-------|-----|
|
||
| `1px` | `margin-top` on meta text, feed separators | Sub-pixel nudge for vertical alignment |
|
||
| `3px` | Badge padding-y, kbd padding | Optical centering within small elements |
|
||
| `5px` | Pill padding-y, chip padding | Between sp-1 (4px) and sp-1.5 (6px), tuned per element |
|
||
| `7px` | View-btn padding-y, sidebar gap | Between sp-1.5 (6px) and sp-2 (8px) |
|
||
| `9px` | Nav item padding-y, suggestion row padding-y | Between sp-2 (8px) and 10px |
|
||
| `10px` | Input padding-y, dropdown padding, various gaps | Common "comfortable touch" size, between sp-2 and sp-3 |
|
||
| `11px` | Entry row padding-y, toggle btn padding-bottom | Asymmetric optical alignment |
|
||
| `13px` | FAB action padding-y, list row padding | Between sp-3 (12px) and 14px |
|
||
| `14px` | Button padding-x, row padding-x, modal gap, field row gap | Most common off-grid value. Used as standard horizontal rhythm for interactive elements. Also `--row-pad-y` for data rows. |
|
||
| `15px` | Transaction row padding-y | Between sp-3.5 and sp-4, tuned for readability |
|
||
| `18px` | Detail header margin-bottom, AI guide padding-top | Between sp-4 (16px) and sp-5 (20px) |
|
||
| `22px` | Trip stats padding-x, modal body padding | Between sp-5 (20px) and sp-6 (24px) |
|
||
|
||
### Non-scale border-radius
|
||
|
||
| Value | Where | Why |
|
||
|-------|-------|-----|
|
||
| `9px` | Status segment control inner radius | Between radius-md (8px) and radius (12px) |
|
||
| `10px` | Event cards, photo thumbnails, notes, modals, unscheduled items, food rows | Heavily used "soft card" radius. Between radius-md (8px) and radius (12px). |
|
||
| `14px` | CommandPalette box, toggle track | Large interactive controls |
|
||
| `20px` | Bottom sheet top corners | Extra-round for sheet feel |
|
||
| `50%` | Circular elements (cover nav dots, meal-number, image-delete) | True circle, differs from radius-full on non-square elements |
|
||
|
||
### Data visualization colors
|
||
|
||
These are visual category identifiers, not semantic UI colors. They must remain consistent within their visualization set, independent of theme.
|
||
|
||
**Fitness macros:**
|
||
| Color | Hex | Use |
|
||
|-------|-----|-----|
|
||
| Protein | `#8B5CF6` | Purple macro bar |
|
||
| Carbs | `#F59E0B` | Amber macro bar |
|
||
| Fat | `#3B82F6` | Blue macro bar |
|
||
|
||
**Fitness meal weights:**
|
||
| Color | Background | Text | Meaning |
|
||
|-------|------------|------|---------|
|
||
| Heavy | `rgba(239,68,68,0.1)` | `#DC2626` | High-calorie meal |
|
||
| Moderate | `rgba(245,158,11,0.1)` | `#B45309` | Medium-calorie meal |
|
||
| Light | `rgba(34,197,94,0.1)` | `#15803D` | Low-calorie meal |
|
||
|
||
**Trip categories:**
|
||
| Color | Background | Text | Category |
|
||
|-------|------------|------|----------|
|
||
| Hotel | `rgba(168,85,247,0.1)` | `#a855f7` | Lodging |
|
||
| Restaurant | `rgba(249,115,22,0.1)` | `#f97316` | Food & dining |
|
||
| Hike | `rgba(34,197,94,0.15)` | `#16a34a` | Outdoor activity |
|
||
| Logistics | `rgba(161,161,170,0.1)` | `--text-3` | Transport, other |
|
||
|
||
**Other:**
|
||
| Color | Hex | Use |
|
||
|-------|-----|-----|
|
||
| Favorite star | `#F59E0B` | Star icon fill (reader, fitness) |
|
||
| AI badge | `rgba(59,130,246,0.1)` / `#3B82F6` | AI-logged entry indicator |
|
||
| Transfer pill | `rgba(59,130,246,0.1)` / `#3b82f6` | Budget transfer indicator |
|
||
|
||
### Glass & overlay effects
|
||
|
||
Applied to elements layered over images. Not theme-switchable because they depend on photo content, not UI surface.
|
||
|
||
| Value | Where |
|
||
|-------|-------|
|
||
| `rgba(0,0,0,0.7)..0.1` | Cover image gradient (trips) |
|
||
| `rgba(255,255,255,0.15)` | Cover nav button background |
|
||
| `rgba(255,255,255,0.3)` | Cover nav button hover |
|
||
| `rgba(0,0,0,0.3)` / `rgba(0,0,0,0.5)` | Cover share button / hover |
|
||
| `rgba(0,0,0,0.35)` | Modal overlays (trips), detail overlay (inventory) |
|
||
| `rgba(0,0,0,0.5)` | Image delete button, search saving overlay |
|
||
|
||
### Directional panel shadows
|
||
|
||
Non-standard shadow directions for slide-in panels and bottom sheets. Cannot use elevation tokens.
|
||
|
||
| Value | Where |
|
||
|-------|-------|
|
||
| `0 -8px 32px rgba(0,0,0,0.12)` | FAB bottom sheet (fitness, trips) |
|
||
| `-12px 0 40px rgba(0,0,0,0.12)` | Detail slide-in sheet (inventory) |
|
||
| `-6px 0 28px rgba(0,0,0,0.08)` | Reading pane (reader) |
|
||
| `-8px 0 32px rgba(0,0,0,0.1)` | Edit sheet (trips) |
|
||
| `8px 0 24px rgba(0,0,0,0.08)` | Mobile sidebar (reader) |
|
||
|
||
### Accent-tinted FAB shadows
|
||
|
||
Colored shadows for floating action buttons. Not in the elevation scale because they use accent color, not black.
|
||
|
||
| Value | Where |
|
||
|-------|-------|
|
||
| `0 8px 24px rgba(79,70,229,0.3)` | FAB resting (fitness) |
|
||
| `0 12px 32px rgba(79,70,229,0.4)` | FAB hover (fitness) |
|
||
| `0 6px 20px rgba(79,70,229,0.3)` | FAB resting (trips) |
|
||
| `0 8px 28px rgba(79,70,229,0.4)` | FAB hover (trips) |
|
||
|
||
### Near-match shadows
|
||
|
||
Shadows that are close to tokens but intentionally differ in blur radius or opacity.
|
||
|
||
| Value | Nearest token | Diff |
|
||
|-------|--------------|------|
|
||
| `0 2px 6px rgba(0,0,0,0.05)` | `--shadow-xs` | Larger blur, higher opacity |
|
||
| `0 1px 2px rgba(0,0,0,0.04)` | `--shadow-xs` | Opacity 0.04 vs 0.03 |
|
||
| `0 1px 4px rgba(0,0,0,0.04)` | `--shadow-xs` | Larger blur |
|
||
| `0 1px 4px rgba(0,0,0,0.08)` | `--shadow-xs` | Larger blur + opacity |
|
||
| `0 16px 48px rgba(0,0,0,0.15)` | `--shadow-xl` | Single layer vs dual |
|
||
| `0 1px 3px rgba(0,0,0,0.06)` | `--shadow-xs` | Higher opacity |
|
||
| `0 8px 24px rgba(0,0,0,0.12)` | `--shadow-md` | Higher opacity |
|
||
| `0 20px 60px rgba(0,0,0,0.15)` | `--shadow-xl` | Single layer |
|
||
| `0 20px 60px rgba(0,0,0,0.2)` | `--shadow-xl` | Single layer, higher opacity |
|
||
| `0 1px 3px rgba(0,0,0,0.15)` | `--shadow-sm` | Toggle thumb, much higher opacity |
|
||
|
||
---
|
||
|
||
## Adding a New App (Frontend Guide)
|
||
|
||
Step-by-step for adding a new app page to the platform.
|
||
|
||
### 1. Create the route
|
||
|
||
Create `src/routes/(app)/yourapp/+page.svelte`. Every app is a single self-contained file.
|
||
|
||
### 2. Page structure template
|
||
|
||
```svelte
|
||
<script lang="ts">
|
||
import { onMount } from 'svelte';
|
||
|
||
// -- Types --
|
||
interface Item {
|
||
id: string;
|
||
name: string;
|
||
}
|
||
|
||
// -- State (Svelte 5 runes) --
|
||
let loading = $state(true);
|
||
let items = $state<Item[]>([]);
|
||
let activeTab = $state<'all' | 'recent'>('all');
|
||
let searchQuery = $state('');
|
||
|
||
// -- Derived --
|
||
const filtered = $derived(
|
||
items.filter(i => i.name.toLowerCase().includes(searchQuery.toLowerCase()))
|
||
);
|
||
|
||
// -- API helper (scoped to this app's gateway prefix) --
|
||
async function api(path: string, opts: RequestInit = {}) {
|
||
const res = await fetch(`/api/yourapp${path}`, { credentials: 'include', ...opts });
|
||
if (!res.ok) throw new Error(`${res.status}`);
|
||
return res.json();
|
||
}
|
||
|
||
// -- Data mapper (normalize backend shape to UI interface) --
|
||
function mapItem(raw: any): Item {
|
||
return { id: raw.id || raw.Id, name: raw.name || raw.title || '' };
|
||
}
|
||
|
||
// -- Load --
|
||
async function loadItems() {
|
||
try {
|
||
const data = await api('/items');
|
||
items = (data.items || data).map(mapItem);
|
||
} catch (e) { console.error('Failed to load items', e); }
|
||
}
|
||
|
||
onMount(async () => {
|
||
await loadItems();
|
||
loading = false;
|
||
});
|
||
</script>
|
||
|
||
<div class="page">
|
||
<div class="page-header">
|
||
<h1 class="page-title">Your App</h1>
|
||
</div>
|
||
|
||
{#if loading}
|
||
<div class="module"><div class="skeleton" style="height: 200px"></div></div>
|
||
{:else}
|
||
<div class="tab-bar">
|
||
<button class="tab" class:active={activeTab === 'all'} onclick={() => activeTab = 'all'}>All</button>
|
||
<button class="tab" class:active={activeTab === 'recent'} onclick={() => activeTab = 'recent'}>Recent</button>
|
||
</div>
|
||
|
||
<div class="module">
|
||
<div class="module-header">
|
||
<span class="module-title">Items</span>
|
||
<button class="module-action" onclick={...}>View all →</button>
|
||
</div>
|
||
{#each filtered as item}
|
||
<div class="data-row">
|
||
<span style="color: var(--text-1)">{item.name}</span>
|
||
</div>
|
||
{:else}
|
||
<p style="color: var(--text-3); padding: var(--card-pad)">No items found</p>
|
||
{/each}
|
||
</div>
|
||
{/if}
|
||
</div>
|
||
```
|
||
|
||
### 3. Register in navigation
|
||
|
||
**`(app)/+layout.server.ts`** -- add ID to allApps:
|
||
```ts
|
||
const allApps = ['trips', 'fitness', 'inventory', 'budget', 'reader', 'media', 'yourapp'];
|
||
```
|
||
|
||
**`Navbar.svelte`** -- add link:
|
||
```svelte
|
||
{#if showApp('yourapp')}
|
||
<a href="/yourapp" class="navbar-link" class:active={isActive('/yourapp')}>Your App</a>
|
||
{/if}
|
||
```
|
||
|
||
**`MobileTabBar.svelte`** -- add to primary tabs or the "More" sheet.
|
||
|
||
### 4. Key conventions
|
||
|
||
| Rule | Detail |
|
||
|------|--------|
|
||
| All state uses `$state()` | Never plain `let` for reactive values |
|
||
| Computed values use `$derived()` | For filtered lists, counts, conditions |
|
||
| API calls go through `/api/yourapp/*` | Gateway proxies to backend |
|
||
| `credentials: 'include'` on every fetch | Cookie-based auth |
|
||
| Map raw API data through `mapX()` | Normalize backend shapes to clean interfaces |
|
||
| No shared stores or context | Each page is self-contained |
|
||
| No separate `+page.ts` load function | Data loads in `onMount` |
|
||
| Types defined inline in script | No shared type files |
|
||
|
||
### 5. UI component classes to use
|
||
|
||
| Class | When |
|
||
|-------|------|
|
||
| `.page` / `.page-header` / `.page-title` | Page-level layout |
|
||
| `.module` / `.module.primary` / `.module.flush` | Card containers |
|
||
| `.module-header` / `.module-title` / `.module-action` | Card headers |
|
||
| `.data-row` | List items with hover + zebra |
|
||
| `.badge` + `.error/.success/.warning/.accent/.muted` | Status indicators |
|
||
| `.tab-bar` + `.tab` | Pill-style tabs |
|
||
| `.btn-primary` / `.btn-secondary` / `.btn-icon` | Buttons |
|
||
| `.input` | Text inputs |
|
||
| `.skeleton` | Loading placeholders |
|
||
| `.section-label` | Uppercase group header |
|
||
|
||
### 6. Styling rules
|
||
|
||
- All colors from tokens: `var(--text-1)`, `var(--accent)`, `var(--card)`, etc.
|
||
- All spacing from tokens: `var(--sp-3)`, `var(--card-pad)`, `var(--row-gap)`, etc.
|
||
- All radii from tokens: `var(--radius)`, `var(--radius-md)`, etc.
|
||
- All shadows from tokens: `var(--shadow-md)`, `var(--shadow-lg)`, etc.
|
||
- All font sizes from tokens: `var(--text-sm)`, `var(--text-base)`, etc.
|
||
- Never use raw `#hex` colors, raw `px` spacing, or raw shadows unless listed in the Intentional Raw Values section above
|
||
- Dark mode is automatic via CSS custom properties — no manual `prefers-color-scheme` needed
|