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