Skip to content

docs: restyle the site, and three corrections - #40

Draft
lestex wants to merge 28 commits into
mainfrom
docs-restyle
Draft

docs: restyle the site, and three corrections#40
lestex wants to merge 28 commits into
mainfrom
docs-restyle

Conversation

@lestex

@lestex lestex commented Aug 7, 2026

Copy link
Copy Markdown
Owner

Draft — not for merge. The colour scheme is still under review.

Replaces #39, which GitHub closed when its head branch was renamed.

Look

Stock Material in deep purple became a flat dark palette with hairline borders. Both schemes go through custom, handing Material the variables in docs/stylesheets/torrnado.css — so retuning the colours is a change to that one file, nothing else.

The landing page leads with the wordmark, a tagline, two calls to action and the TUI in a terminal frame, then a stat bar, a feature grid and a tabbed install block. Content width is unchanged — the nav and table of contents stay, so every page still measures the same.

Corrections

Found by checking each page against the code:

Claim Reality
list shows "one torrent per two lines… a thin progress underline beneath the name" renderRow returns one line; progress is a column, and progressCell's comment says it replaced the underline
the command palette section, its ten-row table and the quoting paragraph were each duplicated verbatim on the same page
palette accepts :remove/:remove! it also accepts :rm/:rm!; the Files tab also takes =/_ so priority needs no shift
release ends git push --follow-tags impossible — main is protected
docs/CNAME "keeps it across deployments" Pages ignores it for build_type: workflow

Checked and accurate, so untouched: every CLI subcommand and flag against --help, all TUI keys against the keymap, every config key and section against the struct, and all eight theme names.

CI

This PR is why: it ran seven Go jobs twice for a stylesheet. ci.yml now skips documentation-only changes, and docs.yml gains a pull-request build so those changes still get a strict build — with deploy gated off, its concurrency keyed by ref so a PR cannot cancel a live deployment of main, and Pages write permissions moved down to the deploy job.

lestex added 28 commits August 7, 2026 13:50
The site was stock Material in deep purple, and the home page opened with
a wall of prose. Both now look like what they document.

The colours are GitHub's own Primer tokens rather than approximations, so
a screenshot of the docs sits beside a screenshot of the repo without the
two reading as different products. Both schemes go through `custom`,
which hands Material the CSS variables in docs/stylesheets/torrnado.css.

The landing page leads with the wordmark, a two-line tagline, two calls to
action and the TUI itself in a terminal frame, then a stat bar, a feature
grid and a tabbed install block. Content width is unchanged -- the nav and
table of contents stay, so every page still measures the same.

Note the light scheme's header tokens: Material paints the header
background from --md-primary-fg-color and its text from
--md-primary-bg-color. The names read backwards, and getting them the
wrong way round gives white text on a white header, which is invisible
rather than merely ugly.
Three things the docs asserted that are not true.

The TUI page described the list as "one torrent per two lines: the data
columns, and a thin progress underline beneath the name", and its layout
diagram drew that. Progress became a column -- a bar then its percentage
-- and renderRow returns exactly one line per torrent; the comment on
progressCell even says it replaced the underline. Diagram and prose both
redrawn, and the narrow-pane behaviour said out loud.

The command palette section appeared twice on the same page, the same
ten-row table verbatim, and the paragraph about quoting appeared twice
after it. One of each survives. While there, the table now lists the
`:rm`/`:rm!` aliases the palette accepts, and the Files tab's `=`/`_`
priority keys, which spare you the shift.

The release ritual ended `git push --follow-tags`, which cannot work:
main is protected, so the changelog commit goes through a pull request and
the tag is cut on the merge commit afterwards. Tagging the local commit
instead names one that is not on the branch.
The header claimed docs/CNAME keeps the custom domain across deployments.
It does not: the file is deployed and served, but Pages only reads it to
configure a domain for branch-based sites, not for build_type: workflow.
The API reported cname: null until the domain was set explicitly.

Also worth writing down, because it cost an afternoon: until Pages is
enabled at all, the build job passes and only deploy fails, with a bare
HttpError: Not Found that says nothing about which setting is missing.
A stylesheet and some markdown spent seven jobs on two operating systems
proving the binary still builds -- twice, once for the push and once for
the pull request.

ci.yml now ignores docs/, mkdocs.yml, docs-requirements.txt, every .md and
docs.yml itself. A commit touching both docs and code still runs
everything; these paths only skip the workflow when they are the only
thing that changed. The two lists are written out twice because GitHub
Actions' parser does not support YAML anchors.

Skipping the Go suite would otherwise leave a docs change with no check at
all, since docs.yml only ran on main -- a broken --strict build would
first be seen after the merge, by the job that publishes. So docs.yml now
also runs on pull requests, builds only, with deploy gated on the event
not being one.

Two consequences of sharing that workflow, handled here: its concurrency
group is keyed by ref, or a pull request build would cancel an in-flight
deployment of main; and the Pages write permissions move to the deploy
job, so a build triggered by a pull request never holds them.

Nothing is a required status check today, so a docs-only pull request
merges with ci absent rather than stuck pending. Adding required checks
later would mean mirroring these filters there.
The home page sat 138px right of centre and ran 1174px wide against the
883px every other page uses, with a one-item "Home" breadcrumb and an
edit pencil in a row of their own above the hero.

One cause for all of it: hiding only the table of contents collapses the
right column while the nav keeps the left, so the content both stretches
into the freed space and slides right. Hiding the nav as well makes the
page symmetric, then it is pinned back to the interior measure -- 61rem
of grid less two 12.1rem sidebars, in rem so it tracks the root font size
Material scales.

The edit button goes with it. Nobody edits the landing page from a link
on the landing page, and it was the other half of that stray row.

Scoped with :has(.tn-hero) because Material has no per-page hook.
Verified in the browser rather than by eye: the landing page and an
interior page now both report 883px, at the same -7px offset, and the
interior page keeps its nav, table of contents and edit button.
Copied verbatim from the reference design, where the primary button
reads "$ get started". A $ says "type this at a shell", and this one
navigates to the installation page.
Eighteen em dashes across the README and three docs pages, replaced with
the double hyphen the rest of the repo already uses in prose.

internal/format keeps its en dash: that one is the value the TUI and CLI
print for an unknown ETA, asserted in format_test.go, so it is program
output rather than prose. Changing it would change what the app shows.
392 double hyphens standing in for an em dash, replaced with a single
hyphen across prose, Go comments, shell comments, Dockerfiles, workflow
comments and CLAUDE.md.

The rule applied is narrow on purpose: two hyphens with whitespace on
both sides, or at a line boundary. That shape alone spares the things
that look identical and are not prose:

- Go decrement operators (m.detailCursor--), which have no leading space
- every --flag, which has no trailing space
- cliff.toml's <!-- 0 --> group markers, which order the changelog
  sections and would silently resort it
- markdown --- front matter, horizontal rules and table separators
- tini's end-of-options separator in the Dockerfile ENTRYPOINT

One case had whitespace on both sides and still had to be skipped by
hand: runuser -u torrnado -- env ... in e2e/systemd_test.sh, where the
dash separates runuser's options from the command it runs.

Verified with gofmt, go build, go vet, the full test suite, all 18 e2e
checks and a strict docs build.
Two faults in the landing page's terminal screenshot.

Material puts a copy button on every code block. This one is a picture of
a running program rather than anything worth putting on a clipboard, and
it floated over the frame's top-right corner, on top of the border. The
container is .md-code__nav in Material 9.7; the older .md-clipboard is
named alongside it so a version bump does not quietly bring the button
back.

The border itself was drawn as a dashed line because line-height was
1.35: the leading opened a gap between one row's box glyph and the next.
At exactly 1 they touch and the frame reads as a rule.

A hairline unevenness remains at high zoom. The character advance is
6.55px, so every column starts on a fractional pixel and vertical stems
rasterise slightly differently row to row. That is font rendering rather
than layout, and it is not visible at normal size.
The box drawing never rendered as clean rules: the character advance
works out fractional, so every column began mid-pixel and the vertical
borders rasterised unevenly row to row. No line-height or font-size fixed
it, because it was rasterisation rather than layout.

So the hero shows the running TUI instead, inside the same window frame.
The image is resampled to 1760px, which is 2x the 843px the frame renders
at, and carries width and height attributes so nothing below it jumps
while it loads.

README keeps the ASCII art: it renders on GitHub without an asset, which
is the one place that matters.
Replaces the first capture. That one was 3634px wide against an 843px
frame, and showed every torrent either paused or finished, which says
nothing about what the thing does.

This one is 3006px and catches a torrent mid-flight: 19%, 35.5MiB/s, an
ETA, and a piece map with real gaps in it rather than a solid block.

300px of empty list rows are spliced out of the middle, found by scanning
the pane interior for rows of a single flat colour. That takes it from
1518px to 1218px tall, a 2.47:1 hero rather than a 2:1 one with a hole in
the middle. Resampled to 1760px, which is 2x the frame.
Swaps GitHub's Primer tokens for dracula, which is what `theme` defaults
to in internal/config. The screenshot in the hero and the page around it
are now one scheme rather than two that merely coexist.

The values are copied from internal/theme/builtin.go, not sampled off the
screenshot: a capture carries the display's colour profile with it, and
every colour read back a few points adrift -- the background came out
#292c33 against dracula's actual #282a36.

Two places where matching exactly would have been wrong:

- dracula's Muted, #6272a4, scores 3.03 against the background. That is
  fine for short terminal labels and not for paragraphs of muted prose,
  so the site uses #8b93b8 at 4.72.
- the accent is light, so the primary button takes dark text rather than
  white. Contrast checked at 5.9.

dracula has no light counterpart, so the light scheme keeps the purple on
a neutral ground, darkened until it carries body text on white. Every
pair was measured rather than eyeballed; the lowest is 4.72.

The --gh- prefix is now --tn-, since the tokens are not GitHub's.
The traffic lights and title bar were decoration imitating a window the
screenshot was not taken in. Gone; the frame and its border stay.

The image now breaks out past the text column, 44rem against the 36.8rem
the prose keeps. Only the screenshot moves -- every other page, and every
paragraph on this one, still measures the same. It takes the capture from
a 3.6x downscale to 2.8x, so the TUI text is that much less of a smudge.

Centred by margin arithmetic rather than a half-width translate: a
transform moves where a box is painted but not where it is laid out.

Fixes a bug that predates this commit, found while checking the above at
phone width. The hero's glow is 130% of its container and centred, so at
559px it reached 84px past the viewport and scrolled the whole landing
page sideways -- while looking perfectly centred, which is why it went
unnoticed. Clipped rather than shrunk, so the bleed is unchanged on a
wide screen. An interior page never had it; only the landing page has a
glow.
Clipping the glow to stop it scrolling the page sideways traded one fault
for a worse one: the gradient was cut mid-fade, so it ended in a hard
vertical edge and read as a lighter rectangle behind the wordmark.

Sized to fit instead -- min(44rem, 100%) -- so it never reaches past the
hero and the gradient arrives at transparent on its own. No clip, no
edge, and the sideways scroll on a phone stays fixed.

That edge is also the likeliest reason the page looked off-centre: the
clipped box was hero-width, 845px, while the screenshot beside it is
1056px, so two differently-sized boxes shared a centre and neither lined
up with the other. Measured after this change, the hero, the screenshot
and the stat bar all report an offset of 0 with identical gaps either
side.
Material fixes that block at 11.7rem whatever is in it. "lestex/torrnado"
measures 169px, so 112px of the box was empty and the name sat that far
short of the header's right edge -- left-aligned inside a box wider than
its contents, which reads as the whole thing being misplaced rather than
merely padded.

Sized to its contents instead. The name now ends where the header ends,
and a longer or shorter repo name moves the left edge: measured at 174px
for this repo and 423px for a 45-character name, in both cases finishing
the same 5px from the edge, which is Material's own padding.

max-width goes with it on both the block and the name inside. Dropping it
only on the block left the name capped at 100% of a box that now sizes to
the name, which is circular -- it came out ellipsised. The block only
renders above the tablet breakpoint, so there is no narrow-screen case
the cap was protecting.
Three things.

"Made with Material for MkDocs" is gone, via extra.generator: false --
a switch in the theme's own copyright partial rather than CSS hiding
text that is still in the markup. Verified by grepping the built HTML
rather than by looking at the page.

The copyright line carries a year, and names the same holder LICENSE
does: "© 2026 Andrey Larin". Taken from LICENSE rather than invented,
so the two cannot disagree.

The GitHub mark was 0.8rem, visibly smaller than the line of text beside
it, and is now 1.1rem. Its link keeps a larger box than the glyph so the
target stays comfortable to hit.
Replaces the real name with the handle and links it to
https://github.com/lestex. mkdocs renders the copyright value unescaped,
so the anchor can live in the config rather than needing the theme's
copyright partial overridden.

Styling it took a longer selector than it should. Material forces every
footer link to its muted grey with `html .md-footer-meta.md-typeset a`,
which outranks a plain `.md-copyright a` -- the rule applied, lost, and
left the link the same colour as the prose around it, which is to say
invisible as a link. Matched on specificity instead. Contrast against the
footer is 6.55.
anacrolix/torrent and bubbletea now link to their repositories, taken
from go.mod so the footer credits what the build actually uses:
github.com/anacrolix/torrent and github.com/charmbracelet/bubbletea.

The hover underline is gone. The accent already marks these as links, and
an underline flicking on under one of three links in a single sentence is
more movement than a footer wants. The hover rule stays, because without
it Material would pull the colour back to its own footer grey; it now
just holds the accent and adds nothing.
31 British spellings across comments, docs, workflow and shell scripts:
colour, centre, behaviour, recognise, rasterise, summarise, serialise,
cancelled, labelled, grey.

One of them was not prose. internal/tui had an unexported func centre,
called from overlay_test.go and themepicker.go, now center. The compiler
and the tests cover the rename.

Also mops up four double hyphens that went back into the CSS comments
after the earlier sweep, using the same rule and the same guards.

CHANGELOG.md keeps two, in `Named colour palettes` and `labelled values`.
It is generated from commit subjects, so editing it lasts until the next
`make changelog` and the subjects themselves are in history.
Material leaves 1rem between the logo and the title and 1.4rem between
the search field and the repo block. Now that the repo block is only as
wide as its contents, both gaps read as drift rather than rhythm.

Logo to title goes from 29px to 19px, search to repo block from 34px to
19px, so the two ends of the bar are spaced alike. The block still
finishes flush against the header's right edge.

Both selectors carry the direction attribute, because Material's own do
and a bare class loses to them. Written without it first, the rules
applied to the element and changed nothing.
A spiral: a vortex seen from above, for a client named after a storm.
Replaces Material's default book icon on the site and gives the TUI
something of its own above the sidebar's name.

docs/assets/logo.svg is a real Archimedean spiral, generated rather than
traced, and serves the site header, the favicon and any future desktop
icon. It strokes currentColor, so it takes the accent in dark and light
alike. Two turns at a 1.8 stroke: at 2.25 turns the gap between turns
falls to 1.9 against that stroke and the middle clogs into a blob, and
below 1.75 it stops reading as a spiral and starts reading as a comma.
Judged by rendering it at 128, 96, 48 and 24px rather than by eye at one
size.

internal/branding carries the same shape in character cells, in two
sizes. Its own package because the TUI will not be the only caller: a web
or desktop front end wants the SVG, and neither should import a
bubbletea model to find out what the logo is.

The sidebar draws the small one above the name, in the title's color, and
drops it entirely when the pane is too narrow rather than truncating it.
Half a spiral is not a smaller spiral.

Verified by running the TUI, not just building it.
theme.logo emits an <img>, and an SVG inside an <img> has no document to
inherit from, so stroke="currentColor" resolved to black. On a #31333f
header that is a mark nobody can see.

The header now goes through theme.icon instead, which inlines the file,
so currentColor picks up the header's own text color: #f8f8f2 on dark and
#282a36 on light, matching the wordmark beside it in both. Verified by
reading the computed stroke in each scheme rather than by looking once.

The favicon cannot use that trick at all, since a browser tab is not a
page either. docs/assets/favicon.svg carries the accent literally, which
is legible on light and dark tab strips alike.

That leaves two files with distinct jobs rather than three copies of one
path: the currentColor original under overrides/.icons, and the colored
favicon. The unreferenced docs/assets/logo.svg is deleted.
The gap was 36px, not the 19px reported when it was last tightened. That
measurement compared the logo button's box to the text, and the box is
not where the ink is: three separate things sat in between.

The SVG carried its own padding. A spiral of two turns starting at the
top does not fill a 24x24 box, and the drawn path occupied 14.3 of those
24 units, so every consumer inherited dead space it then had to cancel.
The viewBox is now trimmed to the ink plus half a stroke for the round
caps, and squared so nothing distorts. That alone took 36px to 32.

The button's 9.6px of right padding and the title's margin account for
the rest, now 0.2rem and 0.3rem, ending at 20px. The click target is
still 43px wide.

Both SVGs are regenerated together from one script, so the favicon gets
the tighter box too and fills a 16px tab rather than floating in it.
Material puts two titles in the header and cross-fades them: the site
name until you pass the first heading, the page's own title after. So
"torrnado" turned into "Home", or "The TUI", a few lines down, and the
brand left the page the moment anyone started reading it.

The page title is already in the tab, the breadcrumb and the heading, so
the header holds the site name instead. The second topic never renders,
and the first is exempted from the fade the active state applies to it.

Checked at 1200px down an interior page with the active state on, not
just on the home page where the two happen to agree at the top.
torrnado is a terminal program, so its name is set in JetBrains Mono
wherever it stands as a wordmark: the header and the hero. Inter keeps
the prose.

The hero needed more than a font swap. A fixed-pitch face sets wider at
the same nominal size, so it drops from 4.2rem to 3.6rem, and the -0.04em
that stopped Inter looking loose at that size pulls mono into a huddle,
so it eases to -0.02em. Weight 700 rather than 800; the mono face has no
800 and was being synthesised.

Also rewrites the header spacing comment above, which still quoted the
19px figure from before the gap was properly measured at 36.
The footer sat at 1.109 contrast against the page. That is a real
difference and far below what reads as an edge, so the two ran together
and the credits looked like a stray last paragraph. A hairline in the
border color bounds it, mirroring the rule under the header so the page
is closed the same way top and bottom.

Along the right of the header the gaps were 14px from the palette toggle
to the search field and 34px from the field to the GitHub mark. Both are
19px now, which is also what the logo sits at on the left.

Measured glyph to glyph, not box to box. Every control there carries its
own padding, so the boxes touch while the marks inside them sit far
apart: by their boxes the toggle and the field were 0px apart, which
would have made this look like nothing needed fixing.
Puts what the thing is built on first and the copyright under it, rather
than one long sentence that carried a licence, two dependencies and an
author in a single breath.

  MIT-licensed. Built with anacrolix/torrent and bubbletea.
  © 2026 @lestex

A <br> in the copyright value, which mkdocs renders unescaped, so this
stays config rather than a template override. All three links keep their
targets.
Material's footer is dark in both schemes by default, and only the slate
block was overriding it, so the light site ended on a near-black slab
under a white page.

The light scheme now sets the same five footer tokens: the page's own
light surface, the body text color, and the muted color for the credit
line and the GitHub mark. Measured at 13.22 for the text, 5.81 for the
muted line and 6.04 for the links.

The footer is 1.08 against the page here, as it was in dark, so the
hairline above it is what marks the edge in both.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant