aboutsummaryrefslogtreecommitdiff
path: root/scripts/theme-selector/README.md
blob: ca708c51e0d6b619071d3905e86cce1c0c44fd8f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# theme-selector

A self-contained tool for designing Emacs color themes by eye. One generated
HTML page drives the whole theme: a palette, the syntax (font-lock /
tree-sitter) layer, the built-in UI faces with a live mock-frame preview, and
package-specific faces (org, magit, elfeed, plus every other installed package).
Reassign colors against the palette, judge legibility with live WCAG-contrast
readouts, then export a `theme.json` that a build step turns into
`themes/<name>-*.el`.

## Run

```bash
python3 generate.py          # writes theme-selector.html beside this script
```

Then open it in Chrome (Firefox had color-rendering flakiness during design):

```bash
WAYLAND_DISPLAY=wayland-1 google-chrome-stable theme-selector.html
```

During color work, disable Hyprland inactive-window dimming so colors read true:

```bash
hyprctl keyword decoration:dim_inactive false
```

## Files

- `generate.py` — emits the HTML+JS, and embeds the package data. Edit here to
  change layout or behavior.
- `samples.py` — the six language code samples and the default syntax
  category→color map (`COLS`). `generate.py` reads the part before the `cols=`
  marker.
- `package-inventory.json` — generated map of every installed package to the
  faces it defines (see Package faces below). A committed data artifact.
- `build-inventory.el` — refreshes `package-inventory.json` from a running
  Emacs.
- `theme-selector.html` — generated output. Regenerate; don't hand-edit.

## What it captures

Three tiers of faces, plus the palette:

- **Palette** — named colors. Add by hex or with the in-page color picker
  (saturation/value square, hue slider, palette reuse chips, live contrast
  readout, and an any / AA+ / AAA legibility mask). Remove, rename, reorder with
  arrows or drag. The colors serving as background and foreground are locked.
- **Syntax** — every font-lock / tree-sitter category (keyword, string,
  function, type, comment, and the rest), each with normal/bold/italic and a
  contrast rating. Click a category to flash its tokens in the code; click a
  token to flash its row.
- **UI faces** — cursor, region, mode-line, fringe, line numbers, isearch, paren
  match, link, error/warning/success, and the rest, foreground and background
  per face, shown in a live mock Emacs buffer.
- **Package faces** — per-package face tables with a live preview (below).

## Package faces

Pick an application from the dropdown to edit its faces. Each row has a
foreground and background dropdown, bold/italic toggles, an `inherit` dropdown
(base faces like `fixed-pitch`/`link` plus the app's own faces), a relative
height stepper, a contrast readout, and a per-face reset. There's a per-app
reset and a text filter for the large sets.

org-mode, magit, and elfeed have bespoke previews (a mock org document, a magit
status buffer, an elfeed search list). Every other installed package is reachable
too, with an editable table and a generic preview (each face name in its own
colors), so any package can be themed.

**Inheritance** is modeled, not flattened: a face's effective color is resolved
through its `inherit` chain and shown in the table and preview; setting an
explicit color overrides it. `height` is a float multiplier off the base font
and is read directly off the face (not cascaded through `inherit`, since Emacs
multiplies float heights along a chain). The base monospace family is *not* the
theme's job — it lives in `modules/font-config.el`; the tool owns only relative
size and the `fixed-pitch` inherit relationships.

### Refreshing the package inventory

The reachable packages come from `package-inventory.json`. Regenerate it from a
running Emacs (so it reflects what's actually installed), then rebuild the HTML:

```bash
emacsclient -e '(load "/home/cjennings/.emacs.d/scripts/theme-selector/build-inventory.el")'
python3 generate.py
```

`build-inventory.el` groups each face by the package whose file defines it; it
depends on the target Emacs having those packages loaded. Built-in faces are
skipped (they're covered by the syntax and UI tiers).

## theme.json contract

The export (and what a build step consumes):

```json
{
  "name": "dupre",
  "palette": [["#67809c", "blue"], ["#e8bd30", "gold"]],
  "assignments": {"kw": "#67809c", "str": "#5d9b86", "bg": "#000000", "p": "#ffffff"},
  "bold": ["kw", "fnd"],
  "italic": [],
  "ui": {"region": {"fg": null, "bg": "#264364"}, "cursor": {"fg": null, "bg": "#a9b2bb"}},
  "packages": {
    "org-mode": {
      "org-level-1": {"fg": "#67809c", "bg": null, "bold": true, "italic": false,
                      "inherit": null, "height": 1.3, "source": "default"}
    }
  }
}
```

- `assignments` maps syntax category keys to hexes; `bg` is the `default` face
  background, `p` the foreground.
- `ui` and `packages` faces carry `fg`/`bg` (hex or `null`), `bold`, `italic`,
  and for package faces `inherit` (a face name or `null`), `height` (a float,
  omitted at 1.0), and `source` (`"default"` seeded, `"user"` edited,
  `"cleared"`).
- The theme name is both the `name` field and the download filename. Import a
  `theme.json` to start from a prior theme; a file with no `packages` key still
  loads.

`export` always downloads a fresh file; `save` (shown once a name is entered)
writes the same file in place via the File System Access API.

## Build step — `build-theme.el`

`build-theme.el` converts a `theme.json` into a single self-contained
`themes/<name>-theme.el` deftheme. JSON in, valid Emacs faces out, across all
four tiers: `default` from `assignments.bg`/`.p`, the syntax categories mapped
to their font-lock / tree-sitter faces (with the `bold`/`italic` sets applied),
the UI faces passed through, and the package faces with `:inherit`/`:height`
and weight/slant written.

```bash
emacs --batch -l scripts/theme-selector/build-theme.el \
  --eval '(build-theme/convert-file "scripts/theme-selector/dupre-revised.json" "themes")'
```

Output is a flat generated deftheme, not the palette/faces/theme trio the
original dupre ships — a `theme.json` carries resolved per-face hex, not dupre's
semantic-mapping layer, so a flat deftheme is the faithful output and never
clobbers the curated dupre files.

One mapping limitation: the `dec` (decorator) syntax key has no independent
Emacs face. Emacs renders decorators with `font-lock-type-face`, which the `ty`
key already owns, so `dec` is omitted from the output and decorators follow the
type color (as they do in stock Emacs). Tests live in
`tests/test-build-theme.el` (Normal / Boundary / Error, plus a WCAG-contrast
assertion on the round-tripped result).