1/* forge: the base stylesheet, light and dark, phone first: tokens, then2 layout, controls, lists and code views. CSS for one feature lives with3 its markup, in a template or include ({% css %}), and reaches only the4 pages that use it. The rules for all of it are in DESIGN.md, and5 lib/check-css.ts holds every stylesheet to them in CI.67 The look: a plain page with the chrome on it (header, tabs, toolbars) and8 the content in panels: raised, square, a hairline edge, an optional9 tinted head strip, rows between hairlines inside; Open Sans for the interface, Noto Sans Mono for what is10 copied or compared, Noto Serif for prose people wrote; hierarchy from11 weight, size and colour, never capitals or tracking; one accent12 (#3148F6) for links, the current tab's bar, focus and primary actions. */1314/* == tokens == Every colour, size, weight and family is here; nothing15 below this block names one. The style guide (/design/) shows them. */16/* The fonts, self-hosted, variable in weight, cut to Latin and the symbols17 the pages print (fonts/make-fonts.sh). The sans is preloaded (base.njk). */18@font-face { font-family: "Open Sans"; src: url(./fonts/open-sans.woff2) format("woff2"); font-weight: 300 800; font-style: normal; font-display: swap; }19@font-face { font-family: "Noto Sans Mono"; src: url(./fonts/noto-sans-mono.woff2) format("woff2"); font-weight: 400 700; font-style: normal; font-display: swap; }20@font-face { font-family: "Noto Serif"; src: url(./fonts/noto-serif.woff2) format("woff2"); font-weight: 400 700; font-style: normal; font-display: swap; }21@font-face { font-family: "Noto Serif"; src: url(./fonts/noto-serif-italic.woff2) format("woff2"); font-weight: 400 700; font-style: italic; font-display: swap; }22:root {23 /* colour: the page, panels raised over it with their tinted head strip24 and a row's hover, ink in three strengths (muted keeps 4.5:1 on the25 page and every surface, faint on a panel), rules, and the accent */26 --bg: #e9e9e9;27 --raised: #ffffff;28 --tint: #f6f6f6;29 --hover: #f2f3f5;30 --fg: #1c2127;31 --muted: #5b6169;32 --faint: #6f757d;33 --border: #d2d4d7;34 --border-strong: #a9aeb4;35 --link: #3148f6;36 --accent: #3148f6;37 --deep: #1c4b8f;38 --accent-hover: #1c4b8f;39 --on-accent: #ffffff;40 /* meaning: diffs, CI, warnings */41 --add-bg: rgba(14, 132, 32, 0.1);42 --add-fg: #0b7a1d;43 --add-ln: rgba(14, 132, 32, 0.2);44 --del-bg: rgba(199, 22, 43, 0.08);45 --del-fg: #c7162b;46 --del-ln: rgba(199, 22, 43, 0.17);47 --hunk-bg: rgba(49, 72, 246, 0.07);48 --hunk-fg: #1c4b8f;49 --pass: #0b7a1d;50 --fail: #c7162b;51 --warn: #965800;52 --merged: #3148f6;53 /* status labels: white on a fill, the same in both modes */54 --status-open: #0e8420;55 --status-merged: #3148f6;56 --status-closed: #c7162b;57 --status-other: #5b6169;58 /* avatars (lib/avatar.ts): bauhaus tiles drawn from a name, on a paper59 ground with an ink and three accents; in dark mode the ground and ink60 swap, so the art keeps its figure and ground */61 --avatar-ground: #eee8dc;62 --avatar-ink: #171717;63 --avatar-red: #c83b2e;64 --avatar-blue: #1e5484;65 --avatar-yellow: #dfb631;66 /* the activity grid (ui.activity): a day with nothing, then four steps67 of the accent as the day gets busier */68 --heat-0: #e7e8eb;69 --heat-1: #c3cafd;70 --heat-2: #8796fa;71 --heat-3: #5468f7;72 --heat-4: #2a3fd6;73 /* languages on a repo's front matter: the three biggest, then the rest */74 --lang-1: #1e5484;75 --lang-2: #c83b2e;76 --lang-3: #dfb631;77 --lang-rest: #a9aeb4;78 /* a linked line, floating panels, dialogs' backdrop, masks */79 --target-bg: rgba(255, 213, 79, 0.25);80 --target-gut: rgba(255, 213, 79, 0.4);81 --shadow: 0 8px 24px rgba(0, 0, 0, 0.12);82 --scrim: rgba(0, 0, 0, 0.3);83 --mask: #000;84 /* syntax highlighting (highlight.js classes) */85 --tok-comment: #59636e;86 --tok-keyword: #cf222e;87 --tok-string: #0a3069;88 --tok-const: #0550ae;89 --tok-title: #8250df;90 --tok-builtin: #953800;91 --tok-tag: #116329;92 /* charts (insights.ts): checked for colour-blind readers on both pages */93 --viz-1: #3148f6;94 --viz-fail: #c7162b;95 --viz-grid: #d9d9d9;96 --viz-axis: #a9aeb4;9798 /* type: the interface, what is copied or compared, and prose */99 --sans: "Open Sans", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;100 --mono: "Noto Sans Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;101 --serif: "Noto Serif", Georgia, Cambria, "Times New Roman", serif;102 --text-xs: 12px;103 --text-code: 12.5px;104 --text-sm: 13.5px;105 --text-md: 15px;106 --text-prose: 16px;107 --text-lg: 18px;108 --text-xl: 22px;109 --text-2xl: 28px;110 /* mono inline in sans text runs large; this brings it level */111 --text-inline-mono: 0.9em;112 /* weights: body, links and controls, emphasis and headings */113 --fw-regular: 400;114 --fw-medium: 500;115 --fw-strong: 650;116117 /* space: a 4px scale */118 --space-half: 2px;119 --space-1: 4px;120 --space-2: 8px;121 --space-3: 12px;122 --space-4: 16px;123 --space-5: 20px;124 --space-6: 24px;125 --space-8: 32px;126 --space-10: 40px;127 /* lines, corners and controls: rules are hairlines, the current tab's bar is thicker, nothing is rounded */128 --hairline: 1px;129 --bar: 3px;130 --radius: 0;131 --control-h: 30px;132 /* columns: a time in its own column is as wide as the widest compact133 one, "Sep 24" or "40m", in the mono */134 --col-time: 6ch;135 color-scheme: light dark;136}137@media (prefers-color-scheme: dark) {138 :root {139 --bg: #08090b;140 --raised: #111418;141 --tint: #15191e;142 --hover: #191d23;143 --fg: #e2e4e7;144 --muted: #8f959d;145 --faint: #7d838b;146 --border: #252a31;147 --border-strong: #3a4048;148 --link: #8e9bff;149 --deep: #b4bcff;150 --accent-hover: #2a3ee0;151 --add-bg: rgba(79, 191, 95, 0.12);152 --add-fg: #4fbf5f;153 --add-ln: rgba(79, 191, 95, 0.24);154 --del-bg: rgba(240, 96, 110, 0.12);155 --del-fg: #f0606e;156 --del-ln: rgba(240, 96, 110, 0.24);157 --hunk-bg: rgba(142, 155, 255, 0.09);158 --hunk-fg: #8e9bff;159 --pass: #4fbf5f;160 --fail: #f0606e;161 --warn: #e3a33b;162 --merged: #8e9bff;163 --target-bg: rgba(255, 213, 79, 0.12);164 --target-gut: rgba(255, 213, 79, 0.22);165 --shadow: 0 8px 28px rgba(0, 0, 0, 0.7);166 --scrim: rgba(0, 0, 0, 0.6);167 --tok-comment: #8b929b;168 --tok-keyword: #ff7b72;169 --tok-string: #a5d6ff;170 --tok-const: #79c0ff;171 --tok-title: #d2a8ff;172 --tok-builtin: #ffa657;173 --tok-tag: #7ee787;174 --viz-1: #6f7fff;175 --viz-fail: #f0606e;176 --viz-grid: #1a1e23;177 --viz-axis: #3a4048;178 --avatar-ground: #2b2823;179 --avatar-ink: #ece6da;180 --avatar-red: #df5443;181 --avatar-blue: #5c8fd3;182 --avatar-yellow: #e4bb3d;183 --heat-0: #1b1f25;184 --heat-1: #232f73;185 --heat-2: #3046b8;186 --heat-3: #5a6ff2;187 --heat-4: #9aa6ff;188 --lang-1: #5c8fd3;189 --lang-2: #df5443;190 --lang-3: #e4bb3d;191 --lang-rest: #4a5058;192 }193}194/* == end tokens == */195196*, ::before, ::after { box-sizing: border-box; border-radius: var(--radius); }197html { -webkit-text-size-adjust: 100%; }198/* hidden wins over a display a control's class gives it */199[hidden] { display: none ; }200body { margin: 0; background: var(--bg); color: var(--fg); font: var(--fw-regular) var(--text-md)/1.5 var(--sans); }201a { color: var(--link); text-decoration: none; }202a:hover { color: var(--deep); text-decoration: underline; }203:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }204b, strong, th { font-weight: var(--fw-strong); }205code, kbd, pre, time, .mono, .sha { font-family: var(--mono); }206code, kbd, time, .sha, .mono { font-size: var(--text-inline-mono); }207.mono .sha, .mono time, .mono code, pre code { font-size: inherit; }208h1, h2, h3 { line-height: 1.25; font-weight: var(--fw-strong); }209/* Section headings on the page: sans, strong, over the panel they name; a210 count or note after them is quieter. A panel's own name goes in its head211 strip instead. */212h2 { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-1) var(--space-2); margin: var(--space-8) 0 var(--space-3); font-size: var(--text-md); color: var(--fg); }213h2 .desc, h2 .n { font-weight: var(--fw-regular); color: var(--faint); }214h2 .n { font: var(--fw-regular) var(--text-xs) var(--mono); font-variant-numeric: tabular-nums; }215h2 a { font-weight: var(--fw-medium); }216h3 { font-size: var(--text-md); margin: var(--space-6) 0 var(--space-2); }217.label { font-size: var(--text-sm); font-weight: var(--fw-medium); color: var(--muted); }218.desc, .muted { color: var(--muted); }219.i { color: var(--add-fg); }220.d { color: var(--del-fg); }221.wrap { max-width: 1280px; margin: 0 auto; padding: 0 var(--space-4); }222kbd { border: var(--hairline) solid var(--border-strong); padding: 0 var(--space-1); color: var(--muted); background: var(--raised); }223hr { border: 0; border-top: var(--hairline) solid var(--border); margin: var(--space-6) 0; }224svg.icon { display: inline-block; width: 16px; height: 16px; flex: none; vertical-align: -3px; }225226/* header: on the page, over a rule that the current tab's bar sits on */227header.site { border-bottom: var(--hairline) solid var(--border); }228.top { display: flex; align-items: center; gap: var(--space-3); padding: var(--space-3) 0 var(--space-2); min-width: 0; }229.logo { display: block; color: var(--fg); line-height: 0; flex: none; }230.mark { display: block; width: 28px; height: 28px; }231.title { font-size: var(--text-lg); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--muted); }232.title a { color: var(--muted); }233.title a:hover { color: var(--deep); }234.title b, .title b a { font-weight: var(--fw-strong); color: var(--fg); }235.top .spacer { flex: 1; }236.repo-desc { margin: 0 0 var(--space-2); color: var(--muted); font-size: var(--text-sm); }237238/* tabs: where you are. Words, the current one in ink on the accent bar. */239nav.tabs { display: flex; gap: var(--space-6); overflow-x: auto; scrollbar-width: none; font-size: var(--text-md); font-weight: var(--fw-medium); }240nav.tabs::-webkit-scrollbar { display: none; }241nav.tabs a { color: var(--muted); padding: var(--space-2) 0 var(--space-2); white-space: nowrap; }242nav.tabs a:hover { color: var(--fg); text-decoration: none; }243nav.tabs a.cur, nav.tabs a[aria-current="page"], nav.tabs a[aria-current="true"] { color: var(--fg); box-shadow: inset 0 calc(-1 * var(--bar)) 0 var(--accent); }244nav.tabs .n { margin-left: var(--space-1); font: var(--fw-regular) var(--text-xs) var(--mono); color: var(--faint); font-variant-numeric: tabular-nums; }245@media (max-width: 480px) {246 nav.tabs { gap: var(--space-4); }247}248/* Tabs that don't fit scroll sideways; the edge that has more fades out. */249@property --tabs-l { syntax: "<length>"; inherits: false; initial-value: 0px; }250@property --tabs-r { syntax: "<length>"; inherits: false; initial-value: 0px; }251@supports (animation-timeline: scroll()) {252 nav.tabs { mask-image: linear-gradient(to right, transparent, var(--mask) var(--tabs-l), var(--mask) calc(100% - var(--tabs-r)), transparent); animation: tabs-edge linear both; animation-timeline: scroll(self inline); }253 @keyframes tabs-edge { from { --tabs-l: 0px; --tabs-r: 32px; } to { --tabs-l: 32px; --tabs-r: 0px; } }254}255256/* controls (ui.njk). Buttons do things: secondary is a raised surface,257 primary is filled with the accent. A toggle that is on is filled with258 ink: an icon toggle, or one segment of a segmented control. A checkbox259 is on/off with words. Links only navigate. */260button, .btn, details.clone > summary { display: inline-flex; align-items: center; justify-content: center; gap: var(--space-1); min-height: var(--control-h); font: var(--fw-medium) var(--text-sm)/1.2 var(--sans); color: var(--fg); background: var(--raised); border: var(--hairline) solid var(--border-strong); padding: var(--space-1) var(--space-3); cursor: pointer; white-space: nowrap; }261button:hover, .btn:hover { color: var(--fg); border-color: var(--muted); text-decoration: none; }262button.primary, details.clone > summary { color: var(--on-accent); background: var(--accent); border-color: var(--accent); }263button.primary:hover, details.clone > summary:hover, details.clone[open] > summary { color: var(--on-accent); background: var(--accent-hover); border-color: var(--accent-hover); }264button:disabled { opacity: 0.55; cursor: default; }265button kbd { padding: 0 var(--space-1); }266.icon-btn { width: var(--control-h); padding: 0; color: var(--muted); }267.icon-btn:hover { color: var(--fg); }268.icon-btn[aria-pressed="true"], .segmented > button[aria-pressed="true"] { color: var(--bg); background: var(--fg); border-color: var(--fg); }269.segmented { display: inline-flex; }270.segmented > button + button { margin-left: calc(-1 * var(--hairline)); }271.segmented > button[aria-pressed="true"] { position: relative; }272.check { display: inline-flex; align-items: center; gap: var(--space-2); min-height: var(--control-h); font-size: var(--text-sm); color: var(--muted); cursor: pointer; user-select: none; -webkit-user-select: none; white-space: nowrap; }273.check:has(input:checked) { color: var(--fg); }274.check input { width: 15px; height: 15px; margin: 0; accent-color: var(--accent); }275.input, textarea.input { width: 100%; font: var(--fw-regular) var(--text-md)/1.5 var(--sans); color: var(--fg); background: var(--raised); border: var(--hairline) solid var(--border-strong); padding: var(--space-1) var(--space-2); }276.input.mono { font-family: var(--mono); font-size: var(--text-sm); }277textarea.input { display: block; min-height: 6em; resize: vertical; padding: var(--space-2) var(--space-3); }278.input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 var(--hairline) var(--accent); }279280/* clone */281details.clone { position: relative; flex: none; }282details.clone > summary { list-style: none; }283details.clone > summary::-webkit-details-marker { display: none; }284details.clone .panel { position: absolute; right: 0; top: calc(100% + var(--space-2)); z-index: 10; width: min(440px, 92vw); background: var(--raised); border: var(--hairline) solid var(--border); padding: var(--space-3); box-shadow: var(--shadow); }285.copyrow { display: flex; gap: var(--space-2); align-items: center; margin: var(--space-1) 0; }286.copyrow label { width: 44px; font-size: var(--text-sm); color: var(--muted); }287.copyrow .input { flex: 1; min-width: 0; }288.clone-note { margin: var(--space-2) 0 0; font-size: var(--text-sm); color: var(--muted); }289.private-mark { font-size: var(--text-sm); font-weight: var(--fw-medium); color: var(--warn); }290.private-mark:hover { color: var(--warn); }291292/* live status: words, no dot */293#live { font: var(--fw-regular) var(--text-xs) var(--mono); color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 40vw; }294#live .live-on { color: var(--pass); }295#live .live-off { color: var(--faint); }296#live .live-busy { color: var(--warn); animation: live-pulse 1s ease-in-out infinite alternate; }297@keyframes live-pulse { to { opacity: 0.4; } }298299main.wrap { padding-top: var(--space-5); padding-bottom: var(--space-10); }300main.wrap > :first-child { margin-top: 0; }301footer.site { color: var(--muted); padding-bottom: var(--space-6); font-size: var(--text-sm); }302303/* toolbars above lists and code */304.bar { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-3); margin: 0 0 var(--space-3); }305.bar .grow { flex: 1; }306.crumbs { font-family: var(--mono); font-size: var(--text-sm); overflow-wrap: anywhere; }307details.picker { position: relative; }308details.picker > summary { display: inline-flex; align-items: center; gap: var(--space-2); min-height: var(--control-h); list-style: none; cursor: pointer; border: var(--hairline) solid var(--border-strong); padding: var(--space-1) var(--space-3); background: var(--raised); font: var(--fw-regular) var(--text-sm)/1.2 var(--mono); }309details.picker > summary b { font-weight: var(--fw-medium); }310details.picker > summary:hover { border-color: var(--muted); }311details.picker > summary::-webkit-details-marker { display: none; }312details.picker #branch-menu { position: absolute; z-index: 10; top: calc(100% + var(--space-1)); left: 0; min-width: 220px; max-height: 60vh; overflow: auto; background: var(--raised); border: var(--hairline) solid var(--border); padding: var(--space-1) 0; box-shadow: var(--shadow); display: flex; flex-direction: column; }313details.picker #branch-menu a { padding: var(--space-1) var(--space-3); font-family: var(--mono); font-size: var(--text-sm); }314details.picker #branch-menu a:hover { background: var(--hover); text-decoration: none; }315316/* avatars (lib/avatar.ts): a person's bauhaus tiles, square, on the317 five avatar tokens; the drawing's own classes pick the colour. */318svg.avatar { display: block; flex: none; }319svg.avatar .av-g { fill: var(--avatar-ground); }320svg.avatar .av-i { fill: var(--avatar-ink); }321svg.avatar .av-r { fill: var(--avatar-red); }322svg.avatar .av-b { fill: var(--avatar-blue); }323svg.avatar .av-y { fill: var(--avatar-yellow); }324325/* the activity grid (ui.activity): a column a week, a square button a326 day, coloured by how busy it was. The 53 weeks fill the panel's width,327 square cells with --space-half gaps; in a narrower box the cells328 shrink with it down to 10px, and below that the oldest weeks go (a<n>:329 n weeks before this one), a week each 12px, so the latest always show. A picked day has330 an ink outline. A still grid's days (a repo's insights) are spans, the331 same squares with nothing to hover or pick. */332.activity-fit { container: activity / inline-size; }333.activity { --cell: max(10px, calc((100cqw + var(--space-half)) / 53 - var(--space-half))); display: grid; grid-template-rows: repeat(7, var(--cell)); grid-auto-columns: var(--cell); grid-auto-flow: column; gap: var(--space-half); justify-content: start; }334.activity > :is(button, span) { display: block; min-height: 0; width: 100%; height: 100%; padding: 0; border: 0; background: var(--heat-0); }335.activity > button { cursor: pointer; }336.activity > .l1 { background: var(--heat-1); }337.activity > .l2 { background: var(--heat-2); }338.activity > .l3 { background: var(--heat-3); }339.activity > .l4 { background: var(--heat-4); }340.activity > button:hover { outline: var(--hairline) solid var(--muted); outline-offset: 0; }341.activity > button[aria-pressed="true"] { outline: 2px solid var(--fg); outline-offset: 0; position: relative; z-index: 1; }342.activity > button:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; position: relative; z-index: 2; }343@container activity (width < 634px) { .activity > .a52 { display: none; } }344@container activity (width < 622px) { .activity > .a51 { display: none; } }345@container activity (width < 610px) { .activity > .a50 { display: none; } }346@container activity (width < 598px) { .activity > .a49 { display: none; } }347@container activity (width < 586px) { .activity > .a48 { display: none; } }348@container activity (width < 574px) { .activity > .a47 { display: none; } }349@container activity (width < 562px) { .activity > .a46 { display: none; } }350@container activity (width < 550px) { .activity > .a45 { display: none; } }351@container activity (width < 538px) { .activity > .a44 { display: none; } }352@container activity (width < 526px) { .activity > .a43 { display: none; } }353@container activity (width < 514px) { .activity > .a42 { display: none; } }354@container activity (width < 502px) { .activity > .a41 { display: none; } }355@container activity (width < 490px) { .activity > .a40 { display: none; } }356@container activity (width < 478px) { .activity > .a39 { display: none; } }357@container activity (width < 466px) { .activity > .a38 { display: none; } }358@container activity (width < 454px) { .activity > .a37 { display: none; } }359@container activity (width < 442px) { .activity > .a36 { display: none; } }360@container activity (width < 430px) { .activity > .a35 { display: none; } }361@container activity (width < 418px) { .activity > .a34 { display: none; } }362@container activity (width < 406px) { .activity > .a33 { display: none; } }363@container activity (width < 394px) { .activity > .a32 { display: none; } }364@container activity (width < 382px) { .activity > .a31 { display: none; } }365@container activity (width < 370px) { .activity > .a30 { display: none; } }366@container activity (width < 358px) { .activity > .a29 { display: none; } }367@container activity (width < 346px) { .activity > .a28 { display: none; } }368@container activity (width < 334px) { .activity > .a27 { display: none; } }369@container activity (width < 322px) { .activity > .a26 { display: none; } }370@container activity (width < 310px) { .activity > .a25 { display: none; } }371@container activity (width < 298px) { .activity > .a24 { display: none; } }372@container activity (width < 286px) { .activity > .a23 { display: none; } }373@container activity (width < 274px) { .activity > .a22 { display: none; } }374@container activity (width < 262px) { .activity > .a21 { display: none; } }375@container activity (width < 250px) { .activity > .a20 { display: none; } }376@container activity (width < 238px) { .activity > .a19 { display: none; } }377@container activity (width < 226px) { .activity > .a18 { display: none; } }378@container activity (width < 214px) { .activity > .a17 { display: none; } }379@container activity (width < 202px) { .activity > .a16 { display: none; } }380@container activity (width < 190px) { .activity > .a15 { display: none; } }381@container activity (width < 178px) { .activity > .a14 { display: none; } }382@container activity (width < 166px) { .activity > .a13 { display: none; } }383@container activity (width < 154px) { .activity > .a12 { display: none; } }384@container activity (width < 142px) { .activity > .a11 { display: none; } }385@container activity (width < 130px) { .activity > .a10 { display: none; } }386@container activity (width < 118px) { .activity > .a9 { display: none; } }387@container activity (width < 106px) { .activity > .a8 { display: none; } }388@container activity (width < 94px) { .activity > .a7 { display: none; } }389@container activity (width < 82px) { .activity > .a6 { display: none; } }390@container activity (width < 70px) { .activity > .a5 { display: none; } }391@container activity (width < 58px) { .activity > .a4 { display: none; } }392@container activity (width < 46px) { .activity > .a3 { display: none; } }393@container activity (width < 34px) { .activity > .a2 { display: none; } }394@container activity (width < 22px) { .activity > .a1 { display: none; } }395396/* event rows: what happened, a row each: its kind (as wide as397 "release"), what, and when. The rows share the list's columns (subgrid),398 so every title ends at the same edge; the time is --col-time wide. */399.ev-rows { display: grid; grid-template-columns: 3.6em minmax(0, 1fr) auto; column-gap: var(--space-3); }400.ev { position: relative; grid-column: 1 / -1; display: grid; grid-template-columns: 3.6em minmax(0, 1fr) auto; grid-template-columns: subgrid; column-gap: var(--space-3); align-items: baseline; padding: var(--space-2) var(--space-3); }401.ev:hover, .ev:focus-within { background: var(--hover); }402.ev-kind { font: var(--fw-regular) var(--text-xs) var(--mono); color: var(--muted); }403.ev-what { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--fg); }404a.ev-what:hover { color: var(--fg); }405a.ev-what::after { content: ''; position: absolute; inset: 0; }406.ev-what b { font-weight: var(--fw-strong); }407.ev-what .tag { font-family: var(--mono); font-size: var(--text-inline-mono); color: var(--link); }408.ev time { min-width: var(--col-time); font-size: var(--text-xs); color: var(--faint); text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }409410/* panels (ui.panel): where content sits. Lists, file viewers, READMEs,411 diffs and discussions sit in panels; the header, a page's title, tabs412 and toolbars sit on the page. A panel is a raised surface with a413 hairline edge (square, no shadow) and may have a head strip, tinted,414 naming what it holds: quiet sans words, the key item in ink, shas and415 ages in the mono. Rows inside are between hairlines, none under the416 last, and a row's hover tints the whole row. Panels stack 16px apart. */417.panel { display: block; min-width: 0; margin: 0 0 var(--space-4); background: var(--raised); border: var(--hairline) solid var(--border); }418.panel:last-child { margin-bottom: 0; }419.panel-head { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-1) var(--space-3); min-height: calc(var(--control-h) + 2 * var(--space-1)); padding: var(--space-1) var(--space-3); background: var(--tint); border-bottom: var(--hairline) solid var(--border); font-size: var(--text-sm); line-height: 1.5; color: var(--muted); }420.panel-head :is(b, strong, h2, h3) { display: inline; margin: 0; padding: 0; border: 0; font-size: inherit; font-weight: var(--fw-medium); color: var(--fg); }421.panel-head .key, .panel-head .key:hover, .panel-head :is(b, h2, h3) a, .panel-head :is(b, h2, h3) a:hover { color: var(--fg); }422.panel-head :is(.n, time, .sha, .date) { font: var(--fw-regular) var(--text-xs) var(--mono); color: var(--faint); font-variant-numeric: tabular-nums; white-space: nowrap; }423.panel-head a.sha { color: var(--link); }424.panel-head .grow { flex: 1 1 12em; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }425.panel-head .end { margin-left: auto; display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-1) var(--space-3); }426.panel-head .check, .panel-head button { min-height: 0; }427.panel-head button { padding-block: var(--space-half); }428/* A panel that folds: a details whose summary is the head strip. */429details.panel > summary.panel-head { cursor: pointer; list-style: none; }430details.panel > summary.panel-head::-webkit-details-marker { display: none; }431details.panel > summary.panel-head::before { content: "\25BE"; width: 1em; margin-right: calc(-1 * var(--space-2)); color: var(--muted); }432details.panel:not([open]) > summary.panel-head::before { content: "\25B8"; }433details.panel:not([open]) > summary.panel-head { border-bottom: 0; }434details.panel > summary.panel-head:hover { color: var(--fg); }435.panel-body { padding: var(--space-3); }436.panel-body > :first-child { margin-top: 0; }437.panel-body > :last-child { margin-bottom: 0; }438/* Rows: a list's items between hairlines; the last has none, the panel's edge is its rule. */439.rows { list-style: none; margin: 0; padding: 0; }440.rows > li { border-bottom: var(--hairline) solid var(--border); }441.panel :is(.rows > li:last-child, table.list > tbody > tr:last-child > td, table.list > tbody > tr:last-child) { border-bottom: 0; }442.empty { margin: 0; color: var(--muted); padding: var(--space-6) var(--space-3); text-align: center; }443444/* lists: tables of rows between hairlines, column names in the panel's445 head strip; they restack on phones */446table.list { width: 100%; border-collapse: collapse; }447table.list th, table.list td { text-align: left; padding: var(--space-2); border-bottom: var(--hairline) solid var(--border); vertical-align: baseline; }448table.list :is(th, td):first-child { padding-left: var(--space-3); }449table.list :is(th, td):last-child { padding-right: var(--space-3); }450table.list thead th { font-size: var(--text-sm); font-weight: var(--fw-regular); color: var(--muted); background: var(--tint); white-space: nowrap; }451table.list .num { text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }452table.list td.num, table.list td.date { font: var(--fw-regular) var(--text-xs)/1.6 var(--mono); }453table.list td.date { color: var(--faint); }454table.list td.date time { font-size: inherit; }455/* A time in its own column (DESIGN.md, Times) is compact, and as wide as456 "Sep 24" whatever it says, right-aligned in figures of one width: every457 row's text ends at the same edge, and nothing moves when ui.ts rewrites458 it. A time in a sentence (data-ago) is a word in it. */459table.list th.date { text-align: right; }460:is(table.list td.date, .panel-head) time:not([data-ago]) { display: inline-block; min-width: var(--col-time); text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }461table.list .nowrap, table.list .date, table.list .sha, table.list td.mono { white-space: nowrap; }462table.list td.mono { font-size: var(--text-sm); }463table.list td.main { width: 100%; overflow-wrap: anywhere; }464/* A row's own link (a subject, a file, a branch) is in ink, and stays ink465 on hover; other links are blue. */466table.list td.main > a:first-of-type { color: var(--fg); font-weight: var(--fw-medium); }467table.list td.main > a:first-of-type:hover { color: var(--fg); }468/* Hover tints the row, never a cell or a link, so it is one even block. */469table.list tbody tr:hover, table.list tbody tr:focus-within, table.list tbody tr.sel { background: var(--hover); }470table.list tbody tr.sel > td:first-child { box-shadow: inset 2px 0 0 var(--accent); }471/* A directory: an icon cell, the name, the size. */472table.files td.file-icon { width: 1%; padding-right: 0; color: var(--muted); }473table.files td.size { color: var(--muted); }474475@media (max-width: 720px) {476 table.list thead { display: none; }477 table.list.stack tr { display: flex; flex-wrap: wrap; gap: var(--space-half) var(--space-3); padding: var(--space-2) var(--space-3); border-bottom: var(--hairline) solid var(--border); }478 table.list.stack td, table.list.stack td:first-child, table.list.stack td:last-child { border: 0; padding: 0; font-size: var(--text-sm); color: var(--muted); }479 table.list.stack td.main { flex: 0 0 100%; font-size: var(--text-md); color: var(--fg); }480 table.list.stack tbody tr.sel { box-shadow: inset 2px 0 0 var(--accent); }481 table.list.stack tbody tr.sel > td:first-child { box-shadow: none; }482 table.list.stack .hide-sm { display: none; }483 table.list.stack td { order: 2; }484 table.list.stack td.main { order: 0; }485 table.list.stack td.id { order: 1; }486 /* Stacked, the date is one of the facts under the subject, not a column. */487 table.list.stack td.date time { min-width: 0; }488 .hide-sm { display: none; }489}490491/* statuses and CI. A review's status is the one filled shape: a small492 square label, white on the status colour (the same in both modes, so the493 white stays readable). */494.status { display: inline-block; font: var(--fw-strong) var(--text-xs)/1.6 var(--sans); color: var(--on-accent); background: var(--status-other); padding: 0 var(--space-2); white-space: nowrap; vertical-align: 1px; }495.status.open { background: var(--status-open); }496.status.merged { background: var(--status-merged); }497.status.closed { background: var(--status-closed); }498.ci { white-space: nowrap; font-weight: var(--fw-strong); }499.ci[title] { cursor: help; }500.ci.pass { color: var(--pass); }501.ci.fail { color: var(--fail); }502.ci.partial, .ci.unverified { color: var(--warn); }503.st { font-family: var(--mono); font-weight: var(--fw-strong); }504.st.A { color: var(--add-fg); }505.st.D { color: var(--del-fg); }506.st.R, .st.C { color: var(--warn); }507.st.M { color: var(--muted); }508509/* code views */510/* The gutter (line numbers, and the +/- marker in diffs) is sticky, so code511 scrolls under it: every gutter cell is opaque, its row's tint (translucent512 in dark mode) layered over the code's background. --w is the widest line513 number's digit count, set per file, so the columns never overlap. */514.code { --lnw: max(3.5em, calc(var(--w, 3) * 1ch + 20px)); font: var(--fw-regular) var(--text-code)/1.55 var(--mono); tab-size: 4; -moz-tab-size: 4; overflow-x: auto; border: var(--hairline) solid var(--border); background: var(--raised); }515.code .rows { display: inline-block; min-width: 100%; padding: var(--space-1) 0; }516.code .l { --gut: transparent; display: flex; color: var(--fg); text-decoration: none; }517.code a.l:hover { text-decoration: none; }518.code .ln, .code .mk { position: sticky; background: linear-gradient(var(--gut), var(--gut)) var(--raised); user-select: none; -webkit-user-select: none; }519.code .ln { flex: none; width: var(--lnw); padding: 0 var(--space-3); text-align: right; color: var(--faint); left: 0; }520.code .ln:hover { color: var(--fg); }521.code .ln + .ln { left: var(--lnw); }522.code .lc { white-space: pre; padding: 0 var(--space-4) 0 var(--space-2); flex: 1; }523.code .mk { flex: none; width: 1.2em; left: calc(2 * var(--lnw)); text-align: center; color: var(--faint); }524.code .l.add { background: var(--add-bg); }525.code .l.add .ln { --gut: var(--add-ln); }526.code .l.add .mk { --gut: var(--add-bg); color: var(--add-fg); }527.code .l.del { background: var(--del-bg); }528.code .l.del .ln { --gut: var(--del-ln); }529.code .l.del .mk { --gut: var(--del-bg); color: var(--del-fg); }530.code .l:target { --gut: var(--target-bg); background: var(--gut); }531.code .l:target .ln, .code .l:target .mk { --gut: var(--target-gut); }532.code .l.meta { color: var(--muted); font-style: italic; }533.code .hunk { background: var(--hunk-bg); color: var(--hunk-fg); padding: var(--space-half) var(--space-3); white-space: pre; }534.code .hunk > span { display: inline-block; position: sticky; left: var(--space-3); }535body.wrap-lines .code .lc { white-space: pre-wrap; overflow-wrap: anywhere; }536body.wrap-lines .code .rows { display: block; }537body.wrap-lines .code .hunk { white-space: pre-wrap; overflow-wrap: anywhere; }538/* Narrow screens wrap unless the reader turned it off (ui.ts sets no-wrap). */539@media (max-width: 720px) {540 body:not(.no-wrap) .code .lc, body:not(.no-wrap) .code .hunk { white-space: pre-wrap; overflow-wrap: anywhere; }541 body:not(.no-wrap) .code .rows { display: block; }542}543/* In a panel, code is the panel's body: no second edge. A diff's file is a544 panel whose head strip (.fh) sticks while its lines scroll by. */545.panel > .code { border: 0; }546.fh { position: sticky; top: 0; z-index: 2; gap: var(--space-1) var(--space-2); }547/* A long path shares the line with the status and counts, wrapping at its548 slashes, rather than wrapping onto a line of its own. */549.fh .fname { font-family: var(--mono); color: var(--fg); font-weight: var(--fw-medium); overflow-wrap: break-word; flex: 1 1 0; min-width: min(4em, 100%); max-width: max-content; }550.fh .counts { font: var(--fw-regular) var(--text-xs) var(--mono); }551.fh .st { font-size: var(--text-xs); }552.fh .view { margin-left: auto; font-size: var(--text-sm); }553.file .note { margin: 0; padding: var(--space-3); color: var(--muted); }554table.diffstat td { padding: var(--space-half) var(--space-2); }555table.diffstat td.num { font-size: var(--text-xs); color: var(--muted); }556table.diffstat td.graph { font: var(--fw-regular) var(--text-xs) var(--mono); white-space: nowrap; }557table.diffstat td.main { overflow-wrap: break-word; }558table.diffstat td.main a { font-family: var(--mono); font-size: var(--text-sm); }559/* Phones: the path takes the row and wraps at slashes; the count and graph560 follow on the same line if they fit, else right-aligned below. */561@media (max-width: 720px) {562 table.diffstat tr { display: flex; flex-wrap: wrap; justify-content: flex-end; align-items: baseline; gap: 0 var(--space-2); padding: var(--space-1) var(--space-2); border-bottom: var(--hairline) solid var(--border); }563 table.diffstat td { padding: 0; border: 0; }564 table.diffstat td.main { flex: 1 1 0; width: auto; min-width: min(10em, 100%); }565}566567/* syntax highlighting: highlight.js classes, colours from the tokens */568.hljs-comment, .hljs-code, .hljs-formula { color: var(--tok-comment); }569.hljs-keyword, .hljs-doctag, .hljs-template-tag, .hljs-template-variable, .hljs-type, .hljs-variable.language_, .hljs-meta .hljs-keyword { color: var(--tok-keyword); }570.hljs-string, .hljs-regexp, .hljs-meta .hljs-string { color: var(--tok-string); }571.hljs-number, .hljs-literal, .hljs-attr, .hljs-attribute, .hljs-variable, .hljs-meta, .hljs-operator, .hljs-selector-attr, .hljs-selector-class, .hljs-selector-id { color: var(--tok-const); }572.hljs-title, .hljs-section { color: var(--tok-title); }573.hljs-built_in, .hljs-symbol, .hljs-bullet { color: var(--tok-builtin); }574.hljs-name, .hljs-quote, .hljs-selector-tag, .hljs-selector-pseudo { color: var(--tok-tag); }575.hljs-subst, .hljs-params { color: var(--fg); }576.hljs-section, .hljs-strong { font-weight: var(--fw-strong); }577.hljs-emphasis { font-style: italic; }578.hljs-addition { color: var(--add-fg); }579.hljs-deletion { color: var(--del-fg); }580581/* commit and review pages */582.meta-head { margin: 0 0 var(--space-4); }583.meta-head h1 { font-size: var(--text-xl); margin: 0 0 var(--space-2); overflow-wrap: anywhere; }584.meta-head h1 a { color: var(--fg); }585.meta-head h1 a:hover { color: var(--fg); }586.meta-head h1 .desc { font: var(--fw-regular) var(--text-lg) var(--mono); color: var(--faint); }587.meta-head .byline { color: var(--muted); font-size: var(--text-sm); }588/* A commit message, or a command to copy, as a panel's body. */589.msg, .panel > pre { margin: 0; padding: var(--space-3); white-space: pre-wrap; overflow-wrap: anywhere; font: var(--fw-regular) var(--text-sm)/1.55 var(--mono); }590.panel > pre { font-size: var(--text-xs); }591.panel > .msg + .facts { border-top: var(--hairline) solid var(--border); }592/* A note under a panel's body, on a rule. */593.panel > p.desc { margin: 0; padding: var(--space-2) var(--space-3); border-top: var(--hairline) solid var(--border); font-size: var(--text-sm); }594.panel > p.desc:first-child, .panel > .panel-head + p.desc { border-top: 0; }595.depth { color: var(--faint); white-space: pre; }596597/* Above a diff: the diffstat, a panel with the page's tools in its head strip. */598.count { color: var(--muted); font-size: var(--text-sm); white-space: nowrap; }599@media (max-width: 480px) { .count .wide { display: none; } }600.file .code .l { scroll-margin-top: 2.75rem; }601602/* Diff files fold (diffs.ts); review pages add a viewed checkbox. */603.fh.foldable { cursor: pointer; }604.fh .fold { min-height: 0; border: 0; background: none; padding: 0; width: 1.1em; color: var(--muted); font: var(--fw-regular) var(--text-md)/1 var(--sans); }605.fh .fold:hover { background: none; color: var(--fg); }606.fh .fold::before { content: "\25BE"; display: inline-block; transition: transform 0.1s; }607.file.folded .fold::before { transform: rotate(-90deg); }608.file.folded > :not(.fh) { display: none; }609.file.folded > .fh { border-bottom: 0; }610.file.folded:has(.viewed input:checked) .fname { color: var(--muted); font-weight: var(--fw-regular); }611.fh .viewed { margin-left: auto; min-height: 0; }612.fh .view ~ .viewed { margin-left: 0; }613614/* prose: README, wiki, release notes, review descriptions, comments: the615 serif, for what people wrote */616.markdown { font: var(--fw-regular) var(--text-prose)/1.65 var(--serif); overflow-wrap: break-word; padding: var(--space-5) var(--space-6); }617.markdown > :first-child { margin-top: 0; }618.markdown h1, .markdown h2, .markdown h3, .markdown h4 { font-family: var(--sans); font-weight: var(--fw-strong); color: var(--fg); }619.markdown h1, .markdown h2 { display: block; border-bottom: var(--hairline) solid var(--border); padding-bottom: var(--space-1); }620.markdown h1 { font-size: var(--text-2xl); margin: var(--space-6) 0 var(--space-3); }621.markdown h2 { font-size: var(--text-xl); margin: var(--space-6) 0 var(--space-3); }622.markdown h3 { font-size: var(--text-lg); }623.markdown h4 { font-size: var(--text-md); }624.markdown a:visited { color: var(--deep); }625.markdown img { max-width: 100%; height: auto; }626.markdown p[align="center"], .markdown div[align="center"] { text-align: center; }627/* Code in prose sits on the panel's tint. Inline code never breaks at a628 hyphen: lib/markdown.ts keeps hyphenated words whole (.nobr) and lets a629 path break after its slashes (<wbr>). */630.markdown code { background: var(--tint); border: var(--hairline) solid var(--border); padding: 0 var(--space-1); }631.markdown code .nobr { white-space: nowrap; }632.markdown pre { font: var(--fw-regular) var(--text-code)/1.55 var(--mono); background: var(--tint); border: var(--hairline) solid var(--border); padding: var(--space-3) var(--space-4); overflow-x: auto; tab-size: 4; }633.markdown pre code { background: none; border: 0; padding: 0; }634/* Diagrams, drawn at build time (lib/diagrams.ts): Pele's SVG takes its635 colours and fonts from these, so it follows light and dark as it is. */636.markdown .diagram { margin: var(--space-4) 0; text-align: center; container-type: inline-size; }637/* A wide diagram's second drawing, laid out for a phone, replaces it there. */638.markdown .diagram > svg + svg { display: none; }639@container (max-width: 640px) {640 .markdown .diagram > svg:has(+ svg) { display: none; }641 .markdown .diagram > svg + svg { display: inline; }642}643.markdown .diagram-source { margin: var(--space-4) 0; }644.markdown .diagram-source figcaption { color: var(--muted); font-size: var(--text-sm); }645.pele {646 --pele-bg: var(--raised); --pele-fg: var(--fg); --pele-muted: var(--muted); --pele-line: var(--muted);647 --pele-surface: var(--tint); --pele-surface-alt: var(--hover); --pele-border: var(--border-strong); --pele-accent: var(--accent);648 --pele-critical: var(--fail); --pele-error: var(--fail); --pele-font: var(--sans); --pele-font-mono: var(--mono); --pele-radius: var(--radius);649 --pele-title-weight: var(--fw-strong); --pele-heading-weight: var(--fw-strong); --pele-bold-weight: var(--fw-strong);650 --pele-series-1: var(--viz-1); --pele-series-2: var(--pass); --pele-series-3: var(--warn); --pele-series-4: var(--fail);651 --pele-series-5: var(--tok-title); --pele-series-6: var(--tok-const); --pele-series-7: var(--tok-builtin); --pele-series-8: var(--muted);652}653.markdown blockquote { margin: var(--space-4) 0; padding: 0 var(--space-4); color: var(--muted); border-left: var(--bar) solid var(--border); }654.markdown table { display: block; max-width: 100%; overflow-x: auto; border-collapse: collapse; margin: var(--space-4) 0; font-size: var(--text-md); }655.markdown th, .markdown td { border-bottom: var(--hairline) solid var(--border); padding: var(--space-2) var(--space-3) var(--space-2) 0; text-align: left; vertical-align: top; }656.markdown th { font-family: var(--sans); font-weight: var(--fw-strong); }657.markdown hr { border-top: var(--hairline) solid var(--border); }658.markdown :target { scroll-margin-top: var(--space-4); }659@media (max-width: 720px) {660 .markdown { padding: var(--space-4) var(--space-3); }661 .markdown h1 { font-size: var(--text-xl); }662 .markdown h2 { font-size: var(--text-lg); }663}664/* jev: commits and repos */665/* What Jev says a commit or a repo is: plain words, quiet. */666.jev-kind { font: var(--fw-regular) var(--text-xs) var(--mono); color: var(--muted); white-space: nowrap; }667.jev-topic { color: var(--muted); white-space: nowrap; }668/* Jev's risk score, 0 to 3: a small number, coloured once it is moderate. */669.jev-score { font: var(--fw-regular) var(--text-xs) var(--mono); font-variant-numeric: tabular-nums; color: var(--faint); white-space: nowrap; }670.jev-score.moderate { color: var(--warn); }671.jev-score.high { color: var(--fail); font-weight: var(--fw-strong); }672.jev-mismatch { font-size: var(--text-sm); color: var(--faint); cursor: help; }673.jev-note { font-size: var(--text-sm); font-style: italic; color: var(--faint); }674/* The log: Jev's type in a narrow column on the left. */675table.log th.kind, table.log td.kind { width: 1%; white-space: nowrap; padding-right: var(--space-1); }676table.log td.risk { width: 1%; }677@media (max-width: 720px) {678 /* Stacked: the type leads the line under the subject. */679 table.log td.kind, table.log td.risk { width: auto; padding-right: 0; }680 table.log td.kind { order: 1; }681 table.log td.kind:empty { display: none; }682}683.notes { padding: var(--space-1) var(--space-3) var(--space-3); font: var(--fw-regular) var(--text-md)/1.6 var(--serif); }684.notes h3 { margin: var(--space-4) 0 var(--space-1); font: var(--fw-strong) var(--text-sm)/1.4 var(--sans); color: var(--muted); }685.notes ul { margin: 0; padding-left: var(--space-5); }686.notes li { margin: var(--space-half) 0; overflow-wrap: anywhere; }687/* Each note is a commit's subject, linked: in ink, like a list's rows. */688.notes li > a:first-child { color: var(--fg); }689.notes li > a:first-child:hover { color: var(--fg); }690.notes details { margin-top: var(--space-3); }691.notes summary { cursor: pointer; font: var(--fw-strong) var(--text-sm)/1.4 var(--sans); color: var(--muted); }692.notes summary:hover { color: var(--fg); }693694/* jev: review pages */695.jev-mech, .jev-change { font: var(--fw-regular) var(--text-xs) var(--mono); color: var(--muted); white-space: nowrap; }696.jev-change.behavior, .jev-sum.behavior { color: var(--warn); }697.jev-sum { font-size: var(--text-sm); font-weight: var(--fw-regular); color: var(--muted); }698summary .jev-sum { margin-left: var(--space-2); }699.comment .tag.jev-addressed { color: var(--pass); }