panicOS

BRIEFS

Design System & UI Inventory

REFERENCE / Design System & UI Inventory

2026-06-29

A catalog of every styled / interactive front-end element: what it is, where it lives, the classes + tokens it consumes, its variants/states, and — where relevant — a 🎯 flagged standardization note. This is the component & pattern reference; its companions are:

Scope note: this is a descriptive + recommendation doc. The naming-convention section (§13) has been applied — the component/class names below are live. The remaining 🎯 items in the standardization section (§14) are still proposals for a later pass.


1. Purpose & ethos

panicOS is an open-source artist-portfolio framework with a 90s DOS/CRT aesthetic. The design goal: a forker should be able to take the source, change (or keep) the colours, drop in their own images + markdown posts, and ship — without touching component code. That is why:

  • Almost nothing is hard-coded. Colours, type, dash geometry, motion, and the OS-home surfaces all come from markdown/JSON config (src/content/site/*) compiled to CSS variables.
  • Content is markdown. Projects/briefs/assets/links/about are .md files.
  • Components consume tokens, not literals. Reach for var(--c-*) / var(--os-*) / var(--text-*) / var(--font-*) — never a raw hex.

The two themeability gotchas to keep in mind:

  1. OS-home colours are literal copies, not palette aliases. os.* in theme.md duplicates the palette; changing palette.text won’t move the homepage unless you also change os.*.text. Intentional but the #1 “my change didn’t apply” cause.
  2. Marching-dash borders are SVG, drawn per-element by DashedBorderFull — not CSS border. Geometry comes from circuits.md.

2. Design tokens (summary)

Token families emitted by lib/theme-css.ts into :root:

FamilyPrefixDrivesSource knobs
Palette--c-*colours everywhere (text, accent, borders, glow, links, CRT)theme.md → palette
Chrome--c-top-accent, --tray-*top strip, sidebar traytheme.md → chrome
Type sizes--text-*, --font-size, --prose-sizeevery font-sizetheme.md → typography.sizes
Font roles--font-*font-family per roletheme.md → typography.fonts/faces
Backgrounds--bg-<layer>-*surface image layerstheme.md → backgrounds
Circuits--c-circuit, --c-border-dashed, --dash-*, --circuit-widthmarching-dash borderscircuits.md
OS-home--os-*the / shell surfaces onlytheme.md → os + os-circuits
Effects--flicker-speed, --stagger-step, --crt-speed, --boot-duration, --glitch-durationmotion timingeffects.md

Rule: the markdown key names are the contract. Reorder/rename comments freely; never rename or move a key across top-level groups.


3. Typography & font roles

Seven semantic roles, each emitting a .font-<role> class + --font-<role> var, plus the console’s --font-terminal (used directly).

RoleClassUsed for
title.font-titlecard / hero / section titles
heading.font-headingsection labels, eyebrows, metadata headers, prose h2/h3
nav.font-navsidebar nav, filter bar, home links, buttons, filter chips
badge.font-badgebadges, tag pills, status chips
body.font-bodyprose, descriptions, excerpts, metadata values; body{} default
meta.font-metadates, CTA links, captions
terminalvar(--font-terminal)the whole console (Terminal, Motd, IdentityModal, Radio, Comments)

Sizes are --text-*. Mobile shrinks --text-*/--prose-size via global.css @media blocks. faces[] in theme.md maps family↔font-file (@font-face).


4. Buttons & controls

Six visually-distinct button systems grew up independently:

#Component / classWhereSurfaceBorderFontKey tokens
1Button.astro bevel (via NavButton)Sidebar navbeveled raised face (.nav-btn-face light/dark key edges)none (active adds dashed inset).font-nav--c-accent / -bright / -deep, --c-shadow
2LauncherButton.astro (.launcher-btn)OS-home launcher rail.card-gradientDashedBorderFull (marching).font-nav--c-text, --c-link (► CTA), --c-shadow
3Button.astro rail (.console-tab)CLI view rail (ConsoleNav)transparent (icon + label)none (dashed rail divider)--font-terminal--os-term-text/-accent/-name
4Button.astro bevel/ghostConsole form/action (SEND, SAVE…)bevel raised face (shares §1) / transparent (ghost guest)bevel edges / 1px accent (ghost).font-nav/term--c-accent* (bevel), --os-term-accent (ghost)
5LinkCard.astro (.link-card)Home quick-links + /links.link-cardDashedBorderFull (link circuit).font-nav--c-link-hover*, --c-text-neutral
6FilterBar chipsListing filter dropdownsquare dashedborder-dashed --c-border.font-nav--c-accent (active), --c-border

Other interactive controls: radio transport (⏮ ▶/⏸ ⏭, seek, volume — --os-term-*), slideshow dots ([data-dot]), the hub (HubNode, a focusable role=button hotspot), the colour-swatch grid (IdentityModal / profile view).

🎯 Standardization (§14.1 — in progress): systems 1, 3 and 4 now share one Button.astro primitive (variant: bevel|ghost|rail) — the sidebar bevel was reused for the console form actions so the CLI reads as part of the same OS, and the CLI view rail is the rail variant. Only bespoke left: launcher (2, card). See §13 (naming) + §14 (plan).


5. Primitives

ComponentWhat it isNotes / tokens
DashedBorderFull.astrothe marching-dash “circuit” border on every framed boxtwo SVG rects (solid --c-circuit + dashed --c-border-dashed); animated.dash-animated; geometry from --dash-*.
Badge.astroevery small pill/label (COMMISSION flag, price, tag)variant: solid|outline, tone: accent|accentDark|tag, .font-badge
CategoryTitle.astro[category] / [title] — category coloured, / grey, title white--c-category, --c-category-sep, --c-text
SectionLabel.astroheading row (slot) above a solid Rulewraps Rule
Rule.astrohorizontal solid divider (never animated)--c-rule
Logo.astro / LogoMark.astrobrand wordmark / markbrand SVGs
.t-line / .t-screenconsole “type-in” stagger cascade for lists/cards--stagger-step; OS shell reuses it for the launcher rail

6. Cards & media

All cards share .card-gradient + DashedBorderFull + drop-shadow-…var(--c-shadow), and compose CategoryTitle / Badge.

ComponentWhereComposition
ProjectCard.astro/projects, home floor.card-gradient row + ProjectMedia 16:9 thumb + CategoryTitle
BriefCard.astro/briefs, home floor.card-gradient block + CategoryTitle + date (.font-meta)
AssetCard.astro/assets.card-gradient column + Badge (price)
SpotlightHero.astroHome floor herorotating pinned-project slideshow + CRT glitch (retro.ts)
OsSpotlight.astroOS-home left columncompact spotlight card, console-width; wired to the hub (data-os-wire)
ProjectMedia.astroInside cards / detailimage/video cover; autoplay per effects toggles
CanvasEmbed.astroProject detailembedded interactive canvas

7. Forms & inputs

SurfaceWhereFields / classes
Contact formConsole contact view (Terminal.astro).os-view-select (reason) + .os-view-input (name/email) + .os-view-textarea (message) + Button (bevel SEND)
Identity modalIdentityModal.astro + identity-modal.tsemail/password/display-name inputs; status input; 36-swatch colour grid
Profile viewConsole profile viewstatus input + colour grid + sign-out (Button bevel); sign-in/register bevel, guest ghost
CommentsComments.astro + comments-page.tsterminal-styled message input
Filter barFilterBar.astro + filter.tssearch input + FILTERS ▾ chip dropdown

Shared field look: dashed border on --os-term-surface-deep, --font-terminal, focus → accent border. Always set font-size:16px on mobile inputs (stops iOS focus-zoom; the contact + identity inputs already do this).

🎯 Standardization: the console field styles (.os-view-input/-select/-textarea) and the identity-modal inputs are near-duplicates — candidate for one shared field component/class.


8. Console / CLI (the / + /console shell)

Built from Terminal.astro (markup) + os-terminal.ts (behaviour), driven by terminal.md.

  • View shell: a left icon rail (ConsoleNav.astro, desktop) / bottom bar (mobile) switches the body between views: chat (default) · radio · contact · help (commands) · inventory (locked) · profile. Adding a view = one NAV entry + a matching [data-view-panel]. State machine: setView(id) toggles data-view + panel hidden.
  • Persistent prompt: the input line (red accent outline) + / palette sit in .os-term-prompt below the views, so the command line stays on every view (type /help, still /chat back). Chat view holds only the scrollback log.
  • Chat-row vocabulary (styled inline — Astro scoped CSS can’t reach JS-built rows): name: message (name in nameColor), [system] sender (dim, non-clickable .os-term-sysname), [x] admin-delete chip (.os-term-del, admin-only via hidden), guest lines dimmed + [guest] tag, @mention highlight, pinned [announce].
  • User bar: ● name ▾ at the bottom ([data-term-userbar]).
  • / palette + commands view: typeahead command menu from terminal.md (palette/commands/redirects); built-in behaviour keyed by id in os-terminal.ts. Bare page names route to sections. JS-built rows → styled via :global under .os-term-window.
  • Boot blurb: ASCII header printed once per device (localStorage) at top of chat.
  • MOTD (Motd.astro): bare scrolling ticker across the header (config in terminal.md → motd). No box/frame, pure CSS marquee.
  • Per-page variant: Comments.astro reuses the same chrome (.cmt-*, --os-term-*, identity modal) minus radio/nav.

Tokens: everything here is --os-term-* (fill, titlebar, text, accent, name) + --font-terminal.


9. Overlays & feedback

OverlayFileNotes
Identity modalIdentityModal.astro / identity-modal.tsLogin/register/guest + colour. Profile moved to the console view (not this modal).
Profile popoverProfilePopover.astro / profile-popover.tsClick another user’s name → small popover (role/status).
Toastsnotification-toast.tsTransient notices/announcements.

10. Layouts & shells

ShellFileUsed byChrome
ShellLayoutShellLayout.astro/ + /consolefull-bleed os-shell; no sidebar/header; owns the viewport; scanlines
BaseLayoutBaseLayout.astroevery other pagedesktop sidebar (190px, sticky ≥768px) + mobile top bar (48px)
  • OS-home (/) is an absolute-positioned scene in index.astro: header row (wordmark + MOTD), NAVIGATION panel (ActionCenterBar), launcher rail (ButtonRail/LauncherButton), OsSpotlight, HubNode, Terminal. Shared layout vars: --os-row-top, --os-col-w (= console width; left column mirrors it), --os-rail-bottom, --os-spot-h. Below 768px it reflows to a vertical stack; the NAVIGATION panel + hero hidden. (No social row on /.) Panel glow halo = os.glow--os-glow-* (box-shadow on the 4 panels).
  • Circuit recolour: nav→button tree/tree-stub circuits use the NAVIGATION yellow/orange (--os-ac-*); launcher buttons stay RED. OS-page raster images → src/assets/os/ + <Image>.
  • Region split: / = the only consumer of os.* + os-circuits + --font-terminal + boot + os-parallax. Everything else = global palette/circuits + the sidebar shell.

11. Motion, states & accessibility

  • Flicker (--flicker-speed): data-flicker CRT click-flash on buttons/links (retro.ts).
  • Stagger (--stagger-step): .t-line items type-in within a .t-screen.
  • CRT / boot / route transitions: page CRT phases (--crt-speed), OS power-on (--boot-duration), and the circuit “route via the bus” lead-in (os-route.ts).
  • State conventions: hover = brighten/tint; active = deepen + (often) translate 1px; disabled/locked = reduced opacity + cursor:not-allowed; active nav = .is-active.
  • Reduced motion: every animation has a @media (prefers-reduced-motion: reduce) off-ramp.
  • Toggles: effects.mdhtml[data-*] / runtime flags: scanlines, flicker, marchingAnts, coverAutoplay, videoAutoplay, crtTransition, sidebarParallax, osParallax.

12. Interactive features map

FeatureUI (components)Script(s)Backend
Global chatTerminal chat viewos-terminal.ts, pb-chat.tsPocketBase messages (thread chat)
Per-page commentsCommentscomments-page.ts, pb-chat.tsPocketBase messages (per url thread)
Identity / authIdentityModalidentity-modal.ts, pb-auth.tsPocketBase users
ProfileConsole profile view, ProfilePopoveros-terminal.ts, profile-popover.tsPocketBase users
RadioRadio (radio view)os-terminal.tsradio.md (PB streaming later)
ContactConsole contact viewos-terminal.ts, pb-auth.tsPocketBase contact / bug_reports / wishlist
InventoryLocked viewPocketBase items/user_inventory (scaffold)
Filter/searchFilterBarfilter.tsstatic (client)
Theme editor/dev/themedev-theme.tsdev-only live preview
Circuit wiringCircuitTraces, HubNodeos-circuit.ts, os-route.ts— (see docs/CIRCUIT-SPEC.md)

13. Control-naming convention (APPLIED)

Goal: a control’s name says what it controls / does, not how it looked in the original design. This naming pass is applied in code — the table records each rename (earlier name → the name now live). E.g. .bevel-face described a bevel (a visual), not a purpose.

Rule: <Domain><Role> for components, <scope>-<role> for classes.

Earlier nameComponent (now)Class (now)Controls / does
BevelButton.astro / .bevel-faceButton.astro (via NavButton).nav-btn-face / -activenavigates to a site section (sidebar)
RailButton.astro / .os-rail-btnLauncherButton.astro.launcher-btnlaunches a section from the OS home
.os-nav-item (ConsoleNav)Button.astro rail (in ConsoleNav).console-tabswitches the active console view
.os-view-btn / .console-actionTerminal.astro viewsButton bevel/ghostsubmits/acts inside a console view
HomeLink.astro / .link-gradientLinkCard.astro.link-cardopens an external link
FilterBar chipFilterBar.astro.filter-chiptoggles a category/tag filter
Badge.astroBadge.astro.badgealready intent-named ✅

Note: FilterBar chips already used .filter-chip (no change needed); the container classes (.os-nav, .os-rail) and --os-* tokens stay as theme contract, not renamed.


14. 🎯 Standardization opportunities (PROPOSALS — not yet applied)

  1. Unify the button systems into one Button primitive with a variant (bevel / card / ghost / solid) and a token group, then reskin systems 1–4 (§4) through it.
  2. One shared form-field component/class for .os-view-input/-select/-textarea + the identity-modal inputs (§7), with the 16px mobile guard built in.
  3. Token de-duplication — the “twinned knobs” (STYLE-MAP) hold equal values today; decide which should stay independent vs collapse.
  4. Consistent CTA glyph — finish the literal-> → red chevron (►) cleanup.
  5. Apply the §13 naming convention in a focused rename pass. Done — see the §13 table for the applied renames.

Sequencing: §13 names are done. The §14.1 button primitive (same files) remains — do it behind a green check/build, one button system at a time.


15. Forker / themeability guide

To make this your own portfolio:

  1. Colours/type/motion: edit src/content/site/theme.md (+ circuits.md, effects.md). Use /dev/theme to preview, then Export YAML. Remember os.* colours are separate from palette.* (recolour both for the homepage).
  2. Brand: swap the logo SVGs (Logo/LogoMark, /brand/*) and the hub mark (/brand/dp-logo.svg) + ring (public/media/site-media/hub_ring.svg).
  3. Content: drop markdown into src/content/{projects,briefs,assets,links} and edit about/about.md; see AUTHORING.md. Console copy + commands live in terminal.md; radio in radio.md.
  4. Backends (optional): chat/auth/comments need PocketBase (scripts/pb-setup.mjs provisions collections). Without them the static site still builds.
  5. Don’t: hard-code hex/px in components, rename a theme key across groups, or add a DB/server runtime — keep it static + token-driven.
DISCUSSION
/ commands