BRIEFS
Theming & Frontmatter Guide
REFERENCE / Theming & Frontmatter Guide
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.mdvalue, 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 incircuits.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, anddivider(a thin strip between header & content). - Each:
{ image, mode, position }.mode=tile(repeat) /fit(contain) /full(cover).image: ""= off. divideralso takesheight(px);height: 0or emptyimage= 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 incircuits.md(main.dash.speed). - toggles (on/off):
scanlines,flicker(click),marchingAnts,coverAutoplay,videoAutoplay(false = show controls),crtTransition,sidebarParallax,osParallax(home-page scroll “weightiness”). All respectprefers-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 like2D,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.
Link frontmatter
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
paletteblock → rebuild. - Swap a font: drop the file in
/public/fonts, change that face’sfile(andfamilyif you like) undertypography.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.speedundermainincircuits.md(e.g.2.5s— higher = slower). - Kill the CRT effects: set the relevant
effects:toggles tofalse.
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.
Create a PanicOS account (email + password) to hold a verified name across devices and keep your message streak. Or continue as a guest — a guest name is set on THIS device only and shows as @guest.
CHOOSE A DISPLAY NAME
STATUS