Skip to content
Closed
71 changes: 71 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Minimal blog using Rails 8, designed to be easily [self-hosted on AWS](https://g
* Markdown and Code Highlighting
* [Link Blog](https://capotej.com/links)
* Drag and Drop image uploads for Pages and Posts
* Themable (default minimal look + optional `retro` Memphis/8-bit theme — see [Themes](#themes))

# Getting Started

Expand Down Expand Up @@ -56,6 +57,76 @@ This will scan the given path for files ending in `.markdown` and create a seed

**Note: This will delete everything in the local database and re-seed using `db/seeds/*`.**

# Themes

Abbey ships with an opt-in theme system. The default theme keeps the existing
minimal look. Built-in alternatives currently include:

| Theme | Description |
|-----------|-------------|
| `default` | Original minimal Abbey look (no behavioural change). |
| `retro` | Memphis-style / 8-bit / 80s computer chrome — neo-brutalist cards, CRT scanlines, terminal code blocks, pixel-display headings. |
| `grimoire` | Retro hacker dark fantasy — Matrix-minimal monospace, parchment + void palette with phosphor/ember/gold accents, tome cards, wax-seal tags, an animated summoning circle, and a Konami-code easter egg. |

## Switching themes

Set the `ABBEY_THEME` environment variable before booting the app:

$ ABBEY_THEME=retro bin/dev

Or hardcode it in `config/initializers/themes.rb`:

```ruby
Rails.application.config.theme = "retro"
```

When the active theme is `default`, the app behaves identically to before —
no extra assets are loaded, the default Tailwind build is unchanged, and the
existing markdown renderer is used.

## How themes work

A theme can override any view by placing a same-named file under
`app/views/themes/<theme>/`. Rails' view lookup is augmented at request time
(see `app/controllers/concerns/theming.rb`) so that:

app/views/themes/retro/blog/index.html.erb

wins over the default

app/views/blog/index.html.erb

whenever `Rails.application.config.theme == "retro"`. The same is true for
the `layouts/application.html.erb`, every partial under `shared/`, and the
page/links/papers views.

A theme can also ship its own stylesheets under
`app/assets/stylesheets/themes/<theme>.css` and, optionally,
`themes/<theme>-highlight.css`. These are loaded automatically by the
default layout via the `theme_stylesheets` helper (in
`app/helpers/application_helper.rb`) when the theme is active.

Finally, a theme can request the simpler `MinimalMarkdownRender` (semantic
HTML output, no inline Tailwind utility classes) by adding itself to
`Rails.application.config.themes_using_minimal_renderer` in
`config/initializers/themes.rb`. This lets the theme style markdown content
entirely from a wrapper class (e.g. `.prose-retro`) rather than fighting
the renderer's hard-coded utility classes.

## Authoring a new theme

The simplest theme is just a CSS file:

# add a stylesheet under
app/assets/stylesheets/themes/sunset.css

# set the theme:
ABBEY_THEME=sunset bin/dev

The theme will be loaded after the default stylesheet, so it can override
anything cosmetic without touching the rest of the app. Add views under
`app/views/themes/sunset/` only when you need to change markup.

# Deploying to AWS

## Assumptions
Expand Down
114 changes: 114 additions & 0 deletions app/assets/stylesheets/themes/grimoire-highlight.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/* =============================================================================
Abbey – Grimoire Theme Syntax Highlighting
------------------------------------------------------------------
The wizard's terminal: phosphor green base, gold sigils for keywords,
blood-red for strings (literal incantations), ember for comments
(footnotes from the necromancer).

All Rouge classes are scoped under `.theme-grimoire` so this file is a
no-op when another theme (or the default theme) is active.
=============================================================================*/

.theme-grimoire pre.highlight,
.theme-grimoire .highlight pre {
background: #07080d !important;
color: #57f287;
}

/* Comments — the necromancer's marginalia */
.theme-grimoire .highlight .c,
.theme-grimoire .highlight .ch,
.theme-grimoire .highlight .cd,
.theme-grimoire .highlight .cm,
.theme-grimoire .highlight .cpf,
.theme-grimoire .highlight .c1,
.theme-grimoire .highlight .cs { color: #f08029; font-style: italic; opacity: 0.85; }
.theme-grimoire .highlight .cp { color: #f08029; font-weight: 700; }

/* Errors — broken sigils */
.theme-grimoire .highlight .err { color: #ff5a5f; background: rgba(255,90,95,0.12); }

/* Keywords — true names */
.theme-grimoire .highlight .k,
.theme-grimoire .highlight .kc,
.theme-grimoire .highlight .kd,
.theme-grimoire .highlight .kn,
.theme-grimoire .highlight .kp,
.theme-grimoire .highlight .kr,
.theme-grimoire .highlight .kv { color: #c8a44d; font-weight: 700; }
.theme-grimoire .highlight .kt { color: #c8a44d; }

/* Operators / punctuation */
.theme-grimoire .highlight .o,
.theme-grimoire .highlight .ow { color: #c8a44d; }
.theme-grimoire .highlight .p { color: #e9e0c8; }

/* Names */
.theme-grimoire .highlight .n { color: #e9e0c8; }
.theme-grimoire .highlight .na { color: #c8a44d; }
.theme-grimoire .highlight .nb { color: #c8a44d; }
.theme-grimoire .highlight .nc { color: #f08029; font-weight: 700; }
.theme-grimoire .highlight .no { color: #c8a44d; }
.theme-grimoire .highlight .nd { color: #f08029; }
.theme-grimoire .highlight .ne { color: #ff5a5f; font-weight: 700; }
.theme-grimoire .highlight .nf { color: #57f287; font-weight: 700; }
.theme-grimoire .highlight .nl { color: #c8a44d; }
.theme-grimoire .highlight .nn { color: #e9e0c8; }
.theme-grimoire .highlight .py { color: #c8a44d; }
.theme-grimoire .highlight .nt { color: #f08029; font-weight: 700; }
.theme-grimoire .highlight .nv,
.theme-grimoire .highlight .vc,
.theme-grimoire .highlight .vg,
.theme-grimoire .highlight .vi { color: #c8a44d; }

/* Literals */
.theme-grimoire .highlight .l { color: #57f287; }
.theme-grimoire .highlight .ld { color: #57f287; }

/* Strings — literal incantations */
.theme-grimoire .highlight .s,
.theme-grimoire .highlight .sb,
.theme-grimoire .highlight .sc,
.theme-grimoire .highlight .dl,
.theme-grimoire .highlight .sd,
.theme-grimoire .highlight .s2,
.theme-grimoire .highlight .se,
.theme-grimoire .highlight .sh,
.theme-grimoire .highlight .si,
.theme-grimoire .highlight .sx,
.theme-grimoire .highlight .sr,
.theme-grimoire .highlight .s1,
.theme-grimoire .highlight .ss { color: #ff8d97; }

/* Numbers */
.theme-grimoire .highlight .m,
.theme-grimoire .highlight .mb,
.theme-grimoire .highlight .mf,
.theme-grimoire .highlight .mh,
.theme-grimoire .highlight .mi,
.theme-grimoire .highlight .il,
.theme-grimoire .highlight .mo,
.theme-grimoire .highlight .mx { color: #f08029; }

/* Diff */
.theme-grimoire .highlight .gd { color: #ff5a5f; background: rgba(255,90,95,0.10); }
.theme-grimoire .highlight .gi { color: #57f287; background: rgba(87,242,135,0.10); }

/* Generic */
.theme-grimoire .highlight .ge { font-style: italic; }
.theme-grimoire .highlight .gh { color: #c8a44d; font-weight: 700; }
.theme-grimoire .highlight .gs { font-weight: 700; }
.theme-grimoire .highlight .gu { color: #f08029; font-weight: 700; }
.theme-grimoire .highlight .gp { color: #c8a44d; }
.theme-grimoire .highlight .gt { color: #ff5a5f; }
.theme-grimoire .highlight .gl { color: #e9e0c8; }

/* Line numbers */
.theme-grimoire .highlight .lineno,
.theme-grimoire .highlight .gh.filename {
color: #6a4cab;
border-right: 1px solid rgba(200,164,77,0.25);
padding-right: .55em;
margin-right: .55em;
user-select: none;
}
Loading