Skip to content

Commit 65b518f

Browse files
authored
Merge pull request #37 from tmck-code/new-ui
New UI: local Jekyll theme, article front matter, design system
2 parents c7dac22 + 3c54c52 commit 65b518f

84 files changed

Lines changed: 3524 additions & 7 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.claude/
2+
.scratch/
3+
.playwright-mcp/
4+
.obsidian/
5+
_site/
6+
.jekyll-cache/
7+
.sass-cache/

README.md

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,71 @@
1+
# tmck-code.github.io
2+
3+
My blog — a GitHub *user* Pages site (served from the domain root, `baseurl: ""`)
4+
built with Jekyll and a small **local theme** that lives in this repo. No
5+
`remote_theme`, no gems beyond what `actions/jekyll-build-pages` ships.
6+
7+
## What's built
8+
9+
| Path | Purpose |
10+
|---|---|
11+
| `_layouts/` | `default` (html shell, theme pre-paint, navbar/footer/palette), `home`, `page`, `post` |
12+
| `_includes/` | partials: `head`, `navbar`, `footer`, `article-list`, `article-meta`, `post-row`, `repo-card`, `terminal`, `codeblock`, `icon` (SVG sprite), `palette` (⌘K search), `robot-mark` |
13+
| `assets/css/tokens/*.css` | design tokens — colours (dark/light via `data-theme`), spacing, typography, motion, effects |
14+
| `assets/css/site.css` | all component styles, built on the tokens |
15+
| `assets/js/site.js` | behaviour: theme toggle (persisted in `localStorage['theme']`), copy-to-clipboard + toasts, clock, list filtering/tabs, command palette, GitHub star/fork enrichment |
16+
| `assets/img/` | logo/avatar SVGs, icon sprite, hex texture |
17+
| `_data/pages.yml` | cards for the interactive one-offs under `pages/` (shown on `pages.html`) |
18+
| `_data/repos.yml` | pinned repos for `projects.html` (static star/fork fallbacks) |
19+
| `index.html`, `posts.html`, `projects.html`, `pages.html`, `about.html` | the top-level pages |
20+
| `feed.xml` | Atom feed of listed articles (`/feed.xml`), templated over the same `article-list.html` query |
21+
| `articles/<slug>/<slug>.md` | blog posts — every one has YAML front matter (`title`, `date`, `blurb`, `tags`, `unlisted`) and uses the `post` layout via `_config.yml` defaults |
22+
23+
Fonts (Space Grotesk, IBM Plex Sans, JetBrains Mono, Victor Mono) are loaded from
24+
Google Fonts in `_includes/head.html`.
25+
26+
### Adding an article
27+
28+
```sh
29+
./create_article.py -title 'My Title' -description 'one-line blurb'
30+
```
31+
32+
This creates `articles/<YYYYMMDD>_<slug>/<slug>.md` with front matter + heading,
33+
and prepends an entry to the article list below the `---` in this README.
34+
Set `unlisted: true` in the front matter to keep a post out of the list/palette
35+
while leaving it reachable by URL.
36+
37+
## Building
38+
39+
### GitHub Actions (production)
40+
41+
`.github/workflows/jekyll-gh-pages.yml` runs on every push to `main` (or
42+
manually via *workflow_dispatch*): `actions/jekyll-build-pages@v1` builds the
43+
site into `_site/` with the github-pages gem's default plugin set
44+
(`jekyll-relative-links`, `jekyll-optional-front-matter`,
45+
`jekyll-titles-from-headings`, `jekyll-readme-index`, …) and
46+
`actions/deploy-pages@v4` publishes it. Nothing is built or committed locally.
47+
48+
### Locally
49+
50+
There is no `Gemfile` checked in — match the Actions build by using the
51+
`github-pages` gem, or the same thing in Docker:
52+
53+
```sh
54+
# ruby/bundler
55+
gem install github-pages
56+
jekyll serve --livereload # http://127.0.0.1:4000
57+
58+
# or docker, no ruby on the host
59+
docker run --rm -it -p 4000:4000 -v "$PWD":/site -w /site --user "$(id -u):$(id -g)" \
60+
--entrypoint jekyll ghcr.io/actions/jekyll-build-pages:latest \
61+
serve --host 0.0.0.0 --livereload
62+
```
63+
64+
`_site/`, `.jekyll-cache/` and `.sass-cache/` are gitignored. Because
65+
`baseurl` is empty and assets are root-absolute (`/assets/...`), the local
66+
server must be served from the root too (the default for `jekyll serve`).
67+
68+
---
169
### [20260415 KSF Surf Maps: Data Visualisation](articles/20260415_ksf_surf_maps_data_visualisation/20260415_ksf_surf_maps_data_visualisation.md)
270

371
> _Charts and insights for surf maps on KSF servers_

_config.yml

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,51 @@
11
title: tmck-code.github.io
22
description: my blog
33

4-
remote_theme: pages-themes/hacker@v0.2.0
5-
plugins:
6-
- jekyll-remote-theme
4+
# GitHub *user* site — served from the domain root, so baseurl is empty and every
5+
# asset is referenced root-absolute (/assets/...).
6+
url: https://tmck-code.github.io
7+
baseurl: ""
78

9+
# Local theme lives in _layouts/ + _includes/ + assets/. No remote_theme, no plugins
10+
# list: actions/jekyll-build-pages always loads the github-pages DEFAULT_PLUGINS
11+
# (jekyll-optional-front-matter, jekyll-relative-links, jekyll-titles-from-headings,
12+
# jekyll-readme-index, ...) which the articles and the README links depend on.
13+
14+
defaults:
15+
# Everything that Jekyll renders gets the plain page chrome ...
16+
- scope:
17+
path: ""
18+
values:
19+
layout: page
20+
# ... except articles, which are blog posts.
21+
- scope:
22+
path: "articles"
23+
values:
24+
layout: post
25+
26+
exclude:
27+
# Jekyll's own defaults (re-stated because `exclude` replaces rather than extends)
28+
- Gemfile
29+
- Gemfile.lock
30+
- gemfiles
31+
- node_modules
32+
- vendor/bundle/
33+
- vendor/cache/
34+
- vendor/gems/
35+
- vendor/ruby/
36+
# Repo-local
37+
- create_article.py
38+
- LICENSE
39+
- .scratch
40+
- .claude
41+
- .obsidian
42+
- .playwright-mcp
43+
- tmck-code-design-system
44+
- tmck-code-new-ui-poc
45+
46+
# Keep GitHub-flavoured heading anchors stable: many articles hand-write TOCs
47+
# against kramdown's default id generation.
48+
markdown: kramdown
49+
kramdown:
50+
input: GFM
51+
syntax_highlighter: rouge

_data/pages.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# The interactive one-off apps under pages/ (and one that still lives in articles/).
2+
# `name` doubles as the card eyebrow and the thumbnail filename:
3+
# assets/img/pages/<name>.png — a styled placeholder renders when it is absent.
4+
- name: bingo
5+
title: Bingo, by the numbers
6+
url: https://tmck-code.github.io/pages/bingo/index.html
7+
blurb: >-
8+
Two ways to read the same room — the cold mathematics of the odds, and a day
9+
spent inside the hall watching where the money goes.
10+
- name: pikachu-stitch-companion
11+
title: Glowing Pikachu · Stitch Companion
12+
url: https://tmck-code.github.io/pages/pikachu-stitch-companion/pikachu-stitch-companion.html
13+
blurb: >-
14+
An interactive cross-stitch pattern companion — colour isolation, carry audits,
15+
block-by-block progress across 6,088 stitches.
16+
- name: space-planner
17+
title: Space Planner
18+
url: https://tmck-code.github.io/pages/space-planner/index.html
19+
blurb: >-
20+
Set your space footprint, add shelving units, then drag them into place. Plan,
21+
front, side and 3D views.
22+
- name: yas
23+
title: YAS! — Yet Another Statusline
24+
url: https://tmck-code.github.io/pages/yas.html
25+
blurb: >-
26+
The official landing page for yet-another-statusline — a statusline for Claude
27+
Code inspired by terminal monitor programs.
28+
- name: ksf-surf-maps
29+
title: 'KSF Surf Maps: Data Visualisation'
30+
url: https://tmck-code.github.io/articles/20260415_ksf_surf_maps_data_visualisation/analysis.html
31+
blurb: Charts and insights for surf maps on KSF servers.
32+
soon: true

_data/repos.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Pinned/public repos. stars/forks are static fallbacks — site.js may enrich them
2+
# from the GitHub API via [data-repo] / [data-stars] / [data-forks].
3+
- name: yet-another-statusline
4+
description: A statusline for Claude Code inspired by terminal monitor programs
5+
language: Python
6+
stars: 239
7+
forks: 24
8+
- name: pokesay
9+
description: Print pokemon in the CLI! An adaptation of the classic "cowsay"
10+
language: Go
11+
stars: 33
12+
forks: 3
13+
- name: py-ansi-art-convert
14+
description: ANSI > Unicode Converter
15+
language: Python
16+
stars: 6
17+
forks: 0
18+
- name: dotfiles
19+
description: My shell configuration
20+
language: Shell
21+
stars: 4
22+
forks: 0
23+
- name: tmck-code.github.io
24+
description: My blog
25+
language: HTML
26+
stars: 0
27+
forks: 0
28+
- name: laser-prynter
29+
description: terminal/cli/python helpers for colour and pretty-printing
30+
language: Python
31+
stars: 0
32+
forks: 0

_includes/article-list.html

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{%- comment -%}
2+
Defines the site-wide `articles` variable: every *listed* markdown page under
3+
articles/, newest first.
4+
5+
They are *pages*, not posts — there is no _posts/ directory and adding one would
6+
change every existing URL. `.html` files that live inside article directories
7+
(the huge generated one-file reports) are excluded, and `unlisted: true` articles
8+
are hidden from every listing while remaining reachable by direct URL.
9+
10+
Ordering: sorted by `date` descending. The pre-sort on `path` (descending) is a
11+
tie-break — after the final `reverse` it leaves same-day articles in ascending
12+
path order, so multi-part series such as articles/20200419_csv/ read 0 → 4.
13+
Articles without `date:` fall back to their articles/YYYYMMDD_slug/ stamp for
14+
display (see article-meta.html).
15+
16+
Liquid `assign` writes to the global scope, so `articles` is visible to the caller
17+
after {% include article-list.html %}.
18+
{%- endcomment -%}
19+
{%- assign articles = site.pages
20+
| where_exp: "p", "p.path contains 'articles/'"
21+
| where_exp: "p", "p.path contains '.md'"
22+
| where_exp: "p", "p.unlisted != true"
23+
| sort: "path" | reverse | sort: "date" | reverse -%}

_includes/article-meta.html

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{%- comment -%}
2+
{% include article-meta.html article=p %} defines, in the caller's scope:
3+
art_title art_date (YYYY-MM-DD, may be '') art_read ("N min")
4+
art_blurb art_tags (array, may be empty)
5+
6+
Everything degrades: `date`/`blurb`/`tags` front matter is used when present,
7+
otherwise the date comes from the articles/YYYYMMDD_slug/ directory name, the
8+
title from jekyll-titles-from-headings, and the blurb/tags are simply omitted.
9+
{%- endcomment -%}
10+
{%- assign am = include.article -%}
11+
{%- assign art_title = am.title | default: am.name -%}
12+
{%- assign art_blurb = am.blurb | default: am.description | default: '' -%}
13+
{%- assign art_tags = am.tags | default: empty_array -%}
14+
{%- assign art_read = am.content | number_of_words | divided_by: 200 | plus: 1 | append: ' min' -%}
15+
{%- if am.date -%}
16+
{%- assign art_date = am.date | date: '%Y-%m-%d' -%}
17+
{%- else -%}
18+
{%- assign am_parts = am.path | split: '/' -%}
19+
{%- assign am_stamp = am_parts[1] | slice: 0, 8 -%}
20+
{%- assign am_num = am_stamp | plus: 0 -%}
21+
{%- if am_num > 19000000 -%}
22+
{%- assign am_y = am_stamp | slice: 0, 4 -%}
23+
{%- assign am_m = am_stamp | slice: 4, 2 -%}
24+
{%- assign am_d = am_stamp | slice: 6, 2 -%}
25+
{%- assign art_date = am_y | append: '-' | append: am_m | append: '-' | append: am_d -%}
26+
{%- else -%}
27+
{%- assign art_date = '' -%}
28+
{%- endif -%}
29+
{%- endif -%}

_includes/codeblock.html

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{%- comment -%}
2+
{% include codeblock.html label="go" code="go install …" %}
3+
The copy button is a no-JS-safe enhancement: `data-copy` carries the payload.
4+
{%- endcomment -%}
5+
<div class="codeblock">
6+
<div class="codeblock__label eyebrow">{{ include.label }}</div>
7+
<button type="button" class="codeblock__copy" data-copy="{{ include.code | escape }}">{% include icon.html name="copy" size=12 %}copy</button>
8+
<pre>{{ include.code | escape }}</pre>
9+
</div>

_includes/footer.html

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<footer class="site-footer">
2+
<div class="container">
3+
{% include robot-mark.html variant="mark" size=28 %}
4+
<span class="site-footer__text">tmck-code.github.io — built in the terminal, mostly</span>
5+
<span class="site-footer__hint">press <kbd>⌘K</kbd> for the command palette</span>
6+
</div>
7+
</footer>

_includes/head.html

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{%- assign head_title = page.title | default: site.title -%}
2+
{%- assign head_desc = page.blurb | default: page.description | default: site.description -%}
3+
{%- assign head_desc = head_desc | strip_html | normalize_whitespace | truncate: 200 -%}
4+
<meta charset="utf-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1">
6+
<meta name="color-scheme" content="dark light">
7+
<title>{% if page.url == '/' %}{{ site.title }}{% else %}{{ head_title }} · {{ site.title }}{% endif %}</title>
8+
<meta name="description" content="{{ head_desc | escape }}">
9+
<meta property="og:type" content="{% if page.layout == 'post' %}article{% else %}website{% endif %}">
10+
<meta property="og:site_name" content="{{ site.title | escape }}">
11+
<meta property="og:title" content="{{ head_title | escape }}">
12+
<meta property="og:description" content="{{ head_desc | escape }}">
13+
<meta property="og:url" content="{{ page.url | absolute_url }}">
14+
<meta name="twitter:card" content="summary">
15+
<link rel="canonical" href="{{ page.url | absolute_url }}">
16+
<link rel="icon" href="/assets/img/logo-mark.svg" type="image/svg+xml">
17+
18+
{%- comment -%}
19+
site.css @imports the design tokens itself, so it is the only sheet to link.
20+
The font URLs are duplicated as <link>s purely to start those fetches in
21+
parallel with the @import chain; the browser dedupes by URL.
22+
{%- endcomment %}
23+
<link rel="preconnect" href="https://fonts.googleapis.com">
24+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
25+
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;600;700&family=IBM+Plex+Sans:ital,wght@0,400;0,500;0,600;1,400&family=JetBrains+Mono:ital,wght@0,400;0,500;0,700;1,400&display=swap">
26+
{%- if page.victor_mono %}
27+
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Victor+Mono:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500;1,600&display=swap">
28+
{%- endif %}
29+
<link rel="alternate" type="application/atom+xml" title="{{ site.title }}" href="/feed.xml">
30+
<link rel="stylesheet" href="/assets/css/site.css">
31+
32+
{%- comment -%}
33+
The theme toggle's two glyphs. site.css has no rule for them (it only knows the
34+
[data-theme-toggle] button), so the swap lives here with the markup that needs it.
35+
{%- endcomment %}
36+
<style>
37+
[data-theme-toggle] .theme-icon { display: none; }
38+
html[data-theme="dark"] [data-theme-toggle] .theme-icon--sun { display: inline-flex; }
39+
html[data-theme="light"] [data-theme-toggle] .theme-icon--moon { display: inline-flex; }
40+
</style>

0 commit comments

Comments
 (0)