Source for my personal website, published at https://pngdeity.ru/.
The published site is assembled from two sources:
src/— the static root, uploaded to GitHub Pages verbatim.index.html(/),links.html(/links.html, the register),credits.html(/credits.html, the colophon),404.html(served for the whole domain),style.css, pluspngdeity_files/(résumé, vCard, images) anddownloads/(files linked from posts).hugo-src/— the Hugo blog. It builds withbaseURL = 'https://pngdeity.ru/blog/'and is merged intosrc/blog/at deploy, so every Hugo page lives under/blog/. Posts are incontent/posts/; the shared nav/footer arelayouts/_partials/site-navigation.htmlandsite-footer.html, with footer links indata/footer.toml. It is a self-contained Hugo project — there is nothemes/directory; the templates inhugo-src/layouts/and the styles inhugo-src/assets/css/are the site's own theme.tools/ci/— Go helpers used by CI (generatemetadata,validatemetadata,generateregister,sitecheck,healthcheck,manageincident,resolverrollback).
There is no Node toolchain: the project has no .scss/.sass sources and no
Hugo Sass/PostCSS pipeline, so Node and the sass dependency were removed along
with the previous theme.
Static pages are copied as-is with no server-side includes, so shared markup is
either a Hugo partial (blog) or intentionally duplicated by hand (static pages).
Use root-absolute links (/...) everywhere.
| Surface | Source |
|---|---|
| Blog header nav | hugo-src/layouts/_partials/site-navigation.html |
| Blog footer links + copyright | hugo-src/data/footer.toml |
| Home page | src/index.html |
| The register | src/links.html + hugo-src/data/registrations.toml |
| Colophon | src/credits.html |
| 404 page | src/404.html |
The static lists mirror data/footer.toml by hand; update both when links change.
src/links.html is the exception — it is the register, whose content is the
generated wall rather than a nav list (see below).
/links.html lists every institution where the pngdeity handle is registered
— 180 of them — as a full-bleed grid of cells, one per institution. It is the
one page where the handle is the content rather than the signature. Each cell
links to the handle's profile on that service rather than to the service root,
so following any cell lands on pngdeity.
The collection's canonical definition is hugo-src/data/registrations.toml. The
page markup is generated from it by tools/ci/generateregister, which rewrites
only the region between <!-- BEGIN REGISTER --> and <!-- END REGISTER --> in
src/links.html:
cd tools/ci && go run ./generateregister -repo ../..The deploy workflow runs this automatically, so data changes need no manual
regeneration. Adding an entry publishes the fact that the account exists; see
TODO.md for the reasoning behind that trade-off.
DESIGN.md records the site's visual identity in the
DESIGN.md format: design tokens
in YAML front matter, and the reasoning behind them in prose.
The short version:
- The aesthetic position is Deadpan Utility — a synthesis of Digital Brutalism and post-internet surrealism. The interface is a stark, machined container that treats absurd or heavy material with total seriousness, and it rejects Web 2.0 softness and corporate minimalism.
- Three principles follow from it: the Bowtie Principle (bizarre elements presented as entirely factual and ordinary — the cat in the bow tie), Active Interrogation (errors confront rather than apologise — the 404 delivers Paalen's question rather than an apology), and no gimmicks (no simulated terminals; the machined quality is real structure).
- This is deliberately not an editorial, tasteful, or midcentury-modern
framing;
DESIGN.mdrecords that as declined so it is not reintroduced. - The brand is the handle
pngdeity, always lowercase. It is a username, not a title, and it is never capitalised or decorated. - The motif is the image that looks back — the eye/portal favicon, the homepage photograph, and the Paalen quotation on the 404 page.
- Colour, type, and layout rules live in the document, along with explicit Do's and Don'ts.
TODO.md lists what is not yet settled, including a possible CI check that the
two stylesheets' tokens stay in sync and whether the blog chrome should gain a
deliberate element.
DESIGN.md is valid against the format and can be checked:
npx -p @google/design.md designmd lint DESIGN.mdIt reports zero errors. The orphaned-tokens warnings it prints are structural
(the rule counts a colour as used only when a component references it, and page
background, body text, and the header band are not components) — they should not
be "fixed" by inventing components.
The site follows the OS color scheme by default and offers a manual toggle that
persists in localStorage under the pg-theme key.
- The convention is a
data-themeattribute (light/dark) on<html>plus CSS custom properties (--pg-bg,--pg-fg,--pg-link, ...). - Blog styles:
hugo-src/assets/css/theme.css. The blog stylesheet served at/blog/css/site.cssis the concatenation ofcss/utilities.css,css/_code.css, andcss/theme.cssperformed byhugo-src/layouts/_partials/site-style.html. The pre-paint script ishugo-src/layouts/_partials/head-additions.html; the toggle behavior ishugo-src/assets/js/theme.js. - Static pages: the same variables live in
src/style.css, with an inline pre-paint snippet and a copy of the handler atsrc/theme.js.
Because src/ cannot include the blog's partials, these are two hand-synced
implementations of one convention — change them together.
Type uses two registers. Chrome (navigation, section labels, metadata, share
links, the theme toggle) uses the platform UI stack, declared once in
src/style.css and applied to the blog via the .pg-site class. Blog prose —
article body copy and its headings — uses Bitter, a slab serif, self-hosted
from hugo-src/assets/fonts/bitter/ under the SIL Open Font License 1.1 (see the
OFL.txt beside the font files). Bitter was chosen for legibility at body size
rather than for personality. DESIGN.md records the reasoning and the rules.
hugo-src/hugo.toml sets [taxonomies] tag = 'tags', so posts carry
tags = [...] front matter and are browsable at /blog/tags/ (each tag gets a
term page). This is the topical counterpart to the chronological /blog/ list.
The two page kinds are rendered by different templates: /blog/tags/ (the list
of tags) by hugo-src/layouts/taxonomy.html, and each /blog/tags/<tag>/ by
hugo-src/layouts/list.html, which branches on .Kind == "term".
Single post pages render text-only share links (Reddit and X) from the site's
own share partial (hugo-src/layouts/_partials/social/share.html). Networks and
their link/label/particle definitions are set in hugo-src/hugo.toml under
[params.ananke.social.*] (a legacy key name retained from the previous theme).
The share text is the post's front-matter description (falling back to a
truncated summary), via hugo-src/layouts/_partials/func/social/getShareLink.html
— set description on every post you want shared well.
Copy hugo-src/archetypes/default.md to hugo-src/content/posts/<slug>.md and
set draft = false to publish. Drafts are excluded from the build and /blog/.
hugo --source ./hugo-src --gc --minify # build the blog
hugo server --source ./hugo-src # preview the blog
cd tools/ci && go test ./... # metadata tool unit tests
cd tools/ci && go run ./sitecheck -repo ../.. -src ../../src # link checksitecheck expects the merged tree (Hugo output staged as src/blog/), which
the deploy workflow performs.
- Hugo (
extended) — CI pinsHUGO_VERSIONin.github/workflows/build-deploy.yaml; local builds with the system Hugo work. - Go — version in
tools/ci/go.mod; used only by the CI helpers.
The build is Hugo and Go only. There is no Node step, no package.json, and no
.node-version: nothing in the project uses a Sass/PostCSS/JS asset pipeline.
.github/workflows/build-deploy.yaml runs two jobs:
build_hugo— buildshugo-src/and uploads the result.merge_and_deploy— stages the Hugo output atsrc/blog/, runs the advisory site check and the metadata generator/validator, createssrc/.nojekyll, uploadssrc/to GitHub Pages, then health-checks/,/blog/,/sitemap.xml, and/robots.txt.
CNAME pins the canonical origin to pngdeity.ru.
| Workflow | Trigger | Blocking |
|---|---|---|
build-deploy.yaml |
push to main, manual |
yes |
validate-site.yaml |
pull request, manual | no — sitecheck is advisory |
ci-tools.yaml |
push, pull request | yes — go fmt/vet/build/test |
auto-rollback-pages.yaml |
deploy-run failure, manual | yes |
GPL-3.0 — see LICENSE.md.
The GitHub Pages deploy workflow (.github/workflows/build-deploy.yaml) generates and validates deployment metadata before uploading src/:
tools/ci/generatemetadatatools/ci/validatemetadatatools/ci/generateregister(rebuilds the register wall insrc/links.html)
These Go applications create/validate robots.txt, sitemap.xml, llms.txt,
llms-full.txt, and the .well-known/ files (ai-catalog.json, keybase.txt)
under src/.
tools/ci/sitecheck resolves every local href/src/poster reference in the merged src/ tree (including the Hugo output staged under src/blog/), follows same-origin absolute URLs back to files on disk, and asserts that CNAME matches the canonical origin. It is advisory: findings are reported as workflow annotations and in the step summary, but continue-on-error keeps the run green.
- Runs on pull requests via
.github/workflows/validate-site.yaml. - Runs before the Pages upload in
.github/workflows/build-deploy.yaml.
Unit tests for the metadata tools run in .github/workflows/ci-tools.yaml and are blocking.
The repository includes an automated rollback workflow for GitHub Pages incidents:
- Primary deploy workflow:
.github/workflows/build-deploy.yaml - Rollback workflow:
.github/workflows/auto-rollback-pages.yaml
A deployment is considered bad only when post-deploy health verification fails after a successful Pages deploy step.
Deterministic health checks validate HTTP 200 responses for:
//blog//sitemap.xml/robots.txt
Build or test failures alone do not trigger auto-rollback.
When a bad deployment is detected on main:
- Finds the most recent known-good successful run of the deploy workflow before the failing run.
- Promotes the exact immutable
github-pagesartifact from that prior successful run and directly redeploys it, entirely skipping any rebuilding of Hugo. - Runs post-rollback health verification checks using a robust Go-based healthchecker.
- Publishes incident and rollback details in workflow summaries and creates/updates a tracking GitHub issue.
- Rollback only runs for failed deploy runs caused by the
Post-deploy Health Verificationstep. - One auto-rollback attempt per incident run (skips repeated run attempts).
- Never rolls back to the same failing commit.
- Skips rollback when no known-good target can be resolved.
Use Actions → Auto Rollback Bad Pages Deployment → Run workflow and provide:
rollback_sha(required): commit SHA to redeploybad_sha(optional): failing commit SHA for context in summaries/issues
Set repository variable AUTO_ROLLBACK_ENABLED to false.
- This disables automated rollback from failed deploy workflow runs.
- Manual rollback via workflow dispatch remains available.