Skip to content
fossil-skin Artifact [ed927e7713]
Login

Artifact [ed927e7713]

Artifact ed927e7713cf449bd381b9102c90bde509c73370e5ef58469e7329409d5d9031:


# 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

The skin is a dark rendering of Fossil's default skin: same layout (title bar,
menu band, horizontal submenu row), same sizes (16px system sans-serif body,
2.2em project title), with colors picked for WCAG AA contrast on a near-black
page. Defined in `:root` in `css.txt`; `www/index.html` repeats them.

| Token | Value | Usage |
|-------|-------|-------|
| `--bg` | `#1c1e22` | Page background |
| `--surface` | `#272a30` | Menu band, cards, code blocks |
| `--surface2` | `#31353d` | Hover states, selected rows |
| `--border` | `#41454e` | All borders |
| `--text` | `#e8e9eb` | Body text (13:1 on `--bg`) |
| `--muted` | `#b3b8c0` | Secondary text (7.3:1 on `--bg`) |
| `--link` | `#7fb6ef` | Links (7.2:1 on `--bg`); the default skin's `#3779BF`, lightened |
| `--title` | `#8cbdf0` | Project title |
| `--active` | `#f08a3c` | Active menu underline; the default skin's `#D26911`, lightened |
| `--green` / `--red` / `--yellow` | `#6fd48f` / `#f08a7e` / `#f2c94c` | Diff and status colors |
| `--font` | system-ui stack | Body text (no web fonts) |
| `--mono` | ui-monospace stack | Code |

---

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