Files
summercms/.planning/phases/10-admin-vue-spa/design
2026-09-26 23:21:43 +02:00
..
2026-09-26 23:21:43 +02:00
2026-09-26 23:21:43 +02:00

Handoff: SummerCMS Admin Panel — Direction C ("Generic modern dashboard")

Overview

This is the admin panel for SummerCMS, a CMS framework. The mocks use data from one real app: Płytarium, a home catalogue of vinyl, CD and cassette releases. The panel is a schema-driven Vue 3 SPA. Every list and form is generated from YAML-defined columns and fields. Build generic components, not screens: one DataTable, one FormField row, one Tabs component, one RelationManager. The UI language is Polish, and all copy below is final.

Screens covered: Login, Navigation shell (multi-plugin: Płytarium + Użytkownicy), List (Albumy) with its selected/empty/loading states, Form (Edycja albumu) with tabs and 422 errors, and the relation manager (Kolekcje → Redaktorzy) with its "Dodaj redaktora" modal. Light and dark mode are both included, plus tablet width.

About the Design Files

Direction C v2.dc.html is a design reference built in HTML, not production code. Recreate it in the target Vue 3 codebase using its existing patterns. If the codebase has no UI setup yet, use Vue 3 + Vite + Tailwind with headless primitives (e.g. Radix Vue / Reka UI) and lucide-vue-next icons.

To view it, open Direction C v2.dc.html in a browser. support.js must be in the same folder, and you need internet access for the fonts and icons. The component's props act as a state switcher:

  • plugin: plytarium | users (use users with screen=list to see the Users plugin list)
  • flyout: boolean; with narrow, shows the collapsed-rail flyout
  • screen: login | list | form | relation
  • listState: default | selected | empty | loading
  • tab: basic | details
  • dark, narrow (tablet), errors (422), toast, modal, userMenu: booleans

All sample data sits in the ALBUMS, EDITORS and CANDS constants in the logic class.

Fidelity

High-fidelity. Colours, type, spacing, radii and copy are final. Match them closely.


Design Tokens

Colours

Token Light Dark Use
bg #f4f6f9 #111726 App background behind cards
surface #ffffff #182033 Cards, header, inputs, footer bar
subtle #f3f5f8 #1f283d Table header, search input fill, chips, avatar bg
border #e6e9ef #29334b Card borders, row dividers
borderStrong #d2d8e2 #3a4661 Input borders, unchecked checkbox, switch off
text #141b2d #eef1f6 Primary text
muted #566175 #a9b3c6 Secondary text, column headers, icons
placeholder #6b7588 #8a95ab Input placeholder
primary #22304d (navy) #fcd34d (yellow) Primary buttons, checked checkbox
onPrimary #ffffff #1b2540 Text on primary
danger #c62828 #f58a8a Destructive, errors
dangerSoft #fdf0f0 #3a1d24 Error banner bg, destructive hover
okBg / okText #e3f4e8 / #1c6b35 #173826 / #8fdfa8 "Tak" badge
ring rgba(252,196,40,.55) rgba(252,211,77,.45) Focus ring (3px)
hover #f1f3f7 #212b42 Hover bg for ghost buttons/rows
sel #fdf3cf #3a3622 Selected row bg, current page button
skel #eceff4 #263049 Skeleton bars
overlay rgba(20,27,45,.5) rgba(5,8,16,.7) Modal backdrop
side #1d2740 #0d1320 Sidebar bg (sidebar is dark in both modes; dark mode adds a #222b40 right border)
accent #fcd34d same Brand yellow: logo, active nav, avatar
onAccent #1b2540 same Text on accent
sidebar text #c3cbda idle, #9aa6bd section label, #ffffff hover, #fcd34d active
card shadow 0 1px 2px rgba(20,27,45,.04), 0 4px 16px rgba(20,27,45,.05) none

The brand is dark navy + sunny yellow. In light mode the primary is navy and yellow is only an accent. In dark mode yellow becomes the primary.

Typography

  • UI font: DM Sans (Google Fonts, opsz 9..40, weights 400/500/600/700). Base size 14px, line-height 1.5.
  • Mono: DM Mono 400/500. Used for the Półka shelf code and the colorpicker token.
  • Datetimes use DM Sans with font-variant-numeric: tabular-nums, 13px, muted colour.
Role Size / weight / tracking
Page title (list) 26px / 700 / -0.02em
Record title (form) 24px / 700 / -0.02em
Login title 22px / 700 / -0.02em
Modal title 20px / 700 / -0.01em
Section title (Redaktorzy) 17px / 700
Body / cell 14px / 400 (Nazwa cell 600)
Field label 14px / 600
Column header 12px / 600, muted (the sorted column uses the text colour)
Helper / meta / error text 13px

Radii

Inputs, buttons and selects: 10px. Cards: 16px. Login card: 16px. Modal: 20px. Inner table container and toggle cards: 12px. Tabs track: 12px, tab pill: 9px. Checkbox: 5px (6px in the form and modal). Pagination buttons: 8px. Badges, chips and switch: 999px. Avatars: 50%.

Spacing

Page padding is 28px top/bottom and 32px left/right. Gaps between cards and blocks are 20px. Form card padding is 28px. The form grid gap is 22px (rows) × 24px (columns). Table cells use 14px horizontal padding, with 20px on the first and last columns.

Control heights

  • Primary/secondary buttons and search: 42px
  • Form inputs, selects and login inputs: 44px
  • Table header row: 44px. Body row: 54px (56px in the relation manager). Modal option row: 54px.
  • Pagination buttons: 34px
  • Top header: 64px. Sidebar nav item: 40px.

Focus

Every interactive element shows a visible 3px yellow ring (box-shadow: 0 0 0 3px var(--ring)). Inputs also switch their border to primary. Buttons use outline: 3px solid var(--ring); outline-offset: 2px. Style :focus-visible this way and never remove it.


Screens

1. Login (screen=login)

  • Full-viewport background: radial-gradient(ellipse at 50% 35%, #2b3a5c 0%, #1d2740 60%).
  • A centred column, max-width 400px, gap 24px:
    • Wordmark: sun icon (lucide sun, 20px, yellow) in a 34px circle filled rgba(252,211,77,.14), followed by "SummerCMS" at 18px/700 in white, with "CMS" in yellow. This is the only branding, because the admin URL is custom (e.g. /plytadmin).
    • Card: white background, radius 16, padding 32, gap 18, shadow 0 24px 60px rgba(0,0,0,.35).
      • Title: "Witaj ponownie"
      • Field "Login lub e-mail" (input 44px)
      • Field "Hasło" (password)
      • Error state: the password input gets a danger border and aria-invalid. Below it sits an alert block (role="alert", dangerSoft bg, danger text, radius 10, padding 12×14, circle-alert icon 18px) reading "Nieprawidłowe dane logowania".
      • Full-width primary button "Zaloguj" (44px).

2. Navigation shell — multi-plugin (rail + section panel)

SummerCMS replaces WinterCMS. It is a backend that hosts many plugins, and each plugin registers navigation the way Winter's registerNavigation() does: one main-menu item (label, icon, order, permissions) plus its own side menu (items, icons, permissions). Płytarium is just one of these plugins. The shell is built from that registry, and nothing in it is hardcoded per plugin.

Plugin rail (level 1 = Winter main menu)

  • Always visible. Width 80px, colour side (#1d2740 light / #0d1320 dark), padding 14px 0 12px, column gap 6, centred.
  • Top: brand badge, a 40px rounded square (radius 12) filled rgba(252,211,77,.14) holding a sun icon (22px, yellow). title="SummerCMS", bottom margin 14.
  • One item per registered main-menu entry, sorted by order:
    • 68px wide, padding 8/6, radius 12, letter-spacing -0.01em.
    • 20px icon, with the label below it at 11px (line-height 1.2, centred).
    • Idle: #c3cbda. Hover: rgba(255,255,255,.08) with white text.
    • Active: bg rgba(252,211,77,.14), text/icon #fcd34d, weight 700, aria-current="page".
    • Focus: 3px ring.
    • Mock entries: Płytarium (disc-3), Użytkownicy (users), Media (image).
  • A flex spacer, then Ustawienia (settings) pinned to the bottom. Settings is a separate area where plugins register settings pages, the same as Winter.
  • When the panel is collapsed, a 40px "Rozwiń menu" button (panel-left-open) appears above Ustawienia.
  • Wrap the rail in <nav aria-label="Wtyczki">.

Section panel (level 2 = the active plugin's side menu)

  • Width 224px, surface bg, 1px border on the right, padding 14×12, gap 2.
  • Header: 40px tall, 14px bottom margin. It shows the plugin label at 16px/700 (e.g. "Płytarium") and a 30px "Zwiń menu" button (panel-left-close).
  • Items: 40px tall, radius 10, 18px icon, gap 12.
    • Idle: muted, weight 500. Hover: hover bg with text colour.
    • Active: sel bg (#fdf3cf / #3a3622), text colour, weight 600, aria-current="page".
  • Płytarium items: Albumy (disc-3), Artyści (mic-vocal), Kolekcje (library), Gatunki (tags).
    • "Style" is absent because the current admin lacks permission for it.
  • Użytkownicy items: Użytkownicy (users), Grupy (users-round), Role (shield-check).

Switching plugins

  • Clicking a rail item navigates to that plugin's first permitted side-menu item (or to its registered default URL).
  • The panel content changes, and the first breadcrumb changes to the plugin label ("Płytarium › Albumy", "Użytkownicy › Użytkownicy").
  • The active rail item is derived from the current route's owning plugin, e.g. /plytadmin/plytarium/albums → plytarium.

Permissions

  • Filter both levels. An item the user can't access is not rendered at all; don't show it disabled.
  • A plugin whose side menu ends up empty after filtering is not shown in the rail either.

Collapsed / tablet

  • Below about 1100px, or when the user collapses it manually, the panel is hidden and only the rail remains. The manual choice persists in localStorage.
  • In that state, hovering or focusing a rail item, or pressing Enter/↓ on it, opens a flyout with the plugin's side menu:
    • Position: 8px right of the rail, aligned with the header area.
    • Size and shape: 230px wide, surface bg, border, radius 14, padding 8.
    • Shadow: 0 16px 40px rgba(20,27,45,.18).
    • Contents: a bold plugin title, then items 38px tall with radius 9 and the same active and idle styling as the panel.
    • It uses role="menu", closes on Esc or mouse-leave with a ~200ms delay, and returns focus to the rail item.

Suggested registry shape (what the backend exposes to the SPA):

[{ "code": "plytarium", "label": "Płytarium", "icon": "disc-3", "order": 100,
   "url": "/plytarium/albums",
   "sideMenu": [{ "code": "albums", "label": "Albumy", "icon": "disc-3", "url": "/plytarium/albums" }, …] }]

The server filters this by permissions before sending it.

Tip: when there are many plugins, add a Ctrl K command palette that searches every plugin's side-menu entries.

Top header

  • 64px tall, surface bg, bottom border, padding 0 24px 0 32px.
  • Left: breadcrumbs "Płytarium › Albumy › Enigmatic". Parent crumbs are muted links; the current crumb uses the text colour at weight 600. Separator: chevron-right 14px.
  • Right: user button, 44px tall, radius 12, containing:
    • 34px yellow avatar with initials "TW"
    • Name "Tomasz Wójcik" (600) above role "Administrator" (12px muted). The name and role hide on tablet.
    • chevron-down
  • User menu (dropdown, role="menu"): 280px wide, radius 14, padding 8, shadow 0 16px 40px rgba(20,27,45,.16). It holds a header (40px avatar, "Tomasz Wójcik" in bold, "tomasz.wojcik@plytarium.pl" in muted), a divider, and the menu item "Wyloguj" (log-out icon).

3. List — Albumy (screen=list)

Heading row

  • "Albumy" (26/700) with the subtitle "348 pozycji w katalogu".
  • Primary button "Nowy album" (plus icon), aligned right.

Card (radius 16, border, shadow), containing a toolbar, a table and a pagination footer.

Toolbar (padding 16×20)

  • Search input, 320px wide, subtle fill, search icon at left 14px. Placeholder: "Szukaj albumów…".
  • Right side:
    • No rows selected: "Usuń zaznaczone" shows as a disabled outline button (opacity .6, not-allowed, native disabled).
    • 1+ rows selected: a pill "Zaznaczono 3" (sel bg, check icon) appears next to an enabled danger-outline button "Usuń zaznaczone" (trash-2, hover dangerSoft).

Table — min-width 920px; scrolls horizontally inside the card on narrow screens. The header is sticky with subtle bg and borders top and bottom. Columns in order:

  1. Checkbox (52px). The header checkbox is tri-state: empty, mixed (minus icon) or checked.
  2. Nazwa: sortable, currently ascending. The header shows arrow-up 14px in the text colour with aria-sort="ascending". Cells are weight 600 and link to the edit form.
  3. Artyści: relation column, comma-joined names, muted.
  4. Format (110px): a pill (26px tall, subtle bg) with an icon. Winyl uses disc-3, CD uses disc, Kaseta uses cassette-tape.
  5. Gatunek: shows "—" (muted) when empty.
  6. Półka (72px): DM Mono 13px.
  7. Utworzono (150px): YYYY-MM-DD HH:mm, muted, 13px, tabular numbers.
  8. Wypożyczony (128px): switch column rendered as a badge. "Tak" is a green pill with an arrow-up-right icon; "Nie" is a muted outline pill.
  • Row: 54px tall with a bottom border. Selected rows use the sel bg.
  • Column headers are 12px/600 and muted, except the sorted column, which uses the text colour.

Footer (padding 14×20, top border, 13px muted)

  • "1–12 z 348"
  • "Na stronę" followed by a select button showing "12"
  • Right-aligned pager:
    • "Poprzednia" (disabled on page 1)
    • Page buttons 1, 2, 3, …, 29. The current page has sel bg, weight 700 and aria-current.
    • "Następna"

States

  • Selected: in the mock, rows 3, 6 and 7 are selected (Enigmatic, Nevermind, Nowe sytuacje).
  • Empty:
    • 80px vertical padding inside the table area.
    • 56px circle containing a search-x icon.
    • Title "Nic nie znaleziono" (17/700).
    • Text: "Żaden album nie pasuje do frazy „kaseta 1983”. Spróbuj innej nazwy lub artysty."
    • Outline button "Wyczyść wyszukiwanie".
    • The footer shows "0 wyników" with no pager.
  • Loading:
    • 8 skeleton rows at the same row height.
    • Bars are 12px tall with radius 6. Pill placeholders match the badge sizes. Bar widths vary per row.
    • The footer text reads "Wczytywanie…".

Sample rows (sorted by Nazwa). Columns: Nazwa · Artyści · Format · Gatunek · Półka · Utworzono · Wypożyczony.

Nazwa Artyści Format Gatunek Półka Utworzono Wypożyczony
Astigmatic Krzysztof Komeda Winyl Jazz J-02 2026-09-21 14:32 Nie
Ella and Louis Ella Fitzgerald, Louis Armstrong Winyl Jazz J-04 2026-09-20 19:05 Tak
Enigmatic Czesław Niemen, Akwarium Winyl Rock progresywny R-11 2026-09-18 10:47 Nie
Kind of Blue Miles Davis Winyl Jazz J-01 2026-09-17 21:13 Nie
Legenda Armia Kaseta Punk K-03 2026-09-15 08:26 Nie
Nevermind Nirvana CD Grunge C-07 2026-09-14 16:58 Tak
Nowe sytuacje Republika Kaseta Nowa fala K-01 2026-09-12 12:09 Nie
OK Computer Radiohead CD Rock alternatywny C-12 2026-09-10 18:40 Nie
Rumours Fleetwood Mac Winyl — R-04 2026-09-08 20:15 Tak
Spokojnie Kult Winyl Rock R-02 2026-09-05 11:31 Nie
Tutu Miles Davis CD Jazz fusion C-03 2026-09-03 09:52 Nie
Unknown Pleasures Joy Division Winyl Post-punk R-09 2026-09-01 22:04 Nie

4. Form — Edycja albumu (screen=form)

Content column max-width 980px, gap 20.

Header row

  • 40px back button (arrow-left, radius 10, border) labelled "Wróć do listy".
  • Title "Enigmatic" (24/700) with the subtitle "Edycja albumu · Czesław Niemen, Akwarium".
  • Tabs on the right, styled as a segmented control:
    • Track: subtle bg, 1px border, radius 12, padding 4.
    • Tab: 34px tall, radius 9, 600 weight.
    • Active tab: surface bg with shadow 0 1px 3px rgba(20,27,45,.12).
    • Tabs: "Podstawowe" and "Szczegóły", with role="tablist"/"tab" and aria-selected.

422 error banner (errors state only): dangerSoft bg, radius 12, padding 14×18, circle-alert icon 20px. Text: "Nie udało się zapisać. Popraw 2 pola oznaczone poniżej."

Form card: radius 16, padding 28.

  • Grid: repeat(auto-fit, minmax(280px, 1fr)), gap 22×24.
  • In the real implementation, map the YAML span value to columns: left → column 1, right → column 2, full → grid-column: 1 / -1. Below about 600px both columns collapse to one.
  • FormField row: label (600, with a red * for required fields that is aria-hidden; mark the input required / aria-required), then the control, then the error message.
    • Error message: 13px, danger colour, circle-alert 14px icon, linked through aria-describedby.
    • The input border turns danger and the input gets aria-invalid="true".

Podstawowe tab fields

Field Type Span Notes
Nazwa * text left Error: "Pole Nazwa jest wymagane." (value empty)
Artyści * multi-select relation with search right Chips: 30px pill, subtle bg, 22px primary-colour initials avatar, name (600), and a remove button labelled "Usuń: {name}". An inline input "Dodaj artystę…" opens a searchable dropdown.
Format * dropdown left Options: Winyl / CD / Kaseta. The selected value shows its format icon.
Gatunek relation dropdown right First option is "— brak —", shown muted when selected.
Półka text left Error: "Pole Półka może mieć maksymalnie 12 znaków." (value "Regał w salonie, góra")
Opis textarea, 4 rows full Resizes vertically.
Wypożyczony switch left Rendered as a toggle card: border, radius 12, padding 14×16. Title "Wypożyczony", helper text "Płyta jest aktualnie poza domem", and a 44×26 switch (role="switch", off state uses the borderStrong track).
Ulubiony checkbox right Toggle card with helper text "Pokazuj na początku listy" and a 20px checkbox, checked in the mock.

Szczegóły tab fields

  • Rok wydania (1970)
  • Wytwórnia (Polskie Nagrania Muza)
  • Numer katalogowy (XL 0569)
  • Stan nośnika (select: "Bardzo dobry (VG+)")
  • Kolor okładki, which uses the unknown field type fallback:
    • 1.5px dashed borderStrong border, subtle bg, radius 10, min-height 44.
    • puzzle icon with the text "Nieobsługiwany typ pola: colorpicker" (13px muted; the type name in DM Mono, text colour).
    • The FieldRenderer should fall back to this for any unregistered type.

(These Szczegóły fields are placeholder content; the real ones come from the YAML.)

Sticky footer: surface bg, top border, padding 14×32, always visible below the scrolling form.

  • Left: "Usuń", a danger outline button with trash-2.
  • Right, in order:
    • "Anuluj" (ghost, muted)
    • "Zapisz i zamknij" (outline)
    • "Zapisz" (primary)

Toast after save

  • Bottom-centre, 88px from the bottom, so it clears the footer.
  • Navy #1d2740 bg, white text, radius 14, shadow 0 16px 40px rgba(0,0,0,.25).
  • 26px yellow circle with a check icon.
  • Text: "Album „Enigmatic” został zapisany".
  • Close button with aria-label="Zamknij".
  • Use role="status" and auto-dismiss after about 4s.

5. Relation manager — Kolekcje → Redaktorzy (screen=relation)

  • The record header shows "Kolekcja domowa" with the subtitle "Edycja kolekcji". Tabs: "Podstawowe" and "Redaktorzy" (active).
  • The "Redaktorzy" tab (span full) renders only when editing an existing record, never on create.

Section header

  • "Redaktorzy" (17/700) with the helper text "Osoby, które mogą edytować tę kolekcję".
  • On the right:
    • Search, 220px wide, 38px tall, placeholder "Szukaj…"
    • "Usuń zaznaczonych" (danger outline, user-minus)
    • "Dodaj redaktora" (primary, user-plus)
    • These buttons are 38px tall, one size down from page level.

Table: the same DataTable, inside a bordered 12px-radius container.

  • Columns: checkbox, Imię i nazwisko (30px initials avatar plus name, 600), E-mail (muted), Dodano (160px, datetime).
  • Rows are 56px tall. Row selection works the same as the main list. In the mock, Marek Nowak is selected and the header checkbox is mixed.

Sample editors:

"Dodaj redaktora" modal

  • Container: role="dialog", aria-modal, 560px wide, radius 20, padding 24, gap 16, over the backdrop overlay.
  • Title "Dodaj redaktora" (20/700). Helper text: "Wybierz osoby spoza obecnych redaktorów. Właściciel kolekcji nie jest wyświetlany."
  • 34px close button (x, subtle bg).
  • Search input (44px, autofocused, so the focus ring shows), placeholder "Szukaj po nazwisku lub e-mailu…".
  • Candidate list: role="listbox", aria-multiselectable.
    • Each option row: 54px tall, radius 12, border, 32px initials avatar, name (600) above email (13px muted), and a checkbox on the right.
    • Selected option: sel bg and a #e0b020 border.
  • Candidates are users not yet linked to the collection, with the owner excluded on the server side. Results are paginated at 5 per page: "1–5 z 14", with prev/next buttons (34px, radius 9).
  • Footer: two equal-width buttons, "Anuluj" (outline) and "Dodaj (N)" (primary). N is the selection count; the button is disabled when N = 0.
  • Mock: Piotr Zieliński and Katarzyna Lewandowska are selected, so the button reads "Dodaj (2)". Other candidates: Michał Kamiński, Agnieszka Dąbrowska, Paweł Szymański.
  • Keyboard: Esc closes, focus is trapped inside, and focus returns to "Dodaj redaktora" on close.

Interactions & Behavior

  • Sorting: clicking a sortable header cycles asc → desc → none. Show arrow-up / arrow-down, and reflect the state in aria-sort and the URL query.
  • Search: debounce at 300ms, reset to page 1, and keep the term in the URL.
  • Selection:
    • The header checkbox selects the current page.
    • Selection resets on page or search change.
    • Bulk delete opens a confirm dialog: "Usunąć N albumów?".
  • Pagination: per-page options 12 / 24 / 48 / 96. Range text uses the format "{from}–{to} z {total}" with an en dash.
  • Loading: show skeleton rows in place of the table body. Keep the toolbar and footer mounted.
  • Validation:
    • On a 422 {errors: {field: [msg]}} response, show the messages under each field plus the top banner, and focus the first invalid field.
    • Errors clear per field on change.
    • When the invalid field sits on another tab, mark that tab with a count badge.
  • Save:
    • "Zapisz" stays on the form and shows the toast.
    • "Zapisz i zamknij" returns to the list and shows the toast there.
    • "Anuluj" returns without saving; confirm first if the form is dirty.
    • "Usuń" opens a confirm dialog.
  • Relation manager: "Dodaj redaktora" opens the modal, and confirming POSTs the selected IDs. The table then refreshes and a toast reads "Dodano 2 redaktorów". "Usuń zaznaczonych" asks for confirmation, then detaches.
  • Transitions: use subtle ones only, 150ms ease-out for hover/background and 200ms for modal fade and scale (0.98 → 1). No decorative animation.
  • Responsive:
    • 1280px is the design width.
    • Below about 1100px the sidebar collapses to 72px icons; users can also toggle this manually and the choice persists.
    • At tablet width (≥768px) tables scroll horizontally inside their card and the form grid wraps to one column when needed. The header user button hides its name.

State Management

  • Global: auth user and permissions (these drive which menu items render), plus the sidebar collapsed flag.
  • Per list: { search, sort: {column, dir}, page, perPage, selectedIds, loading, rows, total }, synced to the URL query.
  • Per form: { record, dirty, errors: Record<field, string[]>, activeTab, saving, mode: 'create'|'update' }. Tabs and fields come from the YAML schema; relation tabs require mode === 'update'.
  • Relation modal: { open, search, page, candidates, total, selectedIds }.
  • Toast queue: global.

Components to build (generic)

  • PluginRail, SectionPanel, SectionFlyout (all driven by the navigation registry)
  • AppShell (sidebar + header)
  • DataTable (columns from schema; column types text, relation, datetime, switch, badge/format; selection; sort; states)
  • ListToolbar
  • Pagination
  • FormTabs
  • FormGrid
  • FormField (label/control/error row; span)
  • FieldRenderer (type registry → text, textarea, dropdown, relation, relation-multi, switch, checkbox, fallback UnsupportedField)
  • RelationManager
  • RelationPickerModal
  • Toast
  • UserMenu

Assets

  • Icons: lucide (the mock uses the lucide-static icon font). Use lucide-vue-next in production. Icons used:
    • Navigation and brand: sun, disc-3, disc, cassette-tape, mic-vocal, library, tags, settings, panel-left-close
    • Chevrons and arrows: chevron-right, chevron-down, chevron-left, arrow-up, arrow-left, arrow-right, arrow-up-right
    • Actions and status: search, search-x, plus, trash-2, check, minus, x, circle-alert, user-plus, user-minus, log-out, puzzle
  • Fonts: DM Sans and DM Mono from Google Fonts.
  • No images. The logo is the lucide sun icon plus the text wordmark.

Files

  • Direction C v2.dc.html: the full design reference, with every screen and state switchable via props.
  • support.js: runtime needed to open the HTML file locally. It is not part of the design.