docs: restyle the site, and three corrections - #40
Draft
lestex wants to merge 28 commits into
Draft
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 indocs/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:
renderRowreturns one line; progress is a column, andprogressCell's comment says it replaced the underline:remove/:remove!:rm/:rm!; the Files tab also takes=/_so priority needs no shiftgit push --follow-tagsdocs/CNAME"keeps it across deployments"build_type: workflowChecked 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.ymlnow skips documentation-only changes, anddocs.ymlgains 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.