Files
timothyandtimothy 8b9a7ed541
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (push) Has been skipped
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (push) Has been skipped
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 6m23s
Build ErsatzTV Image / Functional E2E (curl contracts) (push) Successful in 14m21s
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 19m9s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 12m1s
feat(414): stamp immutable Channel.Origin (auto-tuned vs user-created) and surface it (#575)
Co-authored-by: Timothy <timothy.look@gmail.com>
Co-committed-by: Timothy <timothy.look@gmail.com>
2026-07-23 18:12:25 +00:00
..

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 1624px; 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: 4050% 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 — 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: 1516px inside rows/buttons, 1314px in dense tables/badges, 1718px 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 @imports). 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.