An open API service indexing awesome lists of open source software.

https://github.com/kako-jun/avel

A minimal, no-JS Zola theme. Inspired by Abe Hiroshi's homepage.
https://github.com/kako-jun/avel

blog minimal zola zola-theme

Last synced: about 1 month ago
JSON representation

A minimal, no-JS Zola theme. Inspired by Abe Hiroshi's homepage.

Awesome Lists containing this project

README

          

# avel

Lighthouse 100 / 100 / 100 / 100

**The fastest-rendering [Zola](https://www.getzola.org/) blog theme.** A homage to [Hiroshi Abe's famously lightweight official homepage](http://abehiroshi.la.coocan.jp/) — the page that renders before you blink — rebuilt as a modern, responsive, fully customizable theme.

- **Instant display** — pure HTML streamed top-to-bottom; the first characters paint immediately, nothing waits on a wrapper to finish loading
- **Instant navigation** — internal links are prerendered on hover (Speculation Rules) and cross-faded with CSS View Transitions; clicking feels like ~0ms
- **No runtime JavaScript, zero external resources** by default (no web fonts, no CDN; the only `` tags are declarative data — JSON-LD structured data and a Speculation Rules hint — never executed code, and both can be switched off)
- **Inline critical CSS** — no render-blocking external stylesheet request
- **`content-visibility` for off-screen content** — long lists/articles skip rendering until scrolled into view, while on-screen text still paints instantly
- **Responsive** — Abe's site isn't; avel is (one CSS media query, no JS)
- **SEO out of the box** — canonical URL, Open Graph, Twitter Card, JSON-LD structured data, sitemap and feed autodiscovery, all emitted by default with zero runtime JS
- **Optional no-JS comments** — show a per-post Nostalgic BBS image that opens the interactive posting page only when clicked
- **Syntax-highlighted code** — build-time highlighting with inline styles (no JS, no extra request)
- **Automatic dark mode** — follows `prefers-color-scheme` with CSS only; set `dark_mode = false` to stay always-light
- **Content organization** — tags, a year-grouped archive, tag-based related posts, and prev/next post navigation
- **Custom 404 page and favicon** (favicon overridable via config)
- **Lazy-loaded content images**, eager LCP image with `fetchpriority`
- **Fully customizable** via `config.toml` — defaults recreate the Abe look, every knob is overridable, no HTML/CSS editing needed

## Demo

[avel.llll-ll.com](https://avel.llll-ll.com)

## Installation

```bash
cd your-zola-site
git submodule add https://github.com/kako-jun/avel themes/avel
```

Set in `config.toml`:

```toml
theme = "avel"
```

## Configuration

All style options are controlled via `[extra]` in `config.toml`. Copy and uncomment what you need:

All defaults recreate the Abe Hiroshi look. Uncomment any line to override it.

```toml
[extra]
# --- Profile (shown centered in the top-page content, like Abe's right frame — not in the sidebar) ---
# name = "Your Name"
# profile_image = "me.webp" # place in static/
# profile_text = "one-line bio" # optional, under the name

# --- Background ---
# background_image = "bg.svg" # place in static/
# body_bg = "#ffffff"

# --- Layout ---
# nav_width = "18%" # default: 18% (Abe's frameset is cols=18,82)
# nav_bg = "#f0f0ff" # default: #f0f0ff (pale lavender)
# nav_border = true # default: true (right divider, a nod to the old frame border)
# nav_item_gap = "1.3em" # default: 1.3em
# nav_bullet = "●" # default: ● (set "" to remove)
# nav_bullet_colors = ["#ffcccc","#00ff00","#33ffff","#0099ff","#0000ff","#333399","#cc00cc"] # default: Abe's 7-color cycle
# main_padding = "1em"
# max_width = "960px"
# content_align = "left" # left / center / justify
# title_align = "center" # page <h1> alignment, default: center
# line_height = "1.5" # default: 1.5
# to_top = true # fixed bottom-right back-to-top "↑" link (no JS, default: true); set false to remove

# --- Font ---
# font = "sans-serif" # serif / sans-serif / monospace, default: sans-serif (Abe is unstyled, so it shows the browser default — gothic for Japanese)
# font_family = "Georgia" # system fonts only = no external requests
# google_fonts_url = "https://fonts.googleapis.com/css2?family=Noto+Serif+JP&display=swap"
# font_size = "16px" # default: 16px (browser default)

# --- Colors ---
# text_color = "#000000" # default: black
# link_color = "#0000ee" # default: browser-default blue
# link_visited = "#551a8b"

# --- Speed (modern, keeps Lighthouse 100) ---
# view_transitions = false # off by default: cross-document CSS fade can flash on dark themes mid-navigation; set true to opt in (no JS)
# speculation_rules = true # prerender internal links on hover for ~0ms navigation (one declarative <script>; external links and /atom.xml excluded)

# --- Date ---
# date_format = "%Y-%m-%d" # e.g. "%Y年%m月%d日"

# --- Nostalgic BBS comments (no JS on article pages; click the image to post) ---
# nostalgic_bbs = false
# nostalgic_bbs_limit = 3
# nostalgic_bbs_width = 760
# nostalgic_bbs_label = "Comments"
# nostalgic_bbs_hint = "Click to comment"

# --- Footer ---
# footer = "© Your Name"

# --- SEO / OGP (canonical, Open Graph, Twitter Card, and JSON-LD are emitted by default) ---
# og_image = "ogp.png" # OGP/Twitter card image, place in static/ (output as an absolute URL)
# og_image_alt = "My site" # alt text for social card images
# og_image_type = "image/png" # optional, helps social crawlers classify the image
# og_image_width = "1200" # optional, recommended for stable social previews
# og_image_height = "630" # optional, recommended for stable social previews
# twitter = "@yourhandle" # twitter:site handle for Twitter Cards

# --- Navigation ---
# nav = [
# { label = "Home", url = "/" },
# { label = "Posts", url = "/posts/" },
# ]
```

## Content structure

```
content/
_index.md # English top page (template = "index.html")
_index.ja.md # Japanese top page, served under /ja/
posts/
_index.md # English posts section
_index.ja.md # Japanese posts section
my-post.md # English post
my-post.ja.md # Japanese translation with the same slug
archive.md # English year-grouped archive
archive.ja.md # Japanese year-grouped archive
```

avel uses Zola's static multilingual support. English is the default language, so English files use the plain slug and Japanese translations use `.ja.md`. The generated URLs are `/posts/my-post/` and `/ja/posts/my-post/`. The theme does not use JavaScript or browser language detection; it renders static language links when `extra.language_switch = true` is set.

The top page pulls the latest posts directly via `get_section(path="posts/_index.md", lang=lang)` in `index.html`, so each language shows only its own posts and no `transparent` flag is needed on `posts/_index.md`.

Prev/next post links at the bottom of each post follow the section's `sort_by` order. `sort_by = "date"` is recommended: without `sort_by` the links are not rendered, and with `weight` the left/right direction no longer means older/newer.

## Nostalgic BBS comments

avel can show a per-post [Nostalgic BBS](https://nostalgic.llll-ll.com/bbs) image below the share links. Article pages stay JavaScript-free; clicking the image opens Nostalgic's interactive BBS page where visitors can post.

Enable rendering:

```toml
[extra]
nostalgic_bbs = true
nostalgic_bbs_limit = 3
nostalgic_bbs_width = 760
nostalgic_bbs_label = "Comments"
nostalgic_bbs_hint = "クリックで書き込めます"
```

For per-post provisioning, set `NOSTALGIC_TOKEN` in the build environment and run:

```bash
npm run sync:nostalgic-bbs
zola build
```

Cloudflare Pages can use:

```bash
npm run sync:nostalgic-bbs && zola build
```

The sync script looks up or creates one BBS per final post URL (`base_url` + article path) and writes the returned public ids to `data/nostalgic_bbs.toml`, keyed by the article URL path.

```toml
[posts]
"/posts/my-post/" = "my-post-bbs-id"
```

Existing ids in this generated mapping are reused, so repeated builds do not call Nostalgic for every article. New posts are looked up with Nostalgic BBS `batchLookup` by their final permalink URL, then only missing BBS entries are created. If `NOSTALGIC_TOKEN` is not set, the script leaves the existing mapping untouched and skips missing posts, so ordinary theme builds still work.

### Large batches (200+ posts)

The sync script is built to provision many BBS entries in one run. These optional environment variables tune its behavior:

| Variable | Default | Description |
|---|---|---|
| `NOSTALGIC_LOOKUP_LIMIT` | `50` | URLs resolved per `batchLookup` request (also the chunk size). |
| `NOSTALGIC_CREATE_DELAY_MS` | `1200` | Delay in ms applied after each create, to pace requests. Set `0` to disable pacing. |
| `NOSTALGIC_RETRY_BASE_MS` | `2000` | Base for the linear backoff on retryable failures (429/503/network). Minimum `500`. |
| `NOSTALGIC_MAX_RETRIES` | `5` | Maximum retry attempts per request. |

When provisioning hundreds of posts, the script paces creates and retries `429`/`503` responses and network exceptions with a linear backoff, so a busy API or a brief network hiccup does not abort the whole run. It also writes `data/nostalgic_bbs.toml` incrementally and atomically — after every successful create and at the end of each chunk, via a temp file plus rename — so if the run is interrupted partway, the ids created so far are preserved and a re-run resumes without re-creating them.

The generated `data/nostalgic_bbs.toml` may be committed for stable local builds, or generated only in the deployment environment. In either case, never put `NOSTALGIC_TOKEN` in front matter, templates, or generated HTML. If you need to pin an existing BBS manually, edit this mapping file rather than individual post Markdown.

Nostalgic handles any lower-level D1 query chunking internally, so splitting requests to satisfy SQLite bind limits is unnecessary. This script still chunks lookups into `NOSTALGIC_LOOKUP_LIMIT` (default `50`) URLs per `batchLookup` — not for bind limits, but to pace requests and checkpoint progress, writing `data/nostalgic_bbs.toml` after each chunk so an interrupted run resumes without re-creating BBS entries.

## Using the auto comment section with any Zola theme

The Nostalgic BBS comment block is an independent component that works with any Zola theme. You do not need to modify the theme itself. Follow these four steps to embed per-post comment sections without adding JavaScript to your article pages.

**Prerequisites:** Nostalgic is free and requires no account sign-up. Choose any 8–16 character string as your token. See [nostalgic.llll-ll.com](https://nostalgic.llll-ll.com) for details.

> **avel users:** If you are using avel as your theme, this is already set up. Just set `nostalgic_bbs = true` in your `config.toml` and run `npm run sync:nostalgic-bbs` before building. No further steps are needed.

### Step 1 — Copy the sync script

Copy `scripts/sync-nostalgic-bbs.mjs` from this repository into your own site's `scripts/` directory. Then change your build command to run the script before Zola:

```bash
node scripts/sync-nostalgic-bbs.mjs && zola build
```

For Cloudflare Pages (or any CI environment), set the same command as the build command in the dashboard.

The script scans `content/posts/` by default. If your posts live in a different directory, set the `NOSTALGIC_CONTENT_DIR` environment variable:

```bash
NOSTALGIC_CONTENT_DIR=content/articles node scripts/sync-nostalgic-bbs.mjs && zola build
```

### Step 2 — Set the token as an environment variable

The script reads `process.env.NOSTALGIC_TOKEN`. **Never commit the token to your repository.**

For local builds, set it in your shell:

```bash
export NOSTALGIC_TOKEN=your-token-here
```

For Cloudflare Pages or other CI environments, add `NOSTALGIC_TOKEN` as an environment variable in the project settings.

If `NOSTALGIC_TOKEN` is not set, the script keeps any existing BBS ids intact and exits without creating new entries. Posts that already have a BBS id will continue to show their comment section; only new posts without an id are skipped.

### Step 3 — Add settings to `config.toml`

Only `nostalgic_bbs = true` is required. The other four keys are optional — the template snippet provides defaults for all of them.

```toml
[extra]
nostalgic_bbs = true
nostalgic_bbs_limit = 3 # number of recent comments shown in the image (optional, default 3)
nostalgic_bbs_width = 760 # pixel width used when generating the SVG image (optional, default 760)
nostalgic_bbs_label = "Comments" # heading text above the image (optional, default "Comments")
nostalgic_bbs_hint = "Click to comment" # caption text below the image (optional, default "Click to comment")
```

### Step 4 — Add the snippet to your `templates/page.html`

Override your theme's page template by creating `templates/page.html` in your site root (Zola looks there before the theme directory). Copy this snippet and place it where you want the comment section to appear — typically after the article body.

> This is the minimal paste-in fragment. avel's own `templates/page.html` is the full version with CSS classes and additional markup. If you want the comment section to match avel's styling, refer to that file instead.

```html
{% if config.extra.nostalgic_bbs | default(value=false) %}
{% set_global nostalgic_bbs_public_id = "" %}
{% set nostalgic_bbs = load_data(path="data/nostalgic_bbs.toml", required=false) %}
{% if nostalgic_bbs and nostalgic_bbs.posts %}
{% for post_path, post_bbs_id in nostalgic_bbs.posts %}
{% if post_path == page.path %}
{% set_global nostalgic_bbs_public_id = post_bbs_id %}
{% endif %}
{% endfor %}
{% endif %}
{% if nostalgic_bbs_public_id %}
{% set limit = config.extra.nostalgic_bbs_limit | default(value=3) %}
{% set width = config.extra.nostalgic_bbs_width | default(value=760) %}
{% set label = config.extra.nostalgic_bbs_label | default(value="Comments") %}
{% set hint = config.extra.nostalgic_bbs_hint | default(value="Click to comment") %}
<div class="nostalgic-bbs">
<p>{{ label }}</p>
<a href="https://nostalgic.llll-ll.com/bbs?id={{ nostalgic_bbs_public_id | urlencode }}"
target="_blank" rel="noopener noreferrer">
<img src="https://api.nostalgic.llll-ll.com/bbs?action=get&id={{ nostalgic_bbs_public_id | urlencode }}&format=image&limit={{ limit }}&width={{ width }}"
alt="{{ label }} for {{ page.title }}"
loading="lazy" decoding="async">
</a>
<p>{{ hint }}</p>
</div>
{% endif %}
{% endif %}
```

### Why no JavaScript?

The comment display is a static `<img>` tag pointing to a Nostalgic API endpoint that returns an SVG. Clicking it navigates to the interactive Nostalgic BBS page where visitors post. This means:

- Article pages contain **zero JavaScript** for comments
- Comments render even with JavaScript disabled in the browser
- No third-party script is loaded; only one image request is made per article page view

## Tags

Add a taxonomy to `config.toml`:

```toml
[[taxonomies]]
name = "tags"
url = "tags"
feed = false

[languages.ja]
taxonomies = [
{ name = "tags", url = "tags", feed = false },
]
```

Then add tags to your posts:

```toml
+++
title = "My Post"
date = 2026-01-01

[taxonomies]
tags = ["foo", "bar"]
+++
```

Tag pages are generated at `/tags/` and `/tags/{name}/`. In multilingual sites, translated tag pages are generated under the language path, such as `/ja/tags/` and `/ja/tags/{name}/`.

## Theme gallery

This theme has been submitted to [getzola.org/themes](https://www.getzola.org/themes/).
GitHub topic: [`zola-theme`](https://github.com/topics/zola-theme)

## License

MIT