Artifact e612da4dc1ac9b3bc26a1355881d02f47988ec9ed6c271c897373252f6c12600:
- File docs/SKIN.md — part of check-in [29f97fa7a7] at 2026-03-14 23:23:29 on branch trunk — Add comprehensive setup and skin architecture docs (user: dmestas, size: 4574) [annotate] [blame] [check-ins using]
Skin Architecture
How the dark theme works and how to modify it.
Files
| File | Purpose |
|---|---|
skin/css.txt |
All CSS — design tokens, layout, timeline fixes, dark-mode overrides |
skin/header.txt |
TH1 template — HTML injected before page content on every Fossil page |
skin/footer.txt |
TH1 template — HTML injected after page content |
skin/apply.py |
Writes skin parts to every repo's SQLite config table |
www/index.html |
Custom landing page served by Caddy at / |
www/a11y.js |
Accessibility + dark-mode JS served by Caddy at /a11y.js |
Design Tokens
Defined in :root in css.txt. Match these in www/index.html too.
| Token | Value | Usage |
|---|---|---|
--bg |
#0f1117 |
Page background |
--surface |
#1a1d27 |
Cards, nav bar, input fields |
--surface2 |
#22263a |
Hover states, tooltips |
--border |
#2a2d3e |
All borders |
--accent |
#6c8ebf |
Links, active nav, focus indicators |
--accent2 |
#4a7c9e |
Logo gradient secondary |
--text |
#e2e8f0 |
Primary text |
--muted |
#8892a4 |
Secondary text, nav items |
--green |
#4ade80 |
Success states |
--red |
#f87171 |
Error states, diff deletions |
--yellow |
#fbbf24 |
Warning, warp commits |
--radius |
8px |
Standard border radius |
--font |
Inter, system-ui | Body text |
--mono |
JetBrains Mono, Fira Code | Code, monospace elements |
Deploying Skin Changes
After editing any skin file, push to repos:
python3 /var/fossil/skin/apply.py
That's it. Fossil serves the updated skin immediately — no restart needed.
Key Dark-Mode Overrides
Timeline graph lines
Fossil's built-in CSS hardcodes all graph elements black (.tl-node, .tl-line, .tl-arrow). On a dark background these are completely invisible. Every graph selector is overridden with !important using --muted for lines/arrows and --accent for nodes.
Branch row colors
Fossil JS sets style.backgroundColor on timeline cells with light pastel branch colors designed for white backgrounds. Two layers:
Inline styles (JS-injected) — caught with
td[style*="background"]. We overlay a semi-transparent dark layer usingbackground-image: linear-gradient(rgba(15,17,23,0.75), ...). CSS rendersbackground-imageon top ofbackground-color, so the pastel bleeds through as a subtle hue tint while keeping text contrast above 6:1.Stylesheet rule — Fossil's built-in CSS also applies
background-color: #efefefto.timelineModernCell[id]. Overridden withbackground-color: transparent.
Chat message bubbles
Same issue as branch colors: Fossil JS sets inline background-color from the user's assigned color (m.uclr). Same background-image overlay technique with 72% dark opacity.
isDark flag
Fossil's C code hardcodes isDark: false for all custom skins, ignoring the skin-detail config key. This affects the timeline graph renderer (it picks white vs. black node colors). Fixed in a11y.js via window.fossil.config.skin.isDark = true — runs after Fossil's own inline scripts because of defer, and again on window.load because Fossil's load handlers may reset it.
TH1 Template Notes
The header.txt and footer.txt files use Fossil's TH1 scripting language (a Tcl subset).
Available variables in header/footer:
$home— base URL of the repo (e.g./edgesync)$<project_name>— repo's project name (HTML-escaped substitution)$<title>— current page title$current_page— path of the current page$mainmenu— list of{name url expr class}tuples for nav links$login— current username if logged in
The capexpr function checks user capabilities — used in the mainmenu loop to hide links the current user can't access.
CSP and JavaScript
Fossil's Content Security Policy:
script-src 'self' 'nonce-<per-request-nonce>'
Inline scripts without the matching nonce are blocked. a11y.js is served as a static asset by Caddy at /a11y.js, which satisfies 'self'. It's loaded in header.txt with:
<script defer src="/a11y.js"></script>
The defer attribute ensures:
- HTML is fully parsed before the script runs
- Fossil's inline config scripts (which set
window.fossil) have already executed - The script runs before
DOMContentLoaded
window.load re-runs the timeline and isDark fixes because Fossil's own load handlers insert SVG graph elements and may reset config values.