Jump to…
snowfix: 500 error because of database is patchedyuzpxzopsouq1mo
1/*
2 * Dogfood design tokens and base styles.
3 *
4 * Ported from the Claude Design project "Dogfood" (Dogfood.dc.html), so the
5 * Maud UI matches the design that was signed off.
6 *
7 * Two accents carry the whole product, and they are not interchangeable:
8 *
9 * --identity gold. Change ids, stack rails, the revision timeline — every
10 * place the UI is pointing at a *thing that keeps its name*
11 * through rewrites. This is the concept the product is about.
12 * --action teal. Links and the one primary control per view. Ordinary
13 * navigation, never identity.
14 *
15 * Using identity for a button, or action for a change id, breaks the only
16 * colour rule the interface has.
17 *
18 * NOTE — deviation from spec §1, recorded deliberately: the spec chose Tailwind.
19 * This is hand-authored CSS instead, because Tailwind would put a Node
20 * toolchain in the release image for what is currently a small, stable set of
21 * styles. The tokens below are the same ones the design defines, so moving
22 * to Tailwind later is a mechanical change rather than a redesign.
23 */
24
25:root {
26 color-scheme: dark;
27
28 /* neutrals — cool near-black, dark is primary */
29 --bg: #15161a;
30 --surface: #1b1d22;
31 --surface-raised: #22252b;
32 --border: #2c3037;
33 --border-strong: #3b4048;
34 --text: #e7e9ec;
35 --text-dim: #989ea9;
36 --text-faint: #686e79;
37
38 /* the two accents */
39 --identity: #d9a441;
40 --action: #6ba9b8;
41 /* Ink for text sitting *on* a filled --action surface. Dark in the dark
42 theme, because the action colour is light enough to need it. */
43 --on-action: #0e1417;
44 /* The identity tint. Marks stacked rows and selected revisions without
45 drawing a border — a border would read as a separate object. */
46 --identity-wash: rgb(217 164 65 / 0.09);
47
48 /* state */
49 --open: #63a06b;
50 --merged: #8b7fd4;
51 --abandoned: #767d88;
52 --conflict: #c77bc4;
53 --danger: #d06b6b;
54
55 /* diff */
56 --diff-add-bg: #1b2f23;
57 --diff-add-text: #7fcb99;
58 --diff-del-bg: #33201f;
59 --diff-del-text: #e39494;
60 --diff-conflict-bg: #2b1f30;
61 --diff-conflict-text: #d9a0d6;
62
63 /* syntax highlighting (spec §8), cooled to sit on the near-black ground */
64 --hl-comment: #6c727d;
65 --hl-keyword: #c78fd0;
66 --hl-string: #8fbf94;
67 --hl-number: #d9a441;
68 --hl-function: #6ba9b8;
69 --hl-type: #d9b877;
70 --hl-constant: #d9a441;
71 --hl-variable: #e7e9ec;
72 --hl-operator: #989ea9;
73 --hl-tag: #c78fd0;
74 --hl-attribute: #6ba9b8;
75 --hl-punctuation: #686e79;
76
77 /* shape */
78 --radius: 3px;
79 --radius-sm: 2px;
80
81 /* Each stack keeps its system fallbacks after the hosted face, so a failed
82 or blocked font request degrades to a near-enough system font rather than
83 to a default serif. */
84 --font-sans: "IBM Plex Sans", ui-sans-serif, system-ui, -apple-system,
85 "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
86 --font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, "SF Mono", Menlo,
87 Consolas, "Liberation Mono", monospace;
88 --font-condensed: "IBM Plex Sans Condensed", ui-sans-serif,
89 "Roboto Condensed", "Arial Narrow", system-ui, sans-serif;
90
91 /* Type scale, base 14/21. The half-pixel 12.5 is the design's, and it is
92 load-bearing: metadata has to read as a rank below body text without
93 dropping to 12, which goes fuzzy in the condensed face. */
94 --text-xs: 11px;
95 --text-sm: 12.5px;
96 --text-base: 14px;
97 --text-md: 16px;
98 --text-lg: 20px;
99 --text-xl: 24px;
100 --text-2xl: 28px;
101 --text-3xl: 44px;
102}
103
104:root.light,
105:root[data-theme="light"] {
106 color-scheme: light;
107
108 --bg: #f6f6f4;
109 --surface: #ffffff;
110 --surface-raised: #ffffff;
111 --border: #e0e0dc;
112 --border-strong: #c6c7c2;
113 --text: #1a1c20;
114 --text-dim: #5c616b;
115 --text-faint: #8a8f98;
116
117 /* Both accents darken sharply here. They are carrying the same meanings
118 against a white ground, which needs far more depth to hold contrast. */
119 --identity: #9a6b12;
120 --action: #20707f;
121 --on-action: #ffffff;
122 --identity-wash: rgb(154 107 18 / 0.08);
123
124 --open: #2e7d3a;
125 --merged: #5b4fb0;
126 --abandoned: #6b7280;
127 --conflict: #9b3e96;
128 --danger: #b03434;
129
130 --diff-add-bg: #e4f3e7;
131 --diff-add-text: #1d6b32;
132 --diff-del-bg: #fae7e6;
133 --diff-del-text: #9e2b25;
134 --diff-conflict-bg: #f6e9f5;
135 --diff-conflict-text: #7e2b79;
136
137 --hl-comment: #6f7580;
138 --hl-keyword: #7e2b79;
139 --hl-string: #2f6b34;
140 --hl-number: #8a5e10;
141 --hl-function: #20707f;
142 --hl-type: #855c14;
143 --hl-constant: #8a5e10;
144 --hl-variable: #1a1c20;
145 --hl-operator: #5c616b;
146 --hl-tag: #7e2b79;
147 --hl-attribute: #20707f;
148 --hl-punctuation: #8a8f98;
149}
150
151/* ─── fonts ──────────────────────────────────────────────────────────────── */
152
153/* Self-hosted rather than pulled from a CDN: the CSP is `font-src 'self'`, and
154 a forge should not phone a third party on every page load.
155 All Latin subsets — see `serve_font` in main.rs for why.
156 `font-display: swap` so text is readable while the face is still in flight;
157 the fallback stacks are chosen to be close enough in width that the swap
158 does not reflow the page badly. */
159
160@font-face {
161 font-family: "IBM Plex Sans";
162 font-style: normal;
163 font-weight: 400;
164 font-display: swap;
165 src: url("/assets/fonts/plex-sans-400.woff2") format("woff2");
166}
167
168@font-face {
169 font-family: "IBM Plex Sans";
170 font-style: normal;
171 font-weight: 500;
172 font-display: swap;
173 src: url("/assets/fonts/plex-sans-500.woff2") format("woff2");
174}
175
176@font-face {
177 font-family: "IBM Plex Sans";
178 font-style: normal;
179 font-weight: 600;
180 font-display: swap;
181 src: url("/assets/fonts/plex-sans-600.woff2") format("woff2");
182}
183
184@font-face {
185 font-family: "IBM Plex Sans Condensed";
186 font-style: normal;
187 font-weight: 500;
188 font-display: swap;
189 src: url("/assets/fonts/plex-condensed-500.woff2") format("woff2");
190}
191
192@font-face {
193 font-family: "IBM Plex Sans Condensed";
194 font-style: normal;
195 font-weight: 600;
196 font-display: swap;
197 src: url("/assets/fonts/plex-condensed-600.woff2") format("woff2");
198}
199
200@font-face {
201 font-family: "IBM Plex Sans Condensed";
202 font-style: normal;
203 font-weight: 700;
204 font-display: swap;
205 src: url("/assets/fonts/plex-condensed-700.woff2") format("woff2");
206}
207
208@font-face {
209 font-family: "JetBrains Mono";
210 font-style: normal;
211 font-weight: 400;
212 font-display: swap;
213 src: url("/assets/fonts/jetbrains-mono-400.woff2") format("woff2");
214}
215
216@font-face {
217 font-family: "JetBrains Mono";
218 font-style: normal;
219 font-weight: 500;
220 font-display: swap;
221 src: url("/assets/fonts/jetbrains-mono-500.woff2") format("woff2");
222}
223
224*,
225*::before,
226*::after {
227 box-sizing: border-box;
228}
229
230html,
231body {
232 margin: 0;
233 padding: 0;
234}
235
236body {
237 background: var(--bg);
238 color: var(--text);
239 font-family: var(--font-sans);
240 font-size: 14px;
241 line-height: 1.5;
242 -webkit-font-smoothing: antialiased;
243}
244
245::selection {
246 background: color-mix(in srgb, var(--action) 32%, transparent);
247 color: var(--text);
248}
249
250a,
251button,
252input,
253select,
254textarea,
255[role="button"],
256[tabindex] {
257 transition:
258 background-color 80ms ease-out,
259 border-color 80ms ease-out,
260 color 80ms ease-out,
261 box-shadow 120ms ease-out,
262 opacity 80ms ease-out;
263}
264
265a {
266 color: var(--action);
267 text-decoration: none;
268}
269
270a:hover {
271 text-decoration: underline;
272}
273
274code,
275pre,
276.mono {
277 font-family: var(--font-mono);
278 font-size: 12.5px;
279}
280
281/* ─── layout ─────────────────────────────────────────────────────────────── */
282
283/* The design runs a 1440px measure. That is far too wide for prose, so it is
284 never the width of a paragraph — long-form text inside it is capped at its
285 own `max-width` (see `.prose`, `.measure`). What the 1440 buys is the dense
286 multi-column tables the forge is actually made of. */
287.masthead-inner,
288.subnav-inner,
289.wrap {
290 max-width: 1440px;
291 margin: 0 auto;
292 padding: 0 24px;
293}
294
295/* Sticky so the wordmark and the jump control stay reachable down a long diff.
296 Opaque rather than translucent: the rows beneath it are 28-34px hairline
297 grids, and sliding them under a blur turns the header into mush. */
298.masthead {
299 position: sticky;
300 top: 0;
301 z-index: 30;
302 border-bottom: 1px solid var(--border);
303 background: var(--surface);
304}
305
306/* Three columns rather than a flex row with a spacer: a grid's centre column
307 sizes to its content and stays put between two equal flexible columns, so
308 the jump control holds the visual centre of the bar no matter how wide the
309 brand/nav on one side or the account controls on the other happen to be. */
310.masthead-inner {
311 display: grid;
312 grid-template-columns: 1fr auto 1fr;
313 align-items: center;
314 column-gap: 14px;
315 height: 46px;
316}
317
318.masthead-group {
319 display: flex;
320 align-items: center;
321 gap: 14px;
322 min-width: 0;
323}
324
325.masthead-group-start {
326 justify-self: start;
327}
328
329.masthead-group-end {
330 justify-self: end;
331}
332
333/* The wordmark: a bordered `df` tile beside the name, both monospace. The
334 product is a command-line tool with a website attached, and the mark says
335 so. */
336.brand {
337 display: inline-flex;
338 align-items: center;
339 gap: 8px;
340 flex-shrink: 0;
341 color: var(--text);
342 font-family: var(--font-mono);
343 font-size: var(--text-base);
344}
345
346.brand:hover {
347 text-decoration: none;
348}
349
350.brand-mark {
351 width: 18px;
352 height: 18px;
353 flex: none;
354 display: inline-flex;
355 align-items: center;
356 justify-content: center;
357 border: 1px solid var(--identity);
358 border-radius: var(--radius-sm);
359 font-size: var(--text-xs);
360 color: var(--identity);
361}
362
363/* A hairline divider between groups in a horizontal bar. */
364.vrule {
365 width: 1px;
366 height: 18px;
367 flex: none;
368 background: var(--border);
369}
370
371.spacer {
372 flex: 1;
373}
374
375.masthead nav {
376 display: flex;
377 gap: 8px;
378 align-items: center;
379}
380
381.masthead nav a {
382 color: var(--text-dim);
383 white-space: nowrap;
384}
385
386.masthead nav a:hover {
387 color: var(--text);
388 text-decoration: none;
389}
390
391/* The "Jump to…" control. A link, not a button: with scripting off it
392 navigates to /search, which is the same destination the palette reaches by
393 another route. The keycap is decorative in that case, so it is hidden from
394 assistive technology and revealed as meaningful only by palette.js. */
395.jump {
396 display: inline-flex;
397 align-items: center;
398 gap: 8px;
399 height: 24px;
400 padding: 0 6px 0 8px;
401 border: 1px solid var(--border-strong);
402 border-radius: var(--radius);
403 background: var(--bg);
404 color: var(--text-faint);
405 font-size: var(--text-sm);
406 cursor: pointer;
407}
408
409.jump:hover {
410 border-color: var(--action);
411 color: var(--action);
412 text-decoration: none;
413}
414
415/* Condensed uppercase, because it is a state readout ("DARK") rather than a
416 verb — pressing it is what changes the state. */
417.theme-toggle {
418 height: 24px;
419 padding: 0 8px;
420 border: 1px solid var(--border-strong);
421 border-radius: var(--radius);
422 background: transparent;
423 color: var(--text-dim);
424 font-family: var(--font-condensed);
425 font-size: var(--text-xs);
426 font-weight: 500;
427 letter-spacing: 0.06em;
428 text-transform: uppercase;
429 cursor: pointer;
430}
431
432/* ─── repository sub-bar ─────────────────────────────────────────────────── */
433
434/* The second row of chrome, present on every page inside a repository: where
435 you are, whether it is public, the four sections, and the repo's vital
436 signs. Full-bleed and hairline-ruled so it reads as part of the frame
437 rather than as page content. */
438.subnav {
439 border-bottom: 1px solid var(--border);
440 background: var(--surface);
441}
442
443.subnav-inner {
444 display: flex;
445 align-items: center;
446 gap: 12px;
447 height: 40px;
448 overflow-x: auto;
449 scrollbar-width: none;
450}
451
452.subnav-inner::-webkit-scrollbar {
453 display: none;
454}
455
456.subnav-path {
457 font-family: var(--font-mono);
458 font-size: var(--text-sm);
459 white-space: nowrap;
460 flex: none;
461}
462
463.subnav-path .sep {
464 color: var(--text-faint);
465}
466
467.subnav-meta {
468 font-family: var(--font-mono);
469 font-size: var(--text-xs);
470 color: var(--text-faint);
471 white-space: nowrap;
472 flex: none;
473}
474
475main {
476 padding: 14px 0 48px;
477}
478
479/* A page built from edge-to-edge bands owns its own vertical rhythm: each band
480 carries its padding and its closing rule, so `main` adds neither. */
481main.flush {
482 padding: 0;
483}
484
485/* ─── the ⌘K palette ─────────────────────────────────────────────────────── */
486
487/* Anchored near the top rather than centred: the list grows downward as you
488 type, and a vertically-centred panel would shift the row under the cursor
489 on every keystroke. */
490.palette-backdrop {
491 position: fixed;
492 inset: 0;
493 z-index: 50;
494 display: flex;
495 justify-content: center;
496 padding: 96px 16px 16px;
497 background: rgb(10 11 13 / 0.55);
498}
499
500.palette-backdrop[hidden] {
501 display: none;
502}
503
504.palette {
505 width: 560px;
506 max-width: 100%;
507 height: max-content;
508 max-height: calc(100vh - 128px);
509 display: flex;
510 flex-direction: column;
511 border: 1px solid var(--border-strong);
512 border-radius: var(--radius);
513 background: var(--surface-raised);
514 overflow: hidden;
515}
516
517.palette-bar {
518 display: flex;
519 align-items: center;
520 gap: 8px;
521 height: 36px;
522 padding: 0 10px;
523 border-bottom: 1px solid var(--border);
524 flex: none;
525}
526
527.palette-prompt {
528 font-family: var(--font-mono);
529 font-size: var(--text-sm);
530 color: var(--identity);
531}
532
533.palette-input {
534 flex: 1;
535 height: 100%;
536 border: none;
537 background: transparent;
538 padding: 0;
539 font-family: var(--font-mono);
540 font-size: var(--text-base);
541}
542
543.palette-input:focus {
544 outline: none;
545 border-color: transparent;
546}
547
548/* Chrome and Safari draw their own clear button inside a search input; it
549 collides with the esc keycap. */
550.palette-input::-webkit-search-cancel-button {
551 display: none;
552}
553
554.palette-results {
555 overflow-y: auto;
556 padding-bottom: 4px;
557}
558
559.palette-group-label {
560 padding: 6px 10px 2px;
561}
562
563.palette-item {
564 display: flex;
565 align-items: center;
566 gap: 8px;
567 min-height: 26px;
568 padding: 0 10px;
569 width: 100%;
570 border: none;
571 background: transparent;
572 color: var(--text);
573 font: inherit;
574 text-align: left;
575 cursor: pointer;
576}
577
578.palette-item:hover,
579.palette-item.is-active {
580 background: var(--surface);
581 text-decoration: none;
582}
583
584.palette-glyph {
585 width: 12px;
586 flex: none;
587 font-family: var(--font-mono);
588 font-size: var(--text-xs);
589 color: var(--action);
590}
591
592.palette-label {
593 font-size: var(--text-sm);
594 white-space: nowrap;
595 flex: none;
596}
597
598.palette-label.mono {
599 font-family: var(--font-mono);
600 color: var(--identity);
601 font-weight: 500;
602}
603
604/* The context line gives up its space first — the label is what you are
605 aiming at. */
606.palette-hint {
607 font-size: var(--text-sm);
608 overflow: hidden;
609 text-overflow: ellipsis;
610 white-space: nowrap;
611 min-width: 0;
612 flex: 1;
613}
614
615.palette-all {
616 border-top: 1px solid var(--border);
617 margin-top: 4px;
618 padding-top: 4px;
619}
620
621.palette-empty,
622.palette-foot {
623 padding: 10px;
624 font-size: var(--text-sm);
625}
626
627.palette-foot {
628 border-top: 1px solid var(--border);
629 font-family: var(--font-mono);
630 font-size: var(--text-xs);
631 color: var(--text-faint);
632 flex: none;
633}
634
635/* ─── typography ─────────────────────────────────────────────────────────── */
636
637h1 {
638 font-size: var(--text-lg);
639 line-height: 28px;
640 font-weight: 600;
641 letter-spacing: -0.01em;
642 margin: 0 0 6px;
643}
644
645h2 {
646 font-size: var(--text-md);
647 line-height: 24px;
648 font-weight: 600;
649 margin: 0 0 10px;
650}
651
652.lede {
653 color: var(--text-dim);
654 margin: 0 0 24px;
655 max-width: 60ch;
656}
657
658.dim {
659 color: var(--text-dim);
660}
661
662.faint {
663 color: var(--text-faint);
664}
665
666/* The eyebrow. Condensed, uppercase, tracked out — used for every section
667 label in the design, and never for anything a reader has to actually read. */
668.label-condensed {
669 font-family: var(--font-condensed);
670 font-weight: 500;
671 font-size: var(--text-xs);
672 line-height: 16px;
673 letter-spacing: 0.06em;
674 text-transform: uppercase;
675 color: var(--text-dim);
676}
677
678/* Display type: the landing headline and the change-detail id. Sans rather
679 than condensed, tight tracking, and only ever used at 24px and above. */
680.display {
681 font-size: var(--text-3xl);
682 line-height: 48px;
683 font-weight: 600;
684 letter-spacing: -0.02em;
685 text-wrap: pretty;
686 margin: 0;
687}
688
689@media (max-width: 800px) {
690 .display {
691 font-size: 30px;
692 line-height: 34px;
693 }
694}
695
696.tnum {
697 font-variant-numeric: tabular-nums;
698}
699
700/* Prose caps its own line length regardless of how wide the column is. */
701.measure {
702 max-width: 72ch;
703 text-wrap: pretty;
704}
705
706/* A keycap. Bottom border doubled so it reads as a physical key at 11px
707 without needing a shadow. */
708.kbd {
709 display: inline-flex;
710 align-items: center;
711 justify-content: center;
712 height: 16px;
713 min-width: 16px;
714 padding: 0 4px;
715 border: 1px solid var(--border-strong);
716 border-bottom-width: 2px;
717 border-radius: var(--radius-sm);
718 font-family: var(--font-mono);
719 font-size: var(--text-xs);
720 color: var(--text-faint);
721}
722
723/* A key/value row joined by a dotted leader — the design's stat blocks. The
724 leader is a flexing border rather than a repeated character so it lands on
725 the same baseline whatever the two ends are. */
726.dotline {
727 display: flex;
728 align-items: baseline;
729 gap: 8px;
730 font-size: var(--text-sm);
731}
732
733.dotline > .dotline-key {
734 color: var(--text-dim);
735}
736
737/* The leader sits in source order after the value but is ordered before it,
738 so the markup stays "key, value" and reads correctly to a screen reader. */
739.dotline::after {
740 content: "";
741 flex: 1;
742 border-bottom: 1px dotted var(--border);
743}
744
745.dotline > .dotline-val {
746 order: 3;
747 font-family: var(--font-mono);
748 font-size: var(--text-xs);
749 font-variant-numeric: tabular-nums;
750}
751
752.float-shadow {
753 box-shadow: 0 8px 28px rgb(0 0 0 / 0.4);
754}
755
756:root.light .float-shadow,
757:root[data-theme="light"] .float-shadow {
758 box-shadow: 0 8px 28px rgb(0 0 0 / 0.12);
759}
760
761/* ─── surfaces ───────────────────────────────────────────────────────────── */
762
763/* The generic bordered block, used by the settings, profile, org and form
764 pages. 14px rather than 20 — the whole system moved a density step, and a
765 panel that still breathes like the old one reads as a different product. */
766.panel {
767 background: var(--surface);
768 border: 1px solid var(--border);
769 border-radius: var(--radius);
770 padding: 14px;
771}
772
773.panel + .panel {
774 margin-top: 16px;
775}
776
777.stack {
778 display: flex;
779 flex-direction: column;
780 gap: 16px;
781}
782
783.row {
784 display: flex;
785 align-items: center;
786 gap: 10px;
787}
788
789/* ─── controls ───────────────────────────────────────────────────────────── */
790
791/* Secondary is the default. Transparent, hairline, and it reaches for
792 `--action` only on hover — a page full of filled buttons has no primary. */
793.btn {
794 display: inline-flex;
795 align-items: center;
796 justify-content: center;
797 gap: 6px;
798 height: 28px;
799 padding: 0 10px;
800 border-radius: var(--radius);
801 border: 1px solid var(--border-strong);
802 background: transparent;
803 color: var(--text);
804 font: inherit;
805 font-size: var(--text-sm);
806 white-space: nowrap;
807 cursor: pointer;
808}
809
810.btn:hover {
811 border-color: var(--action);
812 color: var(--action);
813 text-decoration: none;
814}
815
816/* One per view. Filled `--action` — the single move the page is asking for. */
817.btn-primary {
818 background: var(--action);
819 border-color: var(--action);
820 color: var(--on-action);
821 font-weight: 500;
822}
823
824.btn-primary:hover {
825 filter: brightness(1.08);
826 color: var(--on-action);
827 border-color: var(--action);
828}
829
830.btn-danger {
831 border-color: var(--danger);
832 color: var(--danger);
833}
834
835.btn-danger:hover {
836 border-color: var(--danger);
837 color: var(--danger);
838 background: color-mix(in srgb, var(--danger) 12%, transparent);
839}
840
841/* Sign out is destructive enough to warn about but too routine to shout, so it
842 sits quiet in the masthead and only reddens on approach. */
843.btn-quiet-danger {
844 background: transparent;
845 border-color: transparent;
846 color: var(--text-dim);
847}
848
849.btn-quiet-danger:hover {
850 border-color: color-mix(in srgb, var(--danger) 50%, transparent);
851 color: var(--danger);
852}
853
854.btn[disabled],
855.btn[aria-disabled="true"] {
856 border-color: var(--border);
857 color: var(--text-faint);
858 cursor: default;
859}
860
861.btn[disabled]:hover,
862.btn[aria-disabled="true"]:hover {
863 border-color: var(--border);
864 color: var(--text-faint);
865}
866
867/* The compact monospace button used inside dense bars — blame, replay, edit,
868 copy. Small enough that it has to be mono to stay legible. */
869.btn-mono {
870 height: 24px;
871 padding: 0 8px;
872 font-family: var(--font-mono);
873 font-size: var(--text-xs);
874 color: var(--text-dim);
875}
876
877.btn-mono[aria-pressed="true"] {
878 border-color: var(--action);
879 color: var(--action);
880}
881
882input[type="text"],
883input[type="password"],
884input[type="search"],
885input[type="email"],
886textarea {
887 width: 100%;
888 padding: 0 8px;
889 height: 30px;
890 background: var(--bg);
891 border: 1px solid var(--border-strong);
892 border-radius: var(--radius);
893 color: var(--text);
894 font: inherit;
895 font-size: var(--text-base);
896}
897
898textarea {
899 height: auto;
900 min-height: 64px;
901 padding: 6px 8px;
902 line-height: 21px;
903 resize: vertical;
904}
905
906input:focus,
907textarea:focus,
908select:focus {
909 border-color: var(--action);
910}
911
912input:focus,
913textarea:focus,
914.btn:focus-visible,
915a:focus-visible {
916 outline: 2px solid var(--action);
917 outline-offset: 1px;
918}
919
920.field {
921 margin-bottom: 14px;
922}
923
924.field label {
925 display: block;
926 margin-bottom: 5px;
927 font-family: var(--font-condensed);
928 font-size: var(--text-xs);
929 font-weight: 500;
930 letter-spacing: 0.06em;
931 text-transform: uppercase;
932 color: var(--text-dim);
933}
934
935.hint {
936 margin-top: 5px;
937 font-size: var(--text-sm);
938 color: var(--text-faint);
939}
940
941/* ─── chips and states ───────────────────────────────────────────────────── */
942
943.chip {
944 display: inline-flex;
945 align-items: center;
946 height: 18px;
947 padding: 0 5px;
948 border-radius: var(--radius-sm);
949 border: 1px solid var(--border-strong);
950 font-family: var(--font-mono);
951 font-size: var(--text-xs);
952 color: var(--text-dim);
953 white-space: nowrap;
954}
955
956/* The change id, rendered inline rather than boxed.
957 A jj change id has a *shortest unique prefix* — the part you actually type
958 and paste. The design splits it: the prefix carries `--identity` at weight
959 500, the remainder drops to `--text-faint`. Both halves are selectable as
960 one string, so copying still yields the whole id. This is the single most
961 repeated element in the product; everything else defers to it. */
962.cid {
963 font-family: var(--font-mono);
964 font-variant-numeric: tabular-nums;
965 white-space: nowrap;
966}
967
968.cid > .cid-p {
969 color: var(--identity);
970 font-weight: 500;
971}
972
973.cid > .cid-r {
974 color: var(--text-faint);
975}
976
977a.cid:hover {
978 text-decoration: none;
979}
980
981a.cid:hover > .cid-p {
982 text-decoration: underline;
983}
984
985/* Synthetic ids get no identity colour: a plain-git change has a derived id,
986 and presenting it as a real one would be a lie the reader cannot detect. */
987.cid-synthetic > .cid-p {
988 color: var(--text-dim);
989 font-weight: 400;
990}
991
992/* State pills are outlined in their state colour, not filled with it. Four
993 states appear side by side in listings, and four filled blocks of colour
994 read as a chart rather than as metadata. */
995.badge {
996 display: inline-flex;
997 align-items: center;
998 gap: 5px;
999 height: 20px;
1000 padding: 0 6px;
1001 border: 1px solid var(--border-strong);
1002 border-radius: var(--radius-sm);
1003 font-family: var(--font-condensed);
1004 font-size: var(--text-xs);
1005 font-weight: 500;
1006 letter-spacing: 0.06em;
1007 text-transform: uppercase;
1008 color: var(--text-dim);
1009 white-space: nowrap;
1010}
1011
1012/* The state glyph. Decorative — the pill's text already names the state — so
1013 call sites mark it `aria-hidden`. */
1014.badge > .glyph {
1015 font-family: var(--font-mono);
1016 font-size: var(--text-xs);
1017 line-height: 1;
1018}
1019
1020.badge-open { color: var(--open); border-color: var(--open); }
1021.badge-merged { color: var(--merged); border-color: var(--merged); }
1022.badge-abandoned { color: var(--abandoned); border-color: var(--abandoned); }
1023.badge-conflict { color: var(--conflict); border-color: var(--conflict); }
1024.badge-draft { color: var(--text-dim); border-color: var(--border-strong); }
1025
1026/* ─── notices ────────────────────────────────────────────────────────────── */
1027
1028.banner {
1029 padding: 10px 12px;
1030 font-size: var(--text-sm);
1031 border-radius: var(--radius);
1032 border: 1px solid var(--border-strong);
1033 background: var(--surface);
1034 margin-bottom: 20px;
1035}
1036
1037.banner-error {
1038 border-color: color-mix(in srgb, var(--danger) 50%, transparent);
1039 background: color-mix(in srgb, var(--danger) 12%, transparent);
1040}
1041
1042.banner-ok {
1043 border-color: color-mix(in srgb, var(--open) 50%, transparent);
1044 background: color-mix(in srgb, var(--open) 12%, transparent);
1045}
1046
1047/* ─── empty states ───────────────────────────────────────────────────────── */
1048
1049/* An empty state is a solid bordered block like every other listing, not a
1050 dashed placeholder: on this system a dashed border means "an action goes
1051 here" (see `.repo-card-new`), and an empty change list is not an action. */
1052.empty {
1053 display: flex;
1054 flex-direction: column;
1055 align-items: center;
1056 gap: 6px;
1057 text-align: center;
1058 padding: 28px 20px;
1059 color: var(--text-dim);
1060 border: 1px solid var(--border);
1061 border-radius: var(--radius);
1062 background: var(--surface);
1063}
1064
1065.empty h2 {
1066 margin: 0;
1067 font-size: var(--text-md);
1068 line-height: 24px;
1069 color: var(--text);
1070}
1071
1072.empty p {
1073 margin: 0;
1074 font-size: var(--text-sm);
1075}
1076
1077/* ─── code blocks ────────────────────────────────────────────────────────── */
1078
1079
1080/* Protocol toggle (HTTPS/SSH clone instructions). Two radios drive which
1081 panel shows via a sibling selector — no script needed, so the choice works
1082 identically with JavaScript on or off. */
1083.proto-toggle {
1084 margin-top: 14px;
1085}
1086
1087.proto-toggle input[type="radio"] {
1088 position: absolute;
1089 opacity: 0;
1090 pointer-events: none;
1091}
1092
1093.proto-tabs {
1094 display: inline-flex;
1095 overflow: hidden;
1096 border: 1px solid var(--border);
1097 border-radius: var(--radius);
1098}
1099
1100.proto-tab {
1101 padding: 3px 12px;
1102 font-size: var(--text-xs);
1103 font-weight: 500;
1104 color: var(--text-dim);
1105 cursor: pointer;
1106 user-select: none;
1107}
1108
1109.proto-tab + .proto-tab {
1110 border-left: 1px solid var(--border);
1111}
1112
1113.proto-tab:hover {
1114 background: var(--surface-raised);
1115 color: var(--text);
1116}
1117
1118#proto-https:focus-visible ~ .proto-tabs label[for="proto-https"],
1119#proto-ssh:focus-visible ~ .proto-tabs label[for="proto-ssh"] {
1120 outline: 2px solid var(--action);
1121 outline-offset: 2px;
1122}
1123
1124.proto-panel {
1125 display: none;
1126}
1127
1128#proto-https:checked ~ #panel-https,
1129#proto-ssh:checked ~ #panel-ssh {
1130 display: block;
1131}
1132
1133#proto-https:checked ~ .proto-tabs label[for="proto-https"],
1134#proto-ssh:checked ~ .proto-tabs label[for="proto-ssh"] {
1135 background: var(--action);
1136 color: var(--on-action);
1137}
1138
1139footer {
1140 border-top: 1px solid var(--border);
1141 margin-top: 0;
1142 padding: 16px 0;
1143 background: var(--surface);
1144 color: var(--text-faint);
1145 font-size: var(--text-sm);
1146}
1147
1148footer .wrap {
1149 display: flex;
1150 gap: 8px 16px;
1151 align-items: center;
1152 flex-wrap: wrap;
1153}
1154
1155footer a {
1156 color: var(--text-faint);
1157 font-family: var(--font-mono);
1158 font-size: var(--text-xs);
1159}
1160
1161footer a:hover {
1162 color: var(--action);
1163}
1164
1165.footer-mark {
1166 font-family: var(--font-mono);
1167 font-size: var(--text-sm);
1168 color: var(--text-faint);
1169}
1170
1171/* The spacer collapses to nothing once the row wraps, which is what keeps the
1172 copyright from stranding itself on a line of its own. */
1173.footer-end {
1174 font-size: var(--text-xs);
1175}
1176
1177/* Respect a user's reduced-motion preference (spec §11 accessibility pass). */
1178@media (prefers-reduced-motion: reduce) {
1179 *,
1180 *::before,
1181 *::after {
1182 animation-duration: 0.01ms !important;
1183 transition-duration: 0.01ms !important;
1184 }
1185}
1186
1187.live-indicator {
1188 display: inline-flex;
1189 align-items: center;
1190 gap: 6px;
1191}
1192
1193.live-dot {
1194 width: 6px;
1195 height: 6px;
1196 border-radius: 50%;
1197 background: var(--merged);
1198 animation: live-pulse 2s ease-in-out infinite;
1199}
1200
1201@keyframes live-pulse {
1202 0%, 100% { opacity: 1; }
1203 50% { opacity: 0.35; }
1204}
1205
1206
1207/* ─── full-bleed bands ─────────────────────────────────────────────────────── */
1208
1209/* The landing page is a stack of edge-to-edge bands, alternating ground and
1210 surface, each separated by a single hairline. There are no cards and no
1211 shadows: the rules do all the dividing. */
1212.band {
1213 border-bottom: 1px solid var(--border);
1214}
1215
1216/* The footer draws its own top rule, so the last band must not draw one too —
1217 two hairlines a pixel apart read as a rendering fault. */
1218main.flush > .band:last-child {
1219 border-bottom: none;
1220}
1221
1222.band-surface {
1223 background: var(--surface);
1224}
1225
1226.band > .wrap {
1227 padding-top: 32px;
1228 padding-bottom: 32px;
1229}
1230
1231.band-head {
1232 display: flex;
1233 align-items: baseline;
1234 gap: 12px;
1235 flex-wrap: wrap;
1236 margin-bottom: 12px;
1237}
1238
1239.band-head h2 {
1240 margin: 0;
1241 font-size: var(--text-lg);
1242 line-height: 28px;
1243}
1244
1245.band-note {
1246 font-size: var(--text-sm);
1247 color: var(--text-dim);
1248}
1249
1250/* ─── hero ─────────────────────────────────────────────────────────────────── */
1251
1252.hero > .wrap {
1253 padding-top: 44px;
1254 padding-bottom: 44px;
1255 display: grid;
1256 grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr);
1257 gap: 44px;
1258 align-items: center;
1259}
1260
1261.hero-body {
1262 display: flex;
1263 flex-direction: column;
1264 align-items: flex-start;
1265 gap: 16px;
1266}
1267
1268/* An outlined tag, not a filled one. The hero already has a primary button;
1269 a second saturated block above it would compete with it. */
1270.hero-eyebrow {
1271 display: inline-flex;
1272 align-items: center;
1273 height: 20px;
1274 padding: 0 6px;
1275 border: 1px solid var(--border-strong);
1276 border-radius: var(--radius-sm);
1277}
1278
1279/* 15ch is what puts the break after "pointer." — the headline is a two-part
1280 sentence and the line break is doing the punctuation. */
1281.hero-title {
1282 max-width: 15ch;
1283}
1284
1285.hero-lede {
1286 margin: 0;
1287 font-size: var(--text-md);
1288 line-height: 24px;
1289 color: var(--text-dim);
1290 max-width: 44ch;
1291 text-wrap: pretty;
1292}
1293
1294.hero-lede .mono {
1295 color: var(--text);
1296}
1297
1298.hero-actions {
1299 display: flex;
1300 gap: 8px;
1301 align-items: center;
1302 flex-wrap: wrap;
1303 padding-top: 4px;
1304}
1305
1306.hero-actions .btn {
1307 height: 32px;
1308 padding: 0 12px;
1309 font-size: var(--text-base);
1310}
1311
1312/* The clone line. A `$` and a command — the first thing a visitor actually
1313 needs, so it sits in the hero rather than three scrolls down. */
1314.clone-box {
1315 display: flex;
1316 align-items: center;
1317 gap: 8px;
1318 height: 30px;
1319 max-width: 100%;
1320 padding: 0 8px;
1321 border: 1px solid var(--border-strong);
1322 border-radius: var(--radius);
1323 background: var(--bg);
1324 font-family: var(--font-mono);
1325 font-size: var(--text-sm);
1326 overflow-x: auto;
1327}
1328
1329.clone-box .prompt {
1330 color: var(--text-faint);
1331 flex: none;
1332}
1333
1334.clone-box code {
1335 color: var(--text);
1336 white-space: nowrap;
1337 background: none;
1338 padding: 0;
1339 border: 0;
1340}
1341
1342/* The block form of the clone box, used on the empty-repository page where the
1343 command sits on its own line rather than inline in a hero. */
1344.proto-panel .clone-box {
1345 height: auto;
1346 padding: 8px 10px;
1347}
1348
1349/* ─── the terminal illustration ────────────────────────────────────────────── */
1350
1351/* A transcript, not a live console. It is captioned as an example and it never
1352 animates: a fake terminal that types at you is indistinguishable from one
1353 showing real state, and this one is showing neither. */
1354.term {
1355 margin: 0;
1356 display: flex;
1357 flex-direction: column;
1358 border: 1px solid var(--border-strong);
1359 border-radius: var(--radius);
1360 background: var(--bg);
1361 overflow: hidden;
1362}
1363
1364.term-bar,
1365.term-foot {
1366 display: flex;
1367 align-items: center;
1368 gap: 8px;
1369 padding: 0 8px;
1370 background: var(--surface-raised);
1371 flex: none;
1372}
1373
1374.term-bar {
1375 height: 28px;
1376 border-bottom: 1px solid var(--border);
1377}
1378
1379.term-foot {
1380 padding: 6px 12px;
1381 border-top: 1px solid var(--border);
1382 font-size: var(--text-sm);
1383 color: var(--text-dim);
1384}
1385
1386.term-dots {
1387 display: flex;
1388 gap: 4px;
1389 flex: none;
1390}
1391
1392.term-dots span {
1393 width: 7px;
1394 height: 7px;
1395 border-radius: 50%;
1396 background: var(--border-strong);
1397}
1398
1399.term-host {
1400 font-family: var(--font-mono);
1401 font-size: var(--text-xs);
1402 color: var(--text-faint);
1403}
1404
1405.term-body {
1406 margin: 0;
1407 padding: 10px 12px;
1408 min-height: 300px;
1409 font-family: var(--font-mono);
1410 font-size: var(--text-sm);
1411 line-height: 20px;
1412 overflow-x: auto;
1413 white-space: pre;
1414}
1415
1416.term-cmd {
1417 color: var(--text);
1418}
1419
1420.term-cmd::before {
1421 content: "$ ";
1422 color: var(--identity);
1423}
1424
1425.term-out { color: var(--text-dim); }
1426.term-out.is-faint { color: var(--text-faint); }
1427.term-out.is-add { color: var(--diff-add-text); }
1428.term-out.is-action { color: var(--action); }
1429.term-out.is-open { color: var(--open); }
1430.term-out.is-conflict { color: var(--conflict); }
1431.term-out.is-identity { color: var(--identity); }
1432
1433/* ─── terminal animation ───────────────────────────────────────────────────── */
1434
1435/* While animating, the body keeps its min-height so it does not collapse. */
1436.term-body.term-animating {
1437 min-height: 300px;
1438}
1439
1440/* Lines start hidden and are revealed by adding .term-line-visible, rather
1441 than via inline styles: the CSP's style-src has no 'unsafe-inline'. */
1442.term-body.term-animating [data-term-line] {
1443 visibility: hidden;
1444 height: 0;
1445 overflow: hidden;
1446}
1447
1448.term-body.term-animating [data-term-line].term-line-visible {
1449 visibility: visible;
1450 height: auto;
1451 overflow: visible;
1452}
1453
1454/* The blinking cursor for the line currently being typed. */
1455.term-typing::after {
1456 content: "";
1457 display: inline-block;
1458 width: 0.55em;
1459 height: 1.15em;
1460 margin-left: 1px;
1461 vertical-align: text-bottom;
1462 background: var(--identity);
1463 animation: term-blink 0.6s steps(2, start) infinite;
1464}
1465
1466@keyframes term-blink {
1467 to { opacity: 0; }
1468}
1469
1470/* Once animation is done, no residual style. */
1471.term-body.term-ready [data-term-line] {
1472 visibility: visible !important;
1473 height: auto !important;
1474 overflow: visible !important;
1475}
1476
1477/* ─── ticker ───────────────────────────────────────────────────────────────── */
1478
1479/* The design scrolls this. It does not scroll here: it is decoration carrying
1480 six words of copy, and an infinite marquee is a permanent moving object on
1481 the page for anyone who did not ask for one. It wraps instead. */
1482.ticker {
1483 display: flex;
1484 flex-wrap: wrap;
1485 overflow: hidden;
1486}
1487
1488.ticker-item {
1489 display: inline-flex;
1490 align-items: center;
1491 gap: 10px;
1492 height: 34px;
1493 padding: 0 14px;
1494 white-space: nowrap;
1495 font-family: var(--font-condensed);
1496 font-size: var(--text-xs);
1497 font-weight: 500;
1498 letter-spacing: 0.06em;
1499 text-transform: uppercase;
1500 color: var(--text-dim);
1501}
1502
1503.ticker-dot {
1504 font-family: var(--font-mono);
1505 color: var(--identity);
1506}
1507
1508/* ─── two-column body ──────────────────────────────────────────────────────── */
1509
1510.columns {
1511 display: grid;
1512 grid-template-columns: minmax(0, 1fr) 260px;
1513 gap: 32px;
1514 align-items: start;
1515}
1516
1517.columns-main {
1518 min-width: 0;
1519 display: flex;
1520 flex-direction: column;
1521 gap: 8px;
1522}
1523
1524.columns-aside {
1525 display: flex;
1526 flex-direction: column;
1527 gap: 16px;
1528 min-width: 0;
1529}
1530
1531/* Each aside block is separated by a rule rather than boxed. The last one
1532 drops its rule so the column does not end on a line to nowhere. */
1533.aside-block {
1534 display: flex;
1535 flex-direction: column;
1536 gap: 6px;
1537 padding-bottom: 14px;
1538 border-bottom: 1px solid var(--border);
1539}
1540
1541.aside-block:last-child {
1542 padding-bottom: 0;
1543 border-bottom: none;
1544}
1545
1546.aside-head {
1547 display: flex;
1548 align-items: center;
1549 gap: 8px;
1550}
1551
1552/* ─── activity feed ────────────────────────────────────────────────────────── */
1553
1554.feed {
1555 border: 1px solid var(--border);
1556 border-radius: var(--radius);
1557 background: var(--surface);
1558 overflow: hidden;
1559 margin: 0;
1560 padding: 0;
1561 list-style: none;
1562}
1563
1564.feed-row {
1565 display: flex;
1566 align-items: center;
1567 gap: 8px;
1568 height: 34px;
1569 padding: 0 10px 0 0;
1570 border-bottom: 1px solid var(--border);
1571 font-size: var(--text-sm);
1572}
1573
1574.feed-row:last-child {
1575 border-bottom: none;
1576}
1577
1578.feed-row:hover {
1579 background: var(--surface-raised);
1580}
1581
1582/* The event glyph, in the colour of what happened. */
1583.feed-glyph {
1584 width: 22px;
1585 flex: none;
1586 text-align: center;
1587 font-family: var(--font-mono);
1588 font-size: var(--text-sm);
1589}
1590
1591.feed-glyph.is-push { color: var(--action); }
1592.feed-glyph.is-review { color: var(--open); }
1593.feed-glyph.is-merge { color: var(--merged); }
1594.feed-glyph.is-conflict { color: var(--conflict); }
1595.feed-glyph.is-abandon { color: var(--abandoned); }
1596.feed-glyph.is-open { color: var(--identity); }
1597
1598.feed-repo {
1599 font-family: var(--font-mono);
1600 font-size: var(--text-sm);
1601 white-space: nowrap;
1602 flex: none;
1603 color: var(--text-faint);
1604}
1605
1606.feed-repo .owner {
1607 color: var(--identity);
1608 font-weight: 500;
1609}
1610
1611.feed-verb {
1612 color: var(--text-dim);
1613 white-space: nowrap;
1614 flex: none;
1615}
1616
1617/* The one element allowed to be cut off. Everything else in the row is a
1618 fixed-width fact; the title is the part that can be finished by clicking. */
1619.feed-title {
1620 font-size: var(--text-base);
1621 color: var(--text);
1622 overflow: hidden;
1623 text-overflow: ellipsis;
1624 white-space: nowrap;
1625 min-width: 0;
1626 flex: 1;
1627}
1628
1629.feed-age {
1630 font-family: var(--font-mono);
1631 font-size: var(--text-xs);
1632 color: var(--text-faint);
1633 white-space: nowrap;
1634 flex: none;
1635 width: 56px;
1636 text-align: right;
1637}
1638
1639.feed-foot {
1640 display: flex;
1641 align-items: center;
1642 gap: 8px;
1643 font-size: var(--text-sm);
1644 color: var(--text-faint);
1645}
1646
1647/* A live-ish dot. It does not blink — the page is server-rendered and does not
1648 update itself, so a pulsing indicator would be claiming something false. */
1649.live-dot {
1650 width: 6px;
1651 height: 6px;
1652 border-radius: 50%;
1653 background: var(--open);
1654 display: inline-block;
1655}
1656
1657.live-indicator {
1658 display: inline-flex;
1659 align-items: center;
1660 gap: 6px;
1661 font-family: var(--font-mono);
1662 font-size: var(--text-xs);
1663 color: var(--text-faint);
1664}
1665
1666/* ─── compact aside rows ───────────────────────────────────────────────────── */
1667
1668/* A change in an aside list: rail, glyph, id, title. The rail is what marks it
1669 as part of a stack. */
1670.mini-row {
1671 display: flex;
1672 align-items: center;
1673 gap: 6px;
1674 min-height: 22px;
1675 font-size: var(--text-sm);
1676 min-width: 0;
1677}
1678
1679.mini-row:hover {
1680 background: var(--surface);
1681 text-decoration: none;
1682}
1683
1684.mini-rail {
1685 width: 2px;
1686 align-self: stretch;
1687 background: var(--identity);
1688 flex: none;
1689}
1690
1691.mini-glyph {
1692 width: 14px;
1693 flex: none;
1694 text-align: center;
1695 font-family: var(--font-mono);
1696 font-size: var(--text-xs);
1697}
1698
1699.mini-title {
1700 color: var(--text-dim);
1701 overflow: hidden;
1702 text-overflow: ellipsis;
1703 white-space: nowrap;
1704 min-width: 0;
1705}
1706
1707.mini-age {
1708 font-family: var(--font-mono);
1709 font-size: var(--text-xs);
1710 color: var(--text-faint);
1711 margin-left: auto;
1712 flex: none;
1713}
1714
1715/* ─── bookmark rows ────────────────────────────────────────────────────────── */
1716
1717.bookmark-line {
1718 display: flex;
1719 align-items: center;
1720 gap: 6px;
1721 font-size: var(--text-sm);
1722 color: var(--text-dim);
1723}
1724
1725.bookmark-line:hover {
1726 text-decoration: none;
1727}
1728
1729.bookmark-line:hover .chip {
1730 border-color: var(--action);
1731 color: var(--action);
1732}
1733
1734.bookmark-flag {
1735 font-family: var(--font-condensed);
1736 font-size: var(--text-xs);
1737 font-weight: 500;
1738 letter-spacing: 0.06em;
1739 text-transform: uppercase;
1740 color: var(--text-faint);
1741}
1742
1743.bookmark-flag.is-diverged {
1744 color: var(--conflict);
1745}
1746
1747/* ─── why switch ───────────────────────────────────────────────────────────── */
1748
1749.compare {
1750 display: grid;
1751 grid-template-columns: repeat(4, minmax(0, 1fr));
1752 gap: 8px;
1753 margin: 0;
1754 padding: 0;
1755 list-style: none;
1756}
1757
1758/* Each card is literally a two-line diff: the thing you put up with, then the
1759 thing you get. The pitch is stated in the product's own notation. */
1760.compare-item {
1761 border: 1px solid var(--border);
1762 border-radius: var(--radius);
1763 background: var(--bg);
1764 overflow: hidden;
1765}
1766
1767.compare-them,
1768.compare-us {
1769 display: flex;
1770 align-items: flex-start;
1771 gap: 8px;
1772 padding: 8px 10px;
1773 font-size: var(--text-base);
1774 text-wrap: pretty;
1775}
1776
1777.compare-them {
1778 border-bottom: 1px solid var(--border);
1779 color: var(--text-dim);
1780}
1781
1782.compare-us {
1783 background: var(--identity-wash);
1784 color: var(--text);
1785}
1786
1787.compare-sign {
1788 font-family: var(--font-mono);
1789 font-size: var(--text-sm);
1790 flex: none;
1791}
1792
1793.compare-them .compare-sign { color: var(--danger); }
1794.compare-us .compare-sign { color: var(--identity); }
1795
1796/* ─── repo cards ───────────────────────────────────────────────────────────── */
1797
1798.repo-cards {
1799 display: grid;
1800 grid-template-columns: repeat(3, minmax(0, 1fr));
1801 gap: 8px;
1802}
1803
1804.repo-card {
1805 display: flex;
1806 flex-direction: column;
1807 gap: 6px;
1808 padding: 12px;
1809 border: 1px solid var(--border);
1810 border-radius: var(--radius);
1811 background: var(--surface);
1812 color: var(--text);
1813}
1814
1815.repo-card:hover {
1816 border-color: var(--action);
1817 text-decoration: none;
1818}
1819
1820.repo-card-name {
1821 font-family: var(--font-mono);
1822 font-size: var(--text-base);
1823 color: var(--action);
1824}
1825
1826.repo-card-desc {
1827 font-size: var(--text-sm);
1828 line-height: 18px;
1829 color: var(--text-dim);
1830 text-wrap: pretty;
1831}
1832
1833.repo-card-meta {
1834 display: flex;
1835 align-items: center;
1836 gap: 10px;
1837 padding-top: 2px;
1838 font-family: var(--font-mono);
1839 font-size: var(--text-xs);
1840 color: var(--text-faint);
1841}
1842
1843.repo-card-meta .is-open { color: var(--open); }
1844.repo-card-meta .is-conflict { color: var(--conflict); }
1845.repo-card-meta .at-end { margin-left: auto; }
1846
1847/* A dashed slot rather than a filled card: it is an action in a list of
1848 objects, and it should not look like one of the objects. */
1849.repo-card-new {
1850 display: flex;
1851 align-items: center;
1852 justify-content: center;
1853 min-height: 84px;
1854 border: 1px dashed var(--border-strong);
1855 border-radius: var(--radius);
1856 font-family: var(--font-mono);
1857 font-size: var(--text-sm);
1858 color: var(--text-dim);
1859}
1860
1861.repo-card-new:hover {
1862 border-color: var(--action);
1863 color: var(--action);
1864 text-decoration: none;
1865}
1866
1867/* ─── call to action ───────────────────────────────────────────────────────── */
1868
1869.cta > .wrap {
1870 padding-top: 40px;
1871 padding-bottom: 40px;
1872 display: flex;
1873 align-items: center;
1874 gap: 20px;
1875 flex-wrap: wrap;
1876}
1877
1878.cta-title {
1879 margin: 0;
1880 font-size: var(--text-xl);
1881 line-height: 32px;
1882 font-weight: 600;
1883 text-wrap: pretty;
1884}
1885
1886.cta-lede {
1887 margin: 0;
1888 font-size: var(--text-base);
1889 color: var(--text-dim);
1890 text-wrap: pretty;
1891}
1892
1893/* ─── dashboard ────────────────────────────────────────────────────────────── */
1894
1895.dash-head {
1896 display: flex;
1897 align-items: flex-start;
1898 gap: 16px;
1899 flex-wrap: wrap;
1900 padding-bottom: 14px;
1901 margin-bottom: 16px;
1902 border-bottom: 1px solid var(--border);
1903}
1904
1905.dash-title {
1906 margin: 0;
1907 font-size: var(--text-xl);
1908 line-height: 32px;
1909 font-weight: 600;
1910}
1911
1912.dash-name {
1913 color: var(--identity);
1914}
1915
1916.dash-section {
1917 display: flex;
1918 flex-direction: column;
1919 gap: 8px;
1920}
1921
1922.dash-section + .dash-section {
1923 margin-top: 20px;
1924}
1925
1926.section-head {
1927 display: flex;
1928 align-items: center;
1929 gap: 10px;
1930 flex-wrap: wrap;
1931}
1932
1933.section-note {
1934 margin: 0;
1935 font-size: var(--text-sm);
1936}
1937
1938/* ─── watched activity ─────────────────────────────────────────────────────── */
1939
1940.activity {
1941 margin: 0;
1942 padding: 0;
1943 list-style: none;
1944 display: flex;
1945 flex-direction: column;
1946}
1947
1948/* A left rule instead of a box: these are one-line statements, and forty of
1949 them in forty boxes is unreadable. */
1950.activity-row {
1951 display: flex;
1952 align-items: baseline;
1953 gap: 6px;
1954 padding: 3px 8px;
1955 border-left: 2px solid var(--border);
1956 font-size: var(--text-sm);
1957 color: var(--text-dim);
1958 flex-wrap: wrap;
1959}
1960
1961.activity-actor {
1962 color: var(--text);
1963 font-weight: 500;
1964}
1965
1966.activity-when {
1967 margin-left: auto;
1968 font-family: var(--font-mono);
1969 font-size: var(--text-xs);
1970 white-space: nowrap;
1971}
1972
1973/* ─── responsive ───────────────────────────────────────────────────────────── */
1974
1975@media (max-width: 1120px) {
1976 .columns {
1977 grid-template-columns: minmax(0, 1fr);
1978 }
1979
1980 .compare {
1981 grid-template-columns: repeat(2, minmax(0, 1fr));
1982 }
1983
1984 .repo-cards {
1985 grid-template-columns: repeat(2, minmax(0, 1fr));
1986 }
1987}
1988
1989@media (max-width: 800px) {
1990 .hero > .wrap {
1991 grid-template-columns: minmax(0, 1fr);
1992 gap: 28px;
1993 }
1994
1995 .compare,
1996 .repo-cards {
1997 grid-template-columns: minmax(0, 1fr);
1998 }
1999
2000 /* The feed loses its verb and repo columns before it loses the title. */
2001 .feed-verb,
2002 .feed-repo {
2003 display: none;
2004 }
2005}
2006/* ─── design system pages ──────────────────────────────────────────────────── */
2007
2008.design-head,
2009.rationale > header {
2010 padding-bottom: 20px;
2011 border-bottom: 1px solid var(--border);
2012 margin-bottom: 8px;
2013}
2014
2015.design-title {
2016 margin: 0 0 6px;
2017 font-size: var(--text-xl);
2018 line-height: 32px;
2019 font-weight: 600;
2020 letter-spacing: -0.01em;
2021}
2022
2023/* The type specimen table: a fixed spec column and the sample beside it, so
2024 the sizes line up down the page and can be compared. */
2025.type-specimens {
2026 display: flex;
2027 flex-direction: column;
2028}
2029
2030.type-row {
2031 display: flex;
2032 align-items: baseline;
2033 gap: 12px;
2034 padding-bottom: 6px;
2035 margin-bottom: 6px;
2036 border-bottom: 1px solid var(--border);
2037}
2038
2039.type-spec {
2040 width: 96px;
2041 flex: none;
2042 font-family: var(--font-mono);
2043 font-size: var(--text-xs);
2044 color: var(--text-faint);
2045}
2046
2047.state-list {
2048 display: flex;
2049 flex-direction: column;
2050 max-width: 560px;
2051}
2052
2053.state-row {
2054 display: flex;
2055 align-items: center;
2056 gap: 10px;
2057 padding-bottom: 6px;
2058 margin-bottom: 6px;
2059 border-bottom: 1px solid var(--border);
2060 font-size: var(--text-sm);
2061}
2062
2063.design-section {
2064 padding: 28px 0;
2065 border-bottom: 1px solid var(--border);
2066}
2067
2068.design-section:last-child {
2069 border-bottom: none;
2070}
2071
2072.design-section-title {
2073 margin: 0 0 16px;
2074 font-size: var(--text-md);
2075}
2076
2077.design-row {
2078 display: flex;
2079 flex-wrap: wrap;
2080 align-items: center;
2081 gap: 10px;
2082 margin-bottom: 12px;
2083}
2084
2085.swatch-group-title {
2086 margin: 16px 0 8px;
2087 font-family: var(--font-condensed);
2088 font-size: var(--text-xs);
2089 font-weight: 500;
2090 letter-spacing: 0.08em;
2091 text-transform: uppercase;
2092 color: var(--text-dim);
2093}
2094
2095.swatches {
2096 display: grid;
2097 grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
2098 gap: 8px;
2099}
2100
2101.swatch {
2102 display: flex;
2103 align-items: center;
2104 gap: 10px;
2105 padding: 8px 10px;
2106 border: 1px solid var(--border);
2107 border-radius: var(--radius);
2108 background: var(--surface);
2109 font-size: var(--text-xs);
2110}
2111
2112/* Bordered so a swatch whose colour is close to the surface it sits on is
2113 still legible as a swatch. */
2114.swatch-chip {
2115 width: 22px;
2116 height: 22px;
2117 flex-shrink: 0;
2118 border: 1px solid var(--border-strong);
2119 border-radius: var(--radius-sm);
2120}
2121
2122.swatch-name {
2123 font-size: var(--text-xs);
2124}
2125
2126.type-specimens > * {
2127 margin: 0 0 12px;
2128}
2129
2130.rationale {
2131 max-width: 68ch;
2132}
2133
2134.rationale section {
2135 margin-top: 28px;
2136}
2137
2138.rationale h2 {
2139 font-size: var(--text-md);
2140 margin-bottom: 8px;
2141}
2142
2143.rationale p {
2144 line-height: 1.6;
2145 margin: 0 0 12px;
2146}
2147
2148/* ─── page headings ────────────────────────────────────────────────────────── */
2149
2150/* Everything that used to live in a boxed repository header now lives in the
2151 sub-bar (see `.subnav`), so all that is left here is the heading block a
2152 full-width page opens with. */
2153
2154/* ─── sign in ──────────────────────────────────────────────────────────────── */
2155
2156.signin {
2157 max-width: 380px;
2158 margin: 48px auto;
2159 display: flex;
2160 flex-direction: column;
2161 gap: 14px;
2162}
2163
2164.signin-intro {
2165 display: flex;
2166 flex-direction: column;
2167 gap: 6px;
2168}
2169
2170.signin-title {
2171 margin: 0;
2172 font-size: var(--text-xl);
2173 line-height: 32px;
2174 text-wrap: balance;
2175}
2176
2177/* The live methods are boxed together; anything outside the box is context,
2178 not a way in. */
2179.signin-card {
2180 display: flex;
2181 flex-direction: column;
2182 gap: 10px;
2183 padding: 14px;
2184 border: 1px solid var(--border);
2185 border-radius: var(--radius);
2186 background: var(--surface);
2187}
2188
2189.btn-block {
2190 display: flex;
2191 justify-content: center;
2192 width: 100%;
2193 height: 34px;
2194 font-size: var(--text-base);
2195}
2196
2197/* A control that is present but not offered. Not a `<button disabled>` because
2198 it is a `<span>` — there is nothing to submit, and nothing to focus. */
2199.is-disabled {
2200 opacity: 0.5;
2201 cursor: not-allowed;
2202}
2203
2204.is-disabled:hover {
2205 border-color: var(--border-strong);
2206 color: var(--text);
2207}
2208
2209.signin-or {
2210 display: flex;
2211 align-items: center;
2212 gap: 8px;
2213}
2214
2215.signin-rule {
2216 flex: 1;
2217 height: 1px;
2218 background: var(--border);
2219}
2220
2221.signin-note {
2222 display: flex;
2223 flex-direction: column;
2224 gap: 4px;
2225 padding: 10px;
2226 border: 1px solid var(--border);
2227 border-radius: var(--radius);
2228 background: var(--bg);
2229 font-size: var(--text-sm);
2230}
2231
2232.signin-note code {
2233 font-size: 12px;
2234 color: var(--text);
2235 background: none;
2236 border: 0;
2237 padding: 0;
2238 overflow-x: auto;
2239}
2240
2241/* ─── segmented control ────────────────────────────────────────────────────── */
2242
2243/* Two links styled as one control. Links rather than buttons because each half
2244 is a real, addressable URL — see `blob_view`. */
2245.segmented {
2246 display: inline-flex;
2247 overflow: hidden;
2248 border: 1px solid var(--border);
2249 border-radius: var(--radius);
2250}
2251
2252.segmented-item {
2253 display: inline-flex;
2254 align-items: center;
2255 height: 22px;
2256 padding: 0 10px;
2257 font-family: var(--font-mono);
2258 font-size: var(--text-xs);
2259 color: var(--text-dim);
2260}
2261
2262.segmented-item + .segmented-item {
2263 border-left: 1px solid var(--border);
2264}
2265
2266.segmented-item:hover {
2267 background: var(--surface-raised);
2268 color: var(--text);
2269 text-decoration: none;
2270}
2271
2272.segmented-item.is-on {
2273 background: var(--action);
2274 color: var(--on-action);
2275}
2276
2277.segmented-item.is-on:hover {
2278 background: var(--action);
2279 color: var(--on-action);
2280}
2281
2282/* ─── file listing ─────────────────────────────────────────────────────────── */
2283
2284.avatar {
2285 display: inline-flex;
2286 align-items: center;
2287 justify-content: center;
2288 flex-shrink: 0;
2289 width: 20px;
2290 height: 20px;
2291 border: 1px solid var(--border-strong);
2292 border-radius: var(--radius);
2293 background: var(--surface);
2294 color: var(--text-dim);
2295 font-family: var(--font-mono);
2296 font-size: var(--text-xs);
2297 font-weight: 500;
2298 text-transform: uppercase;
2299 user-select: none;
2300}
2301
2302.tree-crumbs {
2303 display: flex;
2304 align-items: center;
2305 gap: 8px;
2306 margin-bottom: 12px;
2307 flex-wrap: wrap;
2308}
2309
2310/* ─── bookmark switcher ────────────────────────────────────────────────────── */
2311
2312/* A `<details>`, so it opens and closes with no JavaScript at all. */
2313.switcher {
2314 position: relative;
2315 flex: none;
2316}
2317
2318.switcher > summary {
2319 list-style: none;
2320 cursor: pointer;
2321}
2322
2323.switcher > summary::-webkit-details-marker {
2324 display: none;
2325}
2326
2327.switcher-menu {
2328 position: absolute;
2329 z-index: 20;
2330 top: calc(100% + 4px);
2331 left: 0;
2332 min-width: 220px;
2333 max-height: 320px;
2334 overflow-y: auto;
2335 padding-bottom: 4px;
2336 border: 1px solid var(--border-strong);
2337 border-radius: var(--radius);
2338 background: var(--surface-raised);
2339 box-shadow: 0 8px 28px rgb(0 0 0 / 0.4);
2340}
2341
2342.switcher-label {
2343 padding: 6px 10px 2px;
2344}
2345
2346.switcher-item {
2347 display: flex;
2348 align-items: center;
2349 gap: 8px;
2350 height: 26px;
2351 padding: 0 10px;
2352 color: var(--text);
2353 font-size: var(--text-sm);
2354}
2355
2356.switcher-item:hover {
2357 background: var(--surface);
2358 text-decoration: none;
2359}
2360
2361.switcher-item.is-current {
2362 background: var(--identity-wash);
2363}
2364
2365.switcher-item.is-current .mono {
2366 color: var(--identity);
2367}
2368
2369/* A single-bookmark repository has nothing to switch to, so the control
2370 becomes a readout. */
2371.btn-mono.is-static {
2372 cursor: default;
2373 color: var(--text-dim);
2374}
2375
2376.btn-mono.is-static:hover {
2377 border-color: var(--border-strong);
2378 color: var(--text-dim);
2379}
2380
2381/* ─── the listing ──────────────────────────────────────────────────────────── */
2382
2383/* The tip-of-branch bar. Inside the listing's border rather than stacked on
2384 top of it, so the two are literally one object. */
2385.commit-bar {
2386 display: flex;
2387 align-items: center;
2388 gap: 8px;
2389 padding: 7px 8px;
2390 border-bottom: 1px solid var(--border);
2391 background: var(--surface-raised);
2392 font-size: var(--text-sm);
2393}
2394
2395/* The identity rail. Two pixels of gold saying "this row is about a change",
2396 the same mark the stack rails and revision timeline use. */
2397.commit-bar-rail {
2398 width: 2px;
2399 height: 14px;
2400 flex: none;
2401 background: var(--identity);
2402}
2403
2404.commit-bar-author {
2405 font-weight: 500;
2406 flex-shrink: 0;
2407}
2408
2409.commit-bar-msg {
2410 min-width: 0;
2411 color: var(--text);
2412 overflow: hidden;
2413 text-overflow: ellipsis;
2414 white-space: nowrap;
2415}
2416
2417.commit-bar-when {
2418 flex-shrink: 0;
2419 font-family: var(--font-mono);
2420 font-size: var(--text-xs);
2421 color: var(--text-faint);
2422}
2423
2424@media (max-width: 620px) {
2425 .commit-bar .cid {
2426 display: none;
2427 }
2428}
2429
2430/* ─── one commit ─── */
2431
2432/* The header of the commit page: the message, who wrote it, and where the
2433 commit sits in history. Three bands separated by rules, because they answer
2434 three different questions and a reader is usually after exactly one. */
2435.commit-meta {
2436 padding: 0;
2437 margin-bottom: 10px;
2438}
2439
2440.commit-msg {
2441 padding: 12px 14px;
2442}
2443
2444.commit-msg h1 {
2445 margin: 0;
2446 font-size: var(--text-lg);
2447 line-height: 1.35;
2448}
2449
2450/* The trailing body of a commit message is preformatted text — wrapped rather
2451 than scrolled, since a paragraph someone hard-wrapped at 72 columns is still
2452 prose and should read like it. */
2453.commit-body {
2454 margin: 8px 0 0;
2455 font-family: var(--font-mono);
2456 font-size: var(--text-sm);
2457 line-height: 1.55;
2458 color: var(--text-dim);
2459 white-space: pre-wrap;
2460 overflow-wrap: anywhere;
2461}
2462
2463.commit-byline,
2464.commit-ids {
2465 display: flex;
2466 align-items: center;
2467 gap: 8px;
2468 flex-wrap: wrap;
2469 padding: 8px 14px;
2470 border-top: 1px solid var(--border);
2471 font-size: var(--text-sm);
2472 color: var(--text-dim);
2473}
2474
2475.commit-ids {
2476 background: var(--surface-raised);
2477 border-radius: 0 0 var(--radius) var(--radius);
2478}
2479
2480.commit-parent {
2481 font-size: var(--text-xs);
2482 color: var(--text-faint);
2483 white-space: nowrap;
2484}
2485
2486.commit-parent:hover { color: var(--action); }
2487
2488/* The bordered container shared by the file listing, the bookmarks table and
2489 the README panel. */
2490.filelist {
2491 border: 1px solid var(--border);
2492 border-radius: var(--radius);
2493 background: var(--surface);
2494 overflow: hidden;
2495}
2496
2497.filelist + .filelist {
2498 margin-top: 12px;
2499}
2500
2501/* Fixed layout is what makes the `max-width: 0` ellipsis trick on
2502 `.filelist-message` / `.bookmark-title` actually hold: under the default auto
2503 layout the browser sizes columns from content instead, so a long commit
2504 message pushes the row wider rather than truncating. */
2505.filelist table {
2506 width: 100%;
2507 table-layout: fixed;
2508 border-collapse: collapse;
2509}
2510
2511.filelist thead th {
2512 padding: 6px 8px;
2513 text-align: left;
2514 border-bottom: 1px solid var(--border);
2515 font-family: var(--font-condensed);
2516 font-size: var(--text-xs);
2517 font-weight: 500;
2518 letter-spacing: 0.06em;
2519 text-transform: uppercase;
2520 color: var(--text-dim);
2521}
2522
2523.filelist tbody tr {
2524 border-bottom: 1px solid var(--border);
2525}
2526
2527.filelist tbody tr:last-child {
2528 border-bottom: none;
2529}
2530
2531.filelist tbody tr:hover {
2532 background: var(--surface-raised);
2533}
2534
2535.filelist td {
2536 padding: 0 8px;
2537 height: 28px;
2538 vertical-align: middle;
2539}
2540
2541.icon-dir {
2542 color: var(--action);
2543}
2544
2545.icon-file {
2546 color: var(--text-faint);
2547}
2548
2549/* The icon shares the name's cell rather than owning a column of its own.
2550 As its own column it was a fixed 28px, and any slack the table had to hand
2551 out widened it, so the icon stayed pinned left while the filename started
2552 further and further right — worst below the breakpoints that drop the
2553 message and change columns. In one cell the two are always 8px apart. */
2554.filelist-name {
2555 width: calc(32% + 28px);
2556}
2557
2558.filelist-entry {
2559 display: flex;
2560 align-items: center;
2561 min-width: 0;
2562}
2563
2564.filelist-entry-link {
2565 display: flex;
2566 align-items: center;
2567 gap: 10px;
2568 min-width: 0;
2569 color: var(--text);
2570}
2571
2572.filelist-entry-link svg {
2573 flex: none;
2574}
2575
2576/* `line-height: 1` here, not just on `.filelist-entry-link`: the body default
2577 of 1.5 gives the name span extra leading that JetBrains Mono splits
2578 unevenly above/below the glyphs, so the text visibly hangs below the
2579 icon's center instead of sitting beside it. */
2580.filelist-entry-name {
2581 overflow: hidden;
2582 text-overflow: ellipsis;
2583 white-space: nowrap;
2584 line-height: 1;
2585}
2586
2587.filelist-entry-link.is-dir {
2588 font-weight: 500;
2589}
2590
2591/* The underline goes on the text, not the link: on the link the rule would
2592 run under the icon as well. */
2593.filelist tr:hover .filelist-entry-link {
2594 color: var(--action);
2595}
2596
2597.filelist tr:hover .filelist-entry-name {
2598 text-decoration: underline;
2599}
2600
2601.filelist-note {
2602 flex: none;
2603 margin-left: 8px;
2604 font-size: var(--text-xs);
2605}
2606
2607/* The commit message column. `max-width: 0` is the "shrink to let the sibling
2608 ellipsis rule take over" trick — without it an auto-layout table lets a long
2609 message push the row wider instead of truncating. */
2610.filelist-message {
2611 max-width: 0;
2612 font-size: var(--text-xs);
2613}
2614
2615.filelist-message-link,
2616.filelist-message-text {
2617 overflow: hidden;
2618 text-overflow: ellipsis;
2619 white-space: nowrap;
2620 display: inline-block;
2621 max-width: 100%;
2622 vertical-align: bottom;
2623 color: var(--text-dim);
2624}
2625
2626.filelist-message-link:hover {
2627 color: var(--action);
2628 text-decoration: underline;
2629}
2630
2631.filelist-change {
2632 width: 132px;
2633 white-space: nowrap;
2634 font-size: var(--text-xs);
2635}
2636
2637.filelist-when {
2638 width: 72px;
2639 text-align: right;
2640 white-space: nowrap;
2641 font-family: var(--font-mono);
2642 font-size: var(--text-xs);
2643 color: var(--text-faint);
2644}
2645
2646/* The blanket `thead th` rule left the header hugging the left edge of a
2647 column whose ages are set flush right. Follow the cells. */
2648.filelist thead th.filelist-when {
2649 text-align: right;
2650}
2651
2652/* Below the wide breakpoint the listing keeps only what a filename needs. */
2653@media (max-width: 1120px) {
2654 .filelist-change {
2655 display: none;
2656 }
2657}
2658
2659@media (max-width: 800px) {
2660 .filelist-message {
2661 display: none;
2662 }
2663
2664 /* The message column was the one soaking up the table's leftover width;
2665 with it gone, hand the slack to the name instead of letting fixed layout
2666 split it across every remaining column. */
2667 .filelist-name {
2668 width: auto;
2669 }
2670}
2671
2672/* ─── the repository sidebar ───────────────────────────────────────────────── */
2673
2674/* Narrower than the landing page's aside: it holds stat lines and clone
2675 commands, not feed rows. */
2676.columns-repo {
2677 grid-template-columns: minmax(0, 1fr) 240px;
2678 gap: 24px;
2679}
2680
2681.aside-about {
2682 font-size: var(--text-sm);
2683 line-height: 18px;
2684 color: var(--text-dim);
2685 text-wrap: pretty;
2686}
2687
2688.aside-clone {
2689 display: flex;
2690 flex-direction: column;
2691 gap: 2px;
2692}
2693
2694/* The command scrolls inside its own box rather than widening the column —
2695 an SSH URL is longer than 240px and always will be. */
2696.aside-clone code {
2697 padding: 4px 6px;
2698 border: 1px solid var(--border-strong);
2699 border-radius: var(--radius);
2700 background: var(--bg);
2701 font-family: var(--font-mono);
2702 font-size: var(--text-xs);
2703 color: var(--text);
2704 white-space: nowrap;
2705 overflow-x: auto;
2706}
2707
2708/* ─── bookmarks page ───────────────────────────────────────────────────────── */
2709
2710.bookmark-table td {
2711 height: 30px;
2712 font-size: var(--text-sm);
2713}
2714
2715/* Explicit width so fixed table layout does not split the remaining space
2716 between this column and `.bookmark-title` — the title is the one column
2717 meant to flex and truncate. */
2718.bookmark-name {
2719 width: 220px;
2720 overflow: hidden;
2721 text-overflow: ellipsis;
2722 white-space: nowrap;
2723}
2724
2725.bookmark-name .mono {
2726 color: var(--text);
2727}
2728
2729.bookmark-points {
2730 width: 132px;
2731 white-space: nowrap;
2732}
2733
2734.bookmark-title {
2735 max-width: 0;
2736 color: var(--text-dim);
2737 overflow: hidden;
2738 text-overflow: ellipsis;
2739 white-space: nowrap;
2740}
2741
2742.bookmark-table thead th:nth-child(4) {
2743 text-align: right;
2744}
2745
2746.bookmark-when {
2747 width: 72px;
2748 text-align: right;
2749 font-family: var(--font-mono);
2750 font-size: var(--text-xs);
2751 color: var(--text-faint);
2752 white-space: nowrap;
2753}
2754
2755/* The heading block above a full-width page table. */
2756.page-head {
2757 display: flex;
2758 align-items: flex-start;
2759 gap: 12px;
2760 flex-wrap: wrap;
2761 margin-bottom: 10px;
2762}
2763
2764/* ─── readme ───────────────────────────────────────────────────────────────── */
2765
2766.readme-head {
2767 display: flex;
2768 align-items: center;
2769 gap: 8px;
2770 padding: 6px 8px;
2771 border-bottom: 1px solid var(--border);
2772 background: var(--surface-raised);
2773 font-family: var(--font-mono);
2774 font-size: var(--text-sm);
2775 line-height: 1;
2776}
2777
2778/* A README is prose. The listing above it can use the full 1440; this cannot,
2779 so it caps its own measure regardless of how wide the column is. */
2780.readme-body {
2781 padding: 16px;
2782 max-width: 76ch;
2783}
2784
2785.readme-body > :first-child {
2786 margin-top: 0;
2787}
2788
2789.readme-body > :last-child {
2790 margin-bottom: 0;
2791}
2792
2793/* Typography for rendered markdown: READMEs, issue/change descriptions, and
2794 comments all go through the same comrak pipeline and land here via
2795 `PreEscaped`, so one set of tag selectors styles every one of them. */
2796.markdown-body h1,
2797.markdown-body h2,
2798.markdown-body h3,
2799.markdown-body h4,
2800.markdown-body h5,
2801.markdown-body h6 {
2802 font-family: var(--font-condensed);
2803 font-weight: 600;
2804 color: var(--text);
2805 line-height: 1.25;
2806}
2807
2808.markdown-body h1 {
2809 margin: 24px 0 12px;
2810 font-size: var(--text-xl);
2811 font-weight: 700;
2812}
2813
2814.markdown-body h2 {
2815 margin: 28px 0 8px;
2816 padding-bottom: 6px;
2817 border-bottom: 1px solid var(--border);
2818 font-size: var(--text-lg);
2819}
2820
2821.markdown-body h3 {
2822 margin: 20px 0 8px;
2823 font-size: var(--text-md);
2824}
2825
2826.markdown-body h4,
2827.markdown-body h5,
2828.markdown-body h6 {
2829 margin: 16px 0 6px;
2830 font-size: var(--text-base);
2831 font-weight: 500;
2832}
2833
2834.markdown-body p {
2835 margin: 12px 0;
2836 line-height: 1.6;
2837 color: var(--text-dim);
2838}
2839
2840.markdown-body a {
2841 color: var(--text);
2842 text-decoration: underline;
2843 text-decoration-color: var(--border-strong);
2844 text-underline-offset: 2px;
2845}
2846
2847.markdown-body a:hover {
2848 text-decoration-color: var(--action);
2849 color: var(--action);
2850}
2851
2852.markdown-body strong {
2853 font-weight: 600;
2854 color: var(--text);
2855}
2856
2857.markdown-body code {
2858 padding: 0.1em 0.35em;
2859 border: 1px solid var(--border);
2860 border-radius: var(--radius-sm);
2861 background: var(--surface-raised);
2862 color: var(--action);
2863 font-family: var(--font-mono);
2864 font-size: 0.85em;
2865}
2866
2867.markdown-body pre {
2868 margin: 16px 0;
2869 padding: 12px;
2870 overflow-x: auto;
2871 border: 1px solid var(--border);
2872 border-radius: var(--radius);
2873 background: var(--surface);
2874}
2875
2876.markdown-body pre code {
2877 padding: 0;
2878 border: none;
2879 background: none;
2880 color: var(--text-dim);
2881 font-size: var(--text-sm);
2882 line-height: 1.6;
2883}
2884
2885.markdown-body blockquote {
2886 margin: 16px 0;
2887 padding-left: 16px;
2888 border-left: 2px solid var(--action);
2889 color: var(--text-dim);
2890 font-style: italic;
2891}
2892
2893.markdown-body hr {
2894 margin: 24px 0;
2895 border: none;
2896 border-top: 1px solid var(--border);
2897}
2898
2899.markdown-body ul,
2900.markdown-body ol {
2901 margin: 12px 0;
2902 padding-left: 22px;
2903 color: var(--text-dim);
2904 line-height: 1.6;
2905}
2906
2907.markdown-body li + li {
2908 margin-top: 4px;
2909}
2910
2911.markdown-body li:has(> input[type="checkbox"]) {
2912 list-style: none;
2913 margin-left: -22px;
2914}
2915
2916.markdown-body table {
2917 width: 100%;
2918 margin: 16px 0;
2919 border: 1px solid var(--border);
2920 border-radius: var(--radius);
2921 border-collapse: collapse;
2922 font-size: var(--text-sm);
2923}
2924
2925.markdown-body th {
2926 padding: 6px 10px;
2927 border-bottom: 1px solid var(--border);
2928 background: var(--surface);
2929 text-align: left;
2930 font-family: var(--font-condensed);
2931 font-weight: 600;
2932 text-transform: uppercase;
2933 letter-spacing: 0.03em;
2934 color: var(--text-dim);
2935}
2936
2937.markdown-body td {
2938 padding: 6px 10px;
2939 border-bottom: 1px solid var(--border);
2940 color: var(--text-dim);
2941 vertical-align: top;
2942}
2943
2944.markdown-body tr:last-child td {
2945 border-bottom: none;
2946}
2947
2948.markdown-body img {
2949 max-width: 100%;
2950}
2951
2952/* ─── code listings ────────────────────────────────────────────────────────── */
2953
2954.codeblock {
2955 overflow-x: auto;
2956 background: var(--bg);
2957 border: 1px solid var(--border);
2958 border-radius: var(--radius);
2959}
2960
2961.codetable {
2962 border-collapse: collapse;
2963 width: 100%;
2964}
2965
2966.codetable .lineno {
2967 text-align: right;
2968 padding: 0 10px;
2969 user-select: none;
2970 width: 1%;
2971 vertical-align: top;
2972}
2973
2974.codetable .lineno a {
2975 color: inherit;
2976}
2977
2978.codetable .codeline {
2979 padding: 0 10px;
2980 white-space: pre;
2981}
2982
2983/* The link target for a permalinked line. `:target` needs no JavaScript, which
2984 is what keeps line links working with scripting disabled (spec §7). */
2985.codetable tr:target {
2986 background: color-mix(in srgb, var(--identity) 14%, transparent);
2987}
2988
2989/* ─── syntax tokens ────────────────────────────────────────────────────────── */
2990
2991/* Class names come from df-render's HIGHLIGHT_NAMES table: `function.builtin`
2992 emits both `hl-function` and `hl-function-builtin`, so a family can be themed
2993 once here and refined where it is worth distinguishing. */
2994
2995.hl-comment { color: var(--hl-comment); font-style: italic; }
2996.hl-keyword { color: var(--hl-keyword); }
2997.hl-string { color: var(--hl-string); }
2998.hl-number,
2999.hl-boolean,
3000.hl-character { color: var(--hl-number); }
3001.hl-constant { color: var(--hl-constant); }
3002.hl-function { color: var(--hl-function); }
3003.hl-constructor { color: var(--hl-type); }
3004.hl-type { color: var(--hl-type); }
3005.hl-variable { color: var(--hl-variable); }
3006.hl-variable-parameter { color: var(--hl-variable); font-style: italic; }
3007.hl-property { color: var(--hl-attribute); }
3008.hl-operator { color: var(--hl-operator); }
3009.hl-punctuation { color: var(--hl-punctuation); }
3010.hl-tag { color: var(--hl-tag); }
3011.hl-attribute { color: var(--hl-attribute); }
3012.hl-label,
3013.hl-module { color: var(--hl-type); }
3014.hl-escape,
3015.hl-string-escape,
3016.hl-string-special { color: var(--hl-number); }
3017.hl-embedded { color: var(--hl-variable); }
3018
3019/* Builtins read as slightly more emphatic than user-defined names of the same
3020 kind — the distinction jj's own CLI colours make, and worth keeping. */
3021.hl-keyword,
3022.hl-function-builtin,
3023.hl-type-builtin,
3024.hl-variable-builtin,
3025.hl-constant-builtin {
3026 font-weight: 500;
3027}
3028
3029/* In a high-contrast context, colour alone must not be the only signal.
3030 Comments and strings keep their shape cues; everything else falls back to
3031 the body colour rather than a low-contrast tint (spec §11 accessibility). */
3032@media (prefers-contrast: more) {
3033 [class^="hl-"],
3034 [class*=" hl-"] {
3035 color: var(--text);
3036 }
3037 .hl-comment { color: var(--text-dim); font-style: italic; }
3038 .hl-string { color: var(--text); font-style: italic; }
3039 .hl-keyword { font-weight: 700; }
3040}
3041
3042
3043/* ─── settings ─────────────────────────────────────────────────────────────── */
3044
3045/* The tab strip, shared by repository sections, change-detail sections and
3046 settings. An inset box-shadow rather than a border-bottom, so activating a
3047 tab does not displace its label by two pixels. */
3048.subtabs {
3049 display: flex;
3050 gap: 0;
3051 overflow-x: auto;
3052 scrollbar-width: none;
3053}
3054
3055.subtabs::-webkit-scrollbar {
3056 display: none;
3057}
3058
3059.subtabs a {
3060 display: inline-flex;
3061 align-items: center;
3062 gap: 6px;
3063 height: 30px;
3064 padding: 0 10px;
3065 color: var(--text-dim);
3066 font-size: var(--text-sm);
3067 white-space: nowrap;
3068}
3069
3070.subtabs a:hover {
3071 color: var(--text);
3072 text-decoration: none;
3073}
3074
3075.subtabs a.active {
3076 color: var(--text);
3077 font-weight: 500;
3078 box-shadow: inset 0 -2px 0 var(--action);
3079}
3080
3081/* The count beside a tab label. Always monospace and always faint: it is a
3082 quantity you glance at, never the thing you are clicking. */
3083.subtabs .tab-count {
3084 font-family: var(--font-mono);
3085 font-size: var(--text-xs);
3086 color: var(--text-faint);
3087 font-weight: 400;
3088}
3089
3090/* Outside the repository sub-bar the strip needs its own rule and some air. */
3091.subtabs.ruled {
3092 border-bottom: 1px solid var(--border);
3093 margin-bottom: 16px;
3094}
3095
3096.listing {
3097 width: 100%;
3098 border-collapse: collapse;
3099 margin: 12px 0;
3100}
3101
3102.listing th {
3103 text-align: left;
3104 font-family: var(--font-condensed);
3105 font-weight: 500;
3106 font-size: var(--text-xs);
3107 letter-spacing: 0.06em;
3108 text-transform: uppercase;
3109 color: var(--text-dim);
3110 padding: 6px 10px 6px 0;
3111 border-bottom: 1px solid var(--border);
3112}
3113
3114.listing td {
3115 padding: 6px 10px 6px 0;
3116 border-bottom: 1px solid var(--border);
3117 vertical-align: middle;
3118 font-size: var(--text-sm);
3119}
3120
3121.listing tr:last-child td {
3122 border-bottom: none;
3123}
3124
3125.kv {
3126 display: grid;
3127 grid-template-columns: minmax(120px, max-content) 1fr;
3128 gap: 6px 16px;
3129 margin: 0;
3130}
3131
3132.kv dt {
3133 color: var(--text-dim);
3134 font-family: var(--font-condensed);
3135 font-size: var(--text-xs);
3136 text-transform: uppercase;
3137 letter-spacing: 0.06em;
3138 align-self: center;
3139}
3140
3141.kv dd {
3142 margin: 0;
3143}
3144
3145/* The danger zone reads as dangerous without relying on colour alone — the
3146 border weight and the heading carry it for anyone who cannot see the hue. */
3147.panel-danger {
3148 border-color: color-mix(in srgb, var(--danger) 55%, var(--border));
3149}
3150
3151.panel-danger h2,
3152.panel-danger h3 {
3153 color: var(--danger);
3154}
3155
3156.panel-danger hr {
3157 border: none;
3158 border-top: 1px solid var(--border);
3159 margin: 20px 0;
3160}
3161
3162textarea,
3163select {
3164 width: 100%;
3165 background: var(--bg);
3166 color: var(--text);
3167 border: 1px solid var(--border);
3168 border-radius: var(--radius);
3169 padding: 8px 10px;
3170 font-family: inherit;
3171 font-size: 14px;
3172}
3173
3174textarea {
3175 font-family: var(--font-mono);
3176 resize: vertical;
3177}
3178
3179textarea:focus,
3180select:focus {
3181 outline: 2px solid var(--action);
3182 outline-offset: -1px;
3183}
3184
3185/* A select inside a table row should not stretch to the full column. */
3186.listing select {
3187 width: auto;
3188 min-width: 120px;
3189}
3190
3191.field label input[type="checkbox"] {
3192 width: auto;
3193 margin-right: 6px;
3194}
3195
3196
3197
3198/* ─── the revset bar ───────────────────────────────────────────────────────── */
3199
3200/* One control, not three: the label, the field, the verdict and the submit
3201 button share a single border so the whole thing reads as the query box it
3202 is. */
3203.revset-bar {
3204 display: flex;
3205 align-items: stretch;
3206 margin-bottom: 8px;
3207 border: 1px solid var(--border-strong);
3208 border-radius: var(--radius);
3209 background: var(--surface);
3210 overflow: hidden;
3211}
3212
3213.revset-tag {
3214 display: flex;
3215 align-items: center;
3216 padding: 0 8px;
3217 border-right: 1px solid var(--border);
3218 background: var(--surface-raised);
3219 font-family: var(--font-condensed);
3220 font-size: var(--text-xs);
3221 font-weight: 500;
3222 letter-spacing: 0.06em;
3223 text-transform: uppercase;
3224 color: var(--identity);
3225 flex: none;
3226}
3227
3228/* The field is monospace because a revset is code. The design paints a
3229 syntax-highlighted layer over a transparent input, which needs a script to
3230 stay in sync with what is being typed; a stale highlight over live text is
3231 worse than none, so the field is plainly monospace instead and the colour
3232 goes on the saved expressions in the aside, where it is always correct. */
3233.revset-bar input[type="text"] {
3234 flex: 1;
3235 min-width: 0;
3236 height: 30px;
3237 border: none;
3238 border-radius: 0;
3239 background: transparent;
3240 font-family: var(--font-mono);
3241}
3242
3243.revset-bar input[type="text"]:focus {
3244 outline: none;
3245 border: none;
3246}
3247
3248.revset-status {
3249 display: flex;
3250 align-items: center;
3251 gap: 4px;
3252 padding: 0 8px;
3253 font-family: var(--font-mono);
3254 font-size: var(--text-xs);
3255 color: var(--text-faint);
3256 white-space: nowrap;
3257 flex: none;
3258}
3259
3260.revset-status.is-bad {
3261 color: var(--danger);
3262 white-space: normal;
3263 max-width: 40ch;
3264}
3265
3266.revset-bar .btn {
3267 height: auto;
3268 border: none;
3269 border-left: 1px solid var(--border);
3270 border-radius: 0;
3271 flex: none;
3272}
3273
3274/* ─── filter tabs ──────────────────────────────────────────────────────────── */
3275
3276.filter-tabs {
3277 margin-bottom: 0;
3278}
3279
3280.filter-glyph {
3281 font-family: var(--font-mono);
3282 font-size: var(--text-xs);
3283 color: var(--text-faint);
3284}
3285
3286.list-summary {
3287 margin: 0;
3288 height: 22px;
3289 display: flex;
3290 align-items: center;
3291 font-family: var(--font-mono);
3292 font-size: var(--text-xs);
3293 color: var(--text-faint);
3294}
3295
3296/* ─── the change list ──────────────────────────────────────────────────────── */
3297
3298/* Five columns. Four are fixed-width facts and one — the title — takes what is
3299 left, so a long title truncates instead of pushing the diffstat off the
3300 right edge. */
3301.changelist {
3302 --cl-cols: 148px minmax(240px, 1fr) 96px 120px 72px;
3303}
3304
3305.changelist-head,
3306.changelist-row {
3307 display: grid;
3308 grid-template-columns: var(--cl-cols);
3309 align-items: center;
3310 border-bottom: 1px solid var(--border);
3311}
3312
3313.changelist-head {
3314 font-family: var(--font-condensed);
3315 font-size: var(--text-xs);
3316 font-weight: 500;
3317 letter-spacing: 0.06em;
3318 text-transform: uppercase;
3319 color: var(--text-dim);
3320}
3321
3322.changelist-head > div {
3323 padding: 6px 8px;
3324}
3325
3326.changelist-head > .at-end {
3327 text-align: right;
3328}
3329
3330.changelist-row {
3331 height: 32px;
3332 font-size: var(--text-sm);
3333}
3334
3335.changelist-row:hover {
3336 background: var(--surface);
3337}
3338
3339/* A stacked row carries the identity tint even at rest, so the group is
3340 visible without reading the rail. */
3341.changelist-row.in-stack {
3342 background: var(--identity-wash);
3343}
3344
3345.changelist-row.in-stack:hover {
3346 background: var(--surface);
3347}
3348
3349.cl-change {
3350 display: flex;
3351 align-self: stretch;
3352 align-items: center;
3353 min-width: 0;
3354}
3355
3356.cl-indent {
3357 flex: none;
3358 align-self: stretch;
3359}
3360
3361/* The rail runs the full height of the row so consecutive stacked rows join
3362 into one continuous line. */
3363.cl-rail {
3364 width: 2px;
3365 flex: none;
3366 align-self: stretch;
3367 background: var(--identity);
3368}
3369
3370.cl-glyph {
3371 width: 20px;
3372 flex: none;
3373 display: flex;
3374 align-items: center;
3375 justify-content: center;
3376 font-family: var(--font-mono);
3377}
3378
3379.cl-title {
3380 display: flex;
3381 align-items: center;
3382 gap: 8px;
3383 padding: 0 8px;
3384 min-width: 0;
3385}
3386
3387.cl-title > a {
3388 font-size: var(--text-base);
3389 color: var(--text);
3390 overflow: hidden;
3391 text-overflow: ellipsis;
3392 white-space: nowrap;
3393}
3394
3395.cl-title > a:hover {
3396 color: var(--action);
3397}
3398
3399.cl-byline,
3400.cl-revs {
3401 font-family: var(--font-mono);
3402 font-size: var(--text-xs);
3403 color: var(--text-faint);
3404 white-space: nowrap;
3405 flex: none;
3406}
3407
3408.cl-byline a {
3409 color: var(--text-faint);
3410}
3411
3412.cl-review {
3413 display: flex;
3414 align-items: center;
3415 gap: 4px;
3416 padding: 0 8px;
3417}
3418
3419/* A reviewer's mark. The ring says what their verdict is and the fill says
3420 whether it still applies to the current revision — a stale approval gets a
3421 conflict-coloured ring, because on a change that has been rewritten it is
3422 no longer an approval of anything you can see. */
3423.cl-avatar {
3424 width: 18px;
3425 height: 18px;
3426 flex: none;
3427 display: inline-flex;
3428 align-items: center;
3429 justify-content: center;
3430 border: 1px solid var(--border-strong);
3431 border-radius: 50%;
3432 background: var(--surface-raised);
3433 font-family: var(--font-mono);
3434 font-size: var(--text-xs);
3435 text-transform: uppercase;
3436 user-select: none;
3437}
3438
3439.cl-comments {
3440 font-family: var(--font-mono);
3441 font-size: var(--text-xs);
3442 color: var(--text-faint);
3443 white-space: nowrap;
3444}
3445
3446.cl-diff {
3447 display: flex;
3448 align-items: center;
3449 justify-content: flex-end;
3450 gap: 6px;
3451 padding: 0 8px;
3452 font-family: var(--font-mono);
3453 font-size: var(--text-xs);
3454 white-space: nowrap;
3455}
3456
3457.cl-bars {
3458 display: inline-flex;
3459 gap: 1px;
3460 flex: none;
3461}
3462
3463.cl-bars > span {
3464 width: 3px;
3465 height: 10px;
3466}
3467
3468.cl-add { color: var(--diff-add-text); }
3469.cl-del { color: var(--diff-del-text); }
3470
3471.cl-when {
3472 padding: 0 8px;
3473 text-align: right;
3474 font-family: var(--font-mono);
3475 font-size: var(--text-xs);
3476 color: var(--text-faint);
3477 white-space: nowrap;
3478}
3479
3480/* ─── stack banner ─────────────────────────────────────────────────────────── */
3481
3482.stack-banner {
3483 display: flex;
3484 align-items: center;
3485 gap: 8px;
3486 height: 26px;
3487 padding: 0 8px;
3488 background: var(--identity-wash);
3489 border-bottom: 1px solid var(--border);
3490}
3491
3492.stack-banner-rail {
3493 font-family: var(--font-mono);
3494 font-size: var(--text-xs);
3495 color: var(--identity);
3496}
3497
3498.stack-banner-label {
3499 font-family: var(--font-condensed);
3500 font-size: var(--text-xs);
3501 font-weight: 600;
3502 letter-spacing: 0.06em;
3503 text-transform: uppercase;
3504 color: var(--identity);
3505}
3506
3507.stack-banner-note {
3508 font-size: var(--text-sm);
3509 color: var(--text-dim);
3510 overflow: hidden;
3511 text-overflow: ellipsis;
3512 white-space: nowrap;
3513}
3514
3515/* ─── saved revsets ────────────────────────────────────────────────────────── */
3516
3517.saved-revset {
3518 display: block;
3519 height: 22px;
3520 padding: 0 6px;
3521 border: 1px solid var(--border);
3522 border-radius: var(--radius-sm);
3523 font-family: var(--font-mono);
3524 font-size: var(--text-xs);
3525 line-height: 20px;
3526 color: var(--text-dim);
3527 overflow: hidden;
3528 text-overflow: ellipsis;
3529 white-space: nowrap;
3530}
3531
3532.saved-revset:hover {
3533 border-color: var(--action);
3534 color: var(--action);
3535 text-decoration: none;
3536}
3537
3538.saved-revset.is-current {
3539 border-color: var(--action);
3540 color: var(--action);
3541}
3542
3543/* ─── responsive ───────────────────────────────────────────────────────────── */
3544
3545@media (max-width: 1120px) {
3546 .columns-repo {
3547 grid-template-columns: minmax(0, 1fr);
3548 }
3549
3550 /* Review and diff go before the title does. */
3551 .changelist {
3552 --cl-cols: 148px minmax(0, 1fr) 72px;
3553 }
3554
3555 .cl-review,
3556 .cl-diff,
3557 .changelist-head > div:nth-child(3),
3558 .changelist-head > div:nth-child(4) {
3559 display: none;
3560 }
3561}
3562
3563@media (max-width: 800px) {
3564 .cl-byline,
3565 .cl-revs {
3566 display: none;
3567 }
3568}
3569
3570/* ─── diffs and review ─────────────────────────────────────────────────────── */
3571
3572/* The diff is read in three passes, and the layout is built for that order:
3573 the bar says what is being compared and how big it is, the index says which
3574 files, and only then the code. Each of the first two stays out of the way of
3575 the third — the bar is one line, the index folds. */
3576
3577/* ─── the sticky diff bar ─── */
3578
3579/* `top` clears the 46px masthead. z-index sits under the masthead (30) and over
3580 the file headers (10), which is the order they overlap in. */
3581.diffbar {
3582 position: sticky;
3583 top: 46px;
3584 z-index: 20;
3585 display: flex;
3586 align-items: center;
3587 gap: 10px;
3588 flex-wrap: wrap;
3589 padding: 7px 12px;
3590 margin-bottom: 8px;
3591 border: 1px solid var(--border);
3592 border-radius: var(--radius);
3593 background: var(--surface-raised);
3594 font-size: var(--text-sm);
3595 color: var(--text-dim);
3596}
3597
3598/* Opening the compare form makes the bar two rows tall, which would leave the
3599 file headers sticking to a height that no longer exists. While it is open the
3600 bar is an ordinary block and scrolls away. */
3601.diffbar:has(.cmpbox[open]) {
3602 position: static;
3603}
3604
3605.diffbar-stat {
3606 display: flex;
3607 align-items: center;
3608 gap: 8px;
3609}
3610
3611.diffbar-stat strong {
3612 color: var(--text);
3613}
3614
3615.diffbar-link {
3616 font-size: var(--text-sm);
3617 white-space: nowrap;
3618}
3619
3620/* The compare control: closed it is a label reading "Comparing v2 → v3", which
3621 is information the reviewer needs on every screen. Open it is the form. */
3622.cmpbox > summary {
3623 cursor: pointer;
3624 list-style: none;
3625 display: flex;
3626 align-items: baseline;
3627 gap: 6px;
3628 padding: 2px 8px;
3629 border: 1px solid var(--border-strong);
3630 border-radius: var(--radius);
3631 color: var(--text);
3632 white-space: nowrap;
3633}
3634
3635.cmpbox > summary::-webkit-details-marker { display: none; }
3636.cmpbox > summary:hover { border-color: var(--action); color: var(--action); }
3637
3638.cmpform {
3639 display: flex;
3640 align-items: center;
3641 gap: 8px;
3642 flex-wrap: wrap;
3643 padding: 10px 0 2px;
3644}
3645
3646/* ─── the changed-file tree, beside the diff ─── */
3647
3648/* Two columns: the tree, and the diff. The tree is fixed-width because it is a
3649 reference the eye returns to at a known place, and `minmax(0, 1fr)` is what
3650 stops a long unbroken code line from widening the grid track and pushing the
3651 tree off the screen. */
3652.difflayout {
3653 display: grid;
3654 grid-template-columns: 250px minmax(0, 1fr);
3655 gap: 12px;
3656 align-items: start;
3657}
3658
3659/* A diff with no files renders no tree, and a one-column grid should not leave
3660 a 250px hole where it would have been. */
3661.difflayout:not(:has(.difftree)) {
3662 grid-template-columns: minmax(0, 1fr);
3663}
3664
3665.diffmain {
3666 min-width: 0;
3667}
3668
3669/* `top` clears the masthead (46px) and the sticky diff bar under it, so the
3670 tree comes to rest just below the bar rather than behind it. Scrolls
3671 independently once it outgrows the viewport — a 200-file commit must not
3672 make the sidebar taller than the page. */
3673.difftree {
3674 position: sticky;
3675 top: 92px;
3676 max-height: calc(100vh - 104px);
3677 overflow-y: auto;
3678 border: 1px solid var(--border);
3679 border-radius: var(--radius);
3680 background: var(--surface);
3681 font-size: var(--text-sm);
3682}
3683
3684.dt-head {
3685 position: sticky;
3686 top: 0;
3687 padding: 8px 10px;
3688 border-bottom: 1px solid var(--border);
3689 background: var(--surface-raised);
3690 color: var(--text-dim);
3691 font-size: var(--text-xs);
3692 text-transform: uppercase;
3693 letter-spacing: 0.04em;
3694}
3695
3696.dt-body {
3697 padding: 6px 8px 8px;
3698}
3699
3700/* Each level indents by one step and draws a rule down its own children, which
3701 is what makes the nesting readable at a glance rather than countable. */
3702.dt-kids {
3703 margin-left: 5px;
3704 padding-left: 7px;
3705 border-left: 1px solid var(--border);
3706}
3707
3708.dt-row,
3709.dt-file {
3710 display: flex;
3711 align-items: center;
3712 gap: 6px;
3713 padding: 2px 4px;
3714 border-radius: 3px;
3715 font-family: var(--font-mono);
3716 line-height: 1.5;
3717 text-decoration: none;
3718 color: var(--text);
3719}
3720
3721.dt-row {
3722 cursor: pointer;
3723 list-style: none;
3724 color: var(--text-dim);
3725}
3726
3727.dt-row::-webkit-details-marker { display: none; }
3728.dt-row:hover,
3729.dt-file:hover { background: var(--surface-raised); }
3730.dt-file:hover .dt-name { color: var(--action); }
3731
3732.dt-caret {
3733 width: 0;
3734 height: 0;
3735 flex: none;
3736 border-left: 5px solid var(--text-faint);
3737 border-top: 4px solid transparent;
3738 border-bottom: 4px solid transparent;
3739 transition: transform 90ms ease;
3740}
3741
3742details[open] > .dt-row .dt-caret {
3743 transform: rotate(90deg);
3744}
3745
3746/* The directory name is context and the file name is the thing, so only the
3747 directory dims — the same rule the file index and the file headers follow. */
3748.dt-dirname,
3749.dt-name {
3750 min-width: 0;
3751 overflow: hidden;
3752 text-overflow: ellipsis;
3753 white-space: nowrap;
3754}
3755
3756.dt-dirname {
3757 font-size: var(--text-xs);
3758}
3759
3760/* The counts are a magnitude, not a figure to read: small, and never allowed to
3761 squeeze the name they sit beside. */
3762.dt-file .cl-add,
3763.dt-file .cl-del {
3764 flex: none;
3765 font-size: var(--text-xs);
3766}
3767
3768/* Under about 900px there is no room for a column beside the diff, so the tree
3769 becomes a capped block above it rather than disappearing. */
3770@media (max-width: 900px) {
3771 .difflayout {
3772 grid-template-columns: minmax(0, 1fr);
3773 }
3774
3775 /* Stacked, the tree is no longer a column and stretching it the full width
3776 of the page strands the counts a screen away from their filenames. */
3777 .difftree {
3778 position: static;
3779 max-width: 560px;
3780 max-height: 260px;
3781 margin-bottom: 10px;
3782 }
3783}
3784
3785/* On a change's files tab the diff already has the reviewers aside to its
3786 right, so the tree makes three columns and needs the room for it. It gives
3787 up its column earlier than on the commit page — the media query measures the
3788 viewport, but what is actually short here is the main column inside it. */
3789@media (max-width: 1280px) {
3790 .columns-main .difflayout {
3791 grid-template-columns: minmax(0, 1fr);
3792 }
3793
3794 .columns-main .difftree {
3795 position: static;
3796 max-width: 560px;
3797 max-height: 260px;
3798 margin-bottom: 10px;
3799 }
3800}
3801
3802/* ─── the file index ─── */
3803
3804.fileindex {
3805 border: 1px solid var(--border);
3806 border-radius: var(--radius);
3807 background: var(--surface);
3808 margin-bottom: 10px;
3809}
3810
3811.fileindex > summary {
3812 cursor: pointer;
3813 list-style: none;
3814 display: flex;
3815 align-items: center;
3816 gap: 8px;
3817 padding: 8px 12px;
3818 font-size: var(--text-sm);
3819 color: var(--text-dim);
3820}
3821
3822.fileindex > summary::-webkit-details-marker { display: none; }
3823
3824.fx-caret,
3825.fd-caret {
3826 width: 0;
3827 height: 0;
3828 border-left: 5px solid var(--text-faint);
3829 border-top: 4px solid transparent;
3830 border-bottom: 4px solid transparent;
3831 transition: transform 90ms ease;
3832 flex: none;
3833}
3834
3835details[open] > summary .fx-caret,
3836details[open] > summary .fd-caret {
3837 transform: rotate(90deg);
3838}
3839
3840/* Capped and scrolled: a forty-file index should not push the diff off the
3841 first screen, which is the thing it exists to get you to. */
3842.fx-list {
3843 max-height: 300px;
3844 overflow-y: auto;
3845 border-top: 1px solid var(--border);
3846}
3847
3848.fx-row {
3849 display: flex;
3850 align-items: center;
3851 gap: 8px;
3852 padding: 3px 12px;
3853 font-size: var(--text-sm);
3854 color: var(--text);
3855 text-decoration: none;
3856 border-bottom: 1px solid var(--border);
3857}
3858
3859.fx-row:last-child { border-bottom: 0; }
3860.fx-row:hover { background: var(--surface-raised); }
3861
3862/* The directory is context, the basename is the thing. Dimming the leading path
3863 is what makes a column of deep paths scannable, and truncating only the
3864 directory means the basename — the part being looked for — never disappears
3865 into an ellipsis. */
3866.fx-path,
3867.fd-path {
3868 display: flex;
3869 min-width: 0;
3870 font-family: var(--font-mono);
3871 font-size: var(--text-xs);
3872 white-space: nowrap;
3873}
3874
3875.fx-dir,
3876.fd-dir {
3877 color: var(--text-faint);
3878 overflow: hidden;
3879 text-overflow: ellipsis;
3880}
3881
3882.fx-name,
3883.fd-name {
3884 color: var(--text);
3885 flex: none;
3886}
3887
3888.fx-bin,
3889.fd-note {
3890 font-size: var(--text-xs);
3891 color: var(--text-faint);
3892 white-space: nowrap;
3893}
3894
3895/* ─── the status letter ─── */
3896
3897.fkind {
3898 flex: none;
3899 width: 16px;
3900 height: 16px;
3901 display: inline-grid;
3902 place-items: center;
3903 border-radius: var(--radius-sm);
3904 font-family: var(--font-mono);
3905 font-size: 10px;
3906 font-weight: 600;
3907 line-height: 1;
3908 color: var(--on-action);
3909}
3910
3911.fkind.is-add { background: var(--diff-add-text); }
3912.fkind.is-mod { background: var(--identity); }
3913.fkind.is-del { background: var(--diff-del-text); }
3914.fkind.is-ren { background: var(--merged); color: #fff; }
3915
3916/* ─── the add/delete ratio bar ─── */
3917
3918.statbar {
3919 flex: none;
3920 display: flex;
3921 width: 44px;
3922 height: 7px;
3923 border-radius: 1px;
3924 overflow: hidden;
3925 background: var(--border);
3926}
3927
3928.statbar-add { background: var(--diff-add-text); }
3929.statbar-del { background: var(--diff-del-text); }
3930
3931/* ─── one file ─── */
3932
3933/* The base box is shared with the interdiff and the conflict view, which build
3934 it out of plain divs and scroll horizontally. Only the foldable version in
3935 the files tab gets the sticky, clickable header below. */
3936.filediff {
3937 border: 1px solid var(--border);
3938 border-radius: var(--radius);
3939 margin-bottom: 10px;
3940 background: var(--surface);
3941 overflow-x: auto;
3942}
3943
3944.filediff-head {
3945 padding: 8px 10px;
3946 background: var(--surface-raised);
3947 border-bottom: 1px solid var(--border);
3948}
3949
3950/* ─── the foldable file, files tab only ─── */
3951
3952details.filediff {
3953 /* Wrapping the code removes the reason to scroll, and a scroll container
3954 would stop the header sticking to the viewport. */
3955 overflow-x: visible;
3956 /* Jumping from the index must not land the header under the sticky bar. */
3957 scroll-margin-top: 92px;
3958}
3959
3960/* Sticky, so the path stays readable a thousand lines into the file. It is the
3961 summary itself rather than a child, because `details` is the containing
3962 block here and a sticky grandchild would stop at its padding box. */
3963summary.filediff-head {
3964 position: sticky;
3965 top: 84px;
3966 z-index: 10;
3967 cursor: pointer;
3968 list-style: none;
3969 display: flex;
3970 align-items: center;
3971 gap: 8px;
3972 padding: 7px 10px;
3973 border-radius: var(--radius) var(--radius) 0 0;
3974}
3975
3976summary.filediff-head::-webkit-details-marker { display: none; }
3977summary.filediff-head:hover .fd-name { color: var(--action); }
3978details.filediff:not([open]) > .filediff-head { border-bottom-color: transparent; }
3979
3980/* A folded large file still has to look like something you can open, not like
3981 something that failed to render. */
3982details.filediff.is-big:not([open]) > .filediff-head {
3983 border-left: 2px solid var(--identity);
3984}
3985
3986.fd-old {
3987 font-size: var(--text-xs);
3988 color: var(--text-faint);
3989 white-space: nowrap;
3990}
3991
3992/* "View file" — only the commit page sets it, where the revision is one the
3993 browse routes can serve. Quiet until the header is hovered: it is the answer
3994 to a question most hunks never raise. */
3995.fd-view {
3996 font-size: var(--text-xs);
3997 color: var(--text-faint);
3998 white-space: nowrap;
3999}
4000
4001summary.filediff-head:hover .fd-view { color: var(--action); }
4002
4003/* ─── the lines ─── */
4004
4005.difftable {
4006 width: 100%;
4007 border-collapse: collapse;
4008 font-size: 12.5px;
4009 line-height: 19px;
4010}
4011
4012/* Two number columns and a sign column, all fixed and all unselectable, so a
4013 copy of the diff is a copy of the code. */
4014.difftable .lineno {
4015 position: relative;
4016 width: 1%;
4017 min-width: 44px;
4018 text-align: right;
4019 padding: 0 8px;
4020 user-select: none;
4021 white-space: nowrap;
4022 color: var(--text-faint);
4023 font-size: var(--text-xs);
4024 background: color-mix(in srgb, var(--bg) 60%, transparent);
4025}
4026
4027.difftable .lineno + .lineno {
4028 border-right: 1px solid var(--border);
4029}
4030
4031.difftable .dl-mark {
4032 width: 1%;
4033 padding: 0 0 0 6px;
4034 user-select: none;
4035 text-align: center;
4036 font-weight: 600;
4037}
4038
4039.difftable .codeline {
4040 padding: 0 10px 0 4px;
4041 /* Wrap rather than scroll: a horizontal scrollbar per file makes a long diff
4042 unreadable, and a scroll container would also break the sticky header.
4043 Continuation lines are hung two characters in so a wrap is visibly a wrap
4044 and not a new line of code. */
4045 white-space: pre-wrap;
4046 overflow-wrap: anywhere;
4047 word-break: normal;
4048 text-indent: -2ch;
4049 padding-left: calc(4px + 2ch);
4050}
4051
4052.difftable .hunkhead td {
4053 padding: 3px 10px;
4054 color: var(--text-faint);
4055 background: var(--bg);
4056 border-top: 1px solid var(--border);
4057 border-bottom: 1px solid var(--border);
4058 font-size: var(--text-xs);
4059}
4060
4061.difftable tbody tr:first-child .hunkhead td,
4062.difftable .hunkhead:first-child td {
4063 border-top: 0;
4064}
4065
4066/* In the commentable table the wash carries the add/delete signal on its own
4067 and the text stays body colour. Colouring whole lines green and red — as this
4068 used to — costs contrast on every character and drowns the word-level
4069 emphasis that says what actually changed on the line. The 2px rule is what
4070 survives a monochrome rendering.
4071
4072 The `.diffline` variant (interdiff, design specimens) keeps the coloured text
4073 it had: it is read in short bursts, and it has no gutter to carry the colour
4074 instead. */
4075.diffline.line-add { background: var(--diff-add-bg); color: var(--diff-add-text); }
4076.diffline.line-del { background: var(--diff-del-bg); color: var(--diff-del-text); }
4077
4078.diff-sign {
4079 display: inline-block;
4080 width: 1ch;
4081}
4082
4083.line-add > .lineno,
4084.line-add > .dl-mark,
4085.line-add > .codeline { background: var(--diff-add-bg); }
4086.line-del > .lineno,
4087.line-del > .dl-mark,
4088.line-del > .codeline { background: var(--diff-del-bg); }
4089
4090.line-add > .dl-mark { color: var(--diff-add-text); }
4091.line-del > .dl-mark { color: var(--diff-del-text); }
4092
4093.line-add > .lineno:first-child { box-shadow: inset 2px 0 0 var(--diff-add-text); }
4094.line-del > .lineno:first-child { box-shadow: inset 2px 0 0 var(--diff-del-text); }
4095
4096.dl:hover > .lineno { color: var(--text-dim); }
4097
4098/* The comment affordance. Absolutely positioned so it costs no layout, hidden
4099 until the row is hovered or it is focused — which is what lets the diff be a
4100 wall of code again instead of a wall of "Comment on line N". */
4101.dl-add {
4102 position: absolute;
4103 left: 2px;
4104 top: 50%;
4105 transform: translateY(-50%);
4106 width: 15px;
4107 height: 15px;
4108 display: grid;
4109 place-items: center;
4110 border-radius: var(--radius-sm);
4111 background: var(--action);
4112 color: var(--on-action);
4113 font-size: 12px;
4114 line-height: 1;
4115 text-decoration: none;
4116 opacity: 0;
4117 pointer-events: none;
4118}
4119
4120.dl:hover .dl-add,
4121.dl-add:focus-visible {
4122 opacity: 1;
4123 pointer-events: auto;
4124}
4125
4126@media (hover: none) {
4127 /* No hover to reveal it with, so it is always there, just quiet. */
4128 .dl-add { opacity: 0.45; pointer-events: auto; }
4129}
4130
4131/* A diff line outside the commentable table: the interdiff and the design
4132 sheet's specimen rows. A grid rather than a table, because there is no
4133 comment column to align against — just a gutter and the code.
4134
4135 The 2px left rule is what carries the add/delete signal in a monochrome or
4136 high-contrast rendering, where the background washes disappear. */
4137.diffline {
4138 display: grid;
4139 grid-template-columns: 44px auto;
4140 min-width: max-content;
4141 font-family: var(--font-mono);
4142 font-size: 13px;
4143 line-height: 21px;
4144 border-left: 2px solid transparent;
4145}
4146
4147.diffline.line-add { border-left-color: var(--diff-add-text); }
4148.diffline.line-del { border-left-color: var(--diff-del-text); }
4149
4150.diffline.diff-hunk {
4151 background: var(--surface-raised);
4152 color: var(--text-dim);
4153}
4154
4155.diffline.line-ctx {
4156 color: var(--text);
4157}
4158
4159.diff-ln {
4160 padding: 0 6px;
4161 text-align: right;
4162 font-size: var(--text-xs);
4163 color: var(--text-faint);
4164 user-select: none;
4165}
4166
4167.diff-text {
4168 padding: 0 6px;
4169 white-space: pre;
4170}
4171
4172/* Word-level intra-line changes (spec §8). Rendered as a background wash
4173 rather than a colour change, so the line's add/delete colour still reads and
4174 the emphasis survives a high-contrast or monochrome display. */
4175.word-changed {
4176 background: color-mix(in srgb, currentColor 22%, transparent);
4177 border-radius: var(--radius-sm);
4178}
4179
4180.inline-thread {
4181 padding: 10px 14px;
4182 background: var(--surface);
4183 border-top: 1px solid var(--border);
4184 border-bottom: 1px solid var(--border);
4185 white-space: normal;
4186}
4187
4188/* The comment form exists in the markup for every commentable line and is shown
4189 for exactly one: the one named in the fragment. `:target` does the work, so
4190 there is no per-line chrome in the resting diff, no script, and an open form
4191 has a URL that can be linked at somebody. */
4192.inline-form {
4193 display: none;
4194}
4195
4196.inline-form:target {
4197 display: table-row;
4198 scroll-margin-top: 120px;
4199}
4200
4201.inline-form > td {
4202 padding: 10px 14px;
4203 background: var(--surface);
4204 border-top: 2px solid var(--action);
4205 border-bottom: 1px solid var(--border);
4206 white-space: normal;
4207}
4208
4209.inline-form textarea {
4210 width: 100%;
4211}
4212
4213/* A comment is a boxed object; an event is a line. That difference is the
4214 timeline's whole structure — somebody wrote the boxes, the system recorded
4215 the lines. */
4216.comment {
4217 border: 1px solid var(--border);
4218 border-radius: var(--radius);
4219 padding: 10px 12px;
4220 background: var(--surface);
4221}
4222
4223.comment-body {
4224 margin-top: 6px;
4225}
4226
4227.comment-body > :first-child { margin-top: 0; }
4228.comment-body > :last-child { margin-bottom: 0; }
4229
4230/* The line a comment was written about, retained when the anchor goes stale
4231 (spec §5 step 4). */
4232.anchor-context {
4233 margin: 6px 0;
4234 padding: 6px 10px;
4235 background: var(--bg);
4236 border-left: 2px solid var(--identity);
4237 overflow-x: auto;
4238 font-size: 12px;
4239}
4240
4241/* An event: one line, hung off a left rule. No box and no rule between
4242 events — a run of them reads as a log, which is what it is. */
4243.timeline-event {
4244 display: flex;
4245 align-items: baseline;
4246 gap: 8px;
4247 padding: 3px 8px;
4248 border-left: 2px solid var(--border);
4249 font-size: var(--text-sm);
4250}
4251
4252.timeline-glyph {
4253 width: 12px;
4254 flex: none;
4255 font-family: var(--font-mono);
4256 font-size: var(--text-xs);
4257}
4258
4259
4260/* ─── change detail header ─────────────────────────────────────────────────── */
4261
4262.change-head {
4263 margin-bottom: 4px;
4264}
4265
4266.change-head-top {
4267 display: flex;
4268 align-items: stretch;
4269 gap: 12px;
4270 min-width: 0;
4271 margin-bottom: 10px;
4272}
4273
4274/* Three pixels of identity down the left of the whole header. The page is
4275 about one change; this is the change. */
4276.change-head-rail {
4277 width: 3px;
4278 flex: none;
4279 background: var(--identity);
4280}
4281
4282.change-head-id {
4283 display: flex;
4284 flex-direction: column;
4285 gap: 6px;
4286 min-width: 0;
4287}
4288
4289.change-head-line {
4290 display: flex;
4291 align-items: center;
4292 gap: 10px;
4293 flex-wrap: wrap;
4294}
4295
4296/* The id, larger than the title. Deliberate: the title is prose somebody can
4297 retype, the id is what every review and permalink is attached to. */
4298.change-id-display {
4299 font-family: var(--font-mono);
4300 font-size: var(--text-2xl);
4301 line-height: 34px;
4302 font-variant-numeric: tabular-nums;
4303}
4304
4305.change-id-display > .cid-p {
4306 font-weight: 600;
4307}
4308
4309.change-id-display.is-synthetic {
4310 font-size: var(--text-lg);
4311 color: var(--text-dim);
4312}
4313
4314.change-title {
4315 margin: 0;
4316 font-size: var(--text-lg);
4317 line-height: 28px;
4318 font-weight: 600;
4319 text-wrap: pretty;
4320}
4321
4322.change-byline {
4323 display: flex;
4324 align-items: center;
4325 gap: 8px;
4326 flex-wrap: wrap;
4327 font-size: var(--text-sm);
4328 color: var(--text-dim);
4329}
4330
4331.change-byline .sep {
4332 color: var(--text-faint);
4333}
4334
4335/* The aside follows the reader down a long diff. `top` clears the 46px
4336 masthead plus a hairline of air. */
4337.columns-aside.is-sticky {
4338 position: sticky;
4339 top: 62px;
4340}
4341
4342.reviewer-line {
4343 display: flex;
4344 align-items: center;
4345 gap: 6px;
4346 font-size: var(--text-sm);
4347}
4348
4349.reviewer-glyph {
4350 width: 10px;
4351 flex: none;
4352 font-family: var(--font-mono);
4353 font-size: var(--text-xs);
4354}
4355
4356.reviewer-meta {
4357 font-family: var(--font-mono);
4358 font-size: var(--text-xs);
4359 white-space: nowrap;
4360}
4361
4362.mini-row.is-current {
4363 background: var(--identity-wash);
4364}
4365
4366/* ─── revisions timeline ───────────────────────────────────────────────────── */
4367
4368.revtimeline {
4369 display: flex;
4370 flex-direction: column;
4371 margin: 12px 0;
4372}
4373
4374.revrow {
4375 display: flex;
4376 gap: 12px;
4377 border-bottom: 1px solid var(--border);
4378}
4379
4380.revrow.is-selected {
4381 background: var(--identity-wash);
4382}
4383
4384/* The rail is two flexing segments with the dot between them, so the line
4385 joins consecutive revisions and stops cleanly at both ends. */
4386.revrail {
4387 width: 24px;
4388 flex: none;
4389 display: flex;
4390 flex-direction: column;
4391 align-items: center;
4392 align-self: stretch;
4393}
4394
4395.revrail-seg {
4396 width: 2px;
4397 flex: 1;
4398}
4399
4400/* Square, not round: a revision is a discrete recorded state, and the shape
4401 is doing that work — round would read as a generic bullet. */
4402.revdot {
4403 width: 9px;
4404 height: 9px;
4405 flex: none;
4406 border: 1px solid var(--identity);
4407 border-radius: 1px;
4408}
4409
4410.revbody {
4411 flex: 1;
4412 min-width: 0;
4413 display: flex;
4414 flex-direction: column;
4415 gap: 4px;
4416 padding: 8px 8px 8px 0;
4417}
4418
4419.revline {
4420 display: flex;
4421 align-items: center;
4422 gap: 8px;
4423 flex-wrap: wrap;
4424}
4425
4426.revlabel {
4427 font-family: var(--font-condensed);
4428 font-size: var(--text-xs);
4429 font-weight: 600;
4430 letter-spacing: 0.06em;
4431 text-transform: uppercase;
4432}
4433
4434.revnote {
4435 font-size: var(--text-base);
4436 color: var(--text);
4437 min-width: 0;
4438 overflow: hidden;
4439 text-overflow: ellipsis;
4440 white-space: nowrap;
4441}
4442
4443.revwhen {
4444 font-family: var(--font-mono);
4445 font-size: var(--text-xs);
4446 color: var(--text-faint);
4447 white-space: nowrap;
4448}
4449
4450.revmeta {
4451 display: flex;
4452 align-items: center;
4453 gap: 12px;
4454 flex-wrap: wrap;
4455 font-family: var(--font-mono);
4456 font-size: var(--text-xs);
4457 color: var(--text-faint);
4458}
4459
4460.revmeta a {
4461 color: var(--text-faint);
4462}
4463
4464.revmeta a:hover {
4465 color: var(--action);
4466}
4467
4468/* The A/B pickers. Links, not radios: each pair is a URL, so a reviewer can
4469 paste "rev 2 → rev 4" into a comment and it resolves for everyone. */
4470.abpick {
4471 width: 16px;
4472 height: 16px;
4473 flex: none;
4474 display: inline-flex;
4475 align-items: center;
4476 justify-content: center;
4477 border: 1px solid var(--border-strong);
4478 border-radius: var(--radius-sm);
4479 font-family: var(--font-mono);
4480 font-size: var(--text-xs);
4481 line-height: 1;
4482 color: var(--text-faint);
4483}
4484
4485.abpick:hover {
4486 border-color: var(--action);
4487 color: var(--action);
4488 text-decoration: none;
4489}
4490
4491.abpick.is-on {
4492 border-color: var(--action);
4493 background: var(--action);
4494 color: var(--on-action);
4495}
4496
4497.interdiff-file + .interdiff-file {
4498 border-top: 1px solid var(--border);
4499}
4500
4501.interdiff-path {
4502 display: flex;
4503 align-items: center;
4504 gap: 8px;
4505 padding: 6px 8px;
4506 border-bottom: 1px solid var(--border);
4507 background: var(--surface-raised);
4508 font-size: var(--text-sm);
4509}
4510
4511/* ─── stack page ───────────────────────────────────────────────────────────── */
4512
4513.stack-chain {
4514 font-size: var(--text-sm);
4515 color: var(--text-faint);
4516}
4517
4518.stackrow {
4519 display: flex;
4520 align-items: center;
4521 gap: 8px;
4522 min-height: 38px;
4523 padding-right: 10px;
4524 border-bottom: 1px solid var(--border);
4525 color: var(--text);
4526 font-size: var(--text-sm);
4527}
4528
4529.stackrow:last-child {
4530 border-bottom: none;
4531}
4532
4533.stackrow:hover {
4534 background: var(--surface-raised);
4535 text-decoration: none;
4536}
4537
4538.stackrow.is-current {
4539 background: var(--identity-wash);
4540}
4541
4542.stackrow-glyph {
4543 width: 22px;
4544 flex: none;
4545 text-align: center;
4546 font-family: var(--font-mono);
4547}
4548
4549.stackrow-title {
4550 font-size: var(--text-base);
4551 overflow: hidden;
4552 text-overflow: ellipsis;
4553 white-space: nowrap;
4554 min-width: 0;
4555}
4556
4557.stackrow-meta {
4558 font-family: var(--font-mono);
4559 font-size: var(--text-xs);
4560 color: var(--text-faint);
4561 white-space: nowrap;
4562 flex: none;
4563}
4564
4565/* The bottom of the graph: what the chain sits on. Not a link, because a
4566 bookmark has no page — it is a pointer, not a thing. */
4567.stackrow-base {
4568 padding-left: 8px;
4569 font-family: var(--font-mono);
4570 font-size: var(--text-xs);
4571 color: var(--text-faint);
4572}
4573
4574.stack-cmd {
4575 margin-top: 12px;
4576 padding: 8px 10px;
4577 border: 1px solid var(--border);
4578 border-radius: var(--radius);
4579 background: var(--bg);
4580 font-size: var(--text-sm);
4581 color: var(--text-dim);
4582 white-space: pre-wrap;
4583}
4584
4585/* ─── conflicts ────────────────────────────────────────────────────────────── */
4586
4587.conflict-columns {
4588 display: grid;
4589 grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
4590 gap: 1px;
4591 background: var(--border);
4592}
4593
4594.conflict-side {
4595 background: var(--surface);
4596 padding: 10px 12px;
4597 min-width: 0;
4598}
4599
4600/* A base is the common ancestor, not another candidate resolution. Dimming it
4601 says so without needing a legend. */
4602.conflict-base {
4603 background: var(--bg);
4604}
4605
4606.conflict-side pre {
4607 margin: 6px 0 0;
4608 overflow-x: auto;
4609 font-size: 12px;
4610 max-height: 420px;
4611}
4612
4613details summary {
4614 cursor: pointer;
4615}
4616
4617
4618/* ─── issues ───────────────────────────────────────────────────────────────── */
4619
4620/* A label's colour comes from the repository, so it is validated as a hex
4621 triple before it reaches the inline style. When it is not one, the chip falls
4622 back to these theme colours rather than being dropped. */
4623.label-chip {
4624 border-width: 1px;
4625 border-style: solid;
4626}
4627
4628.issue-row {
4629 display: flex;
4630 align-items: center;
4631 gap: 8px;
4632 height: 36px;
4633 padding: 0 10px;
4634 border-bottom: 1px solid var(--border);
4635 color: var(--text);
4636 font-size: var(--text-sm);
4637}
4638
4639.issue-row:last-child {
4640 border-bottom: none;
4641}
4642
4643.issue-row:hover {
4644 background: var(--surface-raised);
4645 text-decoration: none;
4646}
4647
4648/* A closed issue is history. It stays fully legible — dimming it to 50% would
4649 make the archive unreadable — but it steps back a little. */
4650.issue-row.is-closed {
4651 opacity: 0.85;
4652}
4653
4654.issue-glyph {
4655 width: 14px;
4656 flex: none;
4657 font-family: var(--font-mono);
4658}
4659
4660.issue-num {
4661 font-family: var(--font-mono);
4662 font-size: var(--text-xs);
4663 color: var(--text-faint);
4664 flex: none;
4665}
4666
4667.issue-title {
4668 font-size: 13.5px;
4669 overflow: hidden;
4670 text-overflow: ellipsis;
4671 white-space: nowrap;
4672 min-width: 0;
4673}
4674
4675.issue-row:hover .issue-title {
4676 color: var(--action);
4677}
4678
4679.issue-meta {
4680 font-size: var(--text-sm);
4681 color: var(--text-faint);
4682 white-space: nowrap;
4683 flex: none;
4684}
4685
4686.issue-comments {
4687 width: 34px;
4688 text-align: right;
4689 font-family: var(--font-mono);
4690 font-size: var(--text-xs);
4691 color: var(--text-faint);
4692 flex: none;
4693}
4694
4695.issue-when {
4696 width: 56px;
4697 text-align: right;
4698 font-family: var(--font-mono);
4699 font-size: var(--text-xs);
4700 color: var(--text-faint);
4701 flex: none;
4702}
4703
4704/* ─── issue detail ─────────────────────────────────────────────────────────── */
4705
4706.backlink {
4707 display: inline-block;
4708 margin-bottom: 10px;
4709 font-size: var(--text-sm);
4710}
4711
4712.issue-head {
4713 display: flex;
4714 align-items: center;
4715 gap: 10px;
4716 flex-wrap: wrap;
4717 margin-bottom: 8px;
4718}
4719
4720.issue-body {
4721 margin: 12px 0;
4722 padding: 12px;
4723 border: 1px solid var(--border);
4724 border-radius: var(--radius);
4725 background: var(--surface);
4726 max-width: 76ch;
4727}
4728
4729.issue-ref {
4730 display: flex;
4731 align-items: center;
4732 gap: 8px;
4733 margin: 8px 0;
4734 padding: 2px 8px;
4735 border-left: 2px solid var(--border);
4736 font-size: var(--text-sm);
4737 color: var(--text-dim);
4738}
4739
4740.issue-ref-rail {
4741 font-family: var(--font-mono);
4742 color: var(--identity);
4743}
4744
4745.aside-chips {
4746 display: flex;
4747 flex-wrap: wrap;
4748 gap: 4px;
4749}
4750
4751/* ─── filter bar ───────────────────────────────────────────────────────────── */
4752
4753/* Tabs and a filter form on one line. The tabs are links (each is a URL) and
4754 the form is a form — they only look like one control. */
4755.filterbar {
4756 display: flex;
4757 align-items: center;
4758 gap: 8px;
4759 flex-wrap: wrap;
4760 margin-bottom: 10px;
4761}
4762
4763.filterbar-tabs {
4764 display: flex;
4765 gap: 2px;
4766}
4767
4768.filterbar select,
4769.filterbar input[type="text"] {
4770 height: 24px;
4771 width: auto;
4772 min-width: 120px;
4773 padding: 0 6px;
4774 border: 1px solid var(--border-strong);
4775 border-radius: var(--radius);
4776 background: var(--bg);
4777 color: var(--text);
4778 font-size: var(--text-sm);
4779}
4780
4781.btn-mono.is-on {
4782 border-color: var(--action);
4783 color: var(--action);
4784}
4785
4786/* The search bar reuses the revset bar's shell, so a select inside it has to
4787 lose its own border and sit flush with the field beside it. */
4788.revset-bar select {
4789 height: 30px;
4790 border: none;
4791 border-left: 1px solid var(--border);
4792 border-radius: 0;
4793 background: var(--surface-raised);
4794 color: var(--text-dim);
4795 font-size: var(--text-sm);
4796 padding: 0 6px;
4797 flex: none;
4798}
4799
4800/* ─── search results ───────────────────────────────────────────────────────── */
4801
4802.search-group + .search-group {
4803 margin-top: 20px;
4804}
4805
4806.search-group > h2 {
4807 margin: 0 0 6px;
4808}
4809
4810.search-hit {
4811 display: flex;
4812 align-items: center;
4813 gap: 8px;
4814 min-height: 32px;
4815 padding: 0 10px;
4816 border-bottom: 1px solid var(--border);
4817 color: var(--text);
4818 font-size: var(--text-sm);
4819}
4820
4821.search-hit:last-child {
4822 border-bottom: none;
4823}
4824
4825.search-hit:hover {
4826 background: var(--surface-raised);
4827 text-decoration: none;
4828}
4829
4830.search-kind {
4831 width: 60px;
4832 flex: none;
4833 font-family: var(--font-condensed);
4834 font-size: var(--text-xs);
4835 font-weight: 500;
4836 letter-spacing: 0.06em;
4837 text-transform: uppercase;
4838 color: var(--text-faint);
4839}
4840
4841.search-title {
4842 font-size: var(--text-base);
4843 white-space: nowrap;
4844 flex: none;
4845}
4846
4847.search-hit:hover .search-title {
4848 color: var(--action);
4849}
4850
4851/* The context gives up its space first — the title is what you are aiming at. */
4852.search-context {
4853 flex: 1;
4854 min-width: 0;
4855 color: var(--text-dim);
4856 overflow: hidden;
4857 text-overflow: ellipsis;
4858 white-space: nowrap;
4859}
4860
4861/* ─── masthead groups ──────────────────────────────────────────────────────── */
4862
4863.masthead-nav,
4864.masthead-account {
4865 display: flex;
4866 align-items: center;
4867 gap: 8px;
4868 min-width: 0;
4869}
4870
4871.masthead-nav a {
4872 display: inline-flex;
4873 align-items: center;
4874 height: 26px;
4875 padding: 0 9px;
4876 border: 1px solid transparent;
4877 border-radius: var(--radius);
4878 font-size: var(--text-sm);
4879}
4880
4881.masthead-nav a:hover {
4882 border-color: var(--border-strong);
4883 background: var(--surface-raised);
4884}
4885
4886.masthead-account a {
4887 font-size: var(--text-sm);
4888}
4889
4890/* The trigger sits in the right-hand grid column alongside the theme toggle;
4891 a long handle should truncate rather than push that column wide enough to
4892 fight the centred jump control for space. The caret truncates separately so
4893 it always stays visible. */
4894.account-handle {
4895 display: inline-block;
4896 max-width: 140px;
4897 overflow: hidden;
4898 text-overflow: ellipsis;
4899 white-space: nowrap;
4900 vertical-align: bottom;
4901}
4902
4903.theme-toggle:hover {
4904 border-color: var(--action);
4905 color: var(--action);
4906}
4907
4908/* The account menu opens under the handle, right-aligned rather than the
4909 bookmark switcher's left: the trigger sits at the end of the bar, and a
4910 left-aligned panel would hang off the edge of the viewport. */
4911.switcher-menu-end {
4912 left: auto;
4913 right: 0;
4914}
4915
4916.switcher-menu form {
4917 margin: 0;
4918}
4919
4920.switcher-item-danger {
4921 width: 100%;
4922 border: none;
4923 background: transparent;
4924 font: inherit;
4925 text-align: left;
4926 cursor: pointer;
4927 color: var(--text);
4928}
4929
4930.switcher-item-danger:hover {
4931 color: var(--danger);
4932}
4933
4934/* Narrow screens shed the chrome from the middle outward: the section links
4935 go first, then the wordmark's "dogfood" text, leaving the jump control,
4936 the handle and the theme readout as the last things standing. */
4937@media (max-width: 900px) {
4938 .masthead-nav,
4939 .masthead-group-start > .vrule {
4940 display: none;
4941 }
4942}
4943
4944@media (max-width: 620px) {
4945 .jump > span:first-child {
4946 display: none;
4947 }
4948
4949 .jump {
4950 padding: 0 6px;
4951 }
4952
4953 .brand > span:last-child {
4954 display: none;
4955 }
4956}
4957
4958@media (max-width: 400px) {
4959 .theme-toggle {
4960 display: none;
4961 }
4962}
4963
4964
4965/* ─── accessibility (spec §11) ─────────────────────────────────────────────── */
4966
4967/* The skip link is the first thing in the tab order and invisible until it has
4968 focus. Positioned rather than hidden — `display:none` would take it out of
4969 the tab order entirely, which defeats the point. */
4970.skip-link {
4971 position: absolute;
4972 left: -9999px;
4973 top: 0;
4974 z-index: 100;
4975 padding: 10px 16px;
4976 background: var(--surface-raised);
4977 color: var(--text);
4978 border: 1px solid var(--border-strong);
4979 border-radius: 0 0 var(--radius) 0;
4980}
4981
4982.skip-link:focus {
4983 left: 0;
4984}
4985
4986/* A visible focus indicator on everything interactive. `:focus-visible` so a
4987 mouse click does not leave a ring behind, but a keyboard user always sees
4988 where they are. */
4989a:focus-visible,
4990button:focus-visible,
4991input:focus-visible,
4992select:focus-visible,
4993textarea:focus-visible,
4994summary:focus-visible,
4995[tabindex]:focus-visible {
4996 outline: 2px solid var(--action);
4997 outline-offset: 2px;
4998 border-radius: var(--radius-sm);
4999}
5000
5001/* `main` is focusable only as the skip link's target; it should never draw a
5002 ring of its own. */
5003main:focus {
5004 outline: none;
5005}
5006
5007/* Windows high-contrast mode replaces our colours wholesale; make sure the
5008 focus ring survives it. */
5009@media (forced-colors: active) {
5010 a:focus-visible,
5011 button:focus-visible,
5012 input:focus-visible,
5013 select:focus-visible,
5014 textarea:focus-visible {
5015 outline: 2px solid CanvasText;
5016 }
5017}
5018
5019/* Badges and chips must not rely on colour alone. They already carry their
5020 meaning as text; this keeps the border in high-contrast mode so the shape
5021 still reads as a distinct token. */
5022@media (forced-colors: active) {
5023 .badge,
5024 .chip {
5025 border: 1px solid CanvasText;
5026 }
5027}
5028
5029/* Visually hidden but announced. */
5030.sr-only {
5031 position: absolute;
5032 width: 1px;
5033 height: 1px;
5034 padding: 0;
5035 margin: -1px;
5036 overflow: hidden;
5037 clip: rect(0, 0, 0, 0);
5038 white-space: nowrap;
5039 border: 0;
5040}
5041
5042
5043/* ─── the in-browser editor ────────────────────────────────────────────────── */
5044
5045/* The editor is one bordered object: a title bar, the code, and a commit bar
5046 along the bottom. The commit message and the submit button live inside that
5047 frame rather than as separate fields below it, because they are part of the
5048 same act. */
5049.editor-shell {
5050 border: 1px solid var(--border);
5051 border-radius: var(--radius);
5052 background: var(--surface);
5053 overflow: hidden;
5054}
5055
5056.editor-bar,
5057.editor-foot {
5058 display: flex;
5059 align-items: center;
5060 gap: 8px;
5061 padding: 6px 8px;
5062 background: var(--surface-raised);
5063 font-size: var(--text-sm);
5064}
5065
5066.editor-bar {
5067 border-bottom: 1px solid var(--border);
5068}
5069
5070.editor-foot {
5071 border-top: 1px solid var(--border);
5072}
5073
5074.editor-foot input[type="text"] {
5075 flex: 1;
5076 min-width: 0;
5077 height: 28px;
5078}
5079
5080.editor-status {
5081 font-size: var(--text-xs);
5082 color: var(--text-faint);
5083}
5084
5085/* The textarea is the editor until CodeMirror replaces it, so it has to look
5086 like one on its own — not like a form field that happens to hold code. */
5087.editor-host textarea {
5088 display: block;
5089 width: 100%;
5090 min-height: 420px;
5091 border: none;
5092 border-radius: 0;
5093 padding: 8px 10px;
5094 background: var(--bg);
5095 font-family: var(--font-mono);
5096 font-size: 13px;
5097 line-height: 21px;
5098 tab-size: 4;
5099 white-space: pre;
5100 overflow: auto;
5101}
5102
5103.editor-host textarea:focus {
5104 outline: none;
5105 border: none;
5106}
5107
5108/* CodeMirror sizes itself; the host must not fight it. */
5109.editor-host .cm-editor {
5110 height: auto;
5111}
5112
5113/* ─── code view three-pane layout ──────────────────────────────────────────── */
5114
5115/* The last commit that modified this file, shown above the pane layout. */
5116.file-commit-bar {
5117 display: flex;
5118 align-items: center;
5119 gap: 10px;
5120 padding: 8px 12px;
5121 margin-bottom: 12px;
5122 border: 1px solid var(--border);
5123 border-radius: var(--radius);
5124 background: var(--surface);
5125 font-size: var(--text-sm);
5126}
5127
5128/* Three-column grid: file tree | code | symbol outline */
5129.code-view-layout {
5130 display: grid;
5131 grid-template-columns: 220px 1fr 200px;
5132 gap: 0;
5133 align-items: start;
5134 border: 1px solid var(--border);
5135 border-radius: var(--radius);
5136 overflow: hidden;
5137}
5138
5139/* When the symbol sidebar is absent, the layout collapses to two columns. */
5140.code-view-layout:not(:has(.code-sidebar-right)) {
5141 grid-template-columns: 220px 1fr;
5142}
5143
5144/* ─── left sidebar (file tree) ──────────────────────────────────────────── */
5145.code-sidebar-left {
5146 background: var(--surface);
5147 border-right: 1px solid var(--border);
5148 max-height: 80vh;
5149 overflow-y: auto;
5150 padding: 0;
5151}
5152
5153.sidebar-header {
5154 display: flex;
5155 align-items: center;
5156 gap: 8px;
5157 padding: 10px 12px;
5158 border-bottom: 1px solid var(--border);
5159 font-size: var(--text-sm);
5160 line-height: 1;
5161}
5162
5163.file-tree {
5164 display: flex;
5165 flex-direction: column;
5166}
5167
5168/* `line-height: 1`: without it the body default of 1.5 gives the label extra
5169 leading that JetBrains Mono splits unevenly above/below the glyphs, so the
5170 text hangs below the icon instead of sitting beside it (same fix as
5171 `.filelist-entry-name`). */
5172.file-tree-item {
5173 display: flex;
5174 align-items: center;
5175 gap: 8px;
5176 padding: 5px 12px;
5177 font-size: var(--text-sm);
5178 font-family: var(--font-mono);
5179 color: var(--text);
5180 text-decoration: none;
5181 border-bottom: 1px solid var(--border);
5182 white-space: nowrap;
5183 overflow: hidden;
5184 text-overflow: ellipsis;
5185 line-height: 1;
5186}
5187
5188.file-tree-item:last-child {
5189 border-bottom: none;
5190}
5191
5192.file-tree-item:hover {
5193 background: var(--surface-raised);
5194}
5195
5196.file-tree-item.is-current {
5197 background: var(--surface-raised);
5198 font-weight: 500;
5199 border-left: 2px solid var(--action);
5200 padding-left: 10px;
5201}
5202
5203.file-tree-item.is-dir {
5204 color: var(--text-dim);
5205}
5206
5207.sidebar-header svg,
5208.file-tree-item svg {
5209 flex: none;
5210}
5211
5212.file-tree-parent {
5213 color: var(--text-dim);
5214}
5215
5216/* ─── center code panel ─────────────────────────────────────────────────── */
5217.code-panel {
5218 min-width: 0;
5219 overflow-x: auto;
5220}
5221
5222.code-panel .panel {
5223 border: none;
5224 border-radius: 0;
5225}
5226
5227/* ─── right sidebar (symbol outline) ────────────────────────────────────── */
5228.code-sidebar-right {
5229 background: var(--surface);
5230 border-left: 1px solid var(--border);
5231 max-height: 80vh;
5232 overflow-y: auto;
5233 padding: 0;
5234}
5235
5236.symbol-list {
5237 display: flex;
5238 flex-direction: column;
5239}
5240
5241.symbol-item {
5242 display: flex;
5243 align-items: center;
5244 gap: 8px;
5245 padding: 5px 12px;
5246 font-size: var(--text-sm);
5247 color: var(--text);
5248 text-decoration: none;
5249 border-bottom: 1px solid var(--border);
5250}
5251
5252.symbol-item:last-child {
5253 border-bottom: none;
5254}
5255
5256.symbol-item:hover {
5257 background: var(--surface-raised);
5258}
5259
5260.symbol-kind {
5261 font-family: var(--font-mono);
5262 font-size: 10px;
5263 font-weight: 600;
5264 text-transform: uppercase;
5265 padding: 1px 5px;
5266 border-radius: 3px;
5267 flex-shrink: 0;
5268 background: var(--border);
5269 color: var(--text-dim);
5270}
5271
5272/* Symbol kinds borrow the syntax palette, so an outline entry is the same
5273 colour as the token it points at in the code beside it. `color-mix` on the
5274 token itself rather than a hard-coded rgb, so both themes stay correct. */
5275.sk-fn { color: var(--hl-function); background: color-mix(in srgb, var(--hl-function) 12%, transparent); }
5276.sk-type { color: var(--hl-type); background: color-mix(in srgb, var(--hl-type) 12%, transparent); }
5277.sk-enum { color: var(--hl-constant); background: color-mix(in srgb, var(--hl-constant) 12%, transparent); }
5278.sk-trait { color: var(--hl-keyword); background: color-mix(in srgb, var(--hl-keyword) 12%, transparent); }
5279.sk-const { color: var(--hl-constant); background: color-mix(in srgb, var(--hl-constant) 12%, transparent); }
5280.sk-mod { color: var(--text-dim); background: var(--border); }
5281
5282.symbol-name {
5283 font-family: var(--font-mono);
5284 font-size: var(--text-sm);
5285 white-space: nowrap;
5286 overflow: hidden;
5287 text-overflow: ellipsis;
5288}
5289
5290/* ─── blame gutter ──────────────────────────────────────────────────────── */
5291.blame-cell {
5292 padding: 0 8px;
5293 white-space: nowrap;
5294 font-size: 11px;
5295 color: var(--text-faint);
5296 border-right: 1px solid var(--border);
5297 max-width: 120px;
5298 overflow: hidden;
5299 text-overflow: ellipsis;
5300 user-select: none;
5301}
5302
5303.blame-author {
5304 font-family: var(--font-mono);
5305}
5306
5307/* Small blame toggle button */
5308.btn-sm {
5309 padding: 3px 10px;
5310 font-size: var(--text-xs);
5311}
5312
5313/* ─── responsive: collapse sidebars on narrow screens ───────────────────── */
5314@media (max-width: 900px) {
5315 .code-view-layout {
5316 grid-template-columns: 1fr;
5317 }
5318
5319 .code-view-layout:not(:has(.code-sidebar-right)) {
5320 grid-template-columns: 1fr;
5321 }
5322
5323 .code-sidebar-left {
5324 display: none;
5325 }
5326
5327 .code-sidebar-right {
5328 display: none;
5329 }
5330}
5331
5332@media (min-width: 901px) and (max-width: 1100px) {
5333 .code-view-layout {
5334 grid-template-columns: 180px 1fr;
5335 }
5336
5337 .code-sidebar-right {
5338 display: none;
5339 }
5340}
5341
5342
5343/* ─── responsive hardening ────────────────────────────────────────────────── */
5344
5345/* A terminal is rendered as a figure for its caption semantics. Browser
5346 figure margins cost 80px of horizontal room unless explicitly reset, which
5347 is especially destructive on a phone. */
5348figure.term {
5349 margin: 0;
5350}
5351
5352/* Generic surfaces may contain grids, tables, or long rendered prose. Let the
5353 component own any necessary scrolling instead of making the document wide. */
5354.panel,
5355.markdown-body {
5356 min-width: 0;
5357}
5358
5359/* The interdiff heading contains independent label, explanation, and summary
5360 spans. Give them real layout and let the explanation move to a second line
5361 rather than running directly into the comparison label. */
5362.interdiff > .filediff-head {
5363 display: flex;
5364 align-items: center;
5365 gap: 4px 8px;
5366 flex-wrap: wrap;
5367}
5368
5369@media (max-width: 900px) {
5370 /* `.masthead nav` has greater specificity than a bare `.masthead-nav`.
5371 Qualifying this rule ensures the intended mobile chrome actually sheds
5372 the nonessential design-system link. */
5373 .masthead .masthead-nav {
5374 display: none;
5375 }
5376}
5377
5378@media (max-width: 620px) {
5379 /* Sixteen pixels is a useful phone gutter; the desktop 24px gutter was
5380 taking almost a sixth of a 320px viewport before content even began. */
5381 .masthead-inner,
5382 .subnav-inner,
5383 .wrap {
5384 padding-left: 16px;
5385 padding-right: 16px;
5386 }
5387
5388 /* On phones the centre control uses the room between the two edge groups.
5389 Equal outer grid tracks can overlap when an account handle is wider than
5390 the brand. */
5391 .masthead-inner {
5392 grid-template-columns: auto minmax(0, 1fr) auto;
5393 column-gap: 10px;
5394 }
5395
5396 .jump {
5397 justify-self: center;
5398 }
5399
5400 .account-handle {
5401 max-width: 88px;
5402 }
5403
5404 /* Repository identity gets one compact row and the four primary sections
5405 get a full-width row. Previously the tab strip was flex-shrunk to as little
5406 as 16px and then asked to host its own 273px scroll area. */
5407 .subnav-inner {
5408 min-height: 60px;
5409 height: auto;
5410 align-content: start;
5411 align-items: center;
5412 flex-wrap: wrap;
5413 gap: 4px 10px;
5414 padding-top: 6px;
5415 padding-bottom: 0;
5416 }
5417
5418 .subnav-inner > .vrule,
5419 .subnav-inner > .spacer,
5420 .subnav-inner > .subnav-meta {
5421 display: none;
5422 }
5423
5424 .subnav-inner > .subtabs {
5425 order: 1;
5426 flex: 0 0 100%;
5427 width: 100%;
5428 }
5429
5430 /* On a narrow bookmark table the title and age are secondary; retaining all
5431 four columns made the bookmark and change id paint over one another. */
5432 .bookmark-table th:nth-child(3),
5433 .bookmark-table th:nth-child(4),
5434 .bookmark-title,
5435 .bookmark-when {
5436 display: none;
5437 }
5438
5439 .bookmark-table td {
5440 height: 44px;
5441 }
5442
5443 .bookmark-name {
5444 width: 45%;
5445 white-space: normal;
5446 }
5447
5448 .bookmark-name > a {
5449 display: block;
5450 max-width: 100%;
5451 overflow: hidden;
5452 text-overflow: ellipsis;
5453 white-space: nowrap;
5454 }
5455
5456 .bookmark-name .bookmark-flag {
5457 display: inline-block;
5458 margin-right: 4px;
5459 }
5460
5461 .bookmark-points {
5462 width: 55%;
5463 overflow: hidden;
5464 text-overflow: ellipsis;
5465 }
5466
5467 .bookmark-points .cid {
5468 display: block;
5469 max-width: 100%;
5470 overflow: hidden;
5471 text-overflow: ellipsis;
5472 }
5473
5474 /* The context is supporting information. On phones the result title gets
5475 the flexible space and truncates cleanly if even that is not enough. */
5476 .search-context,
5477 .palette-hint {
5478 display: none;
5479 }
5480
5481 .search-title,
5482 .palette-label {
5483 flex: 1 1 auto;
5484 min-width: 0;
5485 overflow: hidden;
5486 text-overflow: ellipsis;
5487 }
5488
5489 .issue-meta {
5490 display: none;
5491 }
5492
5493 /* Settings and rendered Markdown can contain genuinely tabular information.
5494 Preserve every column and contain the exceptional width inside the table
5495 instead of introducing a page-level scroll bay. */
5496 .listing,
5497 .markdown-body table {
5498 display: block;
5499 max-width: 100%;
5500 overflow-x: auto;
5501 }
5502
5503 .kv {
5504 grid-template-columns: minmax(0, 1fr);
5505 gap: 3px;
5506 }
5507
5508 .row {
5509 flex-wrap: wrap;
5510 }
5511}
5512
5513@media (max-width: 400px) {
5514 /* Five change-detail tabs fit a 320px viewport at this density, avoiding an
5515 almost-complete last label and a hard-to-discover tiny horizontal scroll. */
5516 .subtabs a {
5517 padding-left: 8px;
5518 padding-right: 8px;
5519 }
5520}

5520 lines · CSS