panicOS

BRIEFS

Theming & Frontmatter Guide

REFERENCE / Theming & Frontmatter Guide

2026-06-16

Everything visual is config-driven. You edit markdown/frontmatter, rebuild, and the site updates — no component edits. This note is the map.

After editing any theme.md value, rebuild with a clean cache: rm -rf .astro && npm run build (the Nextcloud sync locks .astro, which otherwise serves stale content).

Where things live

  • Theme (look & motion): src/content/site/theme.md
  • Site identity / nav / logo: src/content/site/config.json
  • Projects: src/content/projects/<artist>/<project>/index.md
  • Briefs (write-ups): src/content/briefs/<slug>.md
  • Assets (shop/downloads): src/content/assets/<slug>.md
  • Links: src/content/links/<slug>.md
  • About: src/content/about/about.md
  • Fonts & images you reference: anywhere under public/ (e.g. /fonts, /bg, /media)

The three config files

The look is split across THREE files in src/content/site/, each with a legend at the top of its frontmatter. Comments are tagged by region: [all] (every page), [home] (the / OS desktop only), [mobile] (phone overrides), and twin→X (a knob that matches another today but is still independent).

  • theme.md — colour, type, layout, and the OS-home panel surfaces.
  • circuits.md — the dashed “circuit” borders (a solid underlay + marching dashes).
  • effects.md — motion timing + on/off effect toggles.

For the exhaustive knob → CSS-var → on-screen-element table (and the desktop/mobile + home/rest differences), see docs/STYLE-MAP.md.

theme.md — colour, type, layout

Grouped knobs. Every field has a default, so a partial file still works. Section 2 (palette) is grouped by REGION: global surfaces · cards · rows/links · accent · text · borders · CRT effects.

palette (named colours)

Each UI role has its OWN name even when two share a colour, so you can recolour one without the other.

  • Global surfaces: contentBg (the big reading panel — what you usually mean by “the background”), pageBg (outer edge), headerBg.
  • Cards: cardBg / cardBgHover, cardGradientBottom (bottom gradient wash on cards/hero — emits --c-glow).
  • Rows / links: hover (rows/nav/links), linkHover / linkHoverDeep (link-row hover gradient), link.
  • Accent: accent, accentBright (hover/raised), accentDeep (pressed), accentDark (sidebar gradient base).
  • Text: text, textMuted, textNeutral, category, categorySep (the ” / ”), tag, success, versionText, versionMark.
  • Borders & rules: border (structural dashed boxes/tags), rule (horizontal dividers), borderDim, borderNeutral. (The marching-ants colours live in circuits.md.)
  • CRT effects: shadow (the hard drop-shadow), glitchR / glitchC (CRT channel-split colours).

Colours accept #rgb, #rrggbb, or #rrggbbaa (8-digit = with alpha, e.g. #82000280 is half-transparent).

circuits.md (dashed-border geometry)

Each “circuit” is a solid underlay: { color, width } + marching dash: { color, width, length, gap, speed, offset, reverse } + linecap. The marching travel distance auto-syncs to length + gap, so the loop stays seamless at any setting. Circuits: main (site-wide), card (quick-links), and the home-only osBus / osActionCenter / osTerminal / osMotd.

typography

  • faces: list of { family, file, weight, style }registers the available fonts (generates the @font-face at build time). Drop a font (.woff2/.woff/.ttf/.otf) in /public, add a line here.
  • fonts: assigns a registered family to each type role — one independent knob per type location. Roles: title (display titles), heading (section labels/eyebrows), nav (sidebar/filter/buttons), badge (badges/tags), body (prose/descriptions/metadata), meta (dates/CTAs), terminal (the /os console). Each → class .font-<role> + var --font-<role>. Retarget any of them live in /dev/theme (font dropdowns).
  • baseSize (root size), proseSize (markdown body paragraphs).
  • sizes: role-based size scale used across the UI — label (14, small UI/nav/badges/tags/dates), meta (15, metadata rows/header title), cta (16, action links), body (18, excerpts/project body), title (20, card & section titles), hero (24, detail hero). Each → a --text-* var.

backgrounds (images on surfaces)

Per-surface image that layers OVER the colour/gradient (so transparent GIFs still show the theme colour behind them).

  • Surfaces: page (main content), sidebar, header, and divider (a thin strip between header & content).
  • Each: { image, mode, position }. mode = tile (repeat) / fit (contain) / full (cover). image: "" = off.
  • divider also takes height (px); height: 0 or empty image = off.
  • Example:
    backgrounds:
      sidebar: { image: '/bg/loop.gif', mode: tile }
      page: { image: '/bg/grid.gif', mode: tile, position: 'top left' }
      header: { image: '/bg/banner.png', mode: full }
      divider: { image: '/bg/strip.gif', height: 18, mode: tile }

layout

contentWidth — max page width (e.g. 72rem / 90rem / 100%).

effects.md — motion + toggles

  • timing (lower = faster): flicker, stagger (reveal cascade), slideshow (ms), glitch, crt (page-transition), boot (home only). The marching-ants speed is per-circuit in circuits.md (main.dash.speed).
  • toggles (on/off): scanlines, flicker (click), marchingAnts, coverAutoplay, videoAutoplay (false = show controls), crtTransition, sidebarParallax, osParallax (home-page scroll “weightiness”). All respect prefers-reduced-motion.

Project frontmatter

Folder name = URL slug. Nest as projects/<artist>/<project>/index.md for multiple projects per artist. Use hyphens, not spaces, in folder names.

Required: title, description (short blurb; the markdown body is the long form).

Cover media (priority order):

  • embeds → first video/embed wins the hero slot.
  • covers: [a.jpg, b.jpg] → multiple = auto CRT-glitch slideshow on the detail page (cards show the first).
  • cover: a.jpg → single image.
  • Images are co-located in the project folder and referenced by relative path.

Common fields: category, tags: [], date: "04/2026", pinned: true, commissioned: true, visible: false (hide from the public site), brief: <brief-id> (link a write-up).

Category vs tags — two different facets, don’t conflate them:

  • category = the kind of work (PHOTOGRAPHY, TYPOGRAPHY, LIVE VISUALS). One per project; it’s the coloured title prefix and the single-select filter.
  • tags = cross-cutting attributes that span categories — technique/medium/format like 2D, 3D, 35MM, VJ, ANALOG. Many per project; multi-select filter chips on the listing. Write them plain (tags: [2D, 35MM]); the UI shows them as #2D, #35MM (don’t put the # in frontmatter — YAML reads a leading # as a comment).

Sales: status (available/sold/reserved/not-for-sale/delivered/archived), price, buyLinks: [{ label, url }].

Physical/work: medium, materials: [], software: [], dimensions: { width, height, depth?, unit }, year.

Rich media: gallery: [{ src, alt, caption? }], embeds: [{ type: youtube|vimeo|video, url, caption? }]. For a self-hosted file use type: video with a /public URL.

Credits/context: client, role, collaborators: [{ name, role?, url? }], exhibitions: [{ title, venue?, date?, url? }], press: [{ title, outlet?, url }], related: [<project-id>, …].

Freeform blocks — add ANY key/value, no schema edit needed:

metadata: # renders in the METADATA table
  bpm: 174
  format: ['4K', 'ProRes']
credits: # renders in the CREDITS list
  director: Jane Doe
  vfx: Studio X

Contract: the typed fields above are canonical — when one exists, use it (client, role, medium, materials, software, dimensions, year, status, price). They validate and render with proper formatting. Reserve metadata for extras that have no typed field (bpm, timeline, budget, vision, format…). A freeform metadata/credits key that shadows a typed field name is flagged with a build-time warning (it still renders — the warning just nudges you to move it to the typed field).


Brief frontmatter

title, date, excerpt (required). Optional: category, tags: [], project: <project-id> (cross-link), pinned, visible (false hides from the public site — like this guide).

Asset frontmatter

name, platform (GUMROAD/KO-FI/…), url (required). Optional: description, price (“$5”/“FREE”), cover, tags: [], pinned.

label, url (required). Optional: description, category (SOCIAL/SHOP/…), order (sort).

About

name (required). Optional: portrait, facts: [{ label, value }]. Bio is the markdown body.

Site config (config.json)

id, name, tagline, year, logo (path to an image in /public), and nav: [{ id, label, href, title? }].


Quick recipes

  • Rebrand colours: edit the palette block → rebuild.
  • Swap a font: drop the file in /public/fonts, change that face’s file (and family if you like) under typography.faces.
  • Add a tiling GIF background: drop it in /public/bg, set e.g. backgrounds.sidebar: { image: "/bg/x.gif", mode: tile }.
  • Hide a project/brief: add visible: false.
  • Slow the marching ants: raise dash.speed under main in circuits.md (e.g. 2.5s — higher = slower).
  • Kill the CRT effects: set the relevant effects: toggles to false.

For the full knob → CSS-var → on-screen-element reference, see docs/STYLE-MAP.md. The component catalog (buttons, forms, layouts, naming conventions) is in docs/DESIGN-SYSTEM.md.

DISCUSSION
/ commands