Square corners, hairline rules instead of boxes, plain words instead of chips, uppercase mono tabs and buttons, a new palette, and a faint paper (or plaster) texture behind every page.
revisions2r2: behavior change
| r2 | cat | 18f36c2 | what changed since r1 behavior change | |
| r1 | cat | 8318d80 |
No comments yet: forge comment 12 adds one.
1/6 Paint the pages on paper: a new palette and a faint texture
The page is a light grey (#E9E9E9) in ink (#1C2127), or ink-dark in dark mode, with one accent, #3148F6, for links, the current tab, focus and primary actions, and a deep blue for hover. The status colours move to the same family: green, amber and red, readable on both backgrounds. Behind everything sits a texture from CC0 scans (ambientCG): paper, multiplied in light mode, and plaster, screened in dark. It is a fixed pseudo-element behind the content, so scrolling never repaints it, and it goes away for readers who ask for less transparency. The textures ship as hashed assets like the rest: lib/assets.ts takes .webp files, and the stylesheet's url(./paper.webp) becomes the image's hashed name, as a script's imports do. Code views and file headers sit on an opaque raised surface, so what scrolls under a sticky gutter or header stays hidden. The tabs' scroll shadows go: they painted the header's colour, which a texture can't match.
| M | README.md | 3 | +- |
| M | lib/ | 25 | +++-- |
| M | lib/ | 2 | +- |
| M | site/ | 16 | ++-- |
| A | site/ | bin | |
| A | site/ | bin | |
| M | site/ | 131 | ++++++++++++++++-------------- |
@@ -1330,7 +1330,8 @@ rendered, `/assets/<hash>.css`, shared by pages of a kind. Import
13301330 `macros.njk` with context, or blocks in macros don't reach the bundle.13311331 13321332 Assets ship under content-hashed names (`/assets/style.<hash>.css`,1333-`ui.<hash>.js`, mermaid, sqlite-wasm), so browsers can cache them for good. The same1333+`ui.<hash>.js`, mermaid, sqlite-wasm), so browsers can cache them for good;1334+the stylesheet's `url(./paper.webp)` becomes the texture's hashed name. The same13341335 sources give the same names, and pages' fingerprints include them, so13351336 unchanged pages are still hard-linked. A build keeps the files of the last13361337 five code versions (listed in `assets/versions.json`), because pages built by@@ -1,5 +1,6 @@
11 // Assets under content-hashed names, /assets/style.<hash>.css, so browsers2-// can cache them forever: the stylesheet, the logo, the vendored scripts, our2+// can cache them forever: the stylesheet, the logo, the page textures, the3+// vendored scripts, our34 // TypeScript with its types stripped, mermaid, sqlite-wasm (its script and45 // its .wasm, for code search), and @simplewebauthn/browser (passkeys, on56 // the sign-in page). 11ty's bundle plugin@@ -42,15 +43,15 @@ export interface Asset {
4243 data: Buffer;4344 }4445 45-// By short name: style, logo, fixi, ssexi, diffs, keys, live, search, query,46-// codesearch, login, ui, mermaid, sqlite3, sqlite3wasm, webauthn.46+// By short name: style, logo, paper, plaster, fixi, ssexi, diffs, keys, live,47+// search, query, codesearch, login, ui, mermaid, sqlite3, sqlite3wasm, webauthn.4748 export type Assets = Record<string, Asset>;4849 4950 let memo: { key: string; assets: Assets } | null = null;5051 5152 export function assets(): Assets {5253 const files: [string, string, string][] = [53- ...fs.readdirSync(src).filter((f) => /\.(css|svg|js|ts)$/.test(f)).sort().map((f): [string, string, string] => [f.split('.')[0], f, path.join(src, f)]),54+ ...fs.readdirSync(src).filter((f) => /\.(css|svg|webp|js|ts)$/.test(f)).sort().map((f): [string, string, string] => [f.split('.')[0], f, path.join(src, f)]),5455 ...VENDOR,5556 ];5657 // Rebuilt only when a source changes (the dev server rebuilds in one process).@@ -58,7 +59,13 @@ export function assets(): Assets {
5859 if (memo?.key === key) return memo.assets;5960 // Our scripts import each other as './query.ts', which Node and tsc read6061 // as it is; shipped, that is the other file's hashed name, so a script's61- // name changes with what it imports.62+ // name changes with what it imports. The stylesheet's url(./paper.webp)63+ // becomes the image's hashed name the same way.64+ const local = (base: string, dep: string): Asset => {65+ const d = files.find(([, b, p]) => b === dep && path.dirname(p) === src);66+ if (!d) throw new Error(`${base} links ./${dep}, which is not in site/assets`);67+ return make(d);68+ };6269 const made = new Map<string, Asset>();6370 const make = ([short, base, f]: [string, string, string]): Asset => {6471 const done = made.get(short);@@ -67,13 +74,11 @@ export function assets(): Assets {
6774 let ext = path.extname(base);6875 const name = base.split('.')[0];6976 if (ext === '.ts') {70- const js = stripTypeScriptTypes(data.toString('utf8')).replace(/(\bfrom\s*)'\.\/([\w-]+\.ts)'/g, (_, from: string, dep: string) => {71- const d = files.find(([, b, p]) => b === dep && path.dirname(p) === src);72- if (!d) throw new Error(`${base} imports ./${dep}, which is not in site/assets`);73- return `${from}'./${make(d).file}'`;74- });77+ const js = stripTypeScriptTypes(data.toString('utf8')).replace(/(\bfrom\s*)'\.\/([\w-]+\.ts)'/g, (_, from: string, dep: string) => `${from}'./${local(base, dep).file}'`);7578 data = Buffer.from(js);7679 ext = '.js';80+ } else if (ext === '.css') {81+ data = Buffer.from(data.toString('utf8').replace(/\burl\(\.\/([\w-]+\.\w+)\)/g, (_, dep: string) => `url(./${local(base, dep).file})`));7782 }7883 const a = { file: `${name}.${hash(data)}${ext}`, data };7984 made.set(short, a);@@ -257,7 +257,7 @@ export function privateHandler(cfg: PrivateConfig): http.RequestListener {
257257 const css = renderer?.bundles.get(url.pathname);258258 const data = a?.data ?? (css != null ? Buffer.from(css) : null);259259 if (!data) return notFound(res, req.method === 'HEAD');260- const type = /\.css$/.test(name) ? 'text/css; charset=utf-8' : /\.js$/.test(name) ? 'text/javascript; charset=utf-8' : /\.wasm$/.test(name) ? 'application/wasm' : /\.svg$/.test(name) ? 'image/svg+xml' : 'application/octet-stream';260+ const type = /\.css$/.test(name) ? 'text/css; charset=utf-8' : /\.js$/.test(name) ? 'text/javascript; charset=utf-8' : /\.wasm$/.test(name) ? 'application/wasm' : /\.svg$/.test(name) ? 'image/svg+xml' : /\.webp$/.test(name) ? 'image/webp' : 'application/octet-stream';261261 res.writeHead(200, { 'Content-Type': type, 'Cache-Control': 'public, max-age=31536000, immutable', 'Content-Length': data.length });262262 res.end(req.method === 'HEAD' ? undefined : data);263263 }@@ -9,17 +9,17 @@
99 checked for color-blind readers against this page's light and dark1010 backgrounds; text keeps the page's own colors. */1111 .insights {12- --viz-1: #2a78d6;13- --viz-fail: #d03b3b;14- --viz-grid: #eaeef2;15- --viz-axis: #c3c2b7;12+ --viz-1: #3148f6;13+ --viz-fail: #c7162b;14+ --viz-grid: #d9d9d9;15+ --viz-axis: #a9aeb4;1616 }1717 @media (prefers-color-scheme: dark) {1818 .insights {19- --viz-1: #3987e5;20- --viz-fail: #d03b3b;21- --viz-grid: #21262d;22- --viz-axis: #3d444d;19+ --viz-1: #6f7fff;20+ --viz-fail: #f0606e;21+ --viz-grid: #2a3037;22+ --viz-axis: #4a525b;2323 }2424 }2525 .insights .ins-meta { margin: 0.4em 0 1em; }Binary file.
Binary file.
@@ -1,63 +1,84 @@
11 /* forge: the base stylesheet, light and dark, phone first: tokens, layout,22 lists, code views. CSS for one feature lives with its markup, in a33 template or include ({% css %}), and reaches only the pages that use it. */4+/* The palette: ink on a light grey page, one accent (#3148F6) for links,5+ the current tab, focus and primary actions, and a faint paper texture6+ behind it all (plaster in dark mode). */47 :root {5- --bg: #ffffff;6- --fg: #1f2328;7- --muted: #59636e;8- --faint: #8c959f;9- --border: #d1d9e0;10- --subtle: #f6f8fa;11- --hover: #eef1f4;12- --link: #0969da;13- --accent: #2ca9bc;14- --add-bg: #dafbe1;15- --add-fg: #116329;16- --add-ln: #aceebb;17- --del-bg: #ffebe9;18- --del-fg: #a40e26;19- --del-ln: #ffcecb;20- --hunk-bg: #ddf4ff;21- --hunk-fg: #0550ae;22- --pass: #1a7f37;23- --fail: #cf222e;24- --warn: #9a6700;25- --merged: #8250df;26- --sans: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;27- --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;8+ --bg: #e9e9e9;9+ --fg: #1c2127;10+ --muted: #5b6169;11+ --faint: #8a9097;12+ --border: #d2d2d2;13+ /* A raised surface over the page, translucent, and the same as one opaque14+ colour, for what content scrolls under (sticky headers, code gutters). */15+ --subtle: rgba(255, 255, 255, 0.55);16+ --raised: #f5f5f5;17+ --hover: rgba(255, 255, 255, 0.7);18+ --link: #3148f6;19+ --accent: #3148f6;20+ --deep: #1c4b8f;21+ --add-bg: rgba(14, 132, 32, 0.1);22+ --add-fg: #0e8420;23+ --add-ln: rgba(14, 132, 32, 0.2);24+ --del-bg: rgba(199, 22, 43, 0.08);25+ --del-fg: #c7162b;26+ --del-ln: rgba(199, 22, 43, 0.17);27+ --hunk-bg: rgba(49, 72, 246, 0.07);28+ --hunk-fg: #1c4b8f;29+ --pass: #0e8420;30+ --fail: #c7162b;31+ --warn: #b46a00;32+ --merged: #3148f6;33+ --texture: url(./paper.webp);34+ --texture-size: 256px 150px;35+ --texture-blend: multiply;36+ --texture-opacity: 0.5;37+ --sans: "Ubuntu", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;38+ --mono: ui-monospace, SFMono-Regular, "SF Mono", "JetBrains Mono", Menlo, Consolas, monospace;2839 --radius: 8px;2940 color-scheme: light dark;3041 }3142 @media (prefers-color-scheme: dark) {3243 :root {33- --bg: #0d1117;34- --fg: #e6edf3;35- --muted: #9198a1;36- --faint: #6e7681;37- --border: #30363d;38- --subtle: #151b23;39- --hover: #1c232c;40- --link: #4493f8;41- --add-bg: rgba(46, 160, 67, 0.16);42- --add-fg: #3fb950;43- --add-ln: rgba(46, 160, 67, 0.32);44- --del-bg: rgba(248, 81, 73, 0.16);45- --del-fg: #ff7b72;46- --del-ln: rgba(248, 81, 73, 0.32);47- --hunk-bg: rgba(56, 139, 253, 0.12);48- --hunk-fg: #79c0ff;49- --pass: #3fb950;50- --fail: #ff7b72;51- --warn: #d29922;52- --merged: #ab7df8;44+ --bg: #1c2127;45+ --fg: #e9e9e9;46+ --muted: #a3a9b0;47+ --faint: #737a82;48+ --border: #2f363e;49+ --subtle: rgba(255, 255, 255, 0.04);50+ --raised: #23282e;51+ --hover: rgba(255, 255, 255, 0.06);52+ --link: #8e9bff;53+ --deep: #b4bcff;54+ --add-bg: rgba(79, 191, 95, 0.13);55+ --add-fg: #4fbf5f;56+ --add-ln: rgba(79, 191, 95, 0.26);57+ --del-bg: rgba(240, 96, 110, 0.13);58+ --del-fg: #f0606e;59+ --del-ln: rgba(240, 96, 110, 0.26);60+ --hunk-bg: rgba(142, 155, 255, 0.1);61+ --hunk-fg: #8e9bff;62+ --pass: #4fbf5f;63+ --fail: #f0606e;64+ --warn: #e3a33b;65+ --merged: #8e9bff;66+ --texture: url(./plaster.webp);67+ --texture-size: 256px 256px;68+ --texture-blend: screen;69+ --texture-opacity: 0.3;5370 }5471 }5572 5673 * { box-sizing: border-box; }5774 html { -webkit-text-size-adjust: 100%; }5875 body { margin: 0; background: var(--bg); color: var(--fg); font: 15px/1.5 var(--sans); }76+/* The texture: CC0 scans (ambientCG), fixed to the window behind the77+ content, so scrolling never repaints it. */78+body::before { content: ''; position: fixed; inset: 0; z-index: -1; pointer-events: none; background: var(--texture) 0 0 / var(--texture-size) repeat; mix-blend-mode: var(--texture-blend); opacity: var(--texture-opacity); }79+@media (prefers-reduced-transparency: reduce) { body::before { display: none; } }5980 a { color: var(--link); text-decoration: none; }60-a:hover { text-decoration: underline; }81+a:hover { color: var(--deep); text-decoration: underline; }6182 :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; }6283 code, kbd, pre, .mono, .sha { font-family: var(--mono); }6384 .sha { font-size: 0.9em; }@@ -72,7 +93,7 @@ kbd { border: 1px solid var(--border); border-bottom-width: 2px; border-radius:
7293 hr { border: 0; border-top: 1px solid var(--border); margin: 1.5em 0; }7394 7495 /* header */75-header.site { background: var(--subtle); border-bottom: 1px solid var(--border); }96+header.site { border-bottom: 1px solid var(--border); }7697 .top { display: flex; align-items: center; gap: 10px; padding: 12px 0 8px; min-width: 0; }7798 .logo { display: block; color: var(--fg); line-height: 0; flex: none; }7899 .mark { display: block; width: 28px; height: 28px; }@@ -86,15 +107,7 @@ nav.tabs::-webkit-scrollbar { display: none; }
86107 nav.tabs a { color: var(--fg); padding: 8px 12px; border-bottom: 2px solid transparent; white-space: nowrap; }87108 nav.tabs a:hover { text-decoration: none; background: var(--hover); border-radius: 6px 6px 0 0; }88109 nav.tabs a.cur { border-bottom-color: var(--accent); font-weight: 600; }89-/* Tabs that don't fit scroll sideways; a shadow marks the edge that has more. */90-nav.tabs {91- --shade: color-mix(in srgb, var(--fg) 22%, transparent);92- background:93- linear-gradient(to right, var(--subtle) 40%, transparent) left / 28px 100% no-repeat local,94- linear-gradient(to left, var(--subtle) 40%, transparent) right / 28px 100% no-repeat local,95- radial-gradient(farthest-side at 0 50%, var(--shade), transparent) left / 10px 100% no-repeat scroll,96- radial-gradient(farthest-side at 100% 50%, var(--shade), transparent) right / 10px 100% no-repeat scroll;97-}110+/* Tabs that don't fit scroll sideways. */98111 @media (max-width: 480px) {99112 /* Six tabs fit a 360px phone: tighter, and sharing the width. */100113 nav.tabs { gap: 0; padding: 0 8px; }@@ -195,13 +208,13 @@ table.list tbody tr.sel td:first-child { box-shadow: inset 2px 0 0 var(--accent)
195208 /* code views */196209 /* The gutter (line numbers, and the +/- marker in diffs) is sticky, so code197210 scrolls under it: every gutter cell is opaque, its row's tint (translucent198- in dark mode) layered over the page background. --w is the widest line211+ in dark mode) layered over the code's background. --w is the widest line199212 number's digit count, set per file, so the columns never overlap. */200-.code { --lnw: max(3.5em, calc(var(--w, 3) * 1ch + 20px)); font: 12.5px/1.5 var(--mono); tab-size: 4; -moz-tab-size: 4; overflow-x: auto; border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg); }213+.code { --lnw: max(3.5em, calc(var(--w, 3) * 1ch + 20px)); font: 12.5px/1.5 var(--mono); tab-size: 4; -moz-tab-size: 4; overflow-x: auto; border: 1px solid var(--border); border-radius: var(--radius); background: var(--raised); }201214 .code .rows { display: inline-block; min-width: 100%; padding: 4px 0; }202215 .code .l { --gut: transparent; display: flex; color: var(--fg); text-decoration: none; }203216 .code a.l:hover { text-decoration: none; }204-.code .ln, .code .mk { position: sticky; background: linear-gradient(var(--gut), var(--gut)) var(--bg); user-select: none; -webkit-user-select: none; }217+.code .ln, .code .mk { position: sticky; background: linear-gradient(var(--gut), var(--gut)) var(--raised); user-select: none; -webkit-user-select: none; }205218 .code .ln { flex: none; width: var(--lnw); padding: 0 10px; text-align: right; color: var(--faint); left: 0; }206219 .code .ln:hover { color: var(--fg); }207220 .code .ln + .ln { left: var(--lnw); }@@ -226,10 +239,10 @@ body.wrap-lines .code .hunk { white-space: pre-wrap; overflow-wrap: anywhere; }
226239 body:not(.no-wrap) .code .lc, body:not(.no-wrap) .code .hunk { white-space: pre-wrap; overflow-wrap: anywhere; }227240 body:not(.no-wrap) .code .rows { display: block; }228241 }229-.file { margin: 16px 0; border: 1px solid var(--border); border-radius: var(--radius); }242+section.file { margin: 16px 0; border: 1px solid var(--border); border-radius: var(--radius); background: var(--raised); }230243 .file > .code { border: 0; border-radius: 0 0 var(--radius) var(--radius); }231244 .box > .code { border: 0; border-radius: 0; }232-.fh { position: sticky; top: 0; z-index: 2; display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 8px; padding: 8px 12px; background: var(--subtle); border-bottom: 1px solid var(--border); border-radius: var(--radius) var(--radius) 0 0; font-size: 0.9em; }245+.fh { position: sticky; top: 0; z-index: 2; display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 8px; padding: 8px 12px; background: var(--raised); border-bottom: 1px solid var(--border); border-radius: var(--radius) var(--radius) 0 0; font-size: 0.9em; }233246 /* A long path shares the line with the status and counts, wrapping at its234247 slashes, rather than wrapping onto a line of its own. */235248 .fh .fname { font-family: var(--mono); color: var(--fg); font-weight: 600; overflow-wrap: break-word; flex: 1 1 0; min-width: min(4em, 100%); max-width: max-content; }2/6 Square corners, and rules between things instead of boxes
Nothing is rounded any more: boxes, menus, dialogs, inputs, code blocks and the search overlay all have square corners. Lists sit on the page, as in Vanilla: tables lose their outer border and keep a hairline under each row, with headers in small uppercase monospace. Section headings take the same type over a rule. A box is now a heading row over its list or prose; only a file viewer (.viewer) and the code views and diffs stay bordered regions, on a raised surface. Comments are rows between rules, their quoted lines still a small code box. The repos on the index and owner pages are rows too, name, what it is, and its sites, reviews and last push, rather than cards. Labels, numbers, dates, branches and shas are monospace; subjects and prose keep the sans. Insights' tiles become figures under a rule.
| M | site/ | 28 | +++--- |
| M | site/ | 6 | +- |
| M | site/ | 25 | +++--- |
| M | site/ | 2 | +- |
| M | site/ | 51 | ++++++----- |
| M | site/ | 19 | ++-- |
| M | site/ | 140 | +++++++++++++++++------------- |
| M | site/ | 2 | +- |
| M | site/ | 4 | +- |
| M | site/ | 2 | +- |
| M | site/ | 4 | +- |
| M | site/ | 23 | +++-- |
| M | site/ | 10 | +- |
| M | site/ | 9 | +- |
| M | site/ | 8 | +- |
| M | site/ | 11 | ++- |
@@ -5,29 +5,31 @@
55 discuss.ts finds who is signed in, and posts the tip the page shows. #}66 {% set u = p.repo.urls %}77 {% css %}8-ul.approvals { list-style: none; margin: 0 0 12px; padding: 0; display: flex; flex-wrap: wrap; gap: 4px 18px; font-size: 0.92em; }8+ul.approvals { list-style: none; margin: 0; padding: 10px 0; border-bottom: 1px solid var(--border); display: flex; flex-wrap: wrap; gap: 4px 18px; font-size: 0.92em; }99 ul.approvals .tick { color: var(--pass); }1010 ul.approvals .stale { color: var(--muted); }1111 ul.approvals .stale .tick { color: var(--warn); }12-ul.approvals .date { color: var(--muted); font-size: 0.92em; }12+ul.approvals .date { font: 12px var(--mono); color: var(--muted); }13+ul.approvals .date time { font-size: 1em; }1314 ol.comments { list-style: none; margin: 0; padding: 0; }14-.comment { margin: 0 0 12px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg); }15-.comment:target { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }16-.comment-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 8px; padding: 6px 12px; background: var(--subtle); border-bottom: 1px solid var(--border); border-radius: var(--radius) var(--radius) 0 0; font-size: 0.9em; }17-.comment-head .date { color: var(--muted); }18-.comment-head .rev { font-family: var(--mono); font-size: 0.92em; color: var(--muted); }19-.comment .tag { font-size: 0.8em; font-weight: 600; color: var(--warn); border: 1px solid currentColor; border-radius: 999px; padding: 0 7px; }20-.comment .quote { margin: 10px 12px 0; border: 1px solid var(--border); border-radius: 6px; overflow: hidden; }21-.comment .quote .where { padding: 3px 10px; font: 12px/1.6 var(--mono); background: var(--subtle); border-bottom: 1px solid var(--border); overflow-wrap: anywhere; }15+.comment { border-bottom: 1px solid var(--border); padding: 0 0 4px; }16+.comment:target { box-shadow: inset 3px 0 0 var(--accent); }17+.comment-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 10px; padding: 10px 12px 0; font-size: 0.9em; }18+.comment-head .date { font: 12px var(--mono); color: var(--muted); }19+.comment-head .date time { font-size: 1em; }20+.comment-head .rev { font: 12px var(--mono); color: var(--muted); }21+.comment .tag { font-size: 0.8em; font-weight: 600; color: var(--warn); border: 1px solid currentColor; padding: 0 7px; }22+.comment .quote { margin: 8px 12px 0; border: 1px solid var(--border); background: var(--raised); overflow: hidden; }23+.comment .quote .where { padding: 3px 10px; font: 12px/1.6 var(--mono); border-bottom: 1px solid var(--border); overflow-wrap: anywhere; }2224 .comment .quote pre { margin: 0; padding: 4px 10px; font: 12.5px/1.5 var(--mono); white-space: pre-wrap; overflow-wrap: anywhere; tab-size: 4; }2325 .comment.outdated .quote pre { color: var(--muted); }24-.comment .markdown { padding: 8px 12px; font-size: 0.95rem; line-height: 1.55; }26+.comment .markdown { padding: 6px 12px 8px; font-size: 0.95rem; line-height: 1.55; }2527 .comment .markdown > :last-child { margin-bottom: 0; }2628 /* Sent from this page, until the rebuilt page shows it. */27-.comment.pending { border-style: dashed; }29+.comment.pending { border-bottom-style: dashed; }2830 .comment.pending .text { padding: 8px 12px; white-space: pre-wrap; overflow-wrap: anywhere; color: var(--muted); }2931 .reply { margin: 12px 0 0; }30-.reply textarea { display: block; width: 100%; min-height: 6em; resize: vertical; font: 14px/1.5 var(--sans); color: var(--fg); background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius); padding: 8px 10px; }32+.reply textarea { display: block; width: 100%; min-height: 6em; resize: vertical; font: 14px/1.5 var(--sans); color: var(--fg); background: var(--raised); border: 1px solid var(--border); padding: 8px 10px; }3133 .reply textarea:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }3234 .reply-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 8px; margin-top: 8px; font-size: 0.92em; }3335 .reply-bar .grow { flex: 1; }@@ -4,13 +4,13 @@ footer.site .keyhelp { background: none; border: 0; padding: 0; color: var(--mut
44 footer.site .keyhelp:hover { color: var(--fg); }55 @media (pointer: coarse) and (hover: none) { footer.site .keyhelp { display: none; } }66 /* keyboard shortcuts (keys.ts) */7-#help { width: min(440px, 94vw); padding: 0; background: var(--bg); color: var(--fg); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: 0 16px 48px rgba(0, 0, 0, 0.25); }7+#help { width: min(440px, 94vw); padding: 0; background: var(--raised); color: var(--fg); border: 1px solid var(--border); box-shadow: 0 16px 48px rgba(0, 0, 0, 0.2); }88 #help:focus { outline: none; }99 #help::backdrop { background: rgba(0, 0, 0, 0.3); }1010 #help > div { padding: 16px 20px; }11-#help h2 { margin: 0 0 10px; font-size: 1.05rem; }11+#help h2 { margin: 0 0 4px; }1212 #help table { width: 100%; border-collapse: collapse; font-size: 0.92em; }13-#help td { padding: 4px 0; vertical-align: top; }13+#help td { padding: 5px 0; vertical-align: top; border-bottom: 1px solid var(--border); }1414 #help td:first-child { width: 1%; padding-right: 16px; white-space: nowrap; }1515 #help kbd { color: var(--fg); font-size: 0.85em; }1616 #help p { margin: 12px 0 0; font-size: 0.85em; }@@ -23,18 +23,19 @@
2323 }2424 }2525 .insights .ins-meta { margin: 0.4em 0 1em; }26-.insights .kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; margin: 0 0 8px; }27-.insights .kpi { border: 1px solid var(--border); border-radius: var(--radius); padding: 10px 14px; }28-.insights .kpi .label { color: var(--muted); font-size: 0.85em; }29-.insights .kpi .value { font-size: 1.6rem; font-weight: 600; line-height: 1.2; margin-top: 2px; }26+.insights > h3 { margin: 28px 0 0; padding-bottom: 6px; border-bottom: 1px solid var(--border); font: 600 11px/1.4 var(--mono); letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); }27+.insights .kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 0 20px; margin: 0 0 8px; }28+.insights .kpi { border-top: 1px solid var(--border); padding: 10px 0; }29+.insights .kpi .label { font: 600 10.5px/1.4 var(--mono); letter-spacing: 0.08em; text-transform: uppercase; color: var(--faint); }30+.insights .kpi .value { font: 600 1.5rem/1.2 var(--mono); margin-top: 4px; font-variant-numeric: tabular-nums; }3031 .insights .kpi .sub { color: var(--muted); font-size: 0.85em; }3132 .insights .charts { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 440px), 1fr)); gap: 12px 20px; }3233 .insights figure { margin: 0; min-width: 0; position: relative; }33-.insights figcaption h3 { margin: 1.2em 0 0; }34+.insights figcaption h3 { margin: 1.6em 0 2px; font: 600 11px/1.4 var(--mono); letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); }3435 .insights figcaption .desc { font-size: 0.85em; }3536 .insights .plot { position: relative; margin-top: 6px; }36-.insights .plot:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; }37-.insights svg { display: block; width: 100%; overflow: visible; font: 11px var(--sans); font-variant-numeric: tabular-nums; }37+.insights .plot:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }38+.insights svg { display: block; width: 100%; overflow: visible; font: 10.5px var(--mono); font-variant-numeric: tabular-nums; }3839 .insights svg .grid { stroke: var(--viz-grid); stroke-width: 1; shape-rendering: crispEdges; }3940 .insights svg .base { stroke: var(--viz-axis); stroke-width: 1; shape-rendering: crispEdges; }4041 .insights svg .tick { fill: var(--muted); }@@ -44,20 +45,20 @@
4445 .insights svg .hair { stroke: var(--muted); stroke-width: 1; shape-rendering: crispEdges; }4546 .insights svg .on { filter: brightness(1.15); }4647 .insights svg .mark-label { fill: var(--fg); paint-order: stroke; stroke: var(--bg); stroke-width: 3px; stroke-linejoin: round; }47-.insights .empty-chart { color: var(--muted); font-size: 0.9em; padding: 28px 0; text-align: center; border: 1px solid var(--border); border-radius: var(--radius); }48+.insights .empty-chart { color: var(--muted); font-size: 0.9em; padding: 28px 0; text-align: center; border-block: 1px solid var(--border); }4849 .insights .legend { display: flex; gap: 14px; font-size: 0.85em; color: var(--muted); margin-top: 4px; }49-.insights .legend i { display: inline-block; width: 10px; height: 10px; border-radius: 2px; margin-right: 5px; vertical-align: -1px; }50-.insights .tip { position: absolute; z-index: 2; pointer-events: none; background: var(--bg); border: 1px solid var(--border); border-radius: 6px; box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12); padding: 6px 9px; font-size: 0.85em; white-space: nowrap; }50+.insights .legend i { display: inline-block; width: 10px; height: 10px; margin-right: 5px; vertical-align: -1px; }51+.insights .tip { position: absolute; z-index: 2; pointer-events: none; background: var(--raised); border: 1px solid var(--border); box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12); padding: 6px 9px; font-size: 0.85em; white-space: nowrap; }5152 .insights .tip b { font-size: 1.05em; }5253 .insights .tip .k { display: inline-block; width: 10px; height: 2px; margin-right: 6px; vertical-align: middle; }5354 .insights details.table { margin-top: 4px; font-size: 0.85em; }54-.insights details.table summary { color: var(--muted); cursor: pointer; }55+.insights details.table summary { color: var(--muted); cursor: pointer; font: 600 10.5px/1.4 var(--mono); letter-spacing: 0.08em; text-transform: uppercase; }5556 .insights details.table table { border-collapse: collapse; margin-top: 4px; font-variant-numeric: tabular-nums; }5657 .insights details.table td, .insights details.table th { padding: 2px 10px 2px 0; text-align: left; border-bottom: 1px solid var(--border); }5758 .insights details.table .scroll { max-height: 240px; overflow: auto; }5859 .insights .files td { vertical-align: top; padding: 1px 12px 1px 0; }5960 .insights .files td:first-child { white-space: nowrap; }60-.insights pre.cmd { overflow-x: auto; background: var(--subtle); border: 1px solid var(--border); border-radius: var(--radius); padding: 8px 12px; font-size: 0.85em; }61+.insights pre.cmd { overflow-x: auto; background: var(--subtle); border: 1px solid var(--border); padding: 8px 12px; font-size: 0.85em; }6162 @media (forced-colors: active) {6263 .insights svg .s1, .insights svg .fail { fill: CanvasText; }6364 }@@ -13,7 +13,7 @@
1313 {% block content %}1414 {% css %}1515 /* the sign-in page */16-.login { padding: 16px 24px; max-width: 40em; }16+.login { padding: 0 0 16px; max-width: 40em; }1717 .login h1 { font-size: 1.3rem; margin: 4px 0 12px; }1818 .login button.go { font-size: 1em; padding: 6px 14px; background: var(--accent); border-color: var(--accent); color: #fff; }1919 .login button.go:hover { filter: brightness(1.1); background: var(--accent); }@@ -80,26 +80,26 @@
8080 {% macro reviewItem(r, repo, depth = 0) %}8181 {% css %}8282 .rv-list { list-style: none; margin: 0; padding: 0; }83-.rv { position: relative; display: grid; grid-template-columns: auto 1fr auto; column-gap: 10px; row-gap: 3px; align-items: baseline; padding: 11px 14px; border-top: 1px solid var(--border); }84-.rv:first-child { border-top: 0; }83+.rv { position: relative; display: grid; grid-template-columns: auto 1fr auto; column-gap: 10px; row-gap: 3px; align-items: baseline; padding: 10px 8px; border-bottom: 1px solid var(--border); }8584 .rv:hover, .rv.sel { background: var(--hover); }8685 .rv.sel { box-shadow: inset 2px 0 0 var(--accent); }87-.rv-mark { grid-row: 1 / 3; align-self: start; width: 9px; height: 9px; margin-top: 6px; border-radius: 50%; background: var(--muted); }86+.rv-mark { grid-row: 1 / 3; align-self: start; width: 9px; height: 9px; margin-top: 6px; background: var(--muted); }8887 .rv-mark.open { background: var(--pass); }8988 .rv-mark.draft { background: none; box-shadow: inset 0 0 0 2px var(--muted); }9089 .rv-mark.merged { background: var(--merged); }9190 .rv-mark.closed { background: var(--fail); }9291 .rv-title { min-width: 0; font-weight: 600; overflow-wrap: anywhere; }9392 .rv-title a::after { content: ''; position: absolute; inset: 0; }94-.rv-side { display: flex; gap: 12px; align-items: baseline; justify-content: flex-end; white-space: nowrap; font-size: 0.9em; }95-.rv-meta { grid-column: 2 / 4; display: flex; flex-wrap: wrap; column-gap: 14px; row-gap: 2px; font-size: 0.85em; color: var(--muted); }96-.rv-meta .mono { font-size: 0.95em; }93+.rv-side { display: flex; gap: 12px; align-items: baseline; justify-content: flex-end; white-space: nowrap; font: 12.5px var(--mono); }94+.rv-side .desc { font-family: var(--sans); font-size: 13px; }95+.rv-meta { grid-column: 2 / 4; display: flex; flex-wrap: wrap; column-gap: 14px; row-gap: 2px; font: 12px/1.5 var(--mono); color: var(--muted); }96+.rv-meta time { font-size: 1em; }9797 .rv-meta .num { font-variant-numeric: tabular-nums; }9898 .rv-meta .add { color: var(--pass); }9999 .rv-meta .del { color: var(--fail); }100100 .rv-meta a, .rv-side a { position: relative; z-index: 1; }101-.rv.layer { margin-left: calc(var(--depth) * 22px); border-left: 2px solid var(--border); }102-a.rev { font-size: 0.85em; color: var(--muted); border: 1px solid var(--border); border-radius: 4px; padding: 0 4px; }101+.rv.layer { margin-left: calc(var(--depth) * 22px); border-left: 1px solid var(--border); }102+a.rev { font-size: 0.85em; color: var(--muted); border: 1px solid var(--border); padding: 0 4px; }103103 .appr { white-space: nowrap; font-weight: 600; color: var(--pass); }104104 .appr + .appr { margin-left: 8px; }105105 .appr.stale { color: var(--warn); font-weight: normal; }@@ -138,33 +138,38 @@ a.rev { font-size: 0.85em; color: var(--muted); border: 1px solid var(--border);
138138 </table>139139 {% endmacro %}140140 141-{# The index and owner pages: repos as cards on shelves by kind (the shelves141+{# The index and owner pages: repos as rows on shelves by kind (the shelves142142 filter), with chips that jump to a shelf. ui.ts makes the chips filters and143143 adds "has a site" and the sort; keys.ts walks the cards with j and k. #}144144 {% macro shelves(repos, owners = false) %}145145 {% css %}146146 .shelf-bar { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin: 0 0 4px; }147147 .shelf-bar .grow { flex: 1; }148-.chip { font: inherit; font-size: 0.85em; color: var(--muted); background: none; border: 1px solid var(--border); border-radius: 999px; padding: 2px 11px; cursor: pointer; white-space: nowrap; }148+.chip { font: inherit; font-size: 0.85em; color: var(--muted); background: none; border: 1px solid var(--border); padding: 2px 11px; cursor: pointer; white-space: nowrap; }149149 .chip:hover { color: var(--fg); background: var(--hover); text-decoration: none; }150150 .chip[aria-pressed="true"] { color: var(--fg); background: var(--hover); border-color: var(--faint); }151151 .chip .n { color: var(--faint); margin-left: 4px; font-variant-numeric: tabular-nums; }152-.shelf-head { display: flex; align-items: baseline; gap: 8px; margin: 22px 0 10px; scroll-margin-top: 12px; }153-.shelf-head h2 { margin: 0; font-size: 0.78rem; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); }154-.shelf-head .n { color: var(--faint); font-size: 0.78rem; }155-.shelf-head::after { content: ''; flex: 1; border-top: 1px solid var(--border); transform: translateY(-3px); }156-.cards { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 260px), 1fr)); gap: 10px; }157-.card { position: relative; display: flex; flex-direction: column; gap: 6px; min-width: 0; padding: 12px 14px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--subtle); }158-.card:hover, .card:focus-within, .card.sel { border-color: var(--faint); background: var(--hover); }152+.shelf-head { display: flex; align-items: baseline; gap: 8px; margin: 26px 0 0; padding-bottom: 6px; border-bottom: 1px solid var(--border); scroll-margin-top: 12px; }153+.shelf-head h2 { margin: 0; padding: 0; border: 0; }154+.shelf-head .n { font: 600 11px var(--mono); color: var(--faint); }155+/* Repos as rows between hairlines: name, what it is, then its sites, reviews and last push. */156+.cards { list-style: none; margin: 0; padding: 0; }157+.card { position: relative; display: grid; grid-template-columns: minmax(8em, 15em) 1fr auto; column-gap: 16px; align-items: baseline; min-width: 0; padding: 9px 8px; border-bottom: 1px solid var(--border); }158+.card:hover, .card:focus-within, .card.sel { background: var(--hover); }159159 .card.sel { box-shadow: inset 2px 0 0 var(--accent); }160-.card-top { display: flex; align-items: center; gap: 10px; min-width: 0; }161-.card-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 650; }162-.card-name::after { content: ''; position: absolute; inset: 0; border-radius: var(--radius); }160+.card-top { min-width: 0; }161+.card-name { display: block; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 650; }162+.card-name::after { content: ''; position: absolute; inset: 0; }163163 .card-name .desc { font-weight: normal; }164-.card-desc { margin: 0; color: var(--muted); font-size: 0.9em; overflow-wrap: anywhere; }165-.card-foot { display: flex; align-items: center; gap: 10px; margin-top: auto; padding-top: 2px; font-size: 0.8em; color: var(--faint); }164+.card-desc { grid-column: 2; margin: 0; color: var(--muted); font-size: 0.92em; overflow-wrap: anywhere; }165+.card-foot { grid-column: 3; display: flex; align-items: baseline; gap: 14px; font: 12px var(--mono); color: var(--faint); white-space: nowrap; }166166 .card-foot a { position: relative; z-index: 1; }167-.card-foot .grow { flex: 1; }167+.card-foot .grow { display: none; }168+.card-foot time { font-size: 1em; }169+@media (max-width: 720px) {170+ .card { grid-template-columns: 1fr; row-gap: 3px; }171+ .card-desc, .card-foot { grid-column: 1; }172+}168173 .shelves-empty { color: var(--muted); padding: 24px 0; text-align: center; }169174 {% endcss %}170175 {% set list = repos | shelves %}@@ -8,12 +8,13 @@
88 .search-open[hidden] { display: none; }99 .search-open kbd { font-size: 0.8em; line-height: 1.3; }1010 #search { position: fixed; inset: 0; z-index: 20; background: rgba(0, 0, 0, 0.3); }11-#search .search-box { position: absolute; top: max(8px, 8vh); left: 50%; transform: translateX(-50%); width: min(680px, calc(100vw - 16px)); background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius); padding: 8px; box-shadow: 0 16px 48px rgba(0, 0, 0, 0.25); }12-#search input { width: 100%; font: 16px var(--sans); padding: 8px 10px; border: 1px solid var(--border); border-radius: 6px; background: var(--subtle); color: var(--fg); }11+#search .search-box { position: absolute; top: max(8px, 8vh); left: 50%; transform: translateX(-50%); width: min(680px, calc(100vw - 16px)); background: var(--raised); border: 1px solid var(--border); padding: 8px; box-shadow: 0 16px 48px rgba(0, 0, 0, 0.2); }12+#search input { width: 100%; font: 16px var(--sans); padding: 8px 10px; border: 1px solid var(--border); background: var(--bg); color: var(--fg); }13+#search input:focus, #finder input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }1314 #search ol { list-style: none; margin: 4px 0 0; padding: 0; max-height: 65vh; overflow: auto; }14-#search li.group { padding: 10px 8px 3px; font-size: 0.75em; font-weight: 600; color: var(--muted); text-transform: uppercase; letter-spacing: 0.03em; }15+#search li.group { margin: 0 0 2px; padding: 12px 8px 4px; font: 600 10.5px/1.4 var(--mono); color: var(--faint); text-transform: uppercase; letter-spacing: 0.08em; border-bottom: 1px solid var(--border); }1516 #search li.group span { font-weight: normal; margin-left: 4px; }16-#search li a { display: block; padding: 5px 8px; border-radius: 4px; color: var(--fg); }17+#search li a { display: block; padding: 5px 8px; color: var(--fg); }1718 #search li a:hover { background: var(--hover); text-decoration: none; }1819 #search li.sel a, #search li.sel button { background: var(--hover); box-shadow: inset 2px 0 0 var(--accent); }1920 #search .t, #search .m { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }@@ -21,16 +22,16 @@
2122 #search .m.mono { font-family: var(--mono); white-space: pre; }2223 #search .m { font-size: 0.82em; color: var(--muted); }2324 #search b { color: var(--link); font-weight: 600; }24-#search li.more button { border: 0; background: none; color: var(--link); padding: 3px 8px; width: 100%; text-align: left; border-radius: 4px; }25+#search li.more button { border: 0; background: none; color: var(--link); padding: 3px 8px; width: 100%; text-align: left; }2526 #search li.note { padding: 5px 8px; font-size: 0.85em; color: var(--muted); }2627 #search .hint { margin: 8px 8px 2px; font-size: 0.85em; color: var(--muted); }2728 #search .hint:empty { display: none; }2829 /* file finder */29-#finder { position: fixed; top: 10%; left: 50%; transform: translateX(-50%); z-index: 20; width: min(640px, 94vw); background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius); padding: 8px; box-shadow: 0 16px 48px rgba(0, 0, 0, 0.25); }30-#finder input { width: 100%; font: 15px var(--mono); padding: 8px; border: 1px solid var(--border); border-radius: 6px; background: var(--subtle); color: var(--fg); }30+#finder { position: fixed; top: 10%; left: 50%; transform: translateX(-50%); z-index: 20; width: min(640px, 94vw); background: var(--raised); border: 1px solid var(--border); padding: 8px; box-shadow: 0 16px 48px rgba(0, 0, 0, 0.2); }31+#finder input { width: 100%; font: 15px var(--mono); padding: 8px; border: 1px solid var(--border); background: var(--bg); color: var(--fg); }3132 #finder ol { list-style: none; margin: 6px 0 0; padding: 0; max-height: 60vh; overflow: auto; }32-#finder li a { display: block; padding: 4px 8px; border-radius: 4px; font-family: var(--mono); font-size: 0.9em; color: var(--fg); }33-#finder li.sel a { background: var(--hover); }33+#finder li a { display: block; padding: 4px 8px; font-family: var(--mono); font-size: 0.9em; color: var(--fg); }34+#finder li.sel a { background: var(--hover); box-shadow: inset 2px 0 0 var(--accent); }3435 #finder li b { color: var(--link); }3536 {% endcss %}3637 <button type="button" class="search-open" hidden data-search="{{ r.urls.search.href if r else '/repos.json' }}"{% if o and o.owner %} data-owner="{{ o.owner }}"{% endif %}{% if r %} data-code="{{ assets.codesearch }}" data-sqlite="{{ assets.sqlite3 }}" data-wasm="{{ assets.sqlite3wasm }}"{% endif %} title="Search (/)" aria-label="Search"><svg viewBox="0 0 16 16" width="15" height="15" aria-hidden="true"><circle cx="6.8" cy="6.8" r="4.6" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M10.3 10.3 14 14" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg><kbd class="hide-sm">/</kbd></button>@@ -1,9 +1,10 @@
11 /* forge: the base stylesheet, light and dark, phone first: tokens, layout,22 lists, code views. CSS for one feature lives with its markup, in a33 template or include ({% css %}), and reaches only the pages that use it. */4-/* The palette: ink on a light grey page, one accent (#3148F6) for links,5- the current tab, focus and primary actions, and a faint paper texture6- behind it all (plaster in dark mode). */4+/* The look: square corners, content on the page between 1px rules, labels5+ in small uppercase monospace, one accent (#3148F6) for links, the current6+ tab's bar, focus and primary actions, and a faint paper texture behind it7+ all (plaster in dark mode). */78 :root {89 --bg: #e9e9e9;910 --fg: #1c2127;@@ -36,7 +37,6 @@
3637 --texture-opacity: 0.5;3738 --sans: "Ubuntu", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;3839 --mono: ui-monospace, SFMono-Regular, "SF Mono", "JetBrains Mono", Menlo, Consolas, monospace;39- --radius: 8px;4040 color-scheme: light dark;4141 }4242 @media (prefers-color-scheme: dark) {@@ -70,7 +70,7 @@
7070 }7171 }7272 73-* { box-sizing: border-box; }73+*, ::before, ::after { box-sizing: border-box; border-radius: 0; }7474 html { -webkit-text-size-adjust: 100%; }7575 body { margin: 0; background: var(--bg); color: var(--fg); font: 15px/1.5 var(--sans); }7676 /* The texture: CC0 scans (ambientCG), fixed to the window behind the@@ -79,33 +79,39 @@ body::before { content: ''; position: fixed; inset: 0; z-index: -1; pointer-even
7979 @media (prefers-reduced-transparency: reduce) { body::before { display: none; } }8080 a { color: var(--link); text-decoration: none; }8181 a:hover { color: var(--deep); text-decoration: underline; }82-:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; }83-code, kbd, pre, .mono, .sha { font-family: var(--mono); }82+:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }83+code, kbd, pre, time, .mono, .sha { font-family: var(--mono); }84+time { font-size: 0.92em; }8485 .sha { font-size: 0.9em; }8586 h1, h2, h3 { line-height: 1.25; }86-h2 { font-size: 1.15rem; margin: 1.6em 0 0.6em; }87+/* Section headings: small uppercase monospace on a rule. */88+h2, .label { font: 600 11px/1.4 var(--mono); letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); }89+h2 { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 8px; margin: 28px 0 0; padding-bottom: 6px; border-bottom: 1px solid var(--border); }90+h2 .desc, h2 a { font-weight: 600; }91+h2 .desc { color: var(--faint); }8792 h3 { font-size: 1rem; margin: 1.4em 0 0.5em; }8893 .desc, .muted { color: var(--muted); }8994 .i { color: var(--add-fg); }9095 .d { color: var(--del-fg); }9196 .wrap { max-width: 1280px; margin: 0 auto; padding: 0 16px; }92-kbd { border: 1px solid var(--border); border-bottom-width: 2px; border-radius: 4px; padding: 0 0.35em; font-size: 0.8em; color: var(--muted); }97+kbd { border: 1px solid var(--border); padding: 0 0.35em; font-size: 0.8em; color: var(--muted); background: var(--subtle); }9398 hr { border: 0; border-top: 1px solid var(--border); margin: 1.5em 0; }9499 95-/* header */100+/* header: on the page, over a rule that the current tab's bar sits on */96101 header.site { border-bottom: 1px solid var(--border); }97102 .top { display: flex; align-items: center; gap: 10px; padding: 12px 0 8px; min-width: 0; }98103 .logo { display: block; color: var(--fg); line-height: 0; flex: none; }99104 .mark { display: block; width: 28px; height: 28px; }100-.title { font-size: 1.05rem; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }105+.title { font: 1.05rem var(--mono); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }101106 .title a { color: var(--fg); }107+.title a:hover { color: var(--deep); }102108 .title b { font-weight: 650; }103109 .top .spacer { flex: 1; }104-.repo-desc { margin: 0 0 8px; color: var(--muted); }110+.repo-desc { margin: 0 0 10px; color: var(--muted); font-size: 0.93em; }105111 nav.tabs { display: flex; gap: 2px; overflow-x: auto; scrollbar-width: none; margin: 0 -16px; padding: 0 16px; }106112 nav.tabs::-webkit-scrollbar { display: none; }107113 nav.tabs a { color: var(--fg); padding: 8px 12px; border-bottom: 2px solid transparent; white-space: nowrap; }108-nav.tabs a:hover { text-decoration: none; background: var(--hover); border-radius: 6px 6px 0 0; }114+nav.tabs a:hover { text-decoration: none; background: var(--hover); }109115 nav.tabs a.cur { border-bottom-color: var(--accent); font-weight: 600; }110116 /* Tabs that don't fit scroll sideways. */111117 @media (max-width: 480px) {@@ -116,21 +122,21 @@ nav.tabs a.cur { border-bottom-color: var(--accent); font-weight: 600; }
116122 117123 /* clone */118124 details.clone { position: relative; flex: none; }119-details.clone > summary { list-style: none; cursor: pointer; border: 1px solid var(--border); border-radius: 6px; padding: 3px 10px; background: var(--bg); font-size: 0.9em; }125+details.clone > summary { list-style: none; cursor: pointer; border: 1px solid var(--border); padding: 3px 10px; background: var(--bg); font-size: 0.9em; }120126 details.clone > summary::-webkit-details-marker { display: none; }121-details.clone .panel { position: absolute; right: 0; top: calc(100% + 6px); z-index: 10; width: min(440px, 92vw); background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius); padding: 10px; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15); }127+details.clone .panel { position: absolute; right: 0; top: calc(100% + 6px); z-index: 10; width: min(440px, 92vw); background: var(--raised); border: 1px solid var(--border); padding: 10px; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12); }122128 .copyrow { display: flex; gap: 6px; align-items: center; margin: 4px 0; }123129 .clone-note { margin: 6px 0 0; font-size: 0.85em; color: var(--muted); }124-.private-mark { font-size: 0.75em; font-weight: 600; color: var(--warn); border: 1px solid currentColor; border-radius: 999px; padding: 0 7px; vertical-align: 2px; }130+.private-mark { font-size: 0.75em; font-weight: 600; color: var(--warn); border: 1px solid currentColor; padding: 0 7px; vertical-align: 2px; }125131 .private-mark:hover { text-decoration: none; }126-.copyrow label { width: 44px; color: var(--muted); font-size: 0.85em; }127-.copyrow input { flex: 1; min-width: 0; font: 13px var(--mono); padding: 4px 6px; border: 1px solid var(--border); border-radius: 6px; background: var(--subtle); color: var(--fg); }128-button, .btn { font: inherit; font-size: 0.85em; color: var(--fg); background: var(--subtle); border: 1px solid var(--border); border-radius: 6px; padding: 3px 10px; cursor: pointer; }132+.copyrow label { width: 44px; font: 600 10.5px var(--mono); letter-spacing: 0.08em; color: var(--faint); }133+.copyrow input { flex: 1; min-width: 0; font: 13px var(--mono); padding: 4px 6px; border: 1px solid var(--border); background: var(--bg); color: var(--fg); }134+button, .btn { font: inherit; font-size: 0.85em; color: var(--fg); background: var(--subtle); border: 1px solid var(--border); padding: 3px 10px; cursor: pointer; }129135 button:hover, .btn:hover { background: var(--hover); text-decoration: none; }130136 131137 /* live status */132138 #live { font-size: 0.8em; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 40vw; }133-#live span::before { content: ''; display: inline-block; width: 7px; height: 7px; border-radius: 50%; margin-right: 6px; vertical-align: 1px; }139+#live span::before { content: ''; display: inline-block; width: 7px; height: 7px; margin-right: 6px; vertical-align: 1px; }134140 #live .live-on::before { background: var(--pass); }135141 #live .live-off::before { background: var(--faint); }136142 #live .live-busy { color: var(--warn); }@@ -138,6 +144,7 @@ button:hover, .btn:hover { background: var(--hover); text-decoration: none; }
138144 @keyframes live-pulse { to { opacity: 0.2; } }139145 140146 main.wrap { padding-top: 20px; padding-bottom: 40px; }147+main.wrap > :first-child { margin-top: 0; }141148 footer.site { color: var(--muted); padding-bottom: 24px; font-size: 0.85em; }142149 button[aria-pressed="true"] { background: var(--hover); border-color: var(--faint); }143150 @@ -146,26 +153,35 @@ button[aria-pressed="true"] { background: var(--hover); border-color: var(--fain
146153 .bar .grow { flex: 1; }147154 .crumbs { font-family: var(--mono); font-size: 0.95em; overflow-wrap: anywhere; }148155 details.picker { position: relative; }149-details.picker > summary { list-style: none; cursor: pointer; border: 1px solid var(--border); border-radius: 6px; padding: 3px 10px; background: var(--subtle); font-size: 0.9em; }156+details.picker > summary { list-style: none; cursor: pointer; border: 1px solid var(--border); padding: 3px 10px; background: var(--subtle); font: 12.5px/1.5 var(--mono); }157+details.picker > summary:hover { background: var(--hover); border-color: var(--faint); }150158 details.picker > summary::-webkit-details-marker { display: none; }151-details.picker #branch-menu { position: absolute; z-index: 10; top: calc(100% + 4px); left: 0; min-width: 220px; max-height: 60vh; overflow: auto; background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius); padding: 6px; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15); display: flex; flex-direction: column; }152-details.picker #branch-menu a { padding: 4px 8px; border-radius: 4px; font-family: var(--mono); font-size: 0.9em; }159+details.picker #branch-menu { position: absolute; z-index: 10; top: calc(100% + 4px); left: 0; min-width: 220px; max-height: 60vh; overflow: auto; background: var(--raised); border: 1px solid var(--border); padding: 4px 0; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12); display: flex; flex-direction: column; }160+details.picker #branch-menu a { padding: 4px 12px; font-family: var(--mono); font-size: 0.9em; }153161 details.picker #branch-menu a:hover { background: var(--hover); text-decoration: none; }154162 155-/* lists (tables that restack on phones) */163+/* lists: tables without outer borders, rows between hairlines, headers in164+ small uppercase monospace; they restack on phones */156165 table.list { width: 100%; border-collapse: collapse; }157-table.list th, table.list td { text-align: left; padding: 7px 8px; border-bottom: 1px solid var(--border); vertical-align: top; }158-table.list thead th { font-size: 0.8em; font-weight: 600; color: var(--muted); text-transform: uppercase; letter-spacing: 0.03em; }166+table.list th, table.list td { text-align: left; padding: 7px 8px; border-bottom: 1px solid var(--border); vertical-align: baseline; }167+table.list thead th { font: 600 10.5px/1.4 var(--mono); letter-spacing: 0.08em; text-transform: uppercase; color: var(--faint); padding-top: 10px; padding-bottom: 6px; }159168 table.list .num { text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }169+table.list td.num, table.list td.date { font: 12.5px/1.6 var(--mono); }170+table.list td.date { color: var(--muted); }171+table.list td.date time { font-size: 1em; }160172 table.list .nowrap, table.list .date, table.list .sha, table.list td.mono { white-space: nowrap; }161173 table.list td.main { width: 100%; overflow-wrap: anywhere; }162174 table.list tbody tr:hover td, table.list tbody tr:focus-within td, table.list tbody tr.sel td { background: var(--hover); }163175 table.list tbody tr.sel td:first-child { box-shadow: inset 2px 0 0 var(--accent); }164-.box { border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }165-.box + .box { margin-top: 16px; }166-.box > table.list tr:last-child td { border-bottom: 0; }167-.box-head { background: var(--subtle); border-bottom: 1px solid var(--border); padding: 8px 12px; display: flex; flex-wrap: wrap; gap: 6px 12px; align-items: center; font-size: 0.92em; }176+/* A box holds a list or prose under a heading row; only a file viewer177+ (.viewer) is a bordered region. */178+.box + .box { margin-top: 24px; }179+.box-head { border-bottom: 1px solid var(--border); padding: 8px; display: flex; flex-wrap: wrap; gap: 6px 14px; align-items: baseline; font-size: 0.92em; }168180 .box-head .grow { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }181+.box-head .label { margin-right: auto; }182+.box.viewer { border: 1px solid var(--border); background: var(--raised); }183+.box.viewer > .box-head { padding: 6px 12px; align-items: center; }184+.box.viewer > table.list tr:last-child td { border-bottom: 0; }169185 .files td.icon { width: 1.5em; color: var(--muted); padding-right: 0; }170186 .files td.size { color: var(--muted); }171187 .empty { color: var(--muted); padding: 24px; text-align: center; }@@ -185,7 +201,6 @@ table.list tbody tr.sel td:first-child { box-shadow: inset 2px 0 0 var(--accent)
185201 table.list.stack td.main { order: 0; }186202 table.list.stack td.id { order: 1; }187203 .hide-sm { display: none; }188- h2 { font-size: 1.05rem; }189204 }190205 191206 /* statuses and CI */@@ -210,7 +225,7 @@ table.list tbody tr.sel td:first-child { box-shadow: inset 2px 0 0 var(--accent)
210225 scrolls under it: every gutter cell is opaque, its row's tint (translucent211226 in dark mode) layered over the code's background. --w is the widest line212227 number's digit count, set per file, so the columns never overlap. */213-.code { --lnw: max(3.5em, calc(var(--w, 3) * 1ch + 20px)); font: 12.5px/1.5 var(--mono); tab-size: 4; -moz-tab-size: 4; overflow-x: auto; border: 1px solid var(--border); border-radius: var(--radius); background: var(--raised); }228+.code { --lnw: max(3.5em, calc(var(--w, 3) * 1ch + 20px)); font: 12.5px/1.5 var(--mono); tab-size: 4; -moz-tab-size: 4; overflow-x: auto; border: 1px solid var(--border); background: var(--raised); }214229 .code .rows { display: inline-block; min-width: 100%; padding: 4px 0; }215230 .code .l { --gut: transparent; display: flex; color: var(--fg); text-decoration: none; }216231 .code a.l:hover { text-decoration: none; }@@ -239,17 +254,19 @@ body.wrap-lines .code .hunk { white-space: pre-wrap; overflow-wrap: anywhere; }
239254 body:not(.no-wrap) .code .lc, body:not(.no-wrap) .code .hunk { white-space: pre-wrap; overflow-wrap: anywhere; }240255 body:not(.no-wrap) .code .rows { display: block; }241256 }242-section.file { margin: 16px 0; border: 1px solid var(--border); border-radius: var(--radius); background: var(--raised); }243-.file > .code { border: 0; border-radius: 0 0 var(--radius) var(--radius); }244-.box > .code { border: 0; border-radius: 0; }245-.fh { position: sticky; top: 0; z-index: 2; display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 8px; padding: 8px 12px; background: var(--raised); border-bottom: 1px solid var(--border); border-radius: var(--radius) var(--radius) 0 0; font-size: 0.9em; }257+section.file { margin: 16px 0; border: 1px solid var(--border); background: var(--raised); }258+.file > .code { border: 0; }259+.box > .code { border: 0; }260+.fh { position: sticky; top: 0; z-index: 2; display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 8px; padding: 8px 12px; background: var(--raised); border-bottom: 1px solid var(--border); font-size: 0.9em; }246261 /* A long path shares the line with the status and counts, wrapping at its247262 slashes, rather than wrapping onto a line of its own. */248263 .fh .fname { font-family: var(--mono); color: var(--fg); font-weight: 600; overflow-wrap: break-word; flex: 1 1 0; min-width: min(4em, 100%); max-width: max-content; }249264 .fh .counts { font-family: var(--mono); font-size: 0.9em; }250-.fh .view { margin-left: auto; font-size: 0.9em; }265+.fh .st { font-size: 0.9em; }266+.fh .view { margin-left: auto; font: 600 10.5px var(--mono); letter-spacing: 0.08em; text-transform: uppercase; }251267 .file .note { margin: 0; padding: 10px 12px; color: var(--muted); }252268 table.diffstat td { padding: 3px 8px; }269+table.diffstat td.num { font-size: 12px; color: var(--muted); }253270 table.diffstat td.graph { font-family: var(--mono); white-space: nowrap; letter-spacing: -1px; }254271 table.diffstat td.main { overflow-wrap: break-word; }255272 table.diffstat td.main a { font-family: var(--mono); font-size: 0.92em; }@@ -299,9 +316,11 @@ table.diffstat td.main a { font-family: var(--mono); font-size: 0.92em; }
299316 .meta-head { margin: 0 0 16px; }300317 .meta-head h1 { font-size: 1.3rem; margin: 0 0 6px; overflow-wrap: anywhere; }301318 .meta-head .byline { color: var(--muted); font-size: 0.92em; }302-pre.msg { white-space: pre-wrap; overflow-wrap: anywhere; font: 13px/1.5 var(--mono); background: var(--subtle); border: 1px solid var(--border); border-radius: var(--radius); padding: 12px; margin: 12px 0; }303-details.att > summary, details.howto > summary { cursor: pointer; color: var(--muted); font-size: 0.92em; }304-details.att pre, details.howto pre { white-space: pre-wrap; overflow-wrap: anywhere; font: 12px/1.5 var(--mono); background: var(--subtle); border: 1px solid var(--border); border-radius: var(--radius); padding: 10px; }319+pre.msg { white-space: pre-wrap; overflow-wrap: anywhere; font: 13px/1.5 var(--mono); background: var(--subtle); border: 1px solid var(--border); padding: 12px; margin: 12px 0; }320+details.att > summary, details.howto > summary { cursor: pointer; color: var(--muted); font: 600 11px/1.4 var(--mono); letter-spacing: 0.08em; text-transform: uppercase; padding: 8px 0; }321+details.att > summary:hover, details.howto > summary:hover { color: var(--fg); }322+details.howto > summary .jev-sum { text-transform: none; letter-spacing: 0; font: 13px var(--sans); }323+details.att pre, details.howto pre { white-space: pre-wrap; overflow-wrap: anywhere; font: 12px/1.5 var(--mono); background: var(--subtle); border: 1px solid var(--border); padding: 10px; }305324 .depth { color: var(--faint); white-space: pre; }306325 307326 /* Above a diff: the diffstat, with the page's tools on its summary line. */@@ -319,40 +338,40 @@ details.att pre, details.howto pre { white-space: pre-wrap; overflow-wrap: anywh
319338 .fh .fold::before { content: "\25BE"; display: inline-block; transition: transform 0.1s; }320339 .file.folded .fold::before { transform: rotate(-90deg); }321340 .file.folded > :not(.fh) { display: none; }322-.file.folded > .fh { border-bottom: 0; border-radius: var(--radius); }341+.file.folded > .fh { border-bottom: 0; }323342 .file.folded:has(.viewed input:checked) .fname { color: var(--muted); font-weight: normal; }324-.fh .viewed { margin-left: auto; display: inline-flex; align-items: center; gap: 5px; padding: 1px 8px; border: 1px solid var(--border); border-radius: 6px; background: var(--bg); color: var(--muted); font-size: 0.9em; cursor: pointer; user-select: none; -webkit-user-select: none; }343+.fh .viewed { margin-left: auto; display: inline-flex; align-items: center; gap: 5px; padding: 1px 8px; border: 1px solid var(--border); background: var(--bg); color: var(--muted); font-size: 0.9em; cursor: pointer; user-select: none; -webkit-user-select: none; }325344 .fh .view ~ .viewed { margin-left: 0; }326345 .fh .viewed:has(input:checked) { color: var(--fg); }327346 .fh .viewed input { margin: 0; }328347 329348 /* prose: README, wiki, rendered markdown */330-.markdown { font-size: 1rem; line-height: 1.6; overflow-wrap: break-word; padding: 8px 24px 16px; }331-.box > .markdown { padding: 16px 24px; }349+.markdown { font-size: 1rem; line-height: 1.6; overflow-wrap: break-word; padding: 16px 8px; }332350 .markdown > :first-child { margin-top: 0; }333351 .markdown h1, .markdown h2 { border-bottom: 1px solid var(--border); padding-bottom: 0.3em; }334352 .markdown h1 { font-size: 1.8em; margin: 0.8em 0 0.5em; }335-.markdown h2 { font-size: 1.4em; margin: 1.4em 0 0.6em; }353+.markdown h2 { display: block; font: 600 1.4em/1.25 var(--sans); letter-spacing: normal; text-transform: none; color: var(--fg); margin: 1.4em 0 0.6em; }336354 .markdown h3 { font-size: 1.15em; }355+.markdown a:visited { color: var(--deep); }337356 .markdown img { max-width: 100%; height: auto; }338357 .markdown p[align="center"], .markdown div[align="center"] { text-align: center; }339-.markdown code { font-size: 0.875em; background: var(--subtle); border-radius: 4px; padding: 0.15em 0.35em; }340-.markdown pre { font: 13px/1.5 var(--mono); background: var(--subtle); border-radius: var(--radius); padding: 12px 14px; overflow-x: auto; tab-size: 4; }341-.markdown pre code { background: none; padding: 0; font-size: 1em; }342-.markdown pre.mermaid { background: none; text-align: center; }343-.markdown blockquote { margin: 1em 0; padding: 0 1em; color: var(--muted); border-left: 4px solid var(--border); }358+.markdown code { font-size: 0.875em; background: var(--subtle); border: 1px solid var(--border); padding: 0.05em 0.3em; }359+.markdown pre { font: 13px/1.5 var(--mono); background: var(--subtle); border: 1px solid var(--border); padding: 12px 14px; overflow-x: auto; tab-size: 4; }360+.markdown pre code { background: none; border: 0; padding: 0; font-size: 1em; }361+.markdown pre.mermaid { background: none; border: 0; text-align: center; }362+.markdown blockquote { margin: 1em 0; padding: 0 1em; color: var(--muted); border-left: 3px solid var(--border); }344363 .markdown table { display: block; max-width: 100%; overflow-x: auto; border-collapse: collapse; margin: 1em 0; }345-.markdown th, .markdown td { border: 1px solid var(--border); padding: 6px 12px; text-align: left; vertical-align: top; }346-.markdown th { background: var(--subtle); font-weight: 600; }347-.markdown hr { border-top: 2px solid var(--border); }364+.markdown th, .markdown td { border-bottom: 1px solid var(--border); padding: 6px 12px 6px 0; text-align: left; vertical-align: top; }365+.markdown th { font: 600 10.5px/1.4 var(--mono); letter-spacing: 0.08em; text-transform: uppercase; color: var(--faint); padding-top: 8px; }366+.markdown hr { border-top: 1px solid var(--border); }348367 .markdown :target { scroll-margin-top: 16px; }349368 @media (max-width: 720px) {350- .markdown, .box > .markdown { padding: 12px 14px; font-size: 0.97rem; }369+ .markdown { padding: 12px 4px; font-size: 0.97rem; }351370 .markdown h1 { font-size: 1.5em; }352371 .markdown h2 { font-size: 1.25em; }353372 }354373 /* jev: commits and repos */355-.jev-kind, .jev-topic { display: inline-block; padding: 0 6px; border: 1px solid var(--border); border-radius: 999px; font-size: 0.75rem; line-height: 1.5; font-weight: 500; color: var(--muted); white-space: nowrap; vertical-align: 1px; }374+.jev-kind, .jev-topic { display: inline-block; padding: 0 6px; border: 1px solid var(--border); font-size: 0.75rem; line-height: 1.5; font-weight: 500; color: var(--muted); white-space: nowrap; vertical-align: 1px; }356375 .jev-kind.feat { color: var(--pass); }357376 .jev-kind.fix { color: var(--merged); }358377 /* Jev's risk score, 0 to 3: a small number, colored once it is moderate. */@@ -371,16 +390,17 @@ table.log td.risk { width: 1%; }
371390 table.log td.kind { order: 1; }372391 table.log td.kind:empty { display: none; }373392 }374-.releases-link { margin-left: 8px; font-size: 0.8rem; font-weight: normal; }375-.notes { padding: 2px 16px 12px; font-size: 0.95em; }376-.notes h3 { margin: 12px 0 4px; font-size: 0.9rem; }393+.releases-link { margin-left: 4px; }394+.notes { padding: 2px 8px 12px; font-size: 0.95em; }395+.notes h3 { margin: 14px 0 4px; font: 600 10.5px/1.4 var(--mono); letter-spacing: 0.08em; text-transform: uppercase; color: var(--faint); }377396 .notes ul { margin: 0; padding-left: 20px; }378397 .notes li { margin: 2px 0; overflow-wrap: anywhere; }379398 .notes details { margin-top: 12px; }380-.notes summary { cursor: pointer; font-size: 0.9rem; font-weight: 600; color: var(--muted); }399+.notes summary { cursor: pointer; font: 600 10.5px/1.4 var(--mono); letter-spacing: 0.08em; text-transform: uppercase; color: var(--faint); }400+.notes summary:hover { color: var(--fg); }381401 382402 /* jev: review pages */383-.jev-mech, .jev-change { display: inline-block; padding: 0 6px; border: 1px dashed currentColor; border-radius: 999px; font-size: 0.75rem; line-height: 1.5; font-weight: 500; color: var(--muted); white-space: nowrap; }403+.jev-mech, .jev-change { display: inline-block; padding: 0 6px; border: 1px dashed currentColor; font-size: 0.75rem; line-height: 1.5; font-weight: 500; color: var(--muted); white-space: nowrap; }384404 .jev-change.behavior, .jev-sum.behavior { color: var(--warn); }385405 .jev-sum { font-size: 0.9em; color: var(--muted); }386406 summary .jev-sum { margin-left: 6px; font-weight: normal; }@@ -10,7 +10,7 @@ pagination:
1010 .image-view img { max-width: 100%; height: auto; }1111 {% endcss %}1212 <div class="bar">{{ m.branches(p.repo, p.branch.name, 'tree') }}{{ m.crumbs(p.repo, p.branch.name, p.crumbs) }}</div>13-<div class="box">13+<div class="box viewer">1414 <div class="box-head">1515 <span class="grow desc">{{ p.size | size }}{% if p.mode == '100755' %}, executable{% endif %}</span>1616 {% if p.markdown %}<a href="{{ p.sourceHref }}">Source</a>{% elif p.isSource %}<a href="{{ p.renderedHref }}">Rendered</a>{% endif %}@@ -12,8 +12,8 @@ pagination:
1212 {% endblock %}1313 {% block content %}1414 {% css %}15-dl.facts { display: grid; grid-template-columns: max-content 1fr; gap: 4px 16px; margin: 12px 0; font-size: 0.92em; }16-dl.facts dt { color: var(--muted); }15+dl.facts { display: grid; grid-template-columns: max-content 1fr; align-items: baseline; gap: 4px 16px; margin: 12px 0; font-size: 0.92em; }16+dl.facts dt { font: 600 10.5px/2.1 var(--mono); letter-spacing: 0.08em; text-transform: uppercase; color: var(--faint); }1717 dl.facts dd { margin: 0; overflow-wrap: anywhere; }1818 {% endcss %}1919 <div class="meta-head">@@ -9,7 +9,7 @@ pagination:
99 {% block content %}1010 {% if p.reviews.length %}1111 <div class="box">12-<div class="box-head"><b>Open reviews</b><span class="grow"></span><a href="{{ u.reviews.href }}">all reviews</a></div>12+<div class="box-head"><span class="label">Open reviews</span><a href="{{ u.reviews.href }}">all reviews</a></div>1313 {{ m.reviews(p.reviews, p.repo) }}1414 </div>1515 {% endif %}@@ -7,11 +7,11 @@ pagination:
77 {% block content %}88 {% css %}99 /* A release's files: name, size, sha256; then who signed SHA256SUMS. */10-.downloads { padding: 8px 16px 4px; border-bottom: 1px solid var(--border); }10+.downloads { padding: 8px 8px 4px; border-bottom: 1px solid var(--border); }1111 .downloads table { width: 100%; border-collapse: collapse; }1212 .downloads td { padding: 3px 12px 3px 0; vertical-align: top; }1313 .downloads td.file { white-space: nowrap; font-weight: 600; }14-.downloads td.size { white-space: nowrap; text-align: right; }14+.downloads td.size { white-space: nowrap; text-align: right; font: 12px var(--mono); }1515 .downloads td.sum { font-size: 0.8em; overflow-wrap: anywhere; width: 100%; }1616 .downloads p { margin: 6px 0 4px; font-size: 0.9em; }1717 {% endcss %}@@ -12,30 +12,33 @@ pagination:
1212 {% block content %}1313 {% css %}1414 /* Line comments under their lines; they look like the discussion's (discussion.njk). */15-.cmt { border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg); }16-.cmt:target { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }17-.cmt-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 8px; padding: 6px 12px; background: var(--subtle); border-bottom: 1px solid var(--border); border-radius: var(--radius) var(--radius) 0 0; font-size: 0.9em; }15+.cmt { border-top: 1px solid var(--border); }16+.cmt:target { box-shadow: inset 3px 0 0 var(--accent); }17+.cmt-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 10px; padding: 8px 12px 0; font-size: 0.9em; }18+.cmt-head a, .cmt-head .date { font: 12px var(--mono); }19+.cmt-head time { font-size: 1em; }1820 .cmt-head a { color: var(--muted); }19-.cmt .markdown { padding: 8px 12px; font-size: 0.95rem; line-height: 1.55; }21+.cmt .markdown { padding: 4px 12px 8px; font-size: 0.95rem; line-height: 1.55; }2022 .cmt .markdown > :last-child { margin-bottom: 0; }2123 /* In a diff, a comment is as wide as the code box, not its longest line. */2224 .code:has(.cmts) { container-type: inline-size; }23-.code .cmts { position: sticky; left: 0; width: 100cqw; padding: 8px 10px; background: var(--subtle); border-block: 1px solid var(--border); font: 15px/1.5 var(--sans); white-space: normal; }24-.cmt + .cmt { margin-top: 8px; }25+.code .cmts { position: sticky; left: 0; width: 100cqw; padding: 0 10px 8px; background: var(--bg); border-block: 1px solid var(--border); font: 15px/1.5 var(--sans); white-space: normal; }26+.code .cmts > .cmt:first-child { border-top: 0; }27+.code .cmts > form { padding-top: 8px; }2528 /* A new line comment, signed in (discuss.ts): the + on the line under the2629 pointer (or the line last clicked) opens a form under it. */2730 .commenting .code.diff .rows { position: relative; }28-.addline { position: absolute; z-index: 2; width: 20px; height: 17px; padding: 0; font: 600 13px/15px var(--sans); text-align: center; color: #fff; background: var(--accent); border-color: var(--accent); border-radius: 4px; }29-.addline:hover { background: var(--accent); filter: brightness(1.1); }31+.addline { position: absolute; z-index: 2; width: 20px; height: 17px; padding: 0; font: 600 13px/15px var(--sans); letter-spacing: 0; text-align: center; color: #fff; background: var(--accent); border-color: var(--accent); }32+.addline:hover { color: #fff; background: var(--deep); border-color: var(--deep); }3033 .cmts .where { font: 12px/1.6 var(--mono); color: var(--muted); margin-bottom: 4px; overflow-wrap: anywhere; }31-.cmts textarea { display: block; width: 100%; min-height: 5em; resize: vertical; font: 14px/1.5 var(--sans); color: var(--fg); background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius); padding: 8px 10px; }34+.cmts textarea { display: block; width: 100%; min-height: 5em; resize: vertical; font: 14px/1.5 var(--sans); color: var(--fg); background: var(--raised); border: 1px solid var(--border); padding: 8px 10px; }3235 .cmts textarea:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }3336 .cmts .reply-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 8px; margin-top: 8px; font-size: 0.92em; }3437 .cmts .reply-bar .grow { flex: 1; }3538 .cmts .reply-bar button.go { background: var(--accent); border-color: var(--accent); color: #fff; }3639 .cmts .reply-bar button:disabled { opacity: 0.6; cursor: default; }3740 .cmts .reply-status.bad { color: var(--fail); white-space: pre-line; }38-.cmt.pending { border-style: dashed; }41+.cmt.pending { border-top-style: dashed; }3942 .cmt.pending .text { padding: 8px 12px; white-space: pre-wrap; overflow-wrap: anywhere; color: var(--muted); }4043 {% endcss %}4144 <div class="meta-head">@@ -12,22 +12,22 @@ pagination:
1212 {% endblock %}1313 {% block content %}1414 {% css %}15-.review-desc { margin: 0 0 16px; }15+.review-desc { margin: 0 0 16px; border-block: 1px solid var(--border); }1616 .review-desc .markdown > :last-child { margin-bottom: 0; }1717 .stacked { margin-top: 28px; }18-.stacked > h3 { display: flex; flex-wrap: wrap; gap: 4px 8px; align-items: baseline; margin: 0 0 4px; font-size: 1rem; }19-.stacked > h3 .step { color: var(--muted); font-weight: normal; font-family: var(--mono); font-size: 0.9em; }18+.stacked > h3 { display: flex; flex-wrap: wrap; gap: 4px 10px; align-items: baseline; margin: 0 0 4px; padding-top: 12px; border-top: 1px solid var(--border); font-size: 1rem; }19+.stacked > h3 .step { color: var(--faint); font: 600 11px var(--mono); letter-spacing: 0.08em; }2020 #stack tr.this td { font-weight: 600; }2121 /* The sticky bar: layers of the stack, commits of the layer. It has a fixed2222 height so file headers can stick right below it. */23-.revnav { position: sticky; top: 0; z-index: 3; height: 2.75rem; display: flex; align-items: center; gap: 12px; margin: 28px 0 0; background: var(--bg); border-bottom: 1px solid var(--border); white-space: nowrap; overflow-x: auto; scrollbar-width: none; font-size: 0.9em; }23+.revnav { position: sticky; top: 0; z-index: 3; height: 2.75rem; display: flex; align-items: center; gap: 12px; margin: 28px 0 0; background: var(--bg); border-bottom: 1px solid var(--border); white-space: nowrap; overflow-x: auto; scrollbar-width: none; font: 12px var(--mono); }2424 .revnav::-webkit-scrollbar { display: none; }2525 @media (max-width: 480px) {2626 .revnav { gap: 8px; }2727 .revnav .seg > * { padding: 3px 8px; }2828 .revnav .n { display: none; }2929 }30-.revnav .seg { display: inline-flex; align-items: stretch; border: 1px solid var(--border); border-radius: 6px; overflow: hidden; }30+.revnav .seg { display: inline-flex; align-items: stretch; border: 1px solid var(--border); background: var(--subtle); overflow: hidden; }3131 .revnav .seg > * { padding: 3px 10px; }3232 .revnav .seg > .at { color: var(--fg); border-inline: 1px solid var(--border); font-variant-numeric: tabular-nums; }3333 .revnav .seg > a:hover { background: var(--hover); text-decoration: none; }@@ -8,12 +8,11 @@ pagination:
88 {% css %}99 /* Reviews by status: a heading per group, the list under it. Long merged1010 and closed groups start folded. */11-.rv-group { margin: 0 0 18px; }12-.rv-group > summary { display: flex; align-items: baseline; gap: 8px; margin: 0 0 8px; cursor: pointer; list-style: none; }11+.rv-group { margin: 0 0 24px; }12+.rv-group > summary { display: flex; align-items: baseline; gap: 8px; margin: 0; padding-bottom: 6px; border-bottom: 1px solid var(--border); cursor: pointer; list-style: none; }1313 .rv-group > summary::-webkit-details-marker { display: none; }14-.rv-group > summary h2 { margin: 0; font-size: 0.78rem; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); }15-.rv-group > summary .n { color: var(--faint); font-size: 0.78rem; }16-.rv-group > summary::after { content: ''; flex: 1; border-top: 1px solid var(--border); transform: translateY(-3px); }14+.rv-group > summary h2 { margin: 0; padding: 0; border: 0; }15+.rv-group > summary .n { font: 600 11px var(--mono); color: var(--faint); }1716 .rv-group:not([open]) > summary h2::before { content: '\25B8 '; }1817 {% endcss %}1918 {% if not p.groups.length %}@@ -11,15 +11,15 @@ pagination:
1111 {% block content %}1212 {% css %}1313 /* range-diff: a block per pair of commits */14-.pk { font-size: 0.75em; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; padding: 0 5px; border: 1px solid currentColor; border-radius: 4px; }14+.pk { font-size: 0.75em; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; padding: 0 5px; border: 1px solid currentColor; }1515 .pk.same { color: var(--muted); }1616 .pk.changed { color: var(--warn); }1717 .pk.add { color: var(--add-fg); }1818 .pk.del { color: var(--del-fg); }1919 .subj { font-weight: 600; overflow-wrap: anywhere; }20-.pairrow { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 8px; margin: 12px 0; padding: 8px 12px; border: 1px solid var(--border); border-radius: var(--radius); font-size: 0.9em; }21-details.pairs { margin: 12px 0; padding: 8px 12px; border: 1px solid var(--border); border-radius: var(--radius); font-size: 0.9em; }22-details.pairs > summary { cursor: pointer; color: var(--muted); }20+.pairrow { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px; margin: 0; padding: 8px; border-bottom: 1px solid var(--border); font-size: 0.9em; }21+details.pairs { margin: 0; padding: 8px; border-bottom: 1px solid var(--border); font-size: 0.9em; }22+details.pairs > summary { cursor: pointer; color: var(--muted); font: 600 11px/1.4 var(--mono); letter-spacing: 0.08em; text-transform: uppercase; }2323 details.pairs .pairrow { margin: 0; padding: 6px 0 0; border: 0; }2424 .pair .fh .subj { color: var(--fg); }2525 @@ -9,12 +9,15 @@ pagination:
99 {% css %}1010 /* wiki */1111 .wiki-layout { display: grid; grid-template-columns: 220px 1fr; gap: 24px; }12-.wiki-nav a { display: block; padding: 4px 8px; border-radius: 6px; color: var(--fg); }13-.wiki-nav a.cur { background: var(--hover); font-weight: 600; }12+.wiki-nav { border-top: 1px solid var(--border); }13+.wiki-nav a { display: block; padding: 6px 8px; border-bottom: 1px solid var(--border); color: var(--fg); }14+.wiki-nav a:hover { background: var(--hover); text-decoration: none; }15+.wiki-nav a.cur { font-weight: 600; box-shadow: inset 2px 0 0 var(--accent); }1416 @media (max-width: 720px) {1517 .wiki-layout { grid-template-columns: 1fr; gap: 8px; }16- .wiki-nav { display: flex; overflow-x: auto; gap: 4px; }17- .wiki-nav a { white-space: nowrap; }18+ .wiki-nav { display: flex; overflow-x: auto; gap: 16px; border-top: 0; border-bottom: 1px solid var(--border); }19+ .wiki-nav a { white-space: nowrap; padding: 6px 0; border: 0; }20+ .wiki-nav a.cur { box-shadow: inset 0 -2px 0 var(--accent); }1821 }1922 {% endcss %}2023 <div class="wiki-layout">3/6 Words, not chips: plain text for kinds, topics and filters
Jev's kind of a commit is small muted monospace in its column, and a repo's topic, the mechanical mark on a diff file, what changed between two versions of a commit, a comment's outdated or addressed note, and a range-diff's unchanged or rewritten are words in their colour, without the pill around them. The shelf filters on the index and owner pages are plain links, the chosen one bold over a thin accent bar, with their counts as faint numbers. A review's latest revision (r2) is a plain link, and "private" a coloured word. Only a review's status keeps a shape, where it matters: a small square label, white on its colour, on review pages, in branch and stack tables and in review lists, where it replaces the round dot. The live status says what it is in words, without a dot.
| M | README.md | 4 | +- |
| M | site/ | 2 | +- |
| M | site/ | 25 | +++++++---------- |
| M | site/ | 44 | ++++++++++++++---------------- |
| M | site/ | 1 | + |
| M | site/ | 2 | +- |
@@ -1242,11 +1242,11 @@ first build takes minutes.
12421242 12431243 ## Reading pages12441244 1245-The index and each owner's page show repos as cards on shelves by category,1245+The index and each owner's page show repos as rows on shelves by category,12461246 newest first. A repo's category is one line in the bare repo's `category`12471247 file, next to `description` (`printf 'games\n' > /srv/git/dami/greet.git/category`).12481248 A repo without one goes on the shelf for what Jev says it is (a web app, a1249-library), and failing that on "other". Chips above the shelves show one shelf1249+library), and failing that on "other". Links above the shelves show one shelf12501250 alone, only repos with a site, or sort by name.12511251 12521252 A review page reads top to bottom: the stack, the layer's commits, then each@@ -18,7 +18,7 @@ ol.comments { list-style: none; margin: 0; padding: 0; }
1818 .comment-head .date { font: 12px var(--mono); color: var(--muted); }1919 .comment-head .date time { font-size: 1em; }2020 .comment-head .rev { font: 12px var(--mono); color: var(--muted); }21-.comment .tag { font-size: 0.8em; font-weight: 600; color: var(--warn); border: 1px solid currentColor; padding: 0 7px; }21+.comment .tag { font: 600 10.5px var(--mono); letter-spacing: 0.08em; text-transform: uppercase; color: var(--warn); }2222 .comment .quote { margin: 8px 12px 0; border: 1px solid var(--border); background: var(--raised); overflow: hidden; }2323 .comment .quote .where { padding: 3px 10px; font: 12px/1.6 var(--mono); border-bottom: 1px solid var(--border); overflow-wrap: anywhere; }2424 .comment .quote pre { margin: 0; padding: 4px 10px; font: 12.5px/1.5 var(--mono); white-space: pre-wrap; overflow-wrap: anywhere; tab-size: 4; }@@ -83,11 +83,7 @@
8383 .rv { position: relative; display: grid; grid-template-columns: auto 1fr auto; column-gap: 10px; row-gap: 3px; align-items: baseline; padding: 10px 8px; border-bottom: 1px solid var(--border); }8484 .rv:hover, .rv.sel { background: var(--hover); }8585 .rv.sel { box-shadow: inset 2px 0 0 var(--accent); }86-.rv-mark { grid-row: 1 / 3; align-self: start; width: 9px; height: 9px; margin-top: 6px; background: var(--muted); }87-.rv-mark.open { background: var(--pass); }88-.rv-mark.draft { background: none; box-shadow: inset 0 0 0 2px var(--muted); }89-.rv-mark.merged { background: var(--merged); }90-.rv-mark.closed { background: var(--fail); }86+.rv > .status { grid-row: 1; }9187 .rv-title { min-width: 0; font-weight: 600; overflow-wrap: anywhere; }9288 .rv-title a::after { content: ''; position: absolute; inset: 0; }9389 .rv-side { display: flex; gap: 12px; align-items: baseline; justify-content: flex-end; white-space: nowrap; font: 12.5px var(--mono); }@@ -99,23 +95,22 @@
9995 .rv-meta .del { color: var(--fail); }10096 .rv-meta a, .rv-side a { position: relative; z-index: 1; }10197 .rv.layer { margin-left: calc(var(--depth) * 22px); border-left: 1px solid var(--border); }102-a.rev { font-size: 0.85em; color: var(--muted); border: 1px solid var(--border); padding: 0 4px; }10398 .appr { white-space: nowrap; font-weight: 600; color: var(--pass); }10499 .appr + .appr { margin-left: 8px; }105100 .appr.stale { color: var(--warn); font-weight: normal; }106101 @media (max-width: 720px) {107102 .rv { grid-template-columns: auto 1fr; }108103 .rv-side { grid-column: 2; justify-content: flex-start; }109- .rv-meta { grid-column: 2; }104+ .rv-meta { grid-column: 1 / 3; }110105 .rv.layer { margin-left: calc(var(--depth) * 12px); }111106 }112107 {% endcss %}113108 {% set last = r.revisions[r.revisions.length - 1] %}114109 <li class="rv{% if depth %} layer{% endif %}"{% if depth %} style="--depth: {{ depth }}"{% endif %}>115-<span class="rv-mark {{ r.status }}" title="{{ r.status }}"></span>110+{{ status(r.status) }}116111 <span class="rv-title"><a href="{{ repo.urls.review(r.id).href }}">{{ r.title }}</a></span>117112 <span class="rv-side">{{ reviewci(r.ci) }}{% if r.approvals.length %}<span>{{ approvals(r) }}</span>{% endif %}</span>118-<span class="rv-meta"><span class="num">#{{ r.id }}</span><span class="mono">{{ r.branch }} → {{ r.target }}</span>{% if r.author %}<span>{{ r.author }}</span>{% endif %}{% if r.commits.length %}<span>{{ r.commits.length | plural('commit') }}</span>{% endif %}{% if r.combinedStats and (r.combinedStats.add or r.combinedStats.del) %}<span class="num"><span class="add">+{{ r.combinedStats.add }}</span> <span class="del">−{{ r.combinedStats.del }}</span></span>{% endif %}<span class="date">{{ r.updated | ago }}</span>{% if last and last.view %}<a class="rev" href="{{ repo.urls.revision(r.id, last.n).href }}" title="what changed since r{{ last.prev }}">r{{ last.n }}</a>{% endif %}</span>113+<span class="rv-meta"><span class="num">#{{ r.id }}</span><span class="mono">{{ r.branch }} → {{ r.target }}</span>{% if r.author %}<span>{{ r.author }}</span>{% endif %}{% if r.commits.length %}<span>{{ r.commits.length | plural('commit') }}</span>{% endif %}{% if r.combinedStats and (r.combinedStats.add or r.combinedStats.del) %}<span class="num"><span class="add">+{{ r.combinedStats.add }}</span> <span class="del">−{{ r.combinedStats.del }}</span></span>{% endif %}<span class="date">{{ r.updated | ago }}</span>{% if last and last.view %}<a href="{{ repo.urls.revision(r.id, last.n).href }}" title="what changed since r{{ last.prev }}">r{{ last.n }}</a>{% endif %}</span>119114 </li>120115 {% endmacro %}121116 @@ -139,16 +134,16 @@ a.rev { font-size: 0.85em; color: var(--muted); border: 1px solid var(--border);
139134 {% endmacro %}140135 141136 {# The index and owner pages: repos as rows on shelves by kind (the shelves142- filter), with chips that jump to a shelf. ui.ts makes the chips filters and137+ filter), with links (.chip) that jump to a shelf. ui.ts makes them filters and143138 adds "has a site" and the sort; keys.ts walks the cards with j and k. #}144139 {% macro shelves(repos, owners = false) %}145140 {% css %}146-.shelf-bar { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin: 0 0 4px; }141+.shelf-bar { display: flex; flex-wrap: wrap; gap: 4px 18px; align-items: baseline; margin: 0 0 4px; }147142 .shelf-bar .grow { flex: 1; }148-.chip { font: inherit; font-size: 0.85em; color: var(--muted); background: none; border: 1px solid var(--border); padding: 2px 11px; cursor: pointer; white-space: nowrap; }149-.chip:hover { color: var(--fg); background: var(--hover); text-decoration: none; }150-.chip[aria-pressed="true"] { color: var(--fg); background: var(--hover); border-color: var(--faint); }151-.chip .n { color: var(--faint); margin-left: 4px; font-variant-numeric: tabular-nums; }143+.chip { font: inherit; font-size: 0.9em; letter-spacing: normal; text-transform: none; color: var(--link); background: none; border: 0; padding: 4px 0; cursor: pointer; white-space: nowrap; }144+.chip:hover { color: var(--deep); background: none; text-decoration: none; }145+.chip[aria-pressed="true"] { color: var(--fg); font-weight: 600; box-shadow: inset 0 -2px 0 var(--accent); }146+.chip .n { font: 11px var(--mono); color: var(--faint); margin-left: 5px; font-variant-numeric: tabular-nums; }152147 .shelf-head { display: flex; align-items: baseline; gap: 8px; margin: 26px 0 0; padding-bottom: 6px; border-bottom: 1px solid var(--border); scroll-margin-top: 12px; }153148 .shelf-head h2 { margin: 0; padding: 0; border: 0; }154149 .shelf-head .n { font: 600 11px var(--mono); color: var(--faint); }@@ -127,21 +127,19 @@ details.clone > summary::-webkit-details-marker { display: none; }
127127 details.clone .panel { position: absolute; right: 0; top: calc(100% + 6px); z-index: 10; width: min(440px, 92vw); background: var(--raised); border: 1px solid var(--border); padding: 10px; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12); }128128 .copyrow { display: flex; gap: 6px; align-items: center; margin: 4px 0; }129129 .clone-note { margin: 6px 0 0; font-size: 0.85em; color: var(--muted); }130-.private-mark { font-size: 0.75em; font-weight: 600; color: var(--warn); border: 1px solid currentColor; padding: 0 7px; vertical-align: 2px; }131-.private-mark:hover { text-decoration: none; }130+.private-mark { font: 600 10.5px var(--mono); letter-spacing: 0.08em; text-transform: uppercase; color: var(--warn); vertical-align: 2px; }131+.private-mark:hover { color: var(--warn); }132132 .copyrow label { width: 44px; font: 600 10.5px var(--mono); letter-spacing: 0.08em; color: var(--faint); }133133 .copyrow input { flex: 1; min-width: 0; font: 13px var(--mono); padding: 4px 6px; border: 1px solid var(--border); background: var(--bg); color: var(--fg); }134134 button, .btn { font: inherit; font-size: 0.85em; color: var(--fg); background: var(--subtle); border: 1px solid var(--border); padding: 3px 10px; cursor: pointer; }135135 button:hover, .btn:hover { background: var(--hover); text-decoration: none; }136136 137-/* live status */138-#live { font-size: 0.8em; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 40vw; }139-#live span::before { content: ''; display: inline-block; width: 7px; height: 7px; margin-right: 6px; vertical-align: 1px; }140-#live .live-on::before { background: var(--pass); }141-#live .live-off::before { background: var(--faint); }142-#live .live-busy { color: var(--warn); }143-#live .live-busy::before { background: var(--warn); animation: live-pulse 1s ease-in-out infinite alternate; }144-@keyframes live-pulse { to { opacity: 0.2; } }137+/* live status: words, no dot */138+#live { font: 11px var(--mono); color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 40vw; }139+#live .live-on { color: var(--pass); }140+#live .live-off { color: var(--faint); }141+#live .live-busy { color: var(--warn); animation: live-pulse 1s ease-in-out infinite alternate; }142+@keyframes live-pulse { to { opacity: 0.4; } }145143 146144 main.wrap { padding-top: 20px; padding-bottom: 40px; }147145 main.wrap > :first-child { margin-top: 0; }@@ -203,12 +201,13 @@ table.list tbody tr.sel td:first-child { box-shadow: inset 2px 0 0 var(--accent)
203201 .hide-sm { display: none; }204202 }205203 206-/* statuses and CI */207-.status { font-weight: 600; white-space: nowrap; }208-.status.open { color: var(--pass); }209-.status.draft { color: var(--muted); }210-.status.merged { color: var(--merged); }211-.status.closed { color: var(--fail); }204+/* statuses and CI. A review's status is the one filled shape: a small205+ square label, white on the status colour (the same in both modes, so the206+ white stays readable). */207+.status { display: inline-block; font: 600 10px/1.5 var(--mono); letter-spacing: 0.08em; text-transform: uppercase; color: #fff; background: #5b6169; padding: 0 6px; white-space: nowrap; vertical-align: 1px; }208+.status.open { background: #0e8420; }209+.status.merged { background: #3148f6; }210+.status.closed { background: #c7162b; }212211 .ci { white-space: nowrap; font-weight: 600; }213212 .ci[title] { cursor: help; }214213 .ci.pass { color: var(--pass); }@@ -371,18 +370,17 @@ details.att pre, details.howto pre { white-space: pre-wrap; overflow-wrap: anywh
371370 .markdown h2 { font-size: 1.25em; }372371 }373372 /* jev: commits and repos */374-.jev-kind, .jev-topic { display: inline-block; padding: 0 6px; border: 1px solid var(--border); font-size: 0.75rem; line-height: 1.5; font-weight: 500; color: var(--muted); white-space: nowrap; vertical-align: 1px; }375-.jev-kind.feat { color: var(--pass); }376-.jev-kind.fix { color: var(--merged); }373+/* What Jev says a commit or a repo is: plain words, quiet. */374+.jev-kind { font: 12px var(--mono); color: var(--muted); white-space: nowrap; }375+.jev-topic { color: var(--muted); white-space: nowrap; }377376 /* Jev's risk score, 0 to 3: a small number, colored once it is moderate. */378-.jev-score { font-size: 0.8rem; font-variant-numeric: tabular-nums; color: var(--faint); white-space: nowrap; }377+.jev-score { font: 12px var(--mono); font-variant-numeric: tabular-nums; color: var(--faint); white-space: nowrap; }379378 .jev-score.moderate { color: var(--warn); }380379 .jev-score.high { color: var(--fail); font-weight: 600; }381380 .jev-mismatch { font-size: 0.85em; color: var(--faint); cursor: help; }382381 .jev-note { font-size: 0.8rem; font-style: italic; color: var(--faint); }383382 /* The log: Jev's type in a narrow column on the left. */384383 table.log th.kind, table.log td.kind { width: 1%; white-space: nowrap; padding-right: 4px; }385-table.log td.kind .jev-kind { vertical-align: 0; }386384 table.log td.risk { width: 1%; }387385 @media (max-width: 720px) {388386 /* Stacked: the type leads the line under the subject. */@@ -400,8 +398,8 @@ table.log td.risk { width: 1%; }
400398 .notes summary:hover { color: var(--fg); }401399 402400 /* jev: review pages */403-.jev-mech, .jev-change { display: inline-block; padding: 0 6px; border: 1px dashed currentColor; font-size: 0.75rem; line-height: 1.5; font-weight: 500; color: var(--muted); white-space: nowrap; }401+.jev-mech, .jev-change { font: 12px var(--mono); color: var(--muted); white-space: nowrap; }404402 .jev-change.behavior, .jev-sum.behavior { color: var(--warn); }405403 .jev-sum { font-size: 0.9em; color: var(--muted); }406404 summary .jev-sum { margin-left: 6px; font-weight: normal; }407-.comment .tag.jev-addressed { color: var(--pass); border-style: dashed; }405+.comment .tag.jev-addressed { color: var(--pass); }@@ -17,6 +17,7 @@ pagination:
1717 .cmt-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 10px; padding: 8px 12px 0; font-size: 0.9em; }1818 .cmt-head a, .cmt-head .date { font: 12px var(--mono); }1919 .cmt-head time { font-size: 1em; }20+.cmt-head .tag { font: 600 10.5px var(--mono); letter-spacing: 0.08em; text-transform: uppercase; color: var(--warn); }2021 .cmt-head a { color: var(--muted); }2122 .cmt .markdown { padding: 4px 12px 8px; font-size: 0.95rem; line-height: 1.55; }2223 .cmt .markdown > :last-child { margin-bottom: 0; }@@ -11,7 +11,7 @@ pagination:
1111 {% block content %}1212 {% css %}1313 /* range-diff: a block per pair of commits */14-.pk { font-size: 0.75em; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; padding: 0 5px; border: 1px solid currentColor; }14+.pk { font: 600 10.5px var(--mono); text-transform: uppercase; letter-spacing: 0.08em; }1515 .pk.same { color: var(--muted); }1616 .pk.changed { color: var(--warn); }1717 .pk.add { color: var(--add-fg); }4/6 Tabs and buttons in small uppercase monospace
The repo tabs are small uppercase monospace on the header's rule, the current one in ink over a 3px accent bar. On a phone the ones that don't fit scroll sideways as before, and where scroll-driven animations work the edge that has more fades out, in place of the old shadows. Buttons are square and set the same way. Most are the page's raised surface with accent text, like Search; the one that does the thing is filled with ink: Clone, Comment, Sign in. Viewed boxes on diff files match them.
| M | site/ | 3 | -- |
| M | site/ | 4 | +- |
| M | site/ | 4 | +-- |
| M | site/ | 6 | ++-- |
| M | site/ | 42 | +++++++++++++++++++----------- |
| M | site/ | 2 | - |
@@ -33,9 +33,6 @@ ol.comments { list-style: none; margin: 0; padding: 0; }
3333 .reply textarea:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }3434 .reply-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 8px; margin-top: 8px; font-size: 0.92em; }3535 .reply-bar .grow { flex: 1; }36-.reply-bar button.go { background: var(--accent); border-color: var(--accent); color: #fff; }37-.reply-bar button.go:hover { filter: brightness(1.1); }38-.reply-bar button:disabled { opacity: 0.6; cursor: default; filter: none; }3936 .reply-status.bad { color: var(--fail); white-space: pre-line; }4037 {% endcss %}4138 <section class="discussion" id="discussion">@@ -1,7 +1,7 @@
11 {# The footer, with the button that lists the keyboard shortcuts in a dialog (keys.ts). -#}22 {% css %}3-footer.site .keyhelp { background: none; border: 0; padding: 0; color: var(--muted); font-size: 1em; }4-footer.site .keyhelp:hover { color: var(--fg); }3+footer.site .keyhelp { background: none; border: 0; padding: 0; color: var(--muted); }4+footer.site .keyhelp:hover { color: var(--fg); background: none; }55 @media (pointer: coarse) and (hover: none) { footer.site .keyhelp { display: none; } }66 /* keyboard shortcuts (keys.ts) */77 #help { width: min(440px, 94vw); padding: 0; background: var(--raised); color: var(--fg); border: 1px solid var(--border); box-shadow: 0 16px 48px rgba(0, 0, 0, 0.2); }@@ -15,9 +15,7 @@
1515 /* the sign-in page */1616 .login { padding: 0 0 16px; max-width: 40em; }1717 .login h1 { font-size: 1.3rem; margin: 4px 0 12px; }18-.login button.go { font-size: 1em; padding: 6px 14px; background: var(--accent); border-color: var(--accent); color: #fff; }19-.login button.go:hover { filter: brightness(1.1); background: var(--accent); }20-.login button.go:disabled { opacity: 0.6; cursor: default; }18+.login button.go { padding: 7px 14px; }2119 .login-status { min-height: 1.4em; }2220 .login-status.bad { color: var(--fail); }2321 .login form { display: inline; }@@ -4,9 +4,9 @@
44 someone searches file contents. -#}55 {% css %}66 /* search: the header button, and the overlay it and "/" open */7-.search-open { display: inline-flex; align-items: center; gap: 6px; flex: none; background: var(--bg); color: var(--muted); font-size: 0.9em; padding: 4px 9px; }7+.search-open { display: inline-flex; align-items: center; gap: 6px; flex: none; padding: 4px 9px; }88 .search-open[hidden] { display: none; }9-.search-open kbd { font-size: 0.8em; line-height: 1.3; }9+.search-open kbd { line-height: 1.3; color: inherit; border-color: currentColor; background: none; }1010 #search { position: fixed; inset: 0; z-index: 20; background: rgba(0, 0, 0, 0.3); }1111 #search .search-box { position: absolute; top: max(8px, 8vh); left: 50%; transform: translateX(-50%); width: min(680px, calc(100vw - 16px)); background: var(--raised); border: 1px solid var(--border); padding: 8px; box-shadow: 0 16px 48px rgba(0, 0, 0, 0.2); }1212 #search input { width: 100%; font: 16px var(--sans); padding: 8px 10px; border: 1px solid var(--border); background: var(--bg); color: var(--fg); }@@ -22,7 +22,7 @@
2222 #search .m.mono { font-family: var(--mono); white-space: pre; }2323 #search .m { font-size: 0.82em; color: var(--muted); }2424 #search b { color: var(--link); font-weight: 600; }25-#search li.more button { border: 0; background: none; color: var(--link); padding: 3px 8px; width: 100%; text-align: left; }25+#search li.more button { border: 0; background: none; padding: 5px 8px; width: 100%; text-align: left; }2626 #search li.note { padding: 5px 8px; font-size: 0.85em; color: var(--muted); }2727 #search .hint { margin: 8px 8px 2px; font-size: 0.85em; color: var(--muted); }2828 #search .hint:empty { display: none; }@@ -108,21 +108,35 @@ header.site { border-bottom: 1px solid var(--border); }
108108 .title b { font-weight: 650; }109109 .top .spacer { flex: 1; }110110 .repo-desc { margin: 0 0 10px; color: var(--muted); font-size: 0.93em; }111-nav.tabs { display: flex; gap: 2px; overflow-x: auto; scrollbar-width: none; margin: 0 -16px; padding: 0 16px; }111+nav.tabs { display: flex; gap: 22px; overflow-x: auto; scrollbar-width: none; font: 600 11px/1.4 var(--mono); letter-spacing: 0.08em; text-transform: uppercase; }112112 nav.tabs::-webkit-scrollbar { display: none; }113-nav.tabs a { color: var(--fg); padding: 8px 12px; border-bottom: 2px solid transparent; white-space: nowrap; }114-nav.tabs a:hover { text-decoration: none; background: var(--hover); }115-nav.tabs a.cur { border-bottom-color: var(--accent); font-weight: 600; }116-/* Tabs that don't fit scroll sideways. */113+nav.tabs a { color: var(--muted); padding: 9px 0 10px; white-space: nowrap; }114+nav.tabs a:hover { color: var(--fg); text-decoration: none; }115+nav.tabs a.cur { color: var(--fg); box-shadow: inset 0 -3px 0 var(--accent); }117116 @media (max-width: 480px) {118- /* Six tabs fit a 360px phone: tighter, and sharing the width. */119- nav.tabs { gap: 0; padding: 0 8px; }120- nav.tabs a { flex: 1 0 auto; text-align: center; padding: 8px 6px; font-size: 14px; }117+ nav.tabs { gap: 16px; }121118 }119+/* Tabs that don't fit scroll sideways; the edge that has more fades out. */120+@property --tabs-l { syntax: "<length>"; inherits: false; initial-value: 0px; }121+@property --tabs-r { syntax: "<length>"; inherits: false; initial-value: 0px; }122+@supports (animation-timeline: scroll()) {123+ nav.tabs { mask-image: linear-gradient(to right, transparent, #000 var(--tabs-l), #000 calc(100% - var(--tabs-r)), transparent); animation: tabs-edge linear both; animation-timeline: scroll(self inline); }124+ @keyframes tabs-edge { from { --tabs-l: 0px; --tabs-r: 32px; } to { --tabs-l: 32px; --tabs-r: 0px; } }125+}126+127+/* buttons: square, small uppercase monospace; secondary is the page's128+ surface with accent text, primary (.go) is filled with ink */129+button, .btn, details.clone > summary { font: 600 11px/1.4 var(--mono); letter-spacing: 0.08em; text-transform: uppercase; color: var(--link); background: var(--subtle); border: 1px solid var(--border); padding: 5px 10px; cursor: pointer; }130+button:hover, .btn:hover { color: var(--deep); background: var(--hover); border-color: var(--faint); text-decoration: none; }131+button[aria-pressed="true"] { color: var(--fg); border-color: var(--fg); }132+button.go, details.clone > summary { color: var(--bg); background: var(--fg); border-color: var(--fg); }133+button.go:hover, details.clone > summary:hover { color: var(--bg); background: var(--muted); border-color: var(--muted); }134+button:disabled { opacity: 0.6; cursor: default; }135+button kbd { font-size: 1em; padding: 0 0.3em; text-transform: none; }122136 123137 /* clone */124138 details.clone { position: relative; flex: none; }125-details.clone > summary { list-style: none; cursor: pointer; border: 1px solid var(--border); padding: 3px 10px; background: var(--bg); font-size: 0.9em; }139+details.clone > summary { list-style: none; }126140 details.clone > summary::-webkit-details-marker { display: none; }127141 details.clone .panel { position: absolute; right: 0; top: calc(100% + 6px); z-index: 10; width: min(440px, 92vw); background: var(--raised); border: 1px solid var(--border); padding: 10px; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12); }128142 .copyrow { display: flex; gap: 6px; align-items: center; margin: 4px 0; }@@ -131,8 +145,6 @@ details.clone .panel { position: absolute; right: 0; top: calc(100% + 6px); z-in
131145 .private-mark:hover { color: var(--warn); }132146 .copyrow label { width: 44px; font: 600 10.5px var(--mono); letter-spacing: 0.08em; color: var(--faint); }133147 .copyrow input { flex: 1; min-width: 0; font: 13px var(--mono); padding: 4px 6px; border: 1px solid var(--border); background: var(--bg); color: var(--fg); }134-button, .btn { font: inherit; font-size: 0.85em; color: var(--fg); background: var(--subtle); border: 1px solid var(--border); padding: 3px 10px; cursor: pointer; }135-button:hover, .btn:hover { background: var(--hover); text-decoration: none; }136148 137149 /* live status: words, no dot */138150 #live { font: 11px var(--mono); color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 40vw; }@@ -144,7 +156,6 @@ button:hover, .btn:hover { background: var(--hover); text-decoration: none; }
144156 main.wrap { padding-top: 20px; padding-bottom: 40px; }145157 main.wrap > :first-child { margin-top: 0; }146158 footer.site { color: var(--muted); padding-bottom: 24px; font-size: 0.85em; }147-button[aria-pressed="true"] { background: var(--hover); border-color: var(--faint); }148159 149160 /* toolbars above lists and code */150161 .bar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 0 0 12px; }@@ -333,16 +344,17 @@ details.att pre, details.howto pre { white-space: pre-wrap; overflow-wrap: anywh
333344 334345 /* Diff files fold (diffs.ts); review pages add a Viewed box. */335346 .fh.foldable { cursor: pointer; }336-.fh .fold { border: 0; background: none; padding: 0; width: 1.1em; color: var(--muted); font-size: 1.2em; line-height: 1; }347+.fh .fold { border: 0; background: none; padding: 0; width: 1.1em; color: var(--muted); font: 1.2em/1 var(--sans); letter-spacing: 0; }348+.fh .fold:hover { background: none; color: var(--fg); }337349 .fh .fold::before { content: "\25BE"; display: inline-block; transition: transform 0.1s; }338350 .file.folded .fold::before { transform: rotate(-90deg); }339351 .file.folded > :not(.fh) { display: none; }340352 .file.folded > .fh { border-bottom: 0; }341353 .file.folded:has(.viewed input:checked) .fname { color: var(--muted); font-weight: normal; }342-.fh .viewed { margin-left: auto; display: inline-flex; align-items: center; gap: 5px; padding: 1px 8px; border: 1px solid var(--border); background: var(--bg); color: var(--muted); font-size: 0.9em; cursor: pointer; user-select: none; -webkit-user-select: none; }354+.fh .viewed { margin-left: auto; display: inline-flex; align-items: center; gap: 5px; padding: 2px 8px; border: 1px solid var(--border); background: var(--bg); color: var(--muted); font: 600 10.5px/1.4 var(--mono); letter-spacing: 0.08em; text-transform: uppercase; cursor: pointer; user-select: none; -webkit-user-select: none; }343355 .fh .view ~ .viewed { margin-left: 0; }344356 .fh .viewed:has(input:checked) { color: var(--fg); }345-.fh .viewed input { margin: 0; }357+.fh .viewed input { margin: 0; accent-color: var(--accent); }346358 347359 /* prose: README, wiki, rendered markdown */348360 .markdown { font-size: 1rem; line-height: 1.6; overflow-wrap: break-word; padding: 16px 8px; }@@ -36,8 +36,6 @@ pagination:
3636 .cmts textarea:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }3737 .cmts .reply-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 8px; margin-top: 8px; font-size: 0.92em; }3838 .cmts .reply-bar .grow { flex: 1; }39-.cmts .reply-bar button.go { background: var(--accent); border-color: var(--accent); color: #fff; }40-.cmts .reply-bar button:disabled { opacity: 0.6; cursor: default; }4139 .cmts .reply-status.bad { color: var(--fail); white-space: pre-line; }4240 .cmt.pending { border-top-style: dashed; }4341 .cmt.pending .text { padding: 8px 12px; white-space: pre-wrap; overflow-wrap: anywhere; color: var(--muted); }5/6 Keep a range-diff's +/- marker at the left edge
A range-diff's lines have no line numbers, but their marker stuck where it does in a diff, after two columns of them, and covered the code there: "hello=hello" read "he o". It sticks at the left edge instead.
| M | site/ | 2 | ++ |
@@ -23,6 +23,8 @@ details.pairs > summary { cursor: pointer; color: var(--muted); font: 600 11px/1
2323 details.pairs .pairrow { margin: 0; padding: 6px 0 0; border: 0; }2424 .pair .fh .subj { color: var(--fg); }2525 26+/* No line numbers here: the marker sticks at the left edge, not after them. */27+.code.range .mk { left: 0; }2628 .code.range .l.sect .lc { font-weight: 600; }2729 .code.range .lc .i { color: var(--add-fg); font-weight: 600; }2830 .code.range .lc .d { color: var(--del-fg); font-weight: 600; }6/6 Paper in both modes, seamless and fainter; the logo in the palette
The dark plaster read as camouflage repeating every 256px, and the paper scan had a rough band at its right edge that tiled into a vertical streak. Both modes now use the paper: the band is cropped off, and the tile is made seamless the usual way, blended with a copy offset by half its size so its edges meet; it is shown at its own size, 448 by 300, and a third fainter than before (0.35 light, 0.32 dark). A dark-range copy of the same tile is screened onto the dark page. The plaster is gone. The logo's mark is the accent, not the old teal.
| M | lib/ | 2 | +- |
| M | site/ | 2 | +- |
| M | site/ | 2 | +- |
| A | site/ | bin | |
| M | site/ | bin | |
| D | site/ | bin | |
| M | site/ | 11 | +++++------ |
@@ -43,7 +43,7 @@ export interface Asset {
4343 data: Buffer;4444 }4545 46-// By short name: style, logo, paper, plaster, fixi, ssexi, diffs, keys, live,46+// By short name: style, logo, paper, paper-dark, fixi, ssexi, diffs, keys, live,4747 // search, query, codesearch, login, ui, mermaid, sqlite3, sqlite3wasm, webauthn.4848 export type Assets = Record<string, Asset>;4949 @@ -1 +1 @@
1-<svg class="mark" viewBox="0 0 24 24" width="32" height="32" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9" stroke="currentColor"/><path d="M16 15 12 7 8 15Zm-1 0L11.5 8" stroke="#2ca9bc"/></svg>1+<svg class="mark" viewBox="0 0 24 24" width="32" height="32" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9" stroke="currentColor"/><path d="M16 15 12 7 8 15Zm-1 0L11.5 8" stroke="var(--accent)"/></svg>@@ -1 +1 @@
1-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><style>circle{stroke:#000}@media (prefers-color-scheme:dark){circle{stroke:#bdbdbd}}</style><circle cx="12" cy="12" r="9"/><path d="M16 15 12 7 8 15Zm-1 0L11.5 8" stroke="#2ca9bc"/></svg>1+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><style>circle{stroke:#1c2127}path{stroke:#3148f6}@media (prefers-color-scheme:dark){circle{stroke:#e9e9e9}path{stroke:#8e9bff}}</style><circle cx="12" cy="12" r="9"/><path d="M16 15 12 7 8 15Zm-1 0L11.5 8"/></svg>Binary file.
Binary file.
Binary file.
@@ -4,7 +4,7 @@
44 /* The look: square corners, content on the page between 1px rules, labels55 in small uppercase monospace, one accent (#3148F6) for links, the current66 tab's bar, focus and primary actions, and a faint paper texture behind it7- all (plaster in dark mode). */7+ all, the same paper in dark mode. */88 :root {99 --bg: #e9e9e9;1010 --fg: #1c2127;@@ -32,9 +32,9 @@
3232 --warn: #b46a00;3333 --merged: #3148f6;3434 --texture: url(./paper.webp);35- --texture-size: 256px 150px;35+ --texture-size: 448px 300px;3636 --texture-blend: multiply;37- --texture-opacity: 0.5;37+ --texture-opacity: 0.35;3838 --sans: "Ubuntu", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;3939 --mono: ui-monospace, SFMono-Regular, "SF Mono", "JetBrains Mono", Menlo, Consolas, monospace;4040 color-scheme: light dark;@@ -63,10 +63,9 @@
6363 --fail: #f0606e;6464 --warn: #e3a33b;6565 --merged: #8e9bff;66- --texture: url(./plaster.webp);67- --texture-size: 256px 256px;66+ --texture: url(./paper-dark.webp);6867 --texture-blend: screen;69- --texture-opacity: 0.3;68+ --texture-opacity: 0.32;7069 }7170 }7271