Press Start β€” a Zola theme

A side-scrolling 16/32-bit JRPG personal homepage, built as a content-driven Zola theme. The whole site is a single parallax "quest": a fixed pixel world, a walking sprite hero, an SNES-style HUD, and your content rendered as story dialogs, GOT ITEM! project cards, and quest-scroll blog dispatches.

The world/sprite engine and core styling are art-directed and fixed; everything a site owner would want to change is plain text in config.toml and content/. The files ship with demo content (a fictional developer, Alex Pixelsmith at example.com) β€” replace it with your own.

β–Έ Live demo: https://brandonhon.github.io/press-start/

Press Start β€” scrolling through the pixel quest world

Install

Requires Zola β‰₯ 0.19.

Add the theme to your site's themes/ directory:

cd themes
git clone https://github.com/brandonhon/press-start.git
# or, as a submodule:
# git submodule add https://github.com/brandonhon/press-start.git themes/press-start

Enable it at the top level of your config.toml (not under [extra]):

theme = "press-start"

Then give it content. The quickest start is to copy this repo's demo as a template and edit it:

The theme provides templates/, sass/, and static/js/; you provide your own content/ and config.toml. Override any theme file by creating the same path in your own templates/ or static/ β€” see the Zola theme docs.

Preview the demo

This repository is itself a runnable demo site:

zola serve     # dev server with live reload at 127.0.0.1:1111
zola build     # static output to ./public

Before deploying your own site, set base_url in config.toml and replace the demo content.

What you configure vs. what's theme-level

You configure (no template/CSS editing):

ThingWhere
Site title, description, base_urlconfig.toml
Faviconconfig.toml [extra].favicon + static/
Smooth scrolling (desktop)config.toml [extra].smooth_scroll
CRT scanlines + vignetteconfig.toml [extra].crt
Under-construction holding pageconfig.toml [extra].under_construction
HUD text (name, level, HP/XP, labels, zone, scroll hint)config.toml [extra.hud]
Menu items β€” labels and targets/anchorsconfig.toml [extra.menu] + content/_index.md anchors
Card badges + READ/MIN + back-link labelsconfig.toml [extra.labels]
404 / GAME OVER copyconfig.toml [extra.notfound]
Contact signpostconfig.toml [[extra.contact]]
Homepage title screen, story, dialogs, intros, end credits, per-section sky/zone/anchorcontent/_index.md
Projects (cards)content/projects/*.md
Dispatches (blog)content/blog/*.md

Theme-level (the art direction β€” edit the source if you want to retheme):

ThingWhere
The pixel world (clouds, mountains, trees, houses) + hero spritestatic/js/quest.js (pixel-grid data)
Sky / time-of-day color palettesstatic/js/quest.js (SKIES)
Color tokens, fonts, layout, CRT effectsass/main.scss (:root + rules)
Project-icon pixel arttemplates/macros/world.html (project_icon)
Decorative glyphs (β–Ό β–Ί πŸ“)templates

Structure

config.toml              # site + HUD + menu + badges + 404 + favicon + contact
content/
  _index.md              # the homepage "quest" β€” all narrative lives in [extra]
  projects/              # THE WORKSHOP β€” one file per "GOT ITEM!" card
  blog/                  # THE OLD LIBRARY β€” one file per quest-scroll dispatch
templates/
  base.html              # shared shell (world/HUD/menu/CRT); gates the construction page
  index.html             # the scrolling homepage, assembled from content
  section.html           # full /projects and /blog listings
  page.html              # a single dispatch, styled as a reading scroll
  404.html               # GAME OVER
  construction.html      # the under-construction holding page (when toggled on)
  macros/world.html      # project_icon() pixel-sprite macro
sass/main.scss           # all styling (prototype CSS + nav, reading view, responsive)
static/
  js/quest.js            # parallax, sprite animation, sky/zone, XP ramp, menu, blips
  js/construction.js     # the under-construction scene (hero hammering beehives)
  js/lenis.min.js        # vendored Lenis (only loaded when smooth_scroll is on)
  favicon.svg            # pixel-heart favicon (swap it for your own β€” see below)
theme.toml               # theme metadata
docs/index.html          # the original standalone prototype (reference only)

Every page sits inside a fixed chrome shell:

Configuring text β€” config.toml [extra]

HUD β€” every label and value:

[extra.hud]
name        = "A. PIXELSMITH"
level       = 12
level_label = "LV"
hp          = "120/150"
hp_pct      = 80           # HP bar fill (0–100)
hp_label    = "HP"
xp_label    = "XP"
xp_start    = 220          # XP counter start (ramps to xp_max on scroll)
xp_max      = 1200
start_zone  = "PIXEL FOREST"
zone_label  = "ZONE"
scroll_hint = "β–Ό SCROLL TO MOVE β–Ό"

Menu β€” label is the mobile toggle word; each [[extra.menu.items]] is a button. Add, remove, and reorder freely. A target is one of:

targetgoes to
"#village"an on-page anchor (must match a section's anchor β€” see below)
"@/blog/_index.md"an internal Zola page
"https://…"an external URL
[extra.menu]
label = "MENU"

[[extra.menu.items]]
label  = "VILLAGE"
target = "#village"

[[extra.menu.items]]
label  = "PROJECTS"
target = "@/projects/_index.md"

Where anchors land: any homepage section can declare an anchor in content/_index.md (e.g. anchor = "village"). Clicking a #village menu item smooth-scrolls so that section's dialog box sits high in the sky β€” the houses and the hero stay visible beneath it. (The landing offset is the scroll-margin-top on .dialog, .story-box in sass/main.scss β€” bump it to land boxes lower.)

Card badges + small affordances:

[extra.labels]
got_item        = "β˜… GOT ITEM!"     # project cards
quest_log       = "πŸ“œ QUEST LOG"     # blog scroll cards
crossroads      = "✦ CROSSROADS ✦"   # contact signpost
dispatch        = "πŸ“œ DISPATCH"       # single post reading view
read            = "β–Ά READ β€”"         # dispatch card read affordance
min             = "MIN"              # reading-time unit
back_to_section = "β—„ BACK"           # a post β†’ its section
back_to_home    = "β—„ RETURN TO THE QUEST"   # a section β†’ home

404 / GAME OVER screen:

[extra.notfound]
zone    = "THE VOID"
sky     = "night"
heading = "GAME OVER"
message = "This screen does not exist in the known world."
link    = "β€” PRESS START TO CONTINUE β€”"

Contact signpost β€” one entry per road (href is optional):

[[extra.contact]]
kind = "EMAIL"
who  = "hello @ example.com"
href = "mailto:hello@example.com"

The homepage story β€” content/_index.md

The title screen, story crawl, the about dialogs, the section intros, and the end credits are all structured data under [extra]. Each story/dialog screen sets its own zone and sky (dawn, morning, day, afternoon, dusk, night), which drives the parallax sky transition as you scroll past it. Paragraph strings may contain inline HTML (e.g. <em>). blog_count (default 3) controls how many dispatches appear on the homepage.

The title screen:

[extra.title]
small     = "β˜… A PERSONAL HOMEPAGE QUEST β˜…"   # tiny line above
main      = "PIXELSMITH"                       # the large title word
sub       = "A QUEST IN 32 BITS"               # line below
press     = "β–Ό PRESS DOWN TO START β–Ό"
copyright = "Β© MMXXVI EXAMPLE.COM β€” ALL ADVENTURES OBSERVED"

main is the big word; it auto-scales to the viewport (clamp()), so it shrinks on phones and caps out on desktop. One short word reads best (the display font is wide). To change the min/max sizes, edit .title-screen h1 in sass/main.scss.

A project β€” content/projects/<name>.md

+++
title = "FORGE"
weight = 1                 # controls card order
[extra]
icon = "ember"             # ember | server | paper
status = "β˜… STATUS: ACTIVE β˜…"
+++
The card body. One short paragraph of Markdown.

A dispatch β€” content/blog/<slug>.md

+++
title = "ON WRITING HONEST CODE"
date = 2026-06-09
[extra]
volume = "VOL. I"
dispatch = "DISPATCH 06"
read_min = 11              # optional; falls back to Zola's reading_time
summary = "One-line teaser shown on the scroll card."
+++
Full post body in Markdown…

The homepage shows the latest blog_count dispatches; the full list lives at /blog. Clicking a scroll opens the dispatch in a readable panel that keeps the world and HUD intact.

Favicon

The theme ships a pixel-heart favicon at static/favicon.svg. To use your own, either overwrite that file, or drop any image into static/ and point the config at it:

[extra]
favicon = "favicon.svg"   # any file in static/ β€” "favicon.png", "icons/me.svg", …

It's a plain SVG of <rect>s on a 16Γ—16 grid, so you can recolor or redraw it by hand to match your palette.

Under-construction page

Flip the whole site to a single no-scroll holding page β€” the pixel hero shuffling between three beehives, swinging a hammer (sparks, drifting bees, hazard tape):

Under construction β€” the hero hammering beehives

[extra]
under_construction = true
uc_title   = "UNDER CONSTRUCTION"
uc_message = "This corner of the kingdom is being built. Mind the bees."

With it on, every URL serves the holding page (the quest, blog, and projects are hidden until you flip it back to false). The scene is in templates/construction.html + static/js/construction.js.

Adding a project icon

Icons are tiny inline pixel SVGs. Add a branch to the project_icon macro in templates/macros/world.html and reference it via icon = "yourname" in a project's front matter. (This is pixel art, so it lives in a template rather than config.)

Retheming

The look is intentionally art-directed, but it's all in two files:

License

MIT β€” see LICENSE.