commit 980986aba5e3521afc0e2c529ee7d19b4581286b
parent ebdc7300fd19167036ecf52c9c59f10a4610e97c
Author: FedorVinog <fedor.vinogradov@student.howest.be>
Date: Wed, 10 Jun 2026 21:11:40 +0200
nav driven by [[menu.main]]; slim, theme-specific README
- header.html iterates site.Menus.main; first slot is always the home
link, label from params.brandLabel (default "whoami"). Active state
auto-detected via URL prefix match against the menu entry's URL.
- baseof.html drops the $navActive hand-off and passes the Page into
the header partial.
- README rewritten: theme-specific params table, dot-matrix font
extension docs, socials + projects + posts behaviours, override
paths, and a Known Limitations section. No generic Hugo 101.
Diffstat:
3 files changed, 143 insertions(+), 107 deletions(-)
diff --git a/README.md b/README.md
@@ -1,8 +1,8 @@
# hugo-theme-tui
-A terminal-UI Hugo theme: a box-drawn frame around the page, lazygit-style
-row hovers on lists, a restrained Nord palette, a monospace font ladder,
-and a dot-matrix glyph marker on the frame border.
+A terminal-UI Hugo theme. Box-drawn frame around the page, lazygit-style
+row hovers on lists, restrained Nord palette, monospace typography, and a
+dot-matrix glyph marker on the frame border.
Built for personal sites — bio + projects + a blog.
@@ -11,124 +11,155 @@ Built for personal sites — bio + projects + a blog.
│ │
│ user@example.com ~/ [whoami] projects … │
│ ──────────────────────────────────────────────────────── │
-│ Fedor Vinogradov │
-│ one-line description here │
+│ Your Name │
+│ one-line tagline │
│ │
│ ── about ────────────────────────────────────────────── │
-│ · recently graduated … │
-│ · working on … │
-│ · passionate about open source │
+│ · bullet one │
+│ · bullet two │
+│ · bullet three │
│ │
╰──────────────────────────────────────────────────────────────╯
```
-## Install
-
-```bash
-# from your Hugo site root
-git submodule add https://github.com/tddra/hugo-theme-tui themes/hugo-theme-tui
+To try it locally: clone the repo, then `hugo server --source exampleSite
+--themesDir ../..`. The rest of this README documents only what is
+specific to this theme — see `exampleSite/hugo.toml` for a full working
+example.
+
+## Theme-specific parameters
+
+| Param | Type | Default | Purpose |
+| ------------------------- | ------- | ---------------- | ------------------------------------------------------------- |
+| `brandUser` | string | `"user"` | Left side of the `user@host` brand line in the header. |
+| `brandHost` | string | `"example.com"` | Right side of the brand line. |
+| `brandLabel` | string | `"whoami"` | Label of the first nav link (always points to `/`). |
+| `about.title` | string | site title | H1 on the homepage. |
+| `about.description` | string | — | Subtitle under the homepage H1. |
+| `marker.home` | []string| `["Д","О","М"]` | Dot-matrix word on the homepage. |
+| `marker.projects` | []string| `["К","О","Д"]` | Dot-matrix word on the projects page. |
+| `marker.posts_list` | []string| `["П","О","С","Т"]` | Dot-matrix word on the posts list. |
+| `marker.posts_single` | []string| `["С","Т","А","Т"]` | Dot-matrix word on a single post. |
+| `marker.fallback` | []string| `["С","Т","А","Т"]` | Dot-matrix word on any other page. |
+| `socialLinks` | array | — | Tiles in the homepage socials grid (see below). |
+
+## The dot-matrix marker
+
+The bracketed dots on the frame's top border are the theme's signature.
+Each "word" is a list of single characters; each character is rendered
+from a 5×5 bitmap in `layouts/partials/cyrillic-svg.html`.
+
+The bitmap font ships with **eight Cyrillic glyphs**: `А Д К М О П С Т`.
+Anything outside that set renders nothing — so if you want to use
+different letters, add their bitmaps to the `$font` dict in that partial.
+Each entry is a list of five 5-char strings of `1`/`0`. E.g. to add
+Cyrillic `Р`:
+
+```go-html-template
+"Р" (slice "11110" "10001" "11110" "10000" "10000")
```
-Then in your `hugo.toml`:
+## Nav
+
+The first nav link is always the home link; its label comes from
+`brandLabel`. The remaining links are read from `[[menu.main]]` — add or
+remove entries to change the nav. Active state is auto-detected from the
+current page URL (matches the menu entry's URL or any descendant of it).
```toml
-theme = "hugo-theme-tui"
+[[menu.main]]
+identifier = "projects"
+name = "projects"
+url = "/projects/"
+weight = 10
```
-Or clone directly without submodules:
-
-```bash
-git clone https://github.com/tddra/hugo-theme-tui themes/hugo-theme-tui
-```
+## Socials grid
-## Try the demo site
+The homepage renders a tile grid driven by `params.socialLinks`. Each
+entry needs four fields:
-```bash
-git clone https://github.com/tddra/hugo-theme-tui
-cd hugo-theme-tui
-hugo server --source exampleSite --themesDir ../..
+```toml
+[[params.socialLinks]]
+key = "email" # small-caps label inside the tile
+value = "hello@example.com" # value shown below the key
+url = "mailto:hello@example.com"
+icon = "email" # see icon list below
```
-Then open `http://localhost:1313`.
+Built-in icons: `email`, `linkedin`, `github`, `git`, `rss`, `pgp`,
+`coffee`. To add more, edit `layouts/partials/icon.html` and add another
+`{{ else if eq $name "your-name" }}<svg …></svg>` branch — the SVGs
+inherit the current text color via `stroke="currentColor"`.
-## Configure
+## Projects page
-All configuration lives in `hugo.toml`. See `exampleSite/hugo.toml` for a
-complete working example. Minimum to look right:
+The projects page reads its entries from **page params**, not from child
+pages. Create `content/projects.md` with `type = "projects"` and a
+`[[projects]]` array:
```toml
-[params]
-brandUser = "fedor" # left side of the user@host brand line
-brandHost = "fedorvin.com" # right side
++++
+title = "projects"
+type = "projects"
+layout = "projects"
-[params.about]
-title = "Your Name"
-description = "one-line tagline shown under the title"
+[[projects]]
+title = "my-project"
+url = "https://github.com/…" # optional — omit for a plain row
+lang = "rust" # shown in the left column, bracketed
+desc = "one-line description"
++++
```
-### The dot-matrix marker
+The `lang` column is rendered as `[rust]` and is purely visual — it can
+be any short string.
-The bracketed dot-matrix glyphs sitting on the frame's top border are the
-theme's signature element. The word changes per page kind, and is
-configurable:
+## Posts
-```toml
-[params.marker]
-home = ["Д", "О", "М"]
-projects = ["К", "О", "Д"]
-posts_list = ["П", "О", "С", "Т"]
-posts_single = ["С", "Т", "А", "Т"]
-fallback = ["С", "Т", "А", "Т"]
-```
+Standard Hugo `content/posts/*.md`. Theme-specific behaviours:
-Each entry is a list of single characters. The bitmap font ships with
-**eight Cyrillic glyphs**: `А Д К М О П С Т`. To use different
-characters, add 5×5 bitmap entries to the `$font` dict in
-`layouts/partials/cyrillic-svg.html`. (Each glyph is a list of five
-strings of five `1`/`0` digits — see the existing entries for examples.)
+- Posts are grouped by year on the posts list (`GroupByDate "2006"`).
+- The frame title on a single post is `── <basename>.md ──`.
+- A table of contents auto-renders **above** the article when the post
+ has any `##`/`###` headings. Hugo's TOC settings apply (`markup.tableOfContents`).
+- `tags` in front matter render as `[#tag]` chips in the post-list row.
-### Socials
+## Customising
-The home page renders a socials grid driven by `params.socialLinks`:
+Theme structure:
-```toml
-[[params.socialLinks]]
-key = "email"
-value = "hello@example.com"
-url = "mailto:hello@example.com"
-icon = "email"
```
-
-Built-in icons: `email`, `linkedin`, `github`, `git`, `rss`, `pgp`,
-`coffee`. Add more by extending `layouts/partials/icon.html`.
-
-### Projects list
-
-A "projects" page renders a `proj-list` from page params, not from child
-pages. See `exampleSite/content/projects.md` for the structure:
-
-```toml
-[[projects]]
-title = "my-project"
-url = "https://github.com/…"
-lang = "rust"
-desc = "one-line description"
+layouts/
+ _default/ baseof, list, single, projects
+ partials/ head, header, footer, socials, icon, cyrillic-svg
+ posts/ list, single
+ index.html homepage
+static/css/ shared, tui, pattern, custom
```
-## Customizing
+Override any partial or stylesheet by creating a same-named file at the
+same path inside **your site's** `layouts/` or `static/` directory.
+Example: to add an analytics script in `<head>` without forking the
+theme, create `layouts/partials/head.html` in your site and Hugo will use
+that one instead.
+
+The CSS is four small files (~700 LOC total):
-The whole theme is four CSS files under `static/css/`:
+| File | Holds |
+| ------------- | ---------------------------------------------------------- |
+| `shared.css` | Nord palette CSS variables, font import, body reset |
+| `tui.css` | All component styles (frame, header, sections, rows, …) |
+| `pattern.css` | Border-anchored marker positioning |
+| `custom.css` | Adjustments for Hugo-rendered markdown (TOC, images, …) |
-| File | Purpose |
-| ------------ | --------------------------------------------------------- |
-| `shared.css` | Nord palette variables, base typography, body reset |
-| `tui.css` | All component styles (frame, header, sections, rows, etc.) |
-| `pattern.css`| Marker positioning (border-anchored) |
-| `custom.css` | Hugo-rendered-markdown adjustments (TOC, images, etc.) |
+## Known limitations
-Override anything by creating a same-named partial or stylesheet in your
-site's own `layouts/` or `static/` directory — Hugo's lookup order picks
-your site's files over the theme's.
+- The marker bitmap font ships with 8 Cyrillic glyphs only — extend it
+ in `cyrillic-svg.html` to use other characters.
+- Only a dark theme (Nord). No light-mode toggle.
+- The projects page reads from a page-param array, not from child pages.
+ If you want one-page-per-project, you'll need to add a layout.
## License
diff --git a/layouts/_default/baseof.html b/layouts/_default/baseof.html
@@ -6,37 +6,33 @@
<body>
{{- /*
Derive per-page-kind variables: frame title, dot-matrix marker word,
- brand path, active nav state.
+ brand path.
The marker word is a slice of single characters (one bitmap glyph per
char). Site users can override the per-page-kind words via:
[params.marker]
- home = ["H","O","M","E"]
- projects = ["C","O","D","E"]
- posts_list = ["P","O","S","T"]
- posts_single = ["R","E","A","D"]
- fallback = ["P","A","G","E"]
+ home = ["Д","О","М"]
+ projects = ["К","О","Д"]
+ posts_list = ["П","О","С","Т"]
+ posts_single = ["С","Т","А","Т"]
+ fallback = ["С","Т","А","Т"]
Only characters present in the bitmap font (see partials/cyrillic-svg.html
$font dict) will render. Defaults below use the original Cyrillic motif.
*/ -}}
{{- $frameTitle := "" -}}
{{- $cyrWord := slice -}}
{{- $brandPath := "" -}}
-{{- $navActive := "home" -}}
{{- $marker := site.Params.marker | default dict -}}
{{- if .IsHome -}}
{{- $frameTitle = "── whoami.md ──" -}}
{{- $cyrWord = index $marker "home" | default (slice "Д" "О" "М") -}}
- {{- $navActive = "home" -}}
{{- else if eq .Type "projects" -}}
{{- $frameTitle = "── projects/ ──" -}}
{{- $cyrWord = index $marker "projects" | default (slice "К" "О" "Д") -}}
{{- $brandPath = "projects" -}}
- {{- $navActive = "projects" -}}
{{- else if eq .Section "posts" -}}
{{- $brandPath = "posts" -}}
- {{- $navActive = "posts" -}}
{{- if .IsPage -}}
{{- $cyrWord = index $marker "posts_single" | default (slice "С" "Т" "А" "Т") -}}
{{- $slug := .File.BaseFileName -}}
@@ -54,7 +50,7 @@
<div class="frame" data-title="{{ $frameTitle }}">
{{ partial "cyrillic-svg.html" (dict "word" $cyrWord) }}
<div class="wrap">
- {{ partial "header.html" (dict "active" $navActive "path" $brandPath "mobileWord" $cyrWord) }}
+ {{ partial "header.html" (dict "page" . "path" $brandPath "mobileWord" $cyrWord) }}
{{ block "main" . }}{{ end }}
</div>
{{ partial "footer.html" . }}
diff --git a/layouts/partials/header.html b/layouts/partials/header.html
@@ -1,25 +1,34 @@
{{- /*
- Site header — brand (user@host ~/path) + nav (whoami / projects / posts).
- The brand prefix `user@host` links back to the home page; the trailing
- ` ~/path` reflects current section and stays as text.
+ Site header — brand (user@host ~/path) + nav.
+
+ The first nav slot is always the home link; its label comes from
+ `site.Params.brandLabel` (default "whoami"). The remaining slots iterate
+ over `site.Menus.main` — add or remove menu entries in your hugo.toml
+ to change the nav.
Context:
- .active — one of "home" | "projects" | "posts"
- .path — section path shown after `~/` in the brand line
+ .page — the current Page (for active-state detection)
+ .path — string after `~/` in the brand line (e.g. "posts")
.mobileWord — slice of single chars for the mobile dot-matrix marker
*/ -}}
-{{- $active := .active -}}
+{{- $page := .page -}}
{{- $path := .path -}}
{{- $user := site.Params.brandUser | default "user" -}}
{{- $host := site.Params.brandHost | default "example.com" -}}
+{{- $brandLabel := site.Params.brandLabel | default "whoami" -}}
<header class="site">
<div class="brand">
- <a class="brand-link" href="{{ "/" | relURL }}" aria-label="whoami"><span class="user">{{ $user }}</span><span class="at">@</span><span class="host">{{ $host }}</span></a><span class="path"> ~/{{ $path }}</span>
+ <a class="brand-link" href="{{ "/" | relURL }}" aria-label="{{ $brandLabel }}"><span class="user">{{ $user }}</span><span class="at">@</span><span class="host">{{ $host }}</span></a><span class="path"> ~/{{ $path }}</span>
</div>
<nav class="site">
- <a href="{{ "/" | relURL }}"{{ if eq $active "home" }} class="is-active"{{ end }}>whoami</a>
- <a href="{{ "/projects/" | relURL }}"{{ if eq $active "projects" }} class="is-active"{{ end }}>projects</a>
- <a href="{{ "/posts/" | relURL }}"{{ if eq $active "posts" }} class="is-active"{{ end }}>posts</a>
+ <a href="{{ "/" | relURL }}"{{ if $page.IsHome }} class="is-active"{{ end }}>{{ $brandLabel }}</a>
+ {{- range site.Menus.main }}
+ {{- $url := .URL | relURL -}}
+ {{- $isActive := false -}}
+ {{- if eq $page.RelPermalink $url -}}{{- $isActive = true -}}{{- end -}}
+ {{- if and (ne $url "/") (hasPrefix $page.RelPermalink $url) -}}{{- $isActive = true -}}{{- end -}}
+ <a href="{{ $url }}"{{ if $isActive }} class="is-active"{{ end }}>{{ .Name }}</a>
+ {{- end }}
</nav>
{{- with .mobileWord -}}
{{ partial "cyrillic-svg.html" (dict "word" . "W" 320 "H" 26 "class" "frame-marker frame-marker-mobile") }}