| M | README.md | 7 | +- |
| M | lib/ | 25 | +- |
| M | lib/ | 2 | +- |
| M | site/ | 31 | +- |
| M | site/ | 10 | +- |
| M | site/ | 41 | ++-- |
| M | site/ | 6 | +- |
| M | site/ | 2 | +- |
| M | site/ | 70 | +++--- |
| M | site/ | 23 | +- |
| M | site/ | 2 | +- |
| A | site/ | bin | |
| A | site/ | bin | |
| M | site/ | 326 | +++++++++++++++++------------- |
| M | site/ | 2 | +- |
| M | site/ | 4 | +- |
| M | site/ | 2 | +- |
| M | site/ | 4 | +- |
| M | site/ | 26 | +- |
| M | site/ | 10 | +- |
| M | site/ | 9 | +- |
| M | site/ | 10 | +- |
| M | site/ | 11 | +- |
@@ -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@@ -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, paper-dark, 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 }@@ -5,35 +5,34 @@
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: 600 10.5px var(--mono); letter-spacing: 0.08em; text-transform: uppercase; color: var(--warn); }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; }34-.reply-bar button.go { background: var(--accent); border-color: var(--accent); color: #fff; }35-.reply-bar button.go:hover { filter: brightness(1.1); }36-.reply-bar button:disabled { opacity: 0.6; cursor: default; filter: none; }3736 .reply-status.bad { color: var(--fail); white-space: pre-line; }3837 {% endcss %}3938 <section class="discussion" id="discussion">@@ -1,16 +1,16 @@
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) */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; }@@ -9,32 +9,33 @@
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; }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,11 +13,9 @@
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; }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; }@@ -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>@@ -80,42 +80,37 @@
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); }88-.rv-mark.open { background: var(--pass); }89-.rv-mark.draft { background: none; box-shadow: inset 0 0 0 2px var(--muted); }90-.rv-mark.merged { background: var(--merged); }91-.rv-mark.closed { background: var(--fail); }86+.rv > .status { grid-row: 1; }9287 .rv-title { min-width: 0; font-weight: 600; overflow-wrap: anywhere; }9388 .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; }89+.rv-side { display: flex; gap: 12px; align-items: baseline; justify-content: flex-end; white-space: nowrap; font: 12.5px var(--mono); }90+.rv-side .desc { font-family: var(--sans); font-size: 13px; }91+.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); }92+.rv-meta time { font-size: 1em; }9793 .rv-meta .num { font-variant-numeric: tabular-nums; }9894 .rv-meta .add { color: var(--pass); }9995 .rv-meta .del { color: var(--fail); }10096 .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; }97+.rv.layer { margin-left: calc(var(--depth) * 22px); border-left: 1px solid var(--border); }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 @@ -138,33 +133,38 @@ a.rev { font-size: 0.85em; color: var(--muted); border: 1px solid var(--border);
138133 </table>139134 {% endmacro %}140135 141-{# The index and owner pages: repos as cards on shelves by kind (the shelves142- filter), with chips that jump to a shelf. ui.ts makes the chips filters and136+{# The index and owner pages: repos as rows on shelves by kind (the shelves137+ 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); border-radius: 999px; 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; }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); }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; }147+.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; }148+.shelf-head h2 { margin: 0; padding: 0; border: 0; }149+.shelf-head .n { font: 600 11px var(--mono); color: var(--faint); }150+/* Repos as rows between hairlines: name, what it is, then its sites, reviews and last push. */151+.cards { list-style: none; margin: 0; padding: 0; }152+.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); }153+.card:hover, .card:focus-within, .card.sel { background: var(--hover); }159154 .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); }155+.card-top { min-width: 0; }156+.card-name { display: block; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 650; }157+.card-name::after { content: ''; position: absolute; inset: 0; }163158 .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); }159+.card-desc { grid-column: 2; margin: 0; color: var(--muted); font-size: 0.92em; overflow-wrap: anywhere; }160+.card-foot { grid-column: 3; display: flex; align-items: baseline; gap: 14px; font: 12px var(--mono); color: var(--faint); white-space: nowrap; }166161 .card-foot a { position: relative; z-index: 1; }167-.card-foot .grow { flex: 1; }162+.card-foot .grow { display: none; }163+.card-foot time { font-size: 1em; }164+@media (max-width: 720px) {165+ .card { grid-template-columns: 1fr; row-gap: 3px; }166+ .card-desc, .card-foot { grid-column: 1; }167+}168168 .shelves-empty { color: var(--muted); padding: 24px 0; text-align: center; }169169 {% endcss %}170170 {% set list = repos | shelves %}@@ -4,16 +4,17 @@
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); }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; padding: 5px 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 +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.
@@ -1,158 +1,195 @@
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 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, the same paper in dark mode. */48 :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;28- --radius: 8px;9+ --bg: #e9e9e9;10+ --fg: #1c2127;11+ --muted: #5b6169;12+ --faint: #8a9097;13+ --border: #d2d2d2;14+ /* A raised surface over the page, translucent, and the same as one opaque15+ colour, for what content scrolls under (sticky headers, code gutters). */16+ --subtle: rgba(255, 255, 255, 0.55);17+ --raised: #f5f5f5;18+ --hover: rgba(255, 255, 255, 0.7);19+ --link: #3148f6;20+ --accent: #3148f6;21+ --deep: #1c4b8f;22+ --add-bg: rgba(14, 132, 32, 0.1);23+ --add-fg: #0e8420;24+ --add-ln: rgba(14, 132, 32, 0.2);25+ --del-bg: rgba(199, 22, 43, 0.08);26+ --del-fg: #c7162b;27+ --del-ln: rgba(199, 22, 43, 0.17);28+ --hunk-bg: rgba(49, 72, 246, 0.07);29+ --hunk-fg: #1c4b8f;30+ --pass: #0e8420;31+ --fail: #c7162b;32+ --warn: #b46a00;33+ --merged: #3148f6;34+ --texture: url(./paper.webp);35+ --texture-size: 448px 300px;36+ --texture-blend: multiply;37+ --texture-opacity: 0.35;38+ --sans: "Ubuntu", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;39+ --mono: ui-monospace, SFMono-Regular, "SF Mono", "JetBrains Mono", Menlo, Consolas, monospace;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(./paper-dark.webp);67+ --texture-blend: screen;68+ --texture-opacity: 0.32;5369 }5470 }5571 56-* { box-sizing: border-box; }72+*, ::before, ::after { box-sizing: border-box; border-radius: 0; }5773 html { -webkit-text-size-adjust: 100%; }5874 body { margin: 0; background: var(--bg); color: var(--fg); font: 15px/1.5 var(--sans); }75+/* The texture: CC0 scans (ambientCG), fixed to the window behind the76+ content, so scrolling never repaints it. */77+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); }78+@media (prefers-reduced-transparency: reduce) { body::before { display: none; } }5979 a { color: var(--link); text-decoration: none; }60-a:hover { text-decoration: underline; }61-:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; }62-code, kbd, pre, .mono, .sha { font-family: var(--mono); }80+a:hover { color: var(--deep); text-decoration: underline; }81+:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }82+code, kbd, pre, time, .mono, .sha { font-family: var(--mono); }83+time { font-size: 0.92em; }6384 .sha { font-size: 0.9em; }6485 h1, h2, h3 { line-height: 1.25; }65-h2 { font-size: 1.15rem; margin: 1.6em 0 0.6em; }86+/* Section headings: small uppercase monospace on a rule. */87+h2, .label { font: 600 11px/1.4 var(--mono); letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); }88+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); }89+h2 .desc, h2 a { font-weight: 600; }90+h2 .desc { color: var(--faint); }6691 h3 { font-size: 1rem; margin: 1.4em 0 0.5em; }6792 .desc, .muted { color: var(--muted); }6893 .i { color: var(--add-fg); }6994 .d { color: var(--del-fg); }7095 .wrap { max-width: 1280px; margin: 0 auto; padding: 0 16px; }71-kbd { border: 1px solid var(--border); border-bottom-width: 2px; border-radius: 4px; padding: 0 0.35em; font-size: 0.8em; color: var(--muted); }96+kbd { border: 1px solid var(--border); padding: 0 0.35em; font-size: 0.8em; color: var(--muted); background: var(--subtle); }7297 hr { border: 0; border-top: 1px solid var(--border); margin: 1.5em 0; }7398 74-/* header */75-header.site { background: var(--subtle); border-bottom: 1px solid var(--border); }99+/* header: on the page, over a rule that the current tab's bar sits on */100+header.site { border-bottom: 1px solid var(--border); }76101 .top { display: flex; align-items: center; gap: 10px; padding: 12px 0 8px; min-width: 0; }77102 .logo { display: block; color: var(--fg); line-height: 0; flex: none; }78103 .mark { display: block; width: 28px; height: 28px; }79-.title { font-size: 1.05rem; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }104+.title { font: 1.05rem var(--mono); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }80105 .title a { color: var(--fg); }106+.title a:hover { color: var(--deep); }81107 .title b { font-weight: 650; }82108 .top .spacer { flex: 1; }83-.repo-desc { margin: 0 0 8px; color: var(--muted); }84-nav.tabs { display: flex; gap: 2px; overflow-x: auto; scrollbar-width: none; margin: 0 -16px; padding: 0 16px; }109+.repo-desc { margin: 0 0 10px; color: var(--muted); font-size: 0.93em; }110+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; }85111 nav.tabs::-webkit-scrollbar { display: none; }86-nav.tabs a { color: var(--fg); padding: 8px 12px; border-bottom: 2px solid transparent; white-space: nowrap; }87-nav.tabs a:hover { text-decoration: none; background: var(--hover); border-radius: 6px 6px 0 0; }88-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-}112+nav.tabs a { color: var(--muted); padding: 9px 0 10px; white-space: nowrap; }113+nav.tabs a:hover { color: var(--fg); text-decoration: none; }114+nav.tabs a.cur { color: var(--fg); box-shadow: inset 0 -3px 0 var(--accent); }98115 @media (max-width: 480px) {99- /* Six tabs fit a 360px phone: tighter, and sharing the width. */100- nav.tabs { gap: 0; padding: 0 8px; }101- nav.tabs a { flex: 1 0 auto; text-align: center; padding: 8px 6px; font-size: 14px; }116+ nav.tabs { gap: 16px; }117+}118+/* Tabs that don't fit scroll sideways; the edge that has more fades out. */119+@property --tabs-l { syntax: "<length>"; inherits: false; initial-value: 0px; }120+@property --tabs-r { syntax: "<length>"; inherits: false; initial-value: 0px; }121+@supports (animation-timeline: scroll()) {122+ 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); }123+ @keyframes tabs-edge { from { --tabs-l: 0px; --tabs-r: 32px; } to { --tabs-l: 32px; --tabs-r: 0px; } }102124 }103125 126+/* buttons: square, small uppercase monospace; secondary is the page's127+ surface with accent text, primary (.go) is filled with ink */128+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; }129+button:hover, .btn:hover { color: var(--deep); background: var(--hover); border-color: var(--faint); text-decoration: none; }130+button[aria-pressed="true"] { color: var(--fg); border-color: var(--fg); }131+button.go, details.clone > summary { color: var(--bg); background: var(--fg); border-color: var(--fg); }132+button.go:hover, details.clone > summary:hover { color: var(--bg); background: var(--muted); border-color: var(--muted); }133+button:disabled { opacity: 0.6; cursor: default; }134+button kbd { font-size: 1em; padding: 0 0.3em; text-transform: none; }135+104136 /* clone */105137 details.clone { position: relative; flex: none; }106-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; }138+details.clone > summary { list-style: none; }107139 details.clone > summary::-webkit-details-marker { display: none; }108-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); }140+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); }109141 .copyrow { display: flex; gap: 6px; align-items: center; margin: 4px 0; }110142 .clone-note { margin: 6px 0 0; font-size: 0.85em; color: var(--muted); }111-.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; }112-.private-mark:hover { text-decoration: none; }113-.copyrow label { width: 44px; color: var(--muted); font-size: 0.85em; }114-.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); }115-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; }116-button:hover, .btn:hover { background: var(--hover); text-decoration: none; }143+.private-mark { font: 600 10.5px var(--mono); letter-spacing: 0.08em; text-transform: uppercase; color: var(--warn); vertical-align: 2px; }144+.private-mark:hover { color: var(--warn); }145+.copyrow label { width: 44px; font: 600 10.5px var(--mono); letter-spacing: 0.08em; color: var(--faint); }146+.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); }117147 118-/* live status */119-#live { font-size: 0.8em; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 40vw; }120-#live span::before { content: ''; display: inline-block; width: 7px; height: 7px; border-radius: 50%; margin-right: 6px; vertical-align: 1px; }121-#live .live-on::before { background: var(--pass); }122-#live .live-off::before { background: var(--faint); }123-#live .live-busy { color: var(--warn); }124-#live .live-busy::before { background: var(--warn); animation: live-pulse 1s ease-in-out infinite alternate; }125-@keyframes live-pulse { to { opacity: 0.2; } }148+/* live status: words, no dot */149+#live { font: 11px var(--mono); color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 40vw; }150+#live .live-on { color: var(--pass); }151+#live .live-off { color: var(--faint); }152+#live .live-busy { color: var(--warn); animation: live-pulse 1s ease-in-out infinite alternate; }153+@keyframes live-pulse { to { opacity: 0.4; } }126154 127155 main.wrap { padding-top: 20px; padding-bottom: 40px; }156+main.wrap > :first-child { margin-top: 0; }128157 footer.site { color: var(--muted); padding-bottom: 24px; font-size: 0.85em; }129-button[aria-pressed="true"] { background: var(--hover); border-color: var(--faint); }130158 131159 /* toolbars above lists and code */132160 .bar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 0 0 12px; }133161 .bar .grow { flex: 1; }134162 .crumbs { font-family: var(--mono); font-size: 0.95em; overflow-wrap: anywhere; }135163 details.picker { position: relative; }136-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; }164+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); }165+details.picker > summary:hover { background: var(--hover); border-color: var(--faint); }137166 details.picker > summary::-webkit-details-marker { display: none; }138-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; }139-details.picker #branch-menu a { padding: 4px 8px; border-radius: 4px; font-family: var(--mono); font-size: 0.9em; }167+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; }168+details.picker #branch-menu a { padding: 4px 12px; font-family: var(--mono); font-size: 0.9em; }140169 details.picker #branch-menu a:hover { background: var(--hover); text-decoration: none; }141170 142-/* lists (tables that restack on phones) */171+/* lists: tables without outer borders, rows between hairlines, headers in172+ small uppercase monospace; they restack on phones */143173 table.list { width: 100%; border-collapse: collapse; }144-table.list th, table.list td { text-align: left; padding: 7px 8px; border-bottom: 1px solid var(--border); vertical-align: top; }145-table.list thead th { font-size: 0.8em; font-weight: 600; color: var(--muted); text-transform: uppercase; letter-spacing: 0.03em; }174+table.list th, table.list td { text-align: left; padding: 7px 8px; border-bottom: 1px solid var(--border); vertical-align: baseline; }175+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; }146176 table.list .num { text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }177+table.list td.num, table.list td.date { font: 12.5px/1.6 var(--mono); }178+table.list td.date { color: var(--muted); }179+table.list td.date time { font-size: 1em; }147180 table.list .nowrap, table.list .date, table.list .sha, table.list td.mono { white-space: nowrap; }148181 table.list td.main { width: 100%; overflow-wrap: anywhere; }149182 table.list tbody tr:hover td, table.list tbody tr:focus-within td, table.list tbody tr.sel td { background: var(--hover); }150183 table.list tbody tr.sel td:first-child { box-shadow: inset 2px 0 0 var(--accent); }151-.box { border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }152-.box + .box { margin-top: 16px; }153-.box > table.list tr:last-child td { border-bottom: 0; }154-.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; }184+/* A box holds a list or prose under a heading row; only a file viewer185+ (.viewer) is a bordered region. */186+.box + .box { margin-top: 24px; }187+.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; }155188 .box-head .grow { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }189+.box-head .label { margin-right: auto; }190+.box.viewer { border: 1px solid var(--border); background: var(--raised); }191+.box.viewer > .box-head { padding: 6px 12px; align-items: center; }192+.box.viewer > table.list tr:last-child td { border-bottom: 0; }156193 .files td.icon { width: 1.5em; color: var(--muted); padding-right: 0; }157194 .files td.size { color: var(--muted); }158195 .empty { color: var(--muted); padding: 24px; text-align: center; }@@ -172,15 +209,15 @@ table.list tbody tr.sel td:first-child { box-shadow: inset 2px 0 0 var(--accent)
172209 table.list.stack td.main { order: 0; }173210 table.list.stack td.id { order: 1; }174211 .hide-sm { display: none; }175- h2 { font-size: 1.05rem; }176212 }177213 178-/* statuses and CI */179-.status { font-weight: 600; white-space: nowrap; }180-.status.open { color: var(--pass); }181-.status.draft { color: var(--muted); }182-.status.merged { color: var(--merged); }183-.status.closed { color: var(--fail); }214+/* statuses and CI. A review's status is the one filled shape: a small215+ square label, white on the status colour (the same in both modes, so the216+ white stays readable). */217+.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; }218+.status.open { background: #0e8420; }219+.status.merged { background: #3148f6; }220+.status.closed { background: #c7162b; }184221 .ci { white-space: nowrap; font-weight: 600; }185222 .ci[title] { cursor: help; }186223 .ci.pass { color: var(--pass); }@@ -195,13 +232,13 @@ table.list tbody tr.sel td:first-child { box-shadow: inset 2px 0 0 var(--accent)
195232 /* code views */196233 /* The gutter (line numbers, and the +/- marker in diffs) is sticky, so code197234 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 line235+ in dark mode) layered over the code's background. --w is the widest line199236 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); }237+.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); }201238 .code .rows { display: inline-block; min-width: 100%; padding: 4px 0; }202239 .code .l { --gut: transparent; display: flex; color: var(--fg); text-decoration: none; }203240 .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; }241+.code .ln, .code .mk { position: sticky; background: linear-gradient(var(--gut), var(--gut)) var(--raised); user-select: none; -webkit-user-select: none; }205242 .code .ln { flex: none; width: var(--lnw); padding: 0 10px; text-align: right; color: var(--faint); left: 0; }206243 .code .ln:hover { color: var(--fg); }207244 .code .ln + .ln { left: var(--lnw); }@@ -226,17 +263,19 @@ body.wrap-lines .code .hunk { white-space: pre-wrap; overflow-wrap: anywhere; }
226263 body:not(.no-wrap) .code .lc, body:not(.no-wrap) .code .hunk { white-space: pre-wrap; overflow-wrap: anywhere; }227264 body:not(.no-wrap) .code .rows { display: block; }228265 }229-.file { margin: 16px 0; border: 1px solid var(--border); border-radius: var(--radius); }230-.file > .code { border: 0; border-radius: 0 0 var(--radius) var(--radius); }231-.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; }266+section.file { margin: 16px 0; border: 1px solid var(--border); background: var(--raised); }267+.file > .code { border: 0; }268+.box > .code { border: 0; }269+.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; }233270 /* A long path shares the line with the status and counts, wrapping at its234271 slashes, rather than wrapping onto a line of its own. */235272 .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; }236273 .fh .counts { font-family: var(--mono); font-size: 0.9em; }237-.fh .view { margin-left: auto; font-size: 0.9em; }274+.fh .st { font-size: 0.9em; }275+.fh .view { margin-left: auto; font: 600 10.5px var(--mono); letter-spacing: 0.08em; text-transform: uppercase; }238276 .file .note { margin: 0; padding: 10px 12px; color: var(--muted); }239277 table.diffstat td { padding: 3px 8px; }278+table.diffstat td.num { font-size: 12px; color: var(--muted); }240279 table.diffstat td.graph { font-family: var(--mono); white-space: nowrap; letter-spacing: -1px; }241280 table.diffstat td.main { overflow-wrap: break-word; }242281 table.diffstat td.main a { font-family: var(--mono); font-size: 0.92em; }@@ -286,9 +325,11 @@ table.diffstat td.main a { font-family: var(--mono); font-size: 0.92em; }
286325 .meta-head { margin: 0 0 16px; }287326 .meta-head h1 { font-size: 1.3rem; margin: 0 0 6px; overflow-wrap: anywhere; }288327 .meta-head .byline { color: var(--muted); font-size: 0.92em; }289-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; }290-details.att > summary, details.howto > summary { cursor: pointer; color: var(--muted); font-size: 0.92em; }291-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; }328+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; }329+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; }330+details.att > summary:hover, details.howto > summary:hover { color: var(--fg); }331+details.howto > summary .jev-sum { text-transform: none; letter-spacing: 0; font: 13px var(--sans); }332+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; }292333 .depth { color: var(--faint); white-space: pre; }293334 294335 /* Above a diff: the diffstat, with the page's tools on its summary line. */@@ -302,55 +343,55 @@ details.att pre, details.howto pre { white-space: pre-wrap; overflow-wrap: anywh
302343 303344 /* Diff files fold (diffs.ts); review pages add a Viewed box. */304345 .fh.foldable { cursor: pointer; }305-.fh .fold { border: 0; background: none; padding: 0; width: 1.1em; color: var(--muted); font-size: 1.2em; line-height: 1; }346+.fh .fold { border: 0; background: none; padding: 0; width: 1.1em; color: var(--muted); font: 1.2em/1 var(--sans); letter-spacing: 0; }347+.fh .fold:hover { background: none; color: var(--fg); }306348 .fh .fold::before { content: "\25BE"; display: inline-block; transition: transform 0.1s; }307349 .file.folded .fold::before { transform: rotate(-90deg); }308350 .file.folded > :not(.fh) { display: none; }309-.file.folded > .fh { border-bottom: 0; border-radius: var(--radius); }351+.file.folded > .fh { border-bottom: 0; }310352 .file.folded:has(.viewed input:checked) .fname { color: var(--muted); font-weight: normal; }311-.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; }353+.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; }312354 .fh .view ~ .viewed { margin-left: 0; }313355 .fh .viewed:has(input:checked) { color: var(--fg); }314-.fh .viewed input { margin: 0; }356+.fh .viewed input { margin: 0; accent-color: var(--accent); }315357 316358 /* prose: README, wiki, rendered markdown */317-.markdown { font-size: 1rem; line-height: 1.6; overflow-wrap: break-word; padding: 8px 24px 16px; }318-.box > .markdown { padding: 16px 24px; }359+.markdown { font-size: 1rem; line-height: 1.6; overflow-wrap: break-word; padding: 16px 8px; }319360 .markdown > :first-child { margin-top: 0; }320361 .markdown h1, .markdown h2 { border-bottom: 1px solid var(--border); padding-bottom: 0.3em; }321362 .markdown h1 { font-size: 1.8em; margin: 0.8em 0 0.5em; }322-.markdown h2 { font-size: 1.4em; margin: 1.4em 0 0.6em; }363+.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; }323364 .markdown h3 { font-size: 1.15em; }365+.markdown a:visited { color: var(--deep); }324366 .markdown img { max-width: 100%; height: auto; }325367 .markdown p[align="center"], .markdown div[align="center"] { text-align: center; }326-.markdown code { font-size: 0.875em; background: var(--subtle); border-radius: 4px; padding: 0.15em 0.35em; }327-.markdown pre { font: 13px/1.5 var(--mono); background: var(--subtle); border-radius: var(--radius); padding: 12px 14px; overflow-x: auto; tab-size: 4; }328-.markdown pre code { background: none; padding: 0; font-size: 1em; }329-.markdown pre.mermaid { background: none; text-align: center; }330-.markdown blockquote { margin: 1em 0; padding: 0 1em; color: var(--muted); border-left: 4px solid var(--border); }368+.markdown code { font-size: 0.875em; background: var(--subtle); border: 1px solid var(--border); padding: 0.05em 0.3em; }369+.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; }370+.markdown pre code { background: none; border: 0; padding: 0; font-size: 1em; }371+.markdown pre.mermaid { background: none; border: 0; text-align: center; }372+.markdown blockquote { margin: 1em 0; padding: 0 1em; color: var(--muted); border-left: 3px solid var(--border); }331373 .markdown table { display: block; max-width: 100%; overflow-x: auto; border-collapse: collapse; margin: 1em 0; }332-.markdown th, .markdown td { border: 1px solid var(--border); padding: 6px 12px; text-align: left; vertical-align: top; }333-.markdown th { background: var(--subtle); font-weight: 600; }334-.markdown hr { border-top: 2px solid var(--border); }374+.markdown th, .markdown td { border-bottom: 1px solid var(--border); padding: 6px 12px 6px 0; text-align: left; vertical-align: top; }375+.markdown th { font: 600 10.5px/1.4 var(--mono); letter-spacing: 0.08em; text-transform: uppercase; color: var(--faint); padding-top: 8px; }376+.markdown hr { border-top: 1px solid var(--border); }335377 .markdown :target { scroll-margin-top: 16px; }336378 @media (max-width: 720px) {337- .markdown, .box > .markdown { padding: 12px 14px; font-size: 0.97rem; }379+ .markdown { padding: 12px 4px; font-size: 0.97rem; }338380 .markdown h1 { font-size: 1.5em; }339381 .markdown h2 { font-size: 1.25em; }340382 }341383 /* jev: commits and repos */342-.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; }343-.jev-kind.feat { color: var(--pass); }344-.jev-kind.fix { color: var(--merged); }384+/* What Jev says a commit or a repo is: plain words, quiet. */385+.jev-kind { font: 12px var(--mono); color: var(--muted); white-space: nowrap; }386+.jev-topic { color: var(--muted); white-space: nowrap; }345387 /* Jev's risk score, 0 to 3: a small number, colored once it is moderate. */346-.jev-score { font-size: 0.8rem; font-variant-numeric: tabular-nums; color: var(--faint); white-space: nowrap; }388+.jev-score { font: 12px var(--mono); font-variant-numeric: tabular-nums; color: var(--faint); white-space: nowrap; }347389 .jev-score.moderate { color: var(--warn); }348390 .jev-score.high { color: var(--fail); font-weight: 600; }349391 .jev-mismatch { font-size: 0.85em; color: var(--faint); cursor: help; }350392 .jev-note { font-size: 0.8rem; font-style: italic; color: var(--faint); }351393 /* The log: Jev's type in a narrow column on the left. */352394 table.log th.kind, table.log td.kind { width: 1%; white-space: nowrap; padding-right: 4px; }353-table.log td.kind .jev-kind { vertical-align: 0; }354395 table.log td.risk { width: 1%; }355396 @media (max-width: 720px) {356397 /* Stacked: the type leads the line under the subject. */@@ -358,17 +399,18 @@ table.log td.risk { width: 1%; }
358399 table.log td.kind { order: 1; }359400 table.log td.kind:empty { display: none; }360401 }361-.releases-link { margin-left: 8px; font-size: 0.8rem; font-weight: normal; }362-.notes { padding: 2px 16px 12px; font-size: 0.95em; }363-.notes h3 { margin: 12px 0 4px; font-size: 0.9rem; }402+.releases-link { margin-left: 4px; }403+.notes { padding: 2px 8px 12px; font-size: 0.95em; }404+.notes h3 { margin: 14px 0 4px; font: 600 10.5px/1.4 var(--mono); letter-spacing: 0.08em; text-transform: uppercase; color: var(--faint); }364405 .notes ul { margin: 0; padding-left: 20px; }365406 .notes li { margin: 2px 0; overflow-wrap: anywhere; }366407 .notes details { margin-top: 12px; }367-.notes summary { cursor: pointer; font-size: 0.9rem; font-weight: 600; color: var(--muted); }408+.notes summary { cursor: pointer; font: 600 10.5px/1.4 var(--mono); letter-spacing: 0.08em; text-transform: uppercase; color: var(--faint); }409+.notes summary:hover { color: var(--fg); }368410 369411 /* jev: review pages */370-.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; }412+.jev-mech, .jev-change { font: 12px var(--mono); color: var(--muted); white-space: nowrap; }371413 .jev-change.behavior, .jev-sum.behavior { color: var(--warn); }372414 .jev-sum { font-size: 0.9em; color: var(--muted); }373415 summary .jev-sum { margin-left: 6px; font-weight: normal; }374-.comment .tag.jev-addressed { color: var(--pass); border-style: dashed; }416+.comment .tag.jev-addressed { color: var(--pass); }@@ -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,32 @@ 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; }20+.cmt-head .tag { font: 600 10.5px var(--mono); letter-spacing: 0.08em; text-transform: uppercase; color: var(--warn); }1821 .cmt-head a { color: var(--muted); }19-.cmt .markdown { padding: 8px 12px; font-size: 0.95rem; line-height: 1.55; }22+.cmt .markdown { padding: 4px 12px 8px; font-size: 0.95rem; line-height: 1.55; }2023 .cmt .markdown > :last-child { margin-bottom: 0; }2124 /* In a diff, a comment is as wide as the code box, not its longest line. */2225 .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; }26+.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; }27+.code .cmts > .cmt:first-child { border-top: 0; }28+.code .cmts > form { padding-top: 8px; }2529 /* A new line comment, signed in (discuss.ts): the + on the line under the2630 pointer (or the line last clicked) opens a form under it. */2731 .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); }32+.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); }33+.addline:hover { color: #fff; background: var(--deep); border-color: var(--deep); }3034 .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; }35+.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; }3236 .cmts textarea:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }3337 .cmts .reply-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 8px; margin-top: 8px; font-size: 0.92em; }3438 .cmts .reply-bar .grow { flex: 1; }35-.cmts .reply-bar button.go { background: var(--accent); border-color: var(--accent); color: #fff; }36-.cmts .reply-bar button:disabled { opacity: 0.6; cursor: default; }3739 .cmts .reply-status.bad { color: var(--fail); white-space: pre-line; }38-.cmt.pending { border-style: dashed; }40+.cmt.pending { border-top-style: dashed; }3941 .cmt.pending .text { padding: 8px 12px; white-space: pre-wrap; overflow-wrap: anywhere; color: var(--muted); }4042 {% endcss %}4143 <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,18 +11,20 @@ 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: 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); }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 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; }@@ -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">