web_themes #
Shared Web UI themes for ATProto CRA apps (altifier, verifier, sifter, ...). One repo to own the theme, apps pull it at build time — no more copy-paste drift between repos.
Structure #
web_themes/
scripts/sync-theme.sh build-time pull script (copy into consumers)
themes/
<theme-name>/
theme.css the theme: tokens + shared component styles
index.css body/base boilerplate (font, smoothing, transition)
Themes #
witchsky-cyan #
The original shared base theme from altifier + verifier (dark, cyan accent, 18px base font, WCAG AA, 48px touch targets). Altifier's home/login selectors are the superset, so it serves both apps.
Design tokens (defined in :root, components use var() only):
| token | value | use |
|---|---|---|
| --background | #111822 | page background |
| --text | #F9FAFB | primary text |
| --text-muted | #A5B2C5 | secondary text |
| --button-bg | hsl(174, 83%, 38%) | primary buttons, links |
| --button-text | #FFFFFF | text on primary buttons |
| --button-hover-bg | hsl(174, 78%, 32%) | primary buttons hover |
| --card-bg | #1C2B35 | cards, forms, inputs |
| --card-border | #394A58 | borders |
| --navbar-bg | #15232C | navbar, footer, inset fields |
| --success-bg | rgba(45, 242, 143, 0.12) | success status box |
| --success-text | #2CF28F | success text, approve btn |
| --success-border | rgba(45, 242, 143, 0.25) | success status box border |
| --error-bg | rgba(246, 90, 127, 0.12) | error status box |
| --error-text | hsl(348, 92%, 64%) | error text, destructive btn |
| --error-border | rgba(246, 90, 127, 0.25) | error status box border |
Shared component styles: body, typography, app/main containers, forms, inputs/selects, buttons, navbar, footer, home/login cards, status boxes, suggestions list, list items, revoke button (flash-red-warning), load-more, mobile media query.
Consuming a theme (build-time pull) #
-
Copy
scripts/sync-theme.shANDscripts/theme-contract.shinto the consumer repo'sscripts/. -
Wire package.json:
"sync:theme": "bash scripts/sync-theme.sh", "build": "npm run sync:theme && react-scripts build", "start": "npm run sync:theme && react-scripts start" -
Import the synced files BEFORE the app's own CSS so app rules can override theme rules (index.js, imports are order-sensitive):
import "./styles/theme/index.css"; import "./styles/theme/theme.css"; import App from './App'; // App.css comes after — overrides theme -
Gitignore the generated copy: add
/src/styles/themeto .gitignore.
The script shallow-clones into ~/.cache/web_themes and copies
themes/<name>/theme.css + themes/<name>/index.css into
src/styles/theme/. Always pulls latest main. Override the repo with
WEB_THEMES_REPO (e.g. a local path for testing, or the HTTPS form
https://tangled.org/psingletary.com/web_themes for machines without a
Tangled SSH key).
Versioning model (decision 2026-10-16) #
Pull-main, enforced at build: themes are NOT versioned/tagged yet. A theme
change must keep the token NAMES stable (that is the contract) and is
verified by the contract check — see below — which fails the consumer build
at the exact commit that broke a token. If explicit historical themes are
ever wanted, tag themes/<name>/vN and let consumers pin via a new
THEME_REF env in sync-theme.sh. Until then: only change token values, never
names, and update every consumer's build after a token-name change.
Contract check (part of every sync) #
After copying, sync-theme.sh runs scripts/theme-contract.sh --consumer:
every var(--x) in the consumer's CSS must resolve to a token defined in the
theme OR in the consumer's own CSS (App.css may define app-local tokens).
Undefined tokens fail the build with the list. THEME_CONTRACT=0 disables the
check. The theme side is checked with theme-contract.sh --theme <css>:
no hardcoded hex/hsl/rgb colors outside :root.
Consumer matrix #
| Consumer | Repo | Theme | Sync wired |
|---|---|---|---|
| verifier | tangled.org/psingletary.com/verifier | witchsky-cyan | yes |
| altifier | tangled.org/psingletary.com/altifier | witchsky-cyan | yes |
| sifter | tangled.org/psingletary.com/sifter | witchsky-cyan | yes |
| timer | tangled.org/psingletary.com/timer | witchsky-cyan | yes (2026-10-16) |
| slicer | tangled.org/psingletary.com/slicer | witchsky-cyan | yes (2026-10-16) |
To add a consumer: copy both scripts, wire the npm scripts, import the theme files before App.css, gitignore the generated copy, and add a row above.
Adding a new theme #
- Create
themes/<name>/withtheme.css(tokens in:root, then component styles using onlyvar()) andindex.css(body base). - Keep the token NAMES stable across themes — consumers reference the variable names, not values. Only the values change per theme.
- Update this README's theme list.
Conventions #
- AGENTS.md is the primary agent instructions file.
- No hardcoded hex/Tailwind colors outside
:roottoken definitions; all component rules usevar(). Zero emojis in code/docs. - Any frontend design/polish/audit work loads the
impeccableskill first.
- CSS 57%
- Shell 43%