Blog/wwwroot/git.css 15.4 K · 622 lines · raw · history

1 /* ==========================================================================
2 mb.bes.is/git — the repository browser's own stylesheet.
3
4 app.css dresses a page you read: an 80ch measure, type that grows with the
5 viewport, and a spacing scale built on 1.5 so everything breathes. A
6 repository browser is the opposite — a wall of fixed-width rows you scan,
7 where a screenful of commits beats a well-set paragraph.
8
9 GitLayout links this after app.css, on /git pages only, so it can override
10 the base outright rather than fight it. What it keeps is the monospace, the
11 light-dark() colours and the bracketed terminal idiom; what it replaces is
12 every measurement.
13
14 1. Tokens the dense scale, and the colours only a diff needs
15 2. Base what app.css set for prose, undone
16 3. Frame page, status bar, footer
17 4. Blocks the bordered box every section is
18 5. Tables the shape most of these pages are
19 6. Code trees, blobs, diffs
20 7. Small parts badges, pagers, key/value lists
21 8. Syntax what a highlighter has left once you take the colour away
22 ========================================================================== */
23
24
25 /* 1. Tokens ================================================================ */
26
27 /* app.css grows the root font with the viewport (`1rem + 0.4vw`), which lands
28 near 21px on a desktop. That is a reading size. Back to the reader's own
29 base, which the scale below then works down from. */
30 html {
31 font-size: 100%;
32 }
33
34 .git {
35 /* One step under the base: small enough to fit a commit table on a laptop,
36 still sized in rem so a reader who runs their browser at 20px gets a
37 browser at 20px. */
38 font-size: 0.875rem;
39 line-height: 1.45;
40
41 /* A flat four-step scale rather than a ratio. Density is a decision about
42 absolute room — 4px between a label and its value — not a proportion. */
43 --g0: 0.125rem;
44 --g1: 0.25rem;
45 --g2: 0.5rem;
46 --g3: 1rem;
47
48 /* Hairlines and fills. The page's own --color is too heavy for a rule
49 between every row of a forty-row table. */
50 --g-line: light-dark(#d5d5d5, #2b2b2b);
51 --g-fill: light-dark(#f1f1f1, #131313);
52 --g-dim: light-dark(#6b6b6b, #909090);
53
54 /* A row the URL points at: #L42, or the file a diff was opened for. */
55 --g-mark: light-dark(#fff6cc, #2c2611);
56
57 /* The wash behind a literal. Translucent rather than a flat grey, because it has to sit on
58 three different backgrounds - the page, an added line, a removed one - and come out a shade
59 darker than each of them. */
60 --g-wash: light-dark(rgb(0 0 0 / 7%), rgb(255 255 255 / 9%));
61
62 /* Diff colours carry meaning, so they are stated rather than tinted: a
63 wash behind the line, and a stronger shade for the sign in the gutter. */
64 --g-add: light-dark(#e4f5e4, #0e2410);
65 --g-add-mark: light-dark(#186c18, #77d177);
66 --g-del: light-dark(#fbe9e9, #2a1212);
67 --g-del-mark: light-dark(#9e1414, #ff8a8a);
68 --g-hunk: light-dark(#e8eef6, #101720);
69 }
70
71
72 /* 2. Base ================================================================== */
73
74 /* Every one of these exists only to undo a rule app.css set for prose. */
75
76 .git :is(h1, h2, h3, h4, h5, h6) {
77 margin-block: 0;
78 font-size: 1em;
79 line-height: 1.45;
80 text-wrap: nowrap;
81 }
82
83 .git p {
84 margin-block: 0;
85 text-wrap: pretty;
86 }
87
88 .git :is(ul, ol) {
89 margin-block: 0;
90 padding-inline-start: 0;
91 list-style: none;
92 }
93
94 /* app.css sets `pre-line` so indented examples can stay indented in the .razor
95 source. Here the whitespace is the file's, and it is load-bearing. */
96 .git pre {
97 margin-block: 0;
98 white-space: pre;
99 overflow-wrap: normal;
100 }
101
102 /* Underlining every link in a table of hashes and paths turns the page into
103 noise, so links here are plain until you point at one. */
104 .git a {
105 color: inherit;
106 text-decoration: none;
107 }
108
109 .git a:hover,
110 .git a:focus-visible {
111 background-color: transparent;
112 color: inherit;
113 text-decoration: underline;
114 text-decoration-thickness: 1px;
115 text-underline-offset: 0.15em;
116 }
117
118
119 /* 3. Frame ================================================================= */
120
121 .git {
122 flex: 1;
123 display: flex;
124 flex-direction: column;
125 gap: var(--g2);
126 /* Wide, but not edge to edge: past this a commit table is a row of five
127 words stranded at either end of a monitor. */
128 max-inline-size: 100rem;
129 inline-size: 100%;
130 margin-inline: auto;
131 padding: var(--g2);
132 }
133
134 /* The status line, the way a terminal does it: inverted, full width, one row.
135 It is also the whole of the site header here — a logo and a tagline are
136 exactly the vertical room this page is trying not to spend. */
137 .git-bar {
138 display: flex;
139 flex-wrap: wrap;
140 align-items: center;
141 justify-content: space-between;
142 gap: var(--g1) var(--g3);
143 padding: var(--g1) var(--g2);
144 background-color: var(--color);
145 color: var(--background);
146 }
147
148 .git-bar a:focus-visible {
149 outline-color: var(--background);
150 }
151
152 .git-crumbs {
153 display: flex;
154 flex-wrap: wrap;
155 align-items: baseline;
156 gap: var(--g1);
157 min-inline-size: 0;
158 /* A deep path breaks between segments rather than mid-name. */
159 overflow-wrap: anywhere;
160 }
161
162 .git-crumbs .sep {
163 opacity: 60%;
164 }
165
166 .git-crumbs strong {
167 font-weight: 700;
168 }
169
170 /* Tabs read as keys you can press. The current one is punched out of the bar. */
171 .git-tabs {
172 display: flex;
173 gap: var(--g1);
174 flex: none;
175 }
176
177 .git-tabs a {
178 padding-inline: var(--g2);
179 }
180
181 .git-tabs a::before {
182 content: "[";
183 opacity: 50%;
184 }
185
186 .git-tabs a::after {
187 content: "]";
188 opacity: 50%;
189 }
190
191 .git-tabs a.on {
192 background-color: var(--background);
193 color: var(--color);
194 text-decoration: none;
195 }
196
197 .git-tabs a.on::before,
198 .git-tabs a.on::after {
199 opacity: 100%;
200 }
201
202 .git-main {
203 flex: 1;
204 display: flex;
205 flex-direction: column;
206 gap: var(--g2);
207 /* main's rules in app.css frame a reading column with dotted rules. */
208 border: none;
209 padding-block: 0;
210 }
211
212 .git-foot {
213 display: flex;
214 flex-wrap: wrap;
215 justify-content: space-between;
216 gap: var(--g1) var(--g3);
217 padding-block-start: var(--g2);
218 border-block-start: 1px solid var(--g-line);
219 color: var(--g-dim);
220 }
221
222
223 /* 4. Blocks ================================================================ */
224
225 /* One bordered box per thing on the page. Square corners and a single hairline
226 everywhere, so two boxes stacked read as one grid rather than as two cards. */
227 .block {
228 border: 1px solid var(--g-line);
229 min-inline-size: 0;
230 }
231
232 .block-head {
233 display: flex;
234 flex-wrap: wrap;
235 align-items: baseline;
236 justify-content: space-between;
237 gap: var(--g1) var(--g2);
238 padding: var(--g1) var(--g2);
239 background-color: var(--g-fill);
240 border-block-end: 1px solid var(--g-line);
241 font-weight: 700;
242 }
243
244 /* The right-hand half of a heading: counts, stats, a branch name. */
245 .block-note {
246 font-weight: 400;
247 color: var(--g-dim);
248 text-wrap: nowrap;
249 }
250
251 /* Padding for a block holding prose or a list rather than a table, which
252 supplies its own cell padding and wants to run to the border. */
253 .block-body {
254 padding: var(--g2);
255 }
256
257 .block-body > * + * {
258 margin-block-start: var(--g2);
259 }
260
261 /* Nothing to show is worth a row of its own rather than an empty box. */
262 .empty {
263 padding: var(--g2);
264 color: var(--g-dim);
265 }
266
267
268 /* 5. Tables ================================================================ */
269
270 /* A table wider than the screen scrolls inside its block. On a phone that is
271 most of them, and it beats every alternative: wrapping a hash, hiding a
272 column, or pushing the whole page sideways. */
273 .scroll {
274 overflow-x: auto;
275 }
276
277 .git table {
278 inline-size: 100%;
279 border-collapse: collapse;
280 /* Numbers, hashes and sizes are compared down the column. */
281 font-variant-numeric: tabular-nums;
282 }
283
284 .git :is(th, td) {
285 padding: var(--g1) var(--g2);
286 border-block-end: 1px solid var(--g-line);
287 text-align: start;
288 vertical-align: baseline;
289 text-wrap: nowrap;
290 }
291
292 .git th {
293 font-weight: 400;
294 color: var(--g-dim);
295 }
296
297 .git tbody tr:last-child :is(th, td) {
298 border-block-end: none;
299 }
300
301 .git tbody tr:hover {
302 background-color: var(--g-fill);
303 }
304
305 /* The one column per table that carries the sentence: a commit subject, a
306 description, a tag message. It takes the leftover width and gives up its
307 own before any of the fixed columns do. */
308 .git .wide {
309 inline-size: 100%;
310 text-wrap: wrap;
311 overflow-wrap: anywhere;
312 }
313
314 /* Hashes, sizes and counts: right where they are compared, left where they
315 are read. */
316 .git .num {
317 text-align: end;
318 inline-size: 1%;
319 }
320
321 .git .dim {
322 color: var(--g-dim);
323 }
324
325 /* A column that earns its place on a laptop and not on a phone. Dropping it is what lets the
326 remaining columns fit, so the subject wraps in place instead of the whole row scrolling
327 sideways to reach a name you were not reading anyway. */
328 @media (width < 40rem) {
329 .git .when-wide {
330 display: none;
331 }
332 }
333
334 .git .sha {
335 font-variant-numeric: tabular-nums;
336 }
337
338 /* A row whose link fills the cell should feel clickable across the cell. */
339 .git td > a {
340 display: inline-block;
341 max-inline-size: 100%;
342 }
343
344
345 /* 6. Code ================================================================== */
346
347 /* -- Trees --------------------------------------------------------------- */
348
349 /* The mode column is decoration until you need it, and then it is the whole
350 answer, so it stays but stays quiet. */
351 .tree .mode {
352 color: var(--g-dim);
353 }
354
355 /* The link sits in the name cell, not straight under the row. */
356 .tree tr.dir .wide a::after {
357 content: "/";
358 color: var(--g-dim);
359 }
360
361 /* -- Blobs and diffs ------------------------------------------------------ */
362
363 /* Line numbers sit in their own cells and are marked unselectable, so copying
364 a file or a hunk out of the page yields the code and nothing else. */
365 .code-table {
366 inline-size: max-content;
367 min-inline-size: 100%;
368 }
369
370 .code-table td {
371 padding-block: 0;
372 border-block-end: none;
373 vertical-align: top;
374 }
375
376 .code-table .ln {
377 inline-size: 1%;
378 padding-inline: var(--g2);
379 text-align: end;
380 color: var(--g-dim);
381 background-color: var(--g-fill);
382 user-select: none;
383 -webkit-user-select: none;
384 }
385
386 .code-table .ln a {
387 color: inherit;
388 }
389
390 .code-table .code {
391 inline-size: 100%;
392 padding-inline: var(--g2) var(--g3);
393 white-space: pre;
394 }
395
396 /* Hovering a row of code should not repaint the whole line. */
397 .code-table tbody tr:hover {
398 background-color: transparent;
399 }
400
401 .code-table tr:target,
402 .code-table tr:target .ln {
403 background-color: var(--g-mark);
404 }
405
406 /* -- Diffs ---------------------------------------------------------------- */
407
408 .diff .hunk td {
409 padding-inline: var(--g2);
410 background-color: var(--g-hunk);
411 color: var(--g-dim);
412 border-block: 1px solid var(--g-line);
413 }
414
415 /* The first hunk sits straight under the file's heading, which already has a
416 rule of its own. */
417 .diff tr:first-child.hunk td {
418 border-block-start: none;
419 }
420
421 .diff .add .code {
422 background-color: var(--g-add);
423 }
424
425 .diff .del .code {
426 background-color: var(--g-del);
427 }
428
429 /* The sign is repeated from the gutter into the line so the diff survives
430 being copied, pasted or read without colour — but never selected with it. */
431 .diff .sign {
432 user-select: none;
433 -webkit-user-select: none;
434 }
435
436 .diff .add .sign {
437 color: var(--g-add-mark);
438 }
439
440 .diff .del .sign {
441 color: var(--g-del-mark);
442 }
443
444 .diff .note td {
445 color: var(--g-dim);
446 }
447
448 /* One file's heading inside the commit's diff: path on the left, its own
449 stat on the right. */
450 .diff-file + .diff-file {
451 margin-block-start: var(--g2);
452 }
453
454 .diff-file .block-head {
455 overflow-wrap: anywhere;
456 text-wrap: wrap;
457 }
458
459
460 /* 7. Small parts =========================================================== */
461
462 /* A branch or tag name sitting next to a commit. Bracketed rather than
463 pill-shaped: this page has no rounded corners anywhere else. */
464 .ref {
465 margin-inline-start: var(--g2);
466 color: var(--g-dim);
467 /* Breakable: a branch called release/2026-09-the-long-one is one unbreakable word, and on a
468 phone it would otherwise set the width of the whole commit table. */
469 overflow-wrap: anywhere;
470 }
471
472 .ref::before {
473 content: "[";
474 }
475
476 .ref::after {
477 content: "]";
478 }
479
480 .ref.head {
481 color: inherit;
482 font-weight: 700;
483 }
484
485 /* +42 -3, on a commit row or a file heading. */
486 .stat {
487 text-wrap: nowrap;
488 }
489
490 .stat .plus {
491 color: var(--g-add-mark);
492 }
493
494 .stat .minus {
495 color: var(--g-del-mark);
496 }
497
498 /* Key and value down the page: clone URL, author, committer, parents. */
499 .pairs {
500 display: grid;
501 grid-template-columns: max-content minmax(0, 1fr);
502 gap: var(--g1) var(--g3);
503 }
504
505 .pairs dt {
506 color: var(--g-dim);
507 }
508
509 .pairs dd {
510 margin: 0;
511 overflow-wrap: anywhere;
512 }
513
514 /* The clone line is there to be selected in one go. */
515 .pairs .copyable {
516 user-select: all;
517 -webkit-user-select: all;
518 }
519
520 /* Older / newer, at the foot of the log. */
521 .pager {
522 display: flex;
523 gap: var(--g2);
524 justify-content: space-between;
525 padding: var(--g1) var(--g2);
526 border-block-start: 1px solid var(--g-line);
527 }
528
529 .pager a::before,
530 .pager span::before {
531 content: "< ";
532 }
533
534 .pager :last-child::before {
535 content: "";
536 }
537
538 .pager :last-child::after {
539 content: " >";
540 }
541
542 .pager span {
543 color: var(--g-dim);
544 }
545
546 /* A commit's message body, under its subject. Pre-wrapped: a commit message is
547 written to a width and often has a list in it. */
548 .message {
549 white-space: pre-wrap;
550 overflow-wrap: anywhere;
551 color: var(--g-dim);
552 }
553
554 .subject {
555 font-weight: 700;
556 text-wrap: wrap;
557 overflow-wrap: anywhere;
558 }
559
560 /* A branch picker and a path search are one short row of controls, not a form
561 with labels stacked above fields. */
562 .controls {
563 display: flex;
564 flex-wrap: wrap;
565 align-items: center;
566 gap: var(--g2);
567 }
568
569 .controls label {
570 display: flex;
571 align-items: center;
572 gap: var(--g1);
573 color: var(--g-dim);
574 }
575
576 .git :is(button, input, select) {
577 font: inherit;
578 border-radius: 0;
579 border-color: var(--g-line);
580 padding: var(--g0) var(--g1);
581 }
582
583 .git button {
584 min-inline-size: 0;
585 padding-inline: var(--g2);
586 }
587
588
589 /* 8. Syntax =============================================================== */
590
591 /* Grayscale highlighting has three tools - weight, slope and a wash - so it gets three
592 distinctions, which is about as many as a line of code can carry anyway: what the language says,
593 what the program says, and what the author says. Adding a fourth by dimming or underlining
594 something else only makes the first three harder to see.
595
596 Nothing here sets a foreground colour except to dim one, so every token stays legible on an
597 added line, a removed line, and a line the URL has marked. */
598
599 /* The language: `if`, `public`, `<section`, a CSS property, a JSON literal. */
600 .tok-keyword {
601 font-weight: 700;
602 }
603
604 /* The program's own data: strings, numbers, lengths, colours, a fenced code block. */
605 .tok-string,
606 .tok-number {
607 background-color: var(--g-wash);
608 }
609
610 /* The author, talking past the compiler. Italic and quiet: a comment is worth reading second. */
611 .tok-comment {
612 color: var(--g-dim);
613 font-style: italic;
614 }
615
616 /* The line that isn't code: a preprocessor directive, a shell variable, a doctype, a markdown
617 link, a JSON key, a CSS custom property. Bold like the language, dimmed because it is beside
618 it rather than part of it. */
619 .tok-meta {
620 font-weight: 700;
621 color: var(--g-dim);
622 }