hugo-theme-tui

TUI
Log | Files | Refs | README | LICENSE

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:
MREADME.md | 203+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
Mlayouts/_default/baseof.html | 18+++++++-----------
Mlayouts/partials/header.html | 29+++++++++++++++++++----------
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") }}