Skip to content

Add three responsive light and dark blog themes - #160

Open
mraible wants to merge 10 commits into
apache:masterfrom
mraible:feature/modern-blog-themes
Open

mraible wants to merge 10 commits into
apache:masterfrom
mraible:feature/modern-blog-themes

Conversation

@mraible

@mraible mraible commented Aug 13, 2026 •

Copy link
Copy Markdown
Contributor

Adds three blog themes to Roller, each a port of an established, MIT-licensed theme from another blogging community, so new weblogs can start from a design people already know:

  • Casper, Ghost's default theme
  • Minima, Jekyll's default theme
  • PaperMod, the most popular Hugo theme

This replaces the three custom themes this PR first proposed (Horizon, Alpenglow, and Dispatch). Ports of themes with large existing communities seemed like a better bet than new designs, and they're new code, so the earlier inline threads point at files that no longer exist.

Each port rewrites its upstream templates in Velocity and brings the upstream CSS over largely as-is, so the layout, spacing, and typography match. Every theme covers the weblog, permalink (with Roller's comment form), category, tag, search, and archive pages, follows the reader's light or dark color-scheme preference, and makes no external requests: no web fonts, no CDNs. Features that need data Roller doesn't have, such as cover images, Ghost memberships, and Hugo's client-side search, are left out.

A few details per theme:

  • Casper uses Ghost's Classic feed, so the first post is a large card on the home page, followed by the grid. Category and tag pages use its tag header, and the mobile menu is Casper's own script, loaded as a theme resource.
  • Minima is compiled from its SCSS with the auto skin and keeps its CSS-only hamburger menu. Its nav lists the weblog's navbar pages, and a small search box sits in the footer since upstream has none.
  • PaperMod keeps its moon/sun toggle, which remembers the reader's choice and otherwise follows the system setting. It also includes its archives-by-month and tags pages.

Each theme.css starts with its upstream copyright and MIT notice, and LICENSE.txt lists all three, next to the existing Bootstrap and Summernote entries. The templates avoid the Velocity 2 pitfalls found in the earlier review: they use $foreach.count rather than $velocityCount, never put text directly after a directive, and join $model.tags with #foreach.

The previews below are the preview.png files the theme selector shows, with light mode on the left and dark on the right. They were captured from a local Roller with the themes applied to a seeded test weblog.

Casper

Casper theme preview

Minima

Minima theme preview

PaperMod

PaperMod theme preview

ThemeManagerTest checks that all three themes and their stylesheets load. The full build passes locally (524 tests, 0 failures). I also checked every page of each theme at desktop and phone widths in both color schemes for 4xx responses and leftover Velocity syntax.

Introduce Horizon, Alpenglow, and Dispatch as modern Roller themes with responsive layouts and automatic color-scheme support. Add theme discovery coverage for all three.
Expand the compact theme templates into readable, consistently indented HTML and Velocity control flow.
Expand the three theme stylesheets for review and use the same neutral test-blog fixtures as Roller’s existing theme previews.
Set the create-weblog form's default theme explicitly so adding an alphabetically earlier shared theme does not change existing setup behavior.

@snoopdave snoopdave left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for these themes. Three template bugs found by rendering the templates with Velocity 2.4 (the version Roller uses); details inline. Also, CI on this PR predates the current workflow (no JDK 25 or CodeQL); merging current master would re-run it.

@@ -0,0 +1,28 @@
#foreach($entry in $entries)
<article class="story">
<div class="story-index" aria-hidden="true">$velocityCount</div>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Velocity 2 removed $velocityCount, and Roller's velocity.properties doesn't enable the old name, so this prints the literal text $velocityCount on every entry. Rendered with Velocity 2.4: #foreach($x in $l)$velocityCount#end gives $velocityCount$velocityCount. Use $foreach.count (as gaurav and frontpage use $foreach.index).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. It uses $foreach.count now.

@@ -0,0 +1,30 @@
#foreach($entry in $entries)
<article class="dispatch">
<div class="dispatch-number">#if($velocityCount<10)0#end$velocityCount</div>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Velocity 2 removed $velocityCount, and Roller's velocity.properties doesn't enable the old name, so this prints the literal text $velocityCount on every entry. Rendered with Velocity 2.4: #foreach($x in $l)$velocityCount#end gives $velocityCount$velocityCount. Use $foreach.count (as gaurav and frontpage use $foreach.index).

Here: #if($foreach.count<10)0#end$foreach.count.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed with #if($foreach.count < 10)0#end$foreach.count, so entries show 01, 02, and so on.

<footer class="site-footer">
<p>
<strong>$model.weblog.name</strong>
— #if($model.weblog.tagline)$model.weblog.tagline#elseField notes from a life in motion.#end

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Velocity reads #elseField as one unknown directive, not #else + text. With a tagline this prints My tagline#elseField notes from a life in motion.; without one, the fallback never shows. Use #{else}Field notes from a life in motion. (Horizon's footer works because it has a space after #else).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. The fallback text was hard-coded persona copy, so I removed it instead of switching to #{else}. The footer now shows the tagline only when the weblog has one.

<main id="content" class="article-shell">
<header class="page-heading">
<p class="eyebrow">Topic</p>
<h1>$model.tags</h1>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$model.tags is a List<String>, so this renders with list brackets, e.g. [java] or [java, roller]. Same on line 5 (<title>). Suggest joining the tags with a #foreach.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed here and in the <title> on line 5. The tags are joined with a #foreach, so a multi-tag page reads java, roller.

<main id="content" class="reading-shell">
<header class="page-title">
<p class="kicker">Stories filed under</p>
<h1>$model.tags</h1>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$model.tags is a List<String>, so this renders with list brackets, e.g. [java] or [java, roller]. Same on line 5 (<title>). Suggest joining the tags with a #foreach.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed here and in the <title>, the same way as Horizon.

<main id="content" class="reading-shell">
<header class="page-title">
<p class="label">FILTER / TAG</p>
<h1>$model.tags</h1>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$model.tags is a List<String>, so this renders with list brackets, e.g. [java] or [java, roller]. Same on line 5 (<title>). Suggest joining the tags with a #foreach.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed here and in the <title>, the same way as Horizon.

…w themes

Velocity 2 removed $velocityCount, so Alpenglow and Dispatch printed the literal text on every entry; they now use $foreach.count. Alpenglow's footer used #elseField, which Velocity reads as an unknown directive, so it now shows the tagline only when one is set. The tag pages joined $model.tags as a Java list, which rendered as [java, roller] in the heading and title; they now list the tags separated by commas.
Alpenglow, Dispatch, and Horizon shipped with hard-coded copy written for one blog, so every weblog using them showed the same headline, founding year, and about text. The hero headings now show the weblog name, Dispatch derives its founding year from the weblog's creation date, and the about and tagline sections are omitted when those fields are empty. Long weblog names now wrap in the Alpenglow and Dispatch headlines instead of overflowing on narrow screens.
@mraible

mraible commented Oct 11, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the review. I merged current master, so CI now runs on JDK 25 with CodeQL.

I also took two items from the UI audit you attached to #156:

  • UI-07: the three themes now use the weblog's own name, tagline, and about text, and hide those sections when the fields are empty. Dispatch's "EST." year comes from the weblog's creation date.
  • UI-08 Removing empty spaces #1: long names now wrap in the Dispatch and Alpenglow headlines.

I rendered the changed templates with Velocity 2.4.1, with the tagline and about fields both set and empty, and the full test suite passes.

…rMod

Horizon, Alpenglow, and Dispatch are replaced with ports of three established, MIT-licensed blog themes, so Roller users get designs that are already familiar from other blogging communities: Casper (Ghost's default theme), Minima (Jekyll's default theme), and PaperMod (the most popular Hugo theme).

Each port renders weblog, permalink, category, tag, search, and archive pages in its upstream's layout and typography, follows the reader's light or dark color-scheme preference, and makes no external requests. Each theme.css carries its upstream MIT notice, and LICENSE.txt lists all three. Since all three sort after Basic, the create-weblog flow no longer needs to pin Basic as its default.
@mraible

mraible commented Oct 24, 2026

Copy link
Copy Markdown
Contributor Author

A note for a follow-up pass on this PR: Casper, Minima, and PaperMod are all minimal, text-first designs (system fonts, white backgrounds, little color), so even though their layouts differ, they look like one family side by side. They were picked for popularity and license, not variety.

The plan is to keep PaperMod, since it's the most popular, and replace the other two with MIT-licensed themes that have a clearly different look:

  • Minima → Terminal (Hugo): retro console style, dark background, monospace type, accent colors
  • Casper → Hyde (Jekyll): bold colored sidebar with the title and nav, content beside it

Other candidates if either of those doesn't port well: Chirpy (Jekyll, sidebar layout for technical blogs; needs a check for CDN-loaded fonts and scripts) and Minimal Mistakes (Jekyll, author-profile sidebar with color skins).

The replacements would follow the same rules as the current ports: Velocity templates for every page type, upstream CSS with its MIT notice, a LICENSE.txt entry, light and dark support, and no external requests.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants