# LibNovel UI — Agent Context

SvelteKit 2 + Svelte 5 frontend. Node adapter for production; served behind Caddy.

## Design System

**ACTIVE_STYLE: branded**

Custom amber + zinc dark palette. No shadcn CLI defaults — primitives are hand-authored to match.

| Token | Value |
|-------|-------|
| Accent | `#f59e0b` (amber-400) |
| Surface-1 | zinc-900 |
| Surface-2 | zinc-800 |
| Surface-3 | zinc-700 |
| Text-primary | zinc-100 |
| Text-secondary | zinc-400 |
| Destructive | red-400 |

## Tailwind

**Version: 4** — configured entirely via `@theme {}` in `src/app.css` and the `@tailwindcss/vite` plugin.

**There is no `tailwind.config.ts`** — do not create one.
**Do not run `npx shadcn-svelte add ...`** — primitives are hand-authored in `$lib/components/ui/`.

## shadcn-svelte Primitives

All in `src/lib/components/ui/`:

| Component | Variants / Notes |
|-----------|-----------------|
| `button` | default / secondary / outline / ghost / destructive / link; sizes: default / sm / lg / icon |
| `badge` | default / secondary / outline / destructive |
| `card` | Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter |
| `textarea` | bindable `value` prop |
| `dialog` | Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter |
| `separator` | horizontal / vertical |

Always use `cn()` from `$lib/utils` — never template-literal class conditionals.

## Svelte 5 Conventions

- `@Observable` / runes (`$state`, `$derived`, `$effect`, `$props()`) for all new code.
- Do not add `ObservableObject` / `@Published` — they don't exist in Svelte; don't introduce legacy `writable` stores for new code.
- Navigation: `goto()` from `$app/navigation`; `page` from `$app/state`.

## iOS/UX Skill

For any view work, load the `ios-ux` skill at task start:
```
skill({ name: "ios-ux" })
```

## Key Files

| File | Role |
|------|------|
| `src/app.css` | Tailwind v4 `@theme` tokens — source of truth for all design tokens |
| `src/lib/utils.ts` | `cn()` helper (clsx + tailwind-merge) |
| `src/lib/types.ts` | Shared client-safe domain types |
| `src/routes/+layout.svelte` | Root layout: sticky nav, persistent `<audio>` element, mini-player bar |
| `src/lib/audio.svelte.ts` | Global audio store (Svelte 5 runes class) |
| `src/lib/components/ui/` | shadcn-style UI primitives |

## Persistent Audio Element

The `<audio>` element in `+layout.svelte` **must never be conditionally rendered** (no `{#if}`).
Conditional rendering destroys and recreates the element, triggering `onpause` mid-playback.
Keep it always in the DOM; control it via `audioStore`.
