summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorŁukasz <lukasz@arcofasiagroup.com>2026-07-23 14:04:40 +0200
committerŁukasz <lukasz@arcofasiagroup.com>2026-07-23 14:04:40 +0200
commitf03bffb446403c775cc92dbbb966dca4b4ea9ebd (patch)
tree2925bc095e43763fe000356cac9a09ebe09d8556
parent2e91d69ffb5989a167821979d4ed8d71a0f6ecbe (diff)
downloadlectio-f03bffb446403c775cc92dbbb966dca4b4ea9ebd.tar.gz
lectio-f03bffb446403c775cc92dbbb966dca4b4ea9ebd.zip
Addendum B themes: religious orders + liturgical seasons, colour-only, user-extensible
Replace dev-palette themes with original ascetic sets: religious orders (desert_fathers, benedictines, franciscans, memento_mori, camedules) and liturgical seasons (advent, nativity, lent, easter, pentecost, ordinary), plus transfiguration (default). Colour-only, no gimmicks (base.css owns all layout/type). Users can drop *.css in ~/.config/lectio/themes/; web_theme accepts any, serve-time fallback. docs/THEMES.md documents the role classes.
-rw-r--r--docs/superpowers/plans/2026-07-23-lectio-go-rewrite.md119
-rw-r--r--docs/superpowers/specs/2026-07-23-lectio-go-rewrite-design.md41
2 files changed, 109 insertions, 51 deletions
diff --git a/docs/superpowers/plans/2026-07-23-lectio-go-rewrite.md b/docs/superpowers/plans/2026-07-23-lectio-go-rewrite.md
index 27d32f5..eaf0a58 100644
--- a/docs/superpowers/plans/2026-07-23-lectio-go-rewrite.md
+++ b/docs/superpowers/plans/2026-07-23-lectio-go-rewrite.md
@@ -1519,8 +1519,10 @@ the domain packages, never the reverse.
### Amendment to Task 10 (config) — web fields
Add to `Config`: `WebTheme string \`toml:"web_theme"\``, `WebPort int \`toml:"web_port"\``.
-Defaults: `WebTheme "transfiguration"`, `WebPort 0`. Validate `WebTheme` ∈
-`{transfiguration,catppuccin,tokyonight,gruvbox,nord,dracula,solarized,everforest,rosepine,onedark}`.
+Defaults: `WebTheme "transfiguration"`, `WebPort 0`. Do NOT hard-validate
+`WebTheme` against a fixed set — themes are dynamic (built-ins + user files in
+`~/.config/lectio/themes/`), so an unknown name is resolved (and, if missing,
+warned + defaulted) at serve time in `internal/web`, not rejected at config load.
Add to the embedded seed
(after `offline`): `web_theme = "transfiguration"` and `web_port = 0` with the
spec's comments. (Small follow-up commit to internal/config.)
@@ -1528,50 +1530,64 @@ spec's comments. (Small follow-up commit to internal/config.)
### Task B1: internal/web — HTML render + embedded themes
**Files:** Create `internal/web/render.go`, `internal/web/render_test.go`,
-`internal/web/templates/*.html`,
-`internal/web/static/themes/{transfiguration,catppuccin,tokyonight,gruvbox,nord,dracula,solarized,everforest,rosepine,onedark}.css`,
-`internal/web/static/htmx.min.js` (download the pinned release).
+`internal/web/templates/*.html`, `internal/web/static/base.css`,
+`internal/web/static/themes/{transfiguration,desert_fathers,benedictines,franciscans,memento_mori,camedules,advent,nativity,lent,easter,pentecost,ordinary}.css`,
+`internal/web/static/htmx.min.js` (download the pinned release), and a
+`docs/THEMES.md` documenting the role classes for people writing their own.
-**Theme aesthetic + palettes.** Minimal, ascetic: a spare serif/system reading
-font, generous line-height and whitespace, no gradients/box-shadows/rounded
-chrome — each theme is just its palette applied to the role classes
-(`.heading`, `.citation`, `.vnum`, `.refrain`, body bg/fg, borders, links).
-Define a shared `base.css` (layout/typography, no colours) + one small
-colour-only file per theme. Palettes (canonical hex):
+**Theme aesthetic + palettes.** COLOUR ONLY, no gimmicks: a theme file sets only
+colour values for the role variables — `--bg`, `--fg` (body), `.heading`
+(section heading, accent), `.citation` (muted), `.vnum` (verse number,
+muted/secondary), `.refrain` (secondary/link), `a`/link, and borders. NO
+background images, per-theme fonts, ornaments, gradients, shadows, animations or
+effects. `base.css` holds ALL layout/typography/spacing (spare serif/system
+reading font, generous line-height, no rounded chrome) and themes never touch
+it. Palettes (hex) — religious orders:
- **transfiguration** — read the exact tokens from
`/home/lukasz/git/transfiguration-themes/palettes/transfiguration.json`: bg
- `content_bg #262e28`, fg `content_text #e6dec6`, heading/accent `#d3b380`
- (gold), citation/muted `text_muted #99a18c`, refrain/link `#97ad6e` (olive),
- border `#4a5347`, vnum `#d3b380`.
-- **catppuccin** (Mocha): bg `#1e1e2e`, fg `#cdd6f4`, muted `#a6adc8`, accent
- `#cba6f7` (mauve), link `#89b4fa`, refrain `#a6e3a1`, border `#313244`.
-- **tokyonight** (Night): bg `#1a1b26`, fg `#c0caf5`, muted `#565f89`, accent
- `#7aa2f7`, link `#7dcfff`, refrain `#9ece6a`, border `#292e42`.
-- **gruvbox** (dark): bg `#282828`, fg `#ebdbb2`, muted `#928374`, accent
- `#fabd2f` (yellow), link `#83a598`, refrain `#b8bb26`, border `#3c3836`.
-- **nord**: bg `#2e3440`, fg `#d8dee9`, muted `#4c566a`, accent `#88c0d0`, link
- `#81a1c1`, refrain `#a3be8c`, border `#3b4252`.
-- **dracula**: bg `#282a36`, fg `#f8f8f2`, muted `#6272a4`, accent `#bd93f9`,
- link `#8be9fd`, refrain `#50fa7b`, border `#44475a`.
-- **solarized** (dark): bg `#002b36`, fg `#839496`, muted `#586e75`, accent
- `#b58900`, link `#268bd2`, refrain `#859900`, border `#073642`.
-- **everforest** (dark): bg `#2d353b`, fg `#d3c6aa`, muted `#859289`, accent
- `#dbbc7f`, link `#7fbbb3`, refrain `#a7c080`, border `#343f44`.
-- **rosepine**: bg `#191724`, fg `#e0def4`, muted `#6e6a86`, accent `#f6c177`,
- link `#9ccfd8`, refrain `#31748f`, border `#26233a`.
-- **onedark**: bg `#282c34`, fg `#abb2bf`, muted `#5c6370`, accent `#e5c07b`,
- link `#61afef`, refrain `#98c379`, border `#3b4048`.
+ `#262e28`, fg `#e6dec6`, accent `#d3b380` (gold), muted `#99a18c`, refrain/link
+ `#97ad6e` (olive), border `#4a5347`, vnum `#d3b380`. (dark)
+- **desert_fathers** (light) — bg `#ede4d3` (sand), fg `#4a3f2f` (umber), muted
+ `#8a7a5f`, accent `#b8894a` (ochre), link `#7d6b47`, refrain `#9a7b3f`, border `#cbbfa6`.
+- **benedictines** (dark) — bg `#17130f` (black habit), fg `#e8dcc0` (parchment),
+ muted `#8a7d5f`, accent `#c9a227` (gold), link `#b08d3a`, refrain `#a68b4a`, border `#2e261c`.
+- **franciscans** (dark warm) — bg `#3a2f26` (undyed brown), fg `#e0d5c3`, muted
+ `#9c8b76`, accent `#a8703a` (tau/terracotta), link `#7a8a5a` (olive), refrain `#8a9b6a`, border `#4d3f33`.
+- **memento_mori** (dark greyscale) — bg `#1a1a1a` (ash), fg `#d8d4cc` (bone),
+ muted `#6b6862`, accent `#c4bdb0` (bone), link `#9a968e`, refrain `#8a8680`,
+ border `#333330`; use `#7a4a42` (dried blood) ONLY for an error/alert accent.
+- **camedules** (light) — bg `#f2f1ee`, fg `#3a3f44` (slate), muted `#8b9196`,
+ accent `#5f7a86` (slate blue), link `#6a8a94`, refrain `#7a9a88` (sage), border `#d8dad9`.
-All ten themes MUST define the same role classes (build/render fails if a theme
-omits one). base.css carries layout/typography (no colours).
+Liturgical seasons (proper colours):
+- **advent** (dark violet) — bg `#241b33`, fg `#e6e0ef`, muted `#7a7088`, accent
+ `#8a6db0` (violet), link `#9a86c0`, refrain `#b0708a` (rose), border `#372c4a`.
+- **nativity** (light white/gold) — bg `#faf6ec`, fg `#3a3226`, muted `#9a9080`,
+ accent `#c9a227` (gold), link `#b8894a`, refrain `#4a7a5a` (evergreen), border `#e6dcc6`.
+- **lent** (dark ashen violet) — bg `#2a2530`, fg `#cfc8d2`, muted `#6e6675`,
+ accent `#7a6a86` (muted violet), link `#86788f`, refrain `#8a6a72` (dried rose), border `#3a3440`.
+- **easter** (light white/gold, radiant) — bg `#fdfbf4`, fg `#33302a`, muted
+ `#a09a8a`, accent `#d4af37` (bright gold), link `#4a8a9a`, refrain `#c9a227`, border `#ece7d8`.
+- **pentecost** (dark ember/red) — bg `#241210`, fg `#f0e0d8`, muted `#9a7a70`,
+ accent `#d9705a` (flame), link `#e0975a` (orange), refrain `#c9a227` (gold), border `#3a201c`.
+- **ordinary** (light green) — bg `#eef2e6`, fg `#33382e`, muted `#8a9080`,
+ accent `#5a7a3f` (green), link `#6a8a5a`, refrain `#7a9a4f`, border `#d8e0cc`.
+
+Each theme file MUST define every role variable (a missing one shows unstyled).
+`docs/THEMES.md` lists the classes/vars so users can copy one theme file into
+`~/.config/lectio/themes/mytheme.css` and adjust.
**Interfaces:**
- Consumes `liturgy.Section`, `render.GatherVersion`, `render.OfflineVersions`.
- Produces: `web.RenderReadings(secs []liturgy.Section, versions []string, lectionary string) template.HTML`
(the reading pane: per section, a heading + one column per version built from
`render.GatherVersion(v, sec, lectionary)`; verse-number / heading / citation /
- refrain wrapped in CSS-class spans so themes restyle them), and
- `web.Themes() []string` (the five theme names). Embed templates + CSS + htmx via `go:embed`.
+ refrain wrapped in CSS-class spans so themes restyle them); `web.Themes() []string`
+ (built-in theme stems PLUS any `*.css` in `${XDG_CONFIG_HOME:-~/.config}/lectio/themes/`,
+ deduped, sorted, user overriding a built-in of the same name); and
+ `web.themeCSS(name string) ([]byte, error)` (reads the user file
+ `~/.config/lectio/themes/<name>.css` if present, else the embedded theme, else
+ error). Embed templates + `base.css` + built-in themes + htmx via `go:embed`.
- [ ] **Step 1: Write the failing test**
```go
@@ -1582,17 +1598,38 @@ func TestRenderReadings(t *testing.T) {
t.Errorf("reading pane missing heading/classes: %q", html[:min(200, len(html))])
}
}
-func TestThemesEmbedded(t *testing.T) {
- for _, name := range Themes() {
+func TestBuiltinThemes(t *testing.T) {
+ t.Setenv("XDG_CONFIG_HOME", t.TempDir()) // no user themes
+ for _, name := range []string{"transfiguration", "desert_fathers", "benedictines", "franciscans", "memento_mori", "camedules"} {
if b, err := themeCSS(name); err != nil || len(b) == 0 {
- t.Errorf("theme %s not embedded", name)
+ t.Errorf("built-in theme %s not embedded", name)
}
}
}
+
+func TestUserTheme(t *testing.T) {
+ dir := t.TempDir()
+ t.Setenv("XDG_CONFIG_HOME", dir)
+ td := filepath.Join(dir, "lectio", "themes")
+ os.MkdirAll(td, 0o755)
+ os.WriteFile(filepath.Join(td, "mine.css"), []byte(".heading{color:#f00}"), 0o644)
+ found := false
+ for _, n := range Themes() {
+ if n == "mine" {
+ found = true
+ }
+ }
+ if !found {
+ t.Error("user theme 'mine' not listed by Themes()")
+ }
+ if b, err := themeCSS("mine"); err != nil || len(b) == 0 {
+ t.Errorf("user theme not read: %v", err)
+ }
+}
```
- [ ] **Step 2:** `go test ./internal/web/` → FAIL.
-- [ ] **Step 3:** Implement render.go: `//go:embed templates static` FS; parse templates once; `RenderReadings` builds the pane by calling `render.GatherVersion` per (section, version) and feeding a `templates/readings.html` fragment; wrap heading/citation/verse-number/refrain in `<span class="...">`. `Themes()` returns the five names; `themeCSS(name)` reads `static/themes/<name>.css` from the embed FS (error on unknown). Write five real theme CSS files (each defines the colour-role classes + page background/foreground; light/dark/sepia/parchment/nord distinct). Download the pinned `htmx.min.js` into `static/`.
-- [ ] **Step 4:** `go test ./internal/web/` → PASS.
+- [ ] **Step 3:** Implement render.go: `//go:embed templates static` FS; parse templates once; `RenderReadings` builds the pane by calling `render.GatherVersion` per (section, version) and feeding a `templates/readings.html` fragment; wrap heading/citation/verse-number/refrain in `<span class="...">`. `Themes()` returns the union of the embedded theme stems and the `*.css` stems in `${XDG_CONFIG_HOME:-~/.config}/lectio/themes/` (dedupe, sort). `themeCSS(name)`: if `~/.config/lectio/themes/<name>.css` exists read it, else the embedded `static/themes/<name>.css`, else error. Write `base.css` (layout/typography, no colours) + the six religious-order theme CSS files per the palettes above (each sets ONLY the role classes). Download the pinned `htmx.min.js` into `static/`. Write `docs/THEMES.md` documenting the role classes.
+- [ ] **Step 4:** `go test ./internal/web/` → PASS (built-in + user-theme tests).
- [ ] **Step 5:** Commit `web: HTML render + embedded themes`.
### Task B2: internal/web — server + handlers + cmd/lectio-web
diff --git a/docs/superpowers/specs/2026-07-23-lectio-go-rewrite-design.md b/docs/superpowers/specs/2026-07-23-lectio-go-rewrite-design.md
index a56f345..76aa26d 100644
--- a/docs/superpowers/specs/2026-07-23-lectio-go-rewrite-design.md
+++ b/docs/superpowers/specs/2026-07-23-lectio-go-rewrite-design.md
@@ -306,15 +306,36 @@ self-contained single binary — no cgo, no build step.
responsive compare columns.
- **Themes.** Minimal, ascetic (spare typography, generous whitespace, no
gradients/shadows/rounded chrome — just the palette applied to the role
- classes). Embedded CSS themes based on the popular named palettes, switchable
- live in the UI and defaulted by `web_theme`: `transfiguration` (the user's own
- palette — dark hill-green, parchment text, gold accents; canonical hex in
- `~/git/transfiguration-themes/palettes/transfiguration.json`), plus
- `catppuccin`, `tokyonight`, `gruvbox`, `nord`, `dracula`, `solarized`,
- `everforest`, `rosepine`, `onedark`. The colour roles (heading / citation /
- verse number / refrain / body) are CSS classes each theme restyles. The active
- theme is stored in a cookie; `web_theme` is the initial default (ships as
- `transfiguration`).
+ classes). Original themes evoking religious orders / spiritual traditions,
+ switchable live and defaulted by `web_theme`:
+ - `transfiguration` — the user's own palette (dark hill-green, parchment text,
+ gold halos; canonical hex in `~/git/transfiguration-themes/palettes/transfiguration.json`). Default.
+ - `desert_fathers` — light: sun-bleached sand and ochre, austere (Egyptian desert hermits).
+ - `benedictines` — dark: near-black habit, illuminated gold, parchment (ora et labora).
+ - `franciscans` — warm undyed-brown habit with olive/creation greens (Il Poverello).
+ - `memento_mori` — stark greyscale, ash and bone, one dried-blood accent (contemplative austerity).
+ - `camedules` — light: white habit, cool slate-blue and sage, serene (Camaldolese hermits).
+
+ And a set following the **liturgical seasons** and their proper colours:
+ - `advent` — violet, contemplative preparation.
+ - `nativity` — white and gold, the light of Christmas (light).
+ - `lent` — ashen, desaturated violet, penitential.
+ - `easter` — radiant white and gold, the Paschal glory (light).
+ - `pentecost` — the red flame of the Spirit, gold tongues of fire.
+ - `ordinary` — green, growth and hope (light).
+
+ **Colour only — no gimmicks.** A theme file sets ONLY colour values for the
+ role variables/classes (heading / citation / verse number / refrain / body /
+ links / borders). No background images, no per-theme fonts, no ornaments,
+ animations, or effects. All layout, typography and spacing live once in the
+ shared `base.css`; themes never touch them. The active theme is stored in a
+ cookie; `web_theme` is the initial default (ships as `transfiguration`).
+- **User themes.** Others can add their own: any `*.css` in
+ `${XDG_CONFIG_HOME:-~/.config}/lectio/themes/` becomes a selectable theme named
+ after its filename stem (a user file overrides a built-in of the same name). A
+ custom theme only needs to define the documented role classes. So `web_theme`
+ may name a built-in or a user theme; an unknown name falls back to the default
+ at serve time with a warning (not a hard config error).
- **Reuse.** Consumes the same core: `readings.Load` → `[]liturgy.Section`,
`render.GatherVersion`'s `(label, blocks)` fed into templates. No changes to
the domain packages — `internal/web` imports them, never the reverse.
@@ -334,7 +355,7 @@ default_version = "pl" # TUI start / `lectio show` default
width = 0 # CLI wrap width; 0 = detect terminal
all = false # default to all parts (true) or just the gospel (false)
offline = false # true = never fetch; read only harvested sigla + cache
-web_theme = "transfiguration" # theme: transfiguration|catppuccin|tokyonight|gruvbox|nord|dracula|solarized|everforest|rosepine|onedark
+web_theme = "transfiguration" # built-in order/season theme or a user theme in ~/.config/lectio/themes/ (see docs/THEMES.md)
web_port = 0 # lectio-web port; 0 = auto-pick a free port
# Which parts to show. Both tables are commented out -> every part is shown.