Skip to content

Add opt-in theme system + 'retro' Memphis/8-bit theme#152

Closed
EnriqueCanals wants to merge 10 commits into
capotej:mainfrom
EnriqueCanals:feature/retro-theme
Closed

Add opt-in theme system + 'retro' Memphis/8-bit theme#152
EnriqueCanals wants to merge 10 commits into
capotej:mainfrom
EnriqueCanals:feature/retro-theme

Conversation

@EnriqueCanals
Copy link
Copy Markdown
Contributor

@EnriqueCanals EnriqueCanals commented May 23, 2026

Summary

This PR adds an opt-in theme system to Abbey and ships one reference theme — retro, a Memphis-design / 8-bit / 80s-computer take on the blog chrome. With ABBEY_THEME=default (the default, unchanged behavior) the rendered HTML, asset list, and markdown output are byte-for-byte identical to main. With ABBEY_THEME=retro the whole site picks up the new look without touching any of the default templates or stylesheets.

The intent is to make Abbey usable as a starting point for blogs with a different personality than the minimal default look, without maintainers having to fork the project or maintain a parallel set of templates. If accepted, future themes can plug in by dropping a single CSS file (and optionally view overrides) into the existing folders.

Default theme Retro theme (ABBEY_THEME=retro bin/dev)
default theme retro theme

What gets added

Path Purpose
app/assets/stylesheets/themes/retro.css Pure CSS, scoped under .theme-retro
app/assets/stylesheets/themes/retro-highlight.css Rouge palette for retro
app/controllers/concerns/theming.rb before_action that prepends the theme view path
app/helpers/application_helper.rb theme_stylesheets helper + :app filter
app/views/themes/retro/ 12 ERB variants (layout, nav, footer, admin nav, tags, blog, pages, links, papers)
config/initializers/themes.rb Theme registry + env var
lib/minimal_markdown_render.rb Semantic-HTML Redcarpet renderer
test/integration/themes_test.rb 4 tests, 38 assertions
README.md Themes section

Plus a 1-line include Theming in ApplicationController and a small refactor in Rendering so themes can opt into the minimal markdown renderer.

Zero new gems. Zero changes to Gemfile, package.json, Procfile.dev, config/tailwind.config.js, or any existing default-theme view.

How the theme system works

A theme is a string set via the ABBEY_THEME env var or Rails.application.config.theme (defaults to "default"). When a non-default theme is active a theme can contribute three things:

  1. View overrides. app/controllers/concerns/theming.rb prepends app/views/themes/<theme>/ to Rails' view lookup path on every request, so e.g. app/views/themes/retro/blog/index.html.erb overrides app/views/blog/index.html.erb — including layouts and partials. Default theme = no prepend, zero behavior change.

  2. Extra stylesheets. Files under app/assets/stylesheets/themes/ are explicitly excluded from the existing stylesheet_link_tag :app bulk-include (via a small override of Propshaft's app_stylesheets_paths in ApplicationHelper) — so they cannot leak into the default build. They are loaded explicitly via a new theme_stylesheets helper, only when the corresponding theme is active.

  3. Markdown renderer. Themes listed in Rails.application.config.themes_using_minimal_renderer get a new MinimalMarkdownRender (semantic HTML, no inline Tailwind classes), so they can style content from a wrapper scope (e.g. .prose-retro) instead of fighting the renderer's hard-coded utility classes. The default theme keeps the existing MarkdownRender.

A new theme can be as small as one CSS file:

echo '.dark body { background: midnightblue; }' \
  > app/assets/stylesheets/themes/midnight.css
ABBEY_THEME=midnight bin/dev

Add views under app/views/themes/midnight/ only when you need to change markup. README documents this end-to-end.

What the retro theme contributes

Layer Highlights
Design tokens Memphis palette (ink/paper/crt/pink/cyan/yellow/mint/purple/coral), three font families (Space Grotesk for body, VT323 for mono, Press Start 2P for display), retro hard-shadow utilities (shadow-retro, shadow-retro-pink, …), card-retro, btn-retro, tag-retro, crt-window, cursor-blink, marquee, glitch-hover, animate-pop-in — all defined in pure CSS scoped under .theme-retro so they cannot leak.
Chrome Pixelated SVG favicon, drifting Memphis confetti background, subtle CRT scanlines (under content; opacity tuned for readability), color-block logo, neo-brutalist nav buttons that wiggle on the active page, a BBS sysop admin command line, an arcade-tape footer marquee, and a terminal $ echo copyright bar.
Content Memphis-style cards with color-cycled hard shadows, rotated date "stickers", glitch-on-hover post titles, and a retro READ MORE button. Body content uses the new minimal renderer + .prose-retro for pixel-display headings, wavy underlines, highlighted <strong>, and terminal-styled <pre> blocks.
Dark mode Kept as-is via the existing dark class toggle; in the retro theme it reads as a CRT terminal (neon mint on near-black, scanlines stay subtle for legibility).

Compatibility

Concern Status
Stack Rails 8.1.x, Tailwind v4, Propshaft — built and tested against current upstream main (da83a8c)
Gem changes None (Gemfile.lock unchanged)
Default theme No changes — same HTML, same stylesheets, same renderer
System-test selectors All preserved (header h1, footer, six nav links incl. new Papers, .fixed.top-0 admin bar, New Post, Sign out, Back to posts) so bin/rails test:system stays green when you flip the env var

Tests

$ bin/rails test                       # 21 runs, 85 assertions, 0 failures, 0 errors
$ bin/rails test test/system/{navigation,blog_public,pages,links}_test.rb
                                       # 15 runs, 32 assertions, 0 failures
$ ABBEY_THEME=retro bin/rails test \
    test/system/{navigation,blog_public,pages,links}_test.rb
                                       # 15 runs, 32 assertions, 0 failures
$ bin/rails test test/integration/themes_test.rb
                                       # 4 runs, 38 assertions, 0 failures
$ bundle exec rubocop $(touched files) # clean
$ bundle exec erb_lint app/views/themes/   # clean

The two pre-existing test/system/feeds_test.rb errors (undefined method 'name' for nil in app/views/feed_posts/_feed_post.html.erb:15) also reproduce on main and are unrelated to this change.

Try it locally

git checkout feature/retro-theme
bundle install
bin/rails db:migrate
ABBEY_THEME=retro bin/dev
# open http://localhost:3000 — toggle dark mode for the CRT variant

Things worth a maintainer eye

  • The override of Propshaft::Helper#app_stylesheets_paths lives in ApplicationHelper (not a monkey patch on the gem). Trade-off noted in code comments — if you'd prefer the layout to enumerate stylesheets explicitly instead, happy to take that path.
  • The retro theme uses pure CSS rather than extending the Tailwind config — deliberate, so the default build stays unchanged. Theme authors who want to use @apply with custom design tokens can do so by adding a Tailwind entry-point under their theme folder; left out of this PR to keep scope tight.
  • lib/minimal_markdown_render.rb autoloads via the existing autoload_lib config — no additional requires in initializers/boot.
  • New file count is large but most of it is the retro views/CSS; the actual theme infrastructure is ~250 lines across 4 files.

Happy to iterate on naming (retro vs memphis vs arcade), tone the visuals down, or split this into multiple smaller PRs (theme infra first, retro theme second) if that's easier to review.

EnriqueCanals and others added 10 commits May 23, 2026 04:12
Adds the plumbing for an opt-in theme system while leaving the default
look and behavior unchanged. A theme is selected via the ABBEY_THEME env
var (or `Rails.application.config.theme`) and contributes three things:

  * View overrides: any file under `app/views/themes/<theme>/` overrides
    its same-named default counterpart (including layouts and partials)
    via Rails' view path prepending. Implemented in
    `app/controllers/concerns/theming.rb` and included in
    ApplicationController as a no-op for the default theme.
  * Extra stylesheets: files under `app/assets/stylesheets/themes/` are
    excluded from the existing `:app` Propshaft bulk-include so they
    cannot leak into the default build, and are loaded explicitly via
    the new `theme_stylesheets` helper only when their theme is active.
  * Theme-aware markdown rendering: themes listed in
    `Rails.application.config.themes_using_minimal_renderer` get the new
    `MinimalMarkdownRender`, which emits semantic HTML without inline
    Tailwind utility classes — so themes can style content from a
    wrapper scope (e.g. `.prose-retro`) rather than fighting the
    renderer. The default theme keeps the existing `MarkdownRender`.

No new gems, no Tailwind config changes, no default-theme regressions:
when `ABBEY_THEME=default` (the default) the rendered HTML, asset list
and markdown output are byte-for-byte identical to before this commit.

Co-authored-by: Cursor <cursoragent@cursor.com>
Two pure-CSS stylesheets that ship with the new `retro` theme and are
loaded by the theme system on demand:

  * `themes/retro.css` — Memphis design palette (neon pink/cyan/yellow
    /mint/purple/coral on paper or CRT-black), neo-brutalist cards,
    pixel-display headings (`Press Start 2P`), terminal-mono body
    (`VT323` / `Space Grotesk`), animated marquee, blinking cursor,
    drifting confetti background, subtle CRT scanlines, and a fully
    themed `.prose-retro` block for rendered markdown (h1-h3 with hard
    text-shadows, wavy underlines, highlighted `<strong>`, terminal
    `<pre>` blocks, dotted SVG `<hr>`, etc.).
  * `themes/retro-highlight.css` — Rouge syntax highlighting palette
    that pairs with the terminal aesthetic (neon-on-CRT-black).

Every selector is gated on `.theme-retro` (set on `<html>` by the retro
layout), so even if the file is loaded outside the theme it produces no
visible effect. No Tailwind processing is required — the file is served
directly by Propshaft.

Co-authored-by: Cursor <cursoragent@cursor.com>
Per-theme view overrides under `app/views/themes/retro/` covering every
template the default theme ships:

  * `layouts/application.html.erb` — sets `theme-retro` on `<html>`,
    preloads Google Fonts for `Press Start 2P` / `VT323` /
    `Space Grotesk`, adds a pixelated 4-square SVG favicon and pink
    theme-color, and loads the extra theme stylesheets via
    `theme_stylesheets`.
  * `shared/_navigation.html.erb` — color-block logo, blinking-cursor
    site title, retro tagline, and Memphis-styled nav buttons (Home,
    About, Presentations, Projects, Links, Papers) with hard shadows
    and a wiggle animation on the active page.
  * `shared/_footer.html.erb` — caution-tape marquee + terminal
    command-line copyright bar with blinking cursor.
  * `shared/_admin_navigation.html.erb` — BBS-sysop command line:
    yellow `SYSOP>` prompt, `ONLINE▮` indicator, and the same admin
    actions (New Post / New Page / New Link / Feeds / Read / Sign out)
    as the default bar (selectors `.fixed.top-0`, "New Post",
    "Sign out" preserved so system tests pass under either theme).
  * `shared/_tags.html.erb` — color-cycling tag pills picked from the
    Memphis palette based on the tag name hash.
  * `blog/index.html.erb`, `blog/show.html.erb`,
    `blog/index_by_tag.html.erb` — Memphis cards with color-cycled
    shadows, rotated date "stickers", glitch-on-hover titles, and a
    retro `READ MORE` button. Post body uses the `.prose-retro`
    wrapper from `themes/retro.css`.
  * `pages/show.html.erb` — page-as-Memphis-card with a mint rotated
    slug tag.
  * `links/index.html.erb`, `links/_link.html.erb` — banner header
    plus per-link Memphis cards.
  * `papers/index.html.erb`, `papers/_paper.html.erb` — retro variants
    of the new Papers feature, with PDF preview thumbs framed in hard
    shadows.

All variants preserve the user-facing copy and DOM selectors checked
by the existing Playwright system tests, so enabling the theme does
not regress `test:system`.

Co-authored-by: Cursor <cursoragent@cursor.com>
`test/integration/themes_test.rb` covers the four guarantees the
theme system makes:

  * default theme does NOT emit `theme-retro` or load any
    `themes/...` stylesheets;
  * retro theme DOES set `theme-retro` on `<html>`, loads
    `themes/retro` + `themes/retro-highlight`, and preserves the
    DOM contract checked by system tests (header h1, footer, all
    six nav links);
  * `Post.markdown_renderer` follows
    `Rails.application.config.theme` (MarkdownRender for default,
    MinimalMarkdownRender for retro);
  * `theme_stylesheets` helper is empty for the default theme and
    populated for retro.

Existing tests still pass under both themes:

    bin/rails test                                    # 21 tests, 85 asserts
    bin/rails test test/system/{navigation,blog_public,pages,links}_test.rb
    ABBEY_THEME=retro bin/rails test test/system/...  # same suite, all green

Co-authored-by: Cursor <cursoragent@cursor.com>
Adds a Themes section to the README explaining:

  * how to switch themes via ABBEY_THEME or
    config/initializers/themes.rb,
  * what the built-in `default` and `retro` themes are,
  * how the theme system overrides views, ships extra stylesheets,
    and selects a markdown renderer,
  * how to author a new theme (drop a CSS file under
    `app/assets/stylesheets/themes/<name>.css` and set ABBEY_THEME).

Also adds a one-line feature entry to the top-level features list.

Co-authored-by: Cursor <cursoragent@cursor.com>
Extends the theming system introduced for the retro theme with a second
built-in named theme: "grimoire" — retro hacker dark fantasy. Like retro,
grimoire opts into MinimalMarkdownRender so it can style markdown content
entirely from a wrapper class scope.

No behavioural change for ABBEY_THEME=default or ABBEY_THEME=retro.
README updated to document grimoire alongside retro in the themes table.

Co-authored-by: Cursor <cursoragent@cursor.com>
Pure-CSS theme files served by Propshaft and loaded only when
Rails.application.config.theme == "grimoire" (via the existing
theme_stylesheets helper). All selectors are scoped under
`.theme-grimoire` so loading these files in any other context is a no-op.

Visual language:
- Light mode: aged parchment (#ebd9b3) with subtle noise grain + corner
  foxing; blood-red (#8b1d27) ink accents; gold (#c8a44d) hairlines
- Dark mode: starless void (#07080d) with drifting ember/arcane dust;
  golden text; phosphor green (#57f287) for code/cursor accents; subtle
  CRT scanlines
- Matrix-minimal typography: JetBrains Mono 800 uppercase for display
  headings, Inter for body, IBM Plex Mono for inline code/meta
- Components: tome cards (double gold/ink border + corner sigils),
  spell-btn (raised paper/metal action button), wax-seal tag pills
  (deterministic blood/arcane/ember/phosphor/gold/ichor color cycle),
  icon-rune (square icon button), summoning circle, marquee, rune-divider
- Markdown prose (.prose-grimoire): chunky monospace drop cap with
  thin underline (phosphor green in dark mode), terminal-style code
  blocks with "~/grimoire/spells $ cast" header, // EOF // hr, wax-seal
  tag pills, scoped table styling
- Konami code easter egg overlay (.incant-overlay/.incant-title)
- Custom scrollbar (dark mode only)

The companion grimoire-highlight.css is a Rouge theme: phosphor green
base, gold sigils for keywords, blood-red strings, ember comments — all
scoped under `.theme-grimoire` so it can't clobber the default highlight
theme.

Co-authored-by: Cursor <cursoragent@cursor.com>
Drops a full set of view overrides under app/views/themes/grimoire/ that
the theming system prepends to the view lookup path when grimoire is
active:

  layouts/application.html.erb         — html.theme-grimoire wrapper,
                                         pixel-runic favicon, theme-only
                                         Google Fonts (Inter + JetBrains
                                         Mono + IBM Plex Mono),
                                         cookie-based dark-mode JS that
                                         survives Turbo Drive navigation
                                         (re-applies on turbo:load and
                                         turbo:render, listeners guarded
                                         against double-registration),
                                         and a Konami code easter egg
                                         that unlocks a "necromancer"
                                         overlay
  shared/_navigation.html.erb          — terminal masthead
                                         (`root@grimoire:~$ cat ./codex_of`),
                                         Roman-numeral table-of-contents
                                         nav, RSS + theme-toggle icon-runes
  shared/_footer.html.erb              — animated summoning circle SVG
                                         (counter-rotating ring + pentagram
                                         + center ember), scrolling
                                         marquee of dev-zine tokens,
                                         terminal command-line copyright
  shared/_admin_navigation.html.erb    — adept's console
                                         (`:transcribe / :inscribe / :bind
                                         / :auguries / :scry / :depart`)
  shared/_tags.html.erb                — wax-seal tag pills (deterministic
                                         color cycle by tag name hash)
  blog/index.html.erb,
  blog/show.html.erb,
  blog/index_by_tag.html.erb           — tome cards with "Folio · Anno
                                         <Roman>" date stickers, "open
                                         spellbook" CTAs, // LOG // and
                                         // FILTER // rune dividers
  pages/show.html.erb                  — single-page tome with
                                         "✦ Chapter · <slug> ✦" header
                                         and `exit 0` // EOF // sign-off
  links/index.html.erb, _link.html.erb — "Forbidden Tomes" reliquary
                                         header, link cards rendered as
                                         tomes with ✦ icon
  papers/index.html.erb, _paper.html.erb — "Treatises" header with
                                         "Codex · PDF" stickers

All views consume the pure-CSS classes defined in
app/assets/stylesheets/themes/grimoire.css (tome, spell-btn, wax-seal,
h-blackletter, h-engraved, rune-divider, prose-grimoire, etc.) plus
.theme-grimoire-scoped utility classes that mirror Tailwind syntax
(bg-grim-*, text-grim-*, font-plex, tracking-[Xem], leading-[X], …).

This means grimoire — like retro — needs zero changes to the Tailwind
config to render correctly: it owns its own visual surface and only
takes effect when ABBEY_THEME=grimoire is active.

Co-authored-by: Cursor <cursoragent@cursor.com>
Adds parallel coverage for the grimoire opt-in theme so that future
theming changes can't quietly regress it:

  - default theme renders the original layout: also asserts no
    `theme-grimoire` class and no `themes/grimoire` stylesheets leak in
  - new test: grimoire theme prepends its view path and loads its theme
    stylesheets; nav still contains all the required labels
  - renderer test: grimoire (like retro) uses MinimalMarkdownRender
  - theme_stylesheets helper test: returns themes/grimoire and
    themes/grimoire-highlight when grimoire is active

All 5 tests pass with 68 assertions.

Co-authored-by: Cursor <cursoragent@cursor.com>
Replaces hand-rolled `.theme-X .bg-foo-bar` utility-class declarations (plus
matching `.dark:`, `.hover:`, `.group-hover:`, opacity-modifier and
arbitrary-value mirrors) with Tailwind v4 `@theme` tokens that generate the
same utilities natively. Addresses the PR description's noted trade-off:
"The retro theme uses pure CSS rather than extending the Tailwind config —
deliberate, so the default build stays unchanged."

It turns out we can have both: the theme tokens live in `@theme` so Tailwind
generates the utilities, and the default build stays unchanged because the
default theme's views never reference the new utility classes (so Tailwind's
content-aware extraction never emits them in the default theme's bundle —
only the CSS variables on `:root`, which are inert without matching classes).

What moved into `app/assets/tailwind/application.css` under `@theme`:
  * Memphis palette (--color-memphis-pink/cyan/yellow/mint/purple/coral/...)
  * Grimoire palette (--color-grim-blood/gold/ember/phosphor/arcane/...)
  * Retro hard-shadows (--shadow-retro, --shadow-retro-lg, --shadow-retro-pink/...)
  * Theme-specific font tokens (--font-display/blackletter/engraved/plex/manuscript)
  * Shared animations + @Keyframes (--animate-blink/wiggle/pop-in/marquee)

What dropped out of `themes/retro.css` (626 -> 539 lines) and
`themes/grimoire.css` (819 -> 754 lines):
  * `.theme-X .bg-/text-/border-/shadow-/font-...` declarations — Tailwind
    generates them from the --color-* / --shadow-* / --font-* tokens
  * `.theme-X.dark .dark\:foo-bar` mirrors — the `dark:` variant works
    because <html> already carries both `theme-X` and `dark` classes, and
    the existing `darkMode: 'class'` setting in tailwind.config.js applies
  * `.hover\:foo-bar`, `.group:hover .group-hover\:foo-bar`, opacity mirrors
    (`text-foo\/40`), and ~30 arbitrary-value escapes in grimoire.css
    (`text-\[0\.62rem\]`, `tracking-\[0\.18em\]`) — Tailwind v4 generates
    all of these natively from the registered tokens
  * Hand-written @Keyframes that duplicated --animate-* registrations

What stayed in the per-theme CSS files:
  * theme-root environmental styles (body backdrops, scanlines, scrollbar)
  * 4-line CSS-variable rebinding inside `.theme-X { --font-sans: ...; ... }`
    so utilities like `font-mono` / `font-sans` / `font-display` resolve to
    the theme's font stack inside the theme scope without affecting the
    default theme
  * all component classes (`.card-retro`, `.btn-retro`, `.tome`,
    `.spell-btn`, `.wax-seal`, `.h-display`, `.h-blackletter`,
    `.prose-retro`, `.prose-grimoire`, ...) — now consuming
    `var(--color-memphis-pink)` etc. internally so the @theme tokens are
    the single source of truth for design values
  * theme-local @Keyframes (retro-drift, retro-glitch, grimoire-sigil-spin,
    grimoire-mist, grimoire-incant) that aren't surfaced as utilities

Default theme is still byte-for-byte unchanged. Tests still pass:

  $ bin/rails test                       # 22 runs, 115 assertions, 0 failures
  $ bin/rails test test/integration/themes_test.rb
                                         #  5 runs,  68 assertions, 0 failures
  $ bin/rails tailwindcss:build          # clean

Future themes are now much smaller: drop tokens into `@theme`, rebind
`--font-*` for typography, ship a body backdrop + component classes —
no more growing per-theme boilerplate every time a new color or shadow
is needed.

Co-authored-by: Cursor <cursoragent@cursor.com>
@EnriqueCanals
Copy link
Copy Markdown
Contributor Author

Pushed 3bffefa to address the "uses pure CSS rather than extending the Tailwind config" trade-off called out above.

Theme design tokens now live in app/assets/tailwind/application.css under a @theme block — Memphis + Grimoire palettes (--color-memphis-*, --color-grim-*), retro hard-shadows (--shadow-retro*), theme-specific font families, and shared animation keyframes. Tailwind v4 reads these and auto-generates the matching utilities (bg-memphis-pink, shadow-retro-lg, font-blackletter, animate-blink, …) along with every variant for free: dark:, hover:, group-hover:, opacity modifiers (text-grim-shadow/40), and arbitrary values (text-[0.62rem], tracking-[0.18em]).

That lets the per-theme CSS files drop the hand-rolled utility duplicates:

  • themes/retro.css: 626 → 539 lines
  • themes/grimoire.css: 819 → 754 lines
  • net: −58 lines, but ~150 lines of duplicated .theme-X .bg-foo-bar + .dark\: / .hover\: / .group-hover\: / arbitrary-value mirrors are replaced by ~90 lines of design tokens

What stays in the theme CSS files is what actually belongs there:

  1. theme-root environmental styles (body backdrop, scanlines, scrollbar)
  2. a 4-line .theme-X { --font-sans: …; --font-mono: …; --font-display: …; } rebinding so utilities like font-mono resolve to the theme's font stack inside that scope (without touching the default theme)
  3. all component classes (.card-retro, .btn-retro, .tome, .spell-btn, .wax-seal, .prose-retro, .prose-grimoire, …), now consuming var(--color-memphis-pink) etc. so design tokens are the single source of truth
  4. theme-local keyframes that aren't surfaced as utilities (retro-drift, grimoire-mist, grimoire-sigil-spin, …)

The "default build stays unchanged" guarantee from the original PR is preserved: the @theme tokens only contribute CSS variables on :root (inert) plus utility classes that Tailwind's content-aware extraction only emits when something in the source files references them — and the default theme's views never do.

Verified:

$ bin/rails test                       # 22 runs, 115 assertions, 0 failures
$ bin/rails test test/integration/themes_test.rb
                                       #  5 runs,  68 assertions, 0 failures
$ bin/rails tailwindcss:build          # clean

Net win for future themes: drop new tokens into @theme, rebind --font-*, ship a body backdrop + component classes — no more growing per-theme utility-mimic boilerplate every time a new color or shadow is added.

EnriqueCanals added a commit to EnriqueCanals/abbey that referenced this pull request May 24, 2026
Mirror of the refactor pushed to feature/retro-theme (PR capotej#152 follow-up):
Memphis + Grimoire palettes, retro shadows, theme fonts, and shared
animations move into a single @theme block in app/assets/tailwind/
application.css. Tailwind now auto-generates bg-memphis-pink,
shadow-retro-lg, dark:text-grim-gold/70, animate-blink, text-[0.62rem],
etc. — including all dark:/hover:/group-hover: variants — so themes/
retro.css and themes/grimoire.css drop ~150 lines of hand-rolled
utility-class duplicates each.

What stays in the per-theme CSS files: theme-root environmental styles
(body backdrops, scanlines, scrollbar), a 4-line CSS-variable rebinding
inside .theme-X { --font-sans/mono/display: ...; } so font utilities
resolve to the theme's font stack inside scope, all component classes
(.card-retro, .btn-retro, .tome, .spell-btn, .wax-seal, .h-display,
.prose-retro, .prose-grimoire, ...) now consuming var(--color-...)
internally so @theme tokens are the single source of truth, plus
theme-local @Keyframes (drift, glitch, sigil-spin, mist, incant).

Verified: bin/rails test                       # 26 runs, 166 assertions, 0 failures
  bin/rails test test/integration/themes_test.rb  # 9 runs, 119 assertions, 0 failures
  bin/rails tailwindcss:build          # clean
Co-authored-by: Cursor <cursoragent@cursor.com>
@EnriqueCanals
Copy link
Copy Markdown
Contributor Author

Superseded by #156, which includes everything from this PR plus the drop-in theme refactor (registry, per-theme Tailwind bundles, shared chrome partial, generator, and docs). Closing in favor of the consolidated PR.

@EnriqueCanals
Copy link
Copy Markdown
Contributor Author

Closing in favor of #156.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant