# Themes
Three themes ship, and the same palettes drive the diagrams as well as the
pages. `soleprint` is the default; the others are switched to on purpose.
| Theme | Reads as | For |
| --- | --- | --- |
| `soleprint` | dark, rounded, amber | the default — tools and dev chrome |
| `mcrn` | dark, square, monospace, orange glow | the terminal look, matched to mariano.mcrn.ar |
| `lucid` | light, hairline, printable | regulated documents, and sitting beside a real lucid.app export |
Switch with `?theme=lucid`, or the toggle in the corner. The choice persists.
## Where it lives
```
soleprint/common/theme/
tokens.css the vocabulary + neutral defaults
themes/*.css one file per theme
theme.js resolve, apply, remember
bake.py inline the defaults into pages
```
Served together at `/theme.css` — tokens first, then every theme, each scoped to
`[data-theme="..."]`. Adding a theme is adding a file: `run.py` lists the
directory rather than carrying a hardcoded list.
## Two naming families, one set of values
Both are answered, because both were already in use and renaming across a dozen
templates would have been the larger change:
- `--bg` / `--surface` / `--border` / `--text` / `--muted` / `--accent` — the
station tools and the docs site
- `--surface-0..3` / `--text-primary` / `--panel-radius` — `common/ui`'s Vue
components
The second family is derived from the first in `tokens.css`, so a page using
either name gets the same colour and a theme author fills in one set.
## Order matters
```html
```
The theme has to load **before** the page's styles, so its element defaults
underpin the page rather than override it. Get this backwards and
`tokens.css`'s `body { background: var(--bg) }` flattens whatever the page
wanted — which is exactly how artery, atlas and station briefly lost their
coloured content columns.
## Baked defaults, and why
`/theme.css` is an absolute path, and soleprint is not always at the root — in a
room's nginx it sits under `/spr/` while `location /` goes to the frontend. A
page that says `background: var(--bg)` and never receives `--bg` does not fall
back to something plainer: the declaration is invalid at computed-value time, so
the background goes transparent and the text goes initial-black on a design that
assumed dark. Unstyled, not merely unbranded.
So every page carries a generated `:root` block **before** the link. Both are
`:root`, so document order decides: the served stylesheet wins when it loads,
and the baked block is what is left when it does not.
```bash
python3 common/theme/bake.py # regenerate
python3 common/theme/bake.py --check # fail if a page is stale
```
Only the variables a page actually uses are emitted, so the blocks stay small.
## No webfonts
The stacks name faces that exist on the target rather than fetching any:
```css
--font-ui: "Segoe UI", Inter, system-ui, -apple-system, Arial, sans-serif;
--font-mono: "Cascadia Mono", "JetBrains Mono", Consolas, "SF Mono", monospace;
```
Segoe UI and Consolas ship with Windows. A regulated network blocks
`fonts.googleapis.com` and `file://` stalls on it, and neither failure looks
like a missing font — they look like a broken page.
## Diagrams follow
`docs/graphs/themes/*.gvpr` carry the same palettes for graphviz, so a diagram
and the page around it are one visual language. See
[the graphs README](https://git.mcrn.ar/mariano/soleprint/src/branch/main/docs/graphs/README.md)
and [Export / Compile](#export).
```bash
make docs graphs # every graph, every theme
```
Diagrams are baked per theme rather than styled by CSS, because the docs embed
them with `` — which makes the SVG a separate document the page's
stylesheet cannot reach. A plexus that **inlines** the SVG can style it live,
and the bundle plexus does exactly that.
## Contrast
`lucid` is the first light theme, and the palette was measured rather than
guessed — against both `#ffffff` and the `#f5f7fa` panel, at the sizes actually
used. `--dim` drives 11px notes and `--status-warn` drives 10px labels, so both
need 4.5:1 rather than the 3:1 large text gets away with. The obvious lighter
greys came in at 3.4–3.8 and were dropped for that reason.