clone

a personal forge on plain git: static pages, stacked-diff reviews, signed local ci, repo sites and releases

main
forge / DESIGN.md
DESIGN.md 11.0K rendered
1# Design23How the forge's pages look, and the rules for changing them. They are4written for whoever edits the templates, people and agents alike. CI holds5the CSS to them (`node lib/check-css.ts`, run by `.forge/ci`), and the style6guide at `/design/` (`site/design.njk`) draws every token and control with7the real stylesheet and macros. Check it after a change.89## The look1011A plain page (no texture, no grid) with the chrome on it, and the content12in panels. Square corners, no shadows, hairline rules. Structure comes from13the order of things, the panels and the rules between rows, in the spirit14of the Vanilla framework. Phone first: lists restack, tabs scroll sideways.1516## Panels1718Lists, file viewers, READMEs, diffs and discussions sit in panels; the19header, a page's title and description, tabs and toolbars (the branch20picker, breadcrumbs, filters) sit on the page. Use `ui.panel` in21`site/_includes/ui.njk` rather than writing a box of your own.2223- A panel is `--raised` with a `--hairline` `--border` edge: square, no24 shadow. Panels stack `--space-4` (16px) apart.25- It may have a head strip, on `--tint` over a hairline, naming what the26 panel holds: quiet `--muted` sans at `--text-sm`, the key item in ink at27 `--fw-medium` (`<b>`, or a link with class `key`), shas, counts and ages28 in the mono, faint. The page's controls for what is in the panel (wrap29 lines, copy, viewed) go at its end (`.end`). A table's column names are a30 head strip too. A panel that folds is a `details` whose summary is its31 head strip.32- Inside, rows are between hairlines and the last has none: the panel's33 edge is its rule. Rows have `--space-2` `--space-3` padding.34- Hover tints the whole row with `--hover`: the background goes on the row35 (`tr`, `li`), never on a cell or a link, so it is one even block. A row's36 own link (a subject, a file, a branch) is in ink and stays ink on hover,37 underlined; other links are blue.38- Prose in a panel (`.markdown`) has its code on `--tint`; diagrams take39 the panel's surface. An empty state is a quiet centred sentence (`.empty`)40 in the panel it would fill.41- A section heading on the page (`ui.heading`) names several panels; one42 panel's name goes in its own head strip.43- A head strip says something the tabs or page heading don't; never44 repeat them. A shelf's list under its tab has none (the tab names it and45 counts it); it gets one only while a day is picked in the activity grid46 above it, since then it says what the tab doesn't: "oct 8, 2026",47 "3 repos, 11 commits", "show all".4849## Tokens5051Every colour, font size, weight, font family, space, rule width and the52radius is a custom property in the token block at the top of53`site/assets/style.css`, between the `== tokens ==` and `== end tokens ==`54comments, with its dark value in the `prefers-color-scheme: dark` block55under it. Nothing outside that block names a colour, size, weight or56family: not `style.css` below it, not a template's `{% css %}` block, not57a `style=""` attribute. If no token fits, add one there (with a comment if58it starts a group: the style guide titles groups by those comments).5960- Colour: `--bg` the page, `--raised` panels over it (white in light61 mode), `--tint` a panel's head strip, `--hover` a hovered row, ink in62 three strengths `--fg`, `--muted` (at least 4.5:1 on the page and on63 every surface) and `--faint` (at least 4.5:1 on `--raised`), rules64 `--border` and `--border-strong`.65 One accent, `#3148F6`: `--link` for links (lighter in dark mode),66 `--accent` for the current tab's bar, focus, primary fills, `--deep` on67 hover. Meaning: `--add-*`, `--del-*`, `--pass`, `--fail`, `--warn`,68 `--status-*` fills. Avatars are drawn in `--avatar-ground`, `-ink`,69 `-red`, `-blue` and `-yellow`; the activity grid's days in `--heat-0`70 (nothing) to `--heat-4`; a repo's languages in `--lang-1` to `-3` and71 `--lang-rest`.72- Type: `--text-xs` 12, `--text-code` 12.5, `--text-sm` 13.5, `--text-md`73 15 (body), `--text-prose` 16, `--text-lg` 18, `--text-xl` 22,74 `--text-2xl` 28. Weights `--fw-regular` 400 (body), `--fw-medium` 50075 (links, controls), `--fw-strong` 650 (emphasis, headings).76- Space: `--space-1` to `--space-10` on a 4px scale, `--hairline` 1px for77 rules, `--bar` 3px for the current tab, `--radius` 0.7879## Fonts8081Self-hosted variable woff2 in `site/assets/fonts/` (how they were cut:82`make-fonts.sh`; licenses: the `OFL-*.txt` files there).8384- `--sans`, Open Sans: all interface text. Navigation, tabs, lists,85 subjects, page and section headings, forms, buttons, labels.86- `--mono`, Noto Sans Mono: anything copied or compared. Hashes, review87 ids, branch names, paths, counts like +12, commit kinds, code, diffs,88 clone and install commands.89- `--serif`, Noto Serif: only rendered prose people wrote. READMEs and90 markdown files, the wiki, release notes, review descriptions and91 comments, the guide's text. Never interface chrome.9293## Words9495- No capitals by CSS and no letter-spacing, anywhere. Interface chrome96 (tabs, buttons, labels, table headers, section headings, status labels,97 summaries) is written in lowercase in the templates. Hierarchy comes98 from weight, size and colour.99- What users wrote (commit subjects, review titles, repo descriptions) is100 never case-transformed.101- Kinds, topics and filters are plain words, not chips.102- Never the middle dot (U+00B7), in code, comments, UI strings or103 commits; use a comma, a hyphen or a pipe.104- Inline code never breaks at a hyphen (`lib/markdown.ts` keeps a105 hyphenated word whole and lets a path break after its slashes).106107## Times108109A time in its own column is compact; a time in a sentence reads as a110sentence. Every time is a `<time>` with the moment, in UTC, in its111`datetime` and `title`; `site/assets/ui.ts` says it relative to now, every112minute, with the reader's full local date and time on hover.113114- In a column, a head strip or a list's facts (the recent feed, shelves,115 a repo's head strip, commits, reviews, revisions, branches, tags,116 releases, sites) it is `| date`: "now", "5m", "40m", "5h", "1d", "3w",117 then "Sep 24", or "2025-03-01" from an earlier year.118- In a sentence (a byline, "opened by cat, updated 5m ago", "active 2h119 ago", a comment's header) it is `| ago`: "just now", "5m ago", then120 "Sep 24, 2025". Its `<time>` has `data-ago`.121- A time ending a row is at least `--col-time` wide (the widest compact122 one, "Sep 24"), right-aligned, in tabular figures, unwrapped; and a123 list's rows share their columns (a table, or a grid whose rows are124 subgrids), so every row's text cuts at the same edge. A kind column is a125 fixed width, a count column tabular and right-aligned, the same way.126- The page has the day before the script runs, no wider than what replaces127 it, so nothing moves on load.128129## Words in docs130131The guide, the README and other docs are written in AP style, for132readability: no serial comma in a simple series; one through nine spelled133out and figures from 10, always figures for versions, sizes, times and134percentages; sentence-case headings; short sentences in the active voice;135dates like Oct. 8, 2026. Interface chrome follows the lowercase rules136above. Code, CLI output and the interface's compact times keep their own137forms.138139## People and activity140141- A person is shown with their avatar (`| avatar(size)`, `lib/avatar.ts`):142 a 3 by 3 square of bauhaus tiles drawn from their name at build time,143 after cat's bauhaus pattern generator. The same name gives the same art144 everywhere and forever; there are no uploads. 72px on their page, 36px145 in lists. Decoration beside the name, so `aria-hidden`.146- The activity grid (`ui.activity`, data from `lib/activity.ts`, behavior147 in `site/assets/ui.ts`) is the one way the forge shows a year of days.148 A column a week, Sunday at the top, no month row, no weekday column, no149 range picker; square cells with `--space-half` gaps, the 53 weeks150 filling the panel's width. Narrower, the cells shrink to 10px and then151 the oldest weeks go, so the latest always show (27 weeks at 390px). Days are the152 reader's with JS (the page has each commit's or event's time), UTC153 without. Each day is a button named154 "2026-10-08: 11 commits"; clicking it (or Enter, Space) picks it with an155 ink outline and narrows the list directly under it, clicking it again or156 "show all" lets it go; arrow keys move by a day or a week. Without JS157 it is a static picture. It lives on an owner's page (commits on the158 default branches of their public repos, following the picked tab) and a159 repo's insights (its pushes, landed reviews, releases, site deploys and160 CI results); not on the commits tab.161- On a repo's insights the grid is a chart like the others there, in a162 chart panel with its total and what it counts in the head strip. It is163 still (`ui.activity` with `still`): each day is a span, named and164 titled as a button would be ("2026-10-08: 3 events"), the same square165 with no hover, no pointer and nothing to pick, and no list goes under166 it. With JS it only recounts its days in the reader's time from167 `activity.json`; the address never names a day.168- The address keeps a picked day so a narrowed view can be shared:169 `#<day>` (`#2026-10-08`), or `#<shelf>/<day>` on an owner's page with a170 shelf picked, beside the shelves' own `#<shelf>`. The day is always last171 and always `YYYY-MM-DD`.172173## Controls174175Words for where you are, symbols for how you look at it.176177- Tabs say where you are (`ui.tabs`): the repo's sections, the shelves on178 an owner's page and `/topics/` (with an "all" tab). A tab shows one179 panel: "all" is every repo in one list, newest first, not the shelves180 stacked; a shelf's tab is that shelf's repos, and the panel's head181 strip names what it shows ("all 31", "games 6").182- A checkbox turns one thing on or off (`ui.checkbox`): wrap lines,183 viewed.184- An icon toggle is a checkbox drawn as a symbol (`ui.iconToggle`): a185 button with `aria-pressed`, `aria-label` and `title`, the size of an186 icon. Example: the globe, "only repos with a site".187- A segmented control picks one of a few (`ui.segmented`): the sort,188 newest first or by name.189- Buttons do things (`ui.button`); one primary, filled with the accent,190 per place at most. A toggle that is on is filled with ink. A filled primary darkens on hover (`--accent-hover`); `--deep` is for text.191- Links only go places. Never use a link, or a word that changes when192 clicked, as a toggle.193194Use the macros in `site/_includes/ui.njk` (also `ui.panel`, `ui.input`,195`ui.textarea`, `ui.listTable`, `ui.status`, `ui.heading`, `ui.icon`)196rather than writing the markup again. A macro's class belongs to its197element: `.icon` is `ui.icon`'s svg and nothing else (a file list's icon198cell is `td.file-icon`), since a cell that took the icon's class once took199its 16px inline box too, and painted a hovered row in pieces. A new control goes there first,200with its CSS in `style.css` under "controls", and onto `/design/`.201202## After a change203204Run `sh .forge/ci` (it runs the CSS check and the scenarios). After a205deploy, look at the pages: the home page, a repo, its commits, a review206and its diff, a README, releases and `/design/`, in light and dark, at207desktop and phone widths. Grepping HTML is not looking.