Skip to content
fossil-skin Artifact [e612da4dc1]
Login

Artifact [e612da4dc1]

Artifact e612da4dc1ac9b3bc26a1355881d02f47988ec9ed6c271c897373252f6c12600:


# 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:

```bash
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:

1. **Inline styles** (JS-injected) — caught with `td[style*="background"]`. We overlay a semi-transparent dark layer using `background-image: linear-gradient(rgba(15,17,23,0.75), ...)`. CSS renders `background-image` on top of `background-color`, so the pastel bleeds through as a subtle hue tint while keeping text contrast above 6:1.

2. **Stylesheet rule** — Fossil's built-in CSS also applies `background-color: #efefef` to `.timelineModernCell[id]`. Overridden with `background-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:

```html
<script defer src="/a11y.js"></script>
```

The `defer` attribute ensures:
1. HTML is fully parsed before the script runs
2. Fossil's inline config scripts (which set `window.fossil`) have already executed
3. 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.