# ChicoryTV Design System

A dark, slick, app-like admin design system for **ChicoryTV** — a self-hosted IPTV /
custom-TV channel server. ChicoryTV is a modern reimagining of the **ErsatzTV** admin
UI (a .NET / Blazor Server + MudBlazor application): same product, same information
architecture, redrawn as a dense, technical, no-full-reload media-server console in the
spirit of tools like Komodo.

The product lets you turn a media library into personalized live TV channels — with an
EPG, channel scheduling, and streaming (IPTV/HLS/MPEG-TS) to any device.

## Sources
- **Codebase (ground truth):** `ersatztv/` — ErsatzTV fork, C#/.NET 10, Blazor Server, MudBlazor.
  Key screens read for this system:
  - `ErsatzTV/Shared/MainLayout.razor` — app bar, drawer nav, theme palette.
  - `ErsatzTV/Pages/Channels.razor` — channel table + inline actions (the core view).
  - `ErsatzTV/Pages/Playouts.razor` — playout list + playout-detail timeline items.
  - `ErsatzTV/Pages/Schedules.razor`, `Libraries.razor`, `Index.razor` (health dashboard).
- **Upstream project:** ErsatzTV — https://ersatztv.org , https://github.com/ErsatzTV/ErsatzTV
- ChicoryTV is a **rebrand + redesign**, not a fork of the visual layer. The MudBlazor
  green/Roboto theme is intentionally replaced; product vocabulary and IA are preserved.

> **Font substitution — please confirm.** ErsatzTV ships no brand font (MudBlazor defaults to
> Roboto). ChicoryTV adopts **Geist** (UI) + **Geist Mono** (numerics), loaded from Google
> Fonts. If you have a preferred/licensed brand typeface, share the files and it will be swapped in.

---

## Content fundamentals — how ChicoryTV writes

Voice is that of a **precise, technical operator tool** — terse, functional, confident. It
labels and instructs; it does not market or chit-chat.

- **Casing:** Title Case for primary actions and nav ("Add Channel", "Reset All Playouts",
  "Edit Channel Numbers"); sentence case for helper text and descriptions.
- **Person:** mostly **impersonal / imperative** — verbs, not "you". Buttons are bare verbs
  ("Add Schedule", "Scan", "Delete", "Troubleshoot"). Occasional second person in help text.
- **Nouns are the product's domain language — use them exactly:** Channel, Schedule, Block,
  Template, Deco, Playout (Classic / Block / Sequential / Scripted / JSON), Collection (Manual /
  Smart / Multi / Rerun), Playlist, Library, Media Source (Local / Plex / Jellyfin / Emby),
  FFmpeg Profile, Watermark, Filler, Streaming Mode (HLS Segmenter / HLS Direct / MPEG-TS),
  EPG / XMLTV / M3U.
- **Numbers & time are first-class** and always concrete: channel numbers (`1.1`, `4.2`),
  durations (`00:24:00`), clock times (`20:00:00`), counts (`1,204 items`). Rendered in mono.
- **Status is plain and specific:** "On air", "Healthy", "Not normalizing", "Playout build
  failed — no items match the schedule window." State the thing and the consequence.
- **No emoji. No exclamation marks.** Tone stays flat and reliable. Icons carry affect, not punctuation.
- **Empty/blank:** say what is missing and the next action ("No playouts yet — Add Playout").

Examples pulled from the product surface: *"Show disabled"*, *"Show filler"*, *"Reset All
Playouts"*, *"Edit Channel Numbers"*, *"HLS Segmenter"*, *"Schedule Playout Reset"*,
*"This FFmpeg profile is not normalizing audio."*

---

## Visual foundations

**Overall vibe.** Dark, dense, engineered. Flat surfaces separated by hairline borders, one
cool accent, monospaced numerics. No decoration for its own sake — every pixel is data,
control, or the whitespace that organizes them.

**Color.** Near-black layered surfaces: app background `#0F1115`, sunken wells `#0B0D11`,
cards/rows `#171A21`, raised menus/hover `#1C2029`, selected `#222732`. Hairline borders
`#262B36` (dividers) and `#333A48` (controls). Text in three weights only: primary `#E6E9EF`,
muted `#9AA4B2`, faint `#626B7A`. **Exactly one accent** — in the primary *warm chicory* scheme this is roasted-amber `#E08A3C` (the cool *periwinkle* alternate uses `#5B7CFA`)
— reserved for primary actions, active nav, focus rings, and live/on-air state. Status colors
are used sparingly and only semantically: ok `#3FB984`, warn `#E0A83D`, error `#E5484D`. Each
status also has a ~14% soft tint for badges/backgrounds. Never introduce a second decorative hue.

**Type.** Geist for UI, base size **13px** (dense admin). Scale: 11 · 12 · 13 · 14 · 16 · 20 ·
26 · 34. Weights 400/500/600 (700 rare). Section eyebrows are 11px 600 uppercase, `0.06em`
tracking, faint. **Geist Mono with tabular figures** for everything numeric — channel numbers,
clock times, durations, counts, bitrates, percentages — so columns align. Headings use slight
negative tracking (`-0.01em`); everything else is normal.

**Spacing & layout.** 4px base grid (2 → 56). Fixed chrome: sidebar `232px`, top bar `52px`,
table/list rows `44px`, controls `34px` (sm `28px`). Layout is a fixed left sidebar + a
compact top bar + a scrolling content region. Page gutter `32px`; card interior `16–24px`;
table cell padding `16px × 10px`. Dense but never cramped — rows breathe at 44px.

**Corners.** Tight and consistent: chips/badges `3px`, buttons/inputs `5px`, cards/rows/menus
`7px`, dialogs `10px`, pills fully round. Nothing is softer than 10px.

**Elevation.** Dark surfaces lean on **border + a subtle drop shadow**, never glow. `sm`
`0 1px 2px/.40`, `md` `0 4px 12px/.45`, popovers `0 8px 28px/.55`, dialogs `0 16px 40px/.55`.
Depth reads mostly through the surface-lightness ladder (bg → surface → raised → selected),
with shadow as a secondary cue. The one exception: live/on-air elements get a small accent
glow (playhead, pulsing status dot).

**Backgrounds.** No images, gradients, textures, or patterns — flat fills only. The single
"illustration" is the chicory-flower mark. Imagery, when present, is user content (channel
logos), shown on a sunken well and contained, never full-bleed.

**Borders & dividers.** 1px hairlines do the structural work: card edges, table row
separators, section splits, input outlines. Selected rows shift background rather than adding a
border; the active nav item adds a 2.5px accent rail on its left edge plus a soft accent tint.

**Motion.** Quick and functional. Durations 90 / 140 / 220ms; standard easing
`cubic-bezier(0.2,0,0,1)`, entrances `cubic-bezier(0.16,1,0.3,1)`. Hover = background/opacity
shift (`~90ms`); press = 0.5px nudge down, no scale bounce. Switches slide (`140ms`). The only
looping animations are meaningful: the live status-dot ping and indeterminate progress/scan
sweeps. Respect `prefers-reduced-motion`.

**States.**
- *Hover:* raise the surface one step (row → `#1C2029`) or lighten ghost controls; text goes
  from muted to primary.
- *Active/selected:* accent-soft tint + (nav) accent rail; never a heavy fill.
- *Focus:* 3px accent ring (`--focus-ring`) + accent border on inputs.
- *Disabled:* 40–50% opacity, no pointer.
- *Live/on-air:* accent color + pulsing dot + faint glow.

**Cards.** Flat `#171A21` surface, 1px hairline border, `7px` radius, no outer shadow by
default (shadow only when floating, e.g. menus/dialogs). Optional header row (title + subtitle
left, actions right) separated by a hairline; body padding `16px`, or `0` for flush tables.

**Transparency / blur.** Used sparingly: soft status/accent tints are alpha over the dark
surface; overlay scrims behind dialogs. No frosted-glass blur in the core admin.

### Color schemes (themes)
The system ships **three** interchangeable schemes as CSS theme scopes (`tokens/themes.css`).
Set `data-theme` on `<html>` to switch; each scope overrides only the base `--ctv-*` values, so
every component and semantic alias follows automatically.
- **Warm chicory** (primary, default `:root`) — roasted-amber accent `#E08A3C` + chicory-leaf
  green secondary `#6B8F71`; live rides the amber. The signature ChicoryTV look.
- **Cool periwinkle** (`[data-theme="cool"]`) — the cooler, focused alternate; accent `#5B7CFA`.
- **Dual accent** (`[data-theme="dual"]`) — cool periwinkle app, with a warm focal hue `#F0663F`
  driving the attention layer: live/on-air state **plus** primary actions, the active nav item, and
  selection (wired through the `--action-primary` / `--ctv-live` aliases), so the two accents read
  distinctly while warm and cool stay coherent.

The UI kit has a live swatch switcher (bottom-left) to preview all three; the choice persists.

---

## Iconography

- **System:** [**Lucide**](https://lucide.dev) — thin, consistent stroke icons — is the
  ChicoryTV icon set. This is a deliberate substitution for MudBlazor's Material Symbols in the
  source; Lucide's lighter 2px stroke suits the dark, technical aesthetic far better. Loaded
  from CDN (`unpkg.com/lucide`) in cards and the UI kit.
- **Sizes:** 15–16px inside rows/buttons, 13–14px in dense tables/badges, 17–18px in the top
  bar. Stroke width stays at Lucide's default (~2). Icons inherit `currentColor` and sit at
  `--text-secondary` until hover/active (then `--text-primary` or accent).
- **Common glyphs** (mapped from ErsatzTV actions): `Tv`, `CalendarClock`, `ListVideo`,
  `Library`, `FolderTree`, `Settings`, `LayoutDashboard`, `Play`, `Pencil`, `Trash2`,
  `RefreshCw`, `Stethoscope`/`Info` (troubleshoot), `Plus`, `Hash`, `Search`, `Radio` (on air),
  `Film`, `Sparkles` (filler), `HardDrive`/`Server` (media source).
- **No emoji, no unicode-glyph icons** in the UI. The only unicode marks used are small
  affordance triangles on the timeline playhead label.
- **Brand mark:** the chicory flower is an SVG (`assets/chicory-mark.svg`) — 16 radiating
  periwinkle petals around a dark center. Do not redraw it; reference the file.

---

## File index

**Foundations**
- `styles.css` — root entry point (only `@import`s). Consumers link this.
- `tokens/fonts.css` — Geist + Geist Mono (Google Fonts).
- `tokens/colors.css` — surfaces, accent, status, text (base + semantic aliases).
- `tokens/typography.css` — families, scale, weights, tracking, tabular-num helper.
- `tokens/spacing.css` — 4px scale + fixed chrome sizes.
- `tokens/radius-elevation-motion.css` — radii, shadows, easings, durations.
- `guidelines/*.card.html` — foundation specimen cards (Colors, Type, Spacing, Brand).

**Assets** (`assets/`)
- `chicory-mark.svg` — flower mark. `chicorytv-wordmark.svg` — mark + wordmark. `chicorytv-icon.svg` — app icon.

**Components** (`components/`) — React primitives, `window.ChicoryTVDesignSystem_eb3b61`
- `forms/` — Button, IconButton, Input, Select, Switch, Checkbox.
- `data-display/` — Badge, StatusDot, Tag, ChannelLogo, Card, Stat, ProgressBar.
- `feedback/` — Toast, Tooltip, Spinner.
- `navigation/` — NavItem + NavSection, Tabs.
- Each folder has a `*.card.html` specimen and each component a `.d.ts` + `.prompt.md`.

**Admin template** (`templates/chicorytv-admin/`) — the reusable admin shell; the single
source of truth for the full app (consuming projects start from it, and it doubles as the
interactive specimen). Open `ChicoryTvAdmin.dc.html` (or `app.html` directly).
- Screens: **Dashboard** (on-air cards, stat tiles, health panel, activity, release notes),
  **Channels** (bulk-select, `(D)`/`(H)` markers, inline actions), **New Channel** — the
  ground-up *library-to-lineup* Channel Builder (searchable library browser + drag/double-click
  to a reorderable lineup + friendly options, Channel Template picker, and an Advanced override
  disclosure — replacing the old Channel→Schedule→Items→Playout editor chain), **Schedule
  editor** (two-pane, drag-to-reorder), **Guide / EPG grid** (channels × time, now-marker),
  plus **Playouts** and **Libraries**, and a live 3-theme switcher.
- `Shell.jsx` (nav + top bar), `*.jsx` screens, `data.js` (mock data), `ds-base.js` (loads the
  compiled bundle + stylesheets). See its `README.md` for structure.

**Skill**
- `SKILL.md` — Agent-Skills-compatible entry point for reuse in Claude Code.
