repos
/ repos-rust master

repos-rust

mirror archived upstream

A minimal self-hosted git browser on Rust axum: bare repos rendered as a website with commits, diffs, syntax-highlighted blobs, atom feeds, and clone over HTTPS.

axumdockergitgit-browsergitoxidegixrustself-hosted

15.9 KB · 741 lines · SCSS Raw History
  1// repos — warm-earth dark palette aligned with analytics + status.
  2// Mossy forest green primary + warm amber labels on deep earth darks.
  3
  4:root {
  5  --bg:        #0e0d0a;
  6  --surface:   #13120e;
  7  --surface-2: #18160f;
  8  --border:    rgba(107, 158, 120, 0.12);
  9  --border-strong: rgba(107, 158, 120, 0.22);
 10  --text:      #ddd7cd;
 11  --text-mute: #847c72;
 12  --text-dim:  #665f56;
 13  --accent:    #6b9e78; // mossy green primary
 14  --accent-2:  #c9a84c; // warm amber (section labels, callouts)
 15  --green:     #7db88c; // brighter green for links, hover, active
 16  --code-bg:   #090806;
 17  --add-bg:    rgba(107, 158, 120, 0.12);
 18  --add-fg:    #7db88c;
 19  --del-bg:    rgba(196, 112, 85, 0.12);
 20  --del-fg:    #e38871;
 21  --hunk-fg:   #9ec5d2;
 22
 23  --mono: "Monaspace Argon", ui-monospace, "Cascadia Code", Consolas, "SF Mono", Menlo, monospace;
 24  --prose: "Monaspace Argon", ui-monospace, "Cascadia Code", Consolas, "SF Mono", Menlo, monospace;
 25}
 26
 27* { box-sizing: border-box; }
 28
 29html, body {
 30  margin: 0;
 31  padding: 0;
 32  background: var(--bg);
 33  color: var(--text);
 34  font-family: var(--prose);
 35  font-size: 0.92rem;
 36  line-height: 1.7;
 37  letter-spacing: 0.01em;
 38  -webkit-font-smoothing: antialiased;
 39  -moz-osx-font-smoothing: grayscale;
 40}
 41
 42// Sticky footer: body fills viewport, main takes the slack so the
 43// footer-bar pins to the bottom on short pages (empty repo list, 404).
 44body {
 45  min-height: 100vh;
 46  display: flex;
 47  flex-direction: column;
 48}
 49
 50main { flex: 1 0 auto; }
 51
 52a {
 53  color: var(--green);
 54  text-decoration: none;
 55
 56  &:hover { text-decoration: underline; }
 57}
 58
 59code, pre, .sha, .clone-box input {
 60  font-family: var(--mono);
 61}
 62
 63.container {
 64  max-width: 1100px;
 65  margin: 0 auto;
 66  padding: 0 1.25rem;
 67}
 68
 69// --- topbar ---
 70.topbar {
 71  position: relative;
 72  background: #090806;
 73  border-bottom: 1px solid rgba(107, 158, 120, 0.06);
 74  padding: 0.85rem 0;
 75
 76  &__row {
 77    display: flex;
 78    align-items: center;
 79    gap: 1rem;
 80    flex-wrap: wrap;
 81  }
 82
 83  // 1px vertical hairline between brand and tagline, like a path-component
 84  // separator. Hidden on narrow screens where the tagline drops out anyway.
 85  &__divider {
 86    width: 1px;
 87    height: 1.4rem;
 88    background: var(--border);
 89
 90    @media (max-width: 540px) { display: none; }
 91  }
 92}
 93
 94.brand {
 95  display: inline-flex;
 96  align-items: center;
 97  gap: 0.6rem;
 98  color: var(--text);
 99  font-family: var(--mono);
100  font-weight: 600;
101  font-size: 0.78rem;
102  text-transform: uppercase;
103  letter-spacing: 0.1em;
104  text-decoration: none;
105
106  &:hover {
107    text-decoration: none;
108    color: var(--text);
109
110    .brand-mark { transform: translateY(-1px); }
111  }
112}
113
114.brand-mark {
115  display: inline-flex;
116  width: 22px;
117  height: 22px;
118  color: var(--accent);
119  transition: transform 250ms ease;
120
121  svg { width: 100%; height: 100%; display: block; }
122}
123
124.brand-text { color: var(--text); }
125
126.tagline {
127  color: var(--text-mute);
128  font-family: var(--mono);
129  font-style: italic;
130  font-size: 0.85rem;
131  letter-spacing: 0.02em;
132
133  @media (max-width: 540px) { display: none; }
134}
135
136.topnav {
137  margin-left: auto;
138  display: inline-flex;
139  align-items: center;
140  gap: 0.25rem;
141
142  &__link {
143    display: inline-flex;
144    align-items: center;
145    gap: 0.5rem;
146    color: var(--text-mute);
147    font-family: var(--mono);
148    font-size: 0.85rem;
149    text-transform: lowercase;
150    letter-spacing: 0.04em;
151    padding: 0.35rem 0.75rem;
152    border: 1px solid transparent;
153    border-radius: 3px;
154    transition: color 150ms ease, border-color 150ms ease, background 150ms ease;
155
156    &:hover {
157      color: var(--text);
158      border-color: var(--border);
159      background: var(--surface-2);
160      text-decoration: none;
161    }
162
163    // Current-page state: a subtle outlined "tab" with the accent dot lit.
164    &.is-active {
165      color: var(--text);
166      border-color: var(--border);
167      background: var(--surface-2);
168
169      .topnav__dot {
170        background: var(--accent);
171        box-shadow: 0 0 0 2px rgba(107, 158, 120, 0.15);
172      }
173    }
174  }
175
176  &__dot {
177    width: 6px;
178    height: 6px;
179    border-radius: 50%;
180    background: var(--text-mute);
181    opacity: 0.6;
182  }
183}
184
185// --- breadcrumbs ---
186.breadcrumbs {
187  background: var(--surface);
188  border-bottom: 1px solid var(--border);
189  font-family: var(--mono);
190  font-size: 0.9rem;
191  color: var(--text-mute);
192  padding: 0.85rem 0;
193  // A deep path with a long component must wrap, not scroll the page.
194  overflow-wrap: anywhere;
195
196  strong { color: var(--text); font-weight: 600; }
197
198  // Explicit separator span so we don't depend on whitespace stripping in
199  // the templates. Each path component is its own anchor; the slashes are
200  // visual-only and get a touch of horizontal margin.
201  &__sep {
202    margin: 0 0.35rem;
203    color: var(--text-mute);
204    opacity: 0.6;
205  }
206
207  &__ref {
208    margin-left: 0.75rem;
209    color: var(--accent-2);
210  }
211}
212
213main { padding: 3rem 0 4rem; }
214
215// --- hero / index ---
216.hero {
217  margin-bottom: 2.5rem;
218
219  h1 {
220    font-family: var(--mono);
221    font-size: 2rem;
222    margin: 0 0 0.5rem;
223    letter-spacing: -0.01em;
224  }
225
226  .lede { color: var(--text-mute); margin: 0; }
227}
228
229.empty {
230  color: var(--text-mute);
231  font-style: italic;
232}
233
234.repo-list {
235  list-style: none;
236  padding: 0;
237  margin: 0;
238  display: grid;
239  gap: 1px;
240  background: var(--border);
241  border: 1px solid var(--border);
242  border-radius: 4px;
243  overflow: hidden;
244}
245
246.repo-row {
247  background: var(--surface);
248  padding: 1rem 1.25rem;
249
250  &:hover { background: var(--surface-2); }
251
252  &__head {
253    display: flex;
254    justify-content: space-between;
255    align-items: baseline;
256    gap: 1rem;
257  }
258
259  &__name {
260    font-family: var(--mono);
261    font-weight: 600;
262    font-size: 1.1rem;
263    color: var(--green);
264  }
265
266  &__age {
267    color: var(--text-mute);
268    font-size: 0.85rem;
269    white-space: nowrap;
270  }
271
272  &__desc {
273    margin: 0.35rem 0 0;
274    color: var(--text);
275  }
276
277  &__last {
278    margin: 0.35rem 0 0;
279    color: var(--text-mute);
280    font-size: 0.88rem;
281  }
282
283  &__last-sha {
284    font-family: var(--mono);
285    color: var(--accent-2);
286    margin-right: 0.4rem;
287  }
288}
289
290// --- repo head ---
291.repo-head {
292  margin-bottom: 2rem;
293
294  &__title-row {
295    display: flex;
296    align-items: baseline;
297    gap: 0.75rem;
298    flex-wrap: wrap;
299
300    h1 {
301      font-family: var(--mono);
302      font-size: 1.75rem;
303      margin: 0;
304    }
305  }
306
307  &__branch {
308    background: var(--surface-2);
309    color: var(--accent-2);
310    border: 1px solid var(--border);
311    padding: 0.1rem 0.5rem;
312    border-radius: 3px;
313    font-family: var(--mono);
314    font-size: 0.85rem;
315  }
316
317  &__desc {
318    color: var(--text-mute);
319    margin: 0.5rem 0 1rem;
320  }
321}
322
323.clone-box {
324  display: flex;
325  align-items: center;
326  gap: 0.75rem;
327  background: var(--surface);
328  border: 1px solid var(--border);
329  border-radius: 4px;
330  padding: 0.5rem 0.75rem;
331  margin-bottom: 1rem;
332  max-width: 640px;
333
334  label {
335    color: var(--text-mute);
336    font-size: 0.85rem;
337    text-transform: uppercase;
338    letter-spacing: 0.05em;
339  }
340
341  input {
342    flex: 1;
343    background: transparent;
344    border: 0;
345    color: var(--text);
346    font-size: 0.9rem;
347    outline: none;
348    padding: 0.25rem 0;
349  }
350}
351
352.repo-nav {
353  display: flex;
354  gap: 1.25rem;
355  border-bottom: 1px solid var(--border);
356  padding-bottom: 0.5rem;
357  font-family: var(--mono);
358  font-size: 0.92rem;
359
360  a {
361    color: var(--text-mute);
362    &:hover { color: var(--green); text-decoration: none; }
363  }
364}
365
366// --- two column layout (overview) ---
367.two-col {
368  display: grid;
369  // `minmax(0, ...)` is load-bearing: CSS grid items default to min-width:
370  // auto, so an unwrappable child (e.g. a wide <pre> inside the README)
371  // would force the main column wider than its 2fr share and crush the
372  // sidebar. Capping the min at 0 makes the columns respect the fr ratio
373  // regardless of content width; overflow scrolls within the child.
374  grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
375  gap: 2rem;
376
377  @media (max-width: 760px) {
378    grid-template-columns: minmax(0, 1fr);
379  }
380}
381
382.col-main { min-width: 0; }
383.col-side { min-width: 0; }
384
385.side-h {
386  font-family: var(--mono);
387  font-size: 0.85rem;
388  text-transform: uppercase;
389  letter-spacing: 0.06em;
390  color: var(--text-mute);
391  margin: 0 0 0.5rem;
392}
393
394.commit-list {
395  list-style: none;
396  margin: 0;
397  padding: 0;
398
399  li {
400    border-bottom: 1px dashed var(--border);
401    padding: 0.55rem 0;
402    font-size: 0.92rem;
403  }
404
405  .sha {
406    color: var(--accent-2);
407    margin-right: 0.4rem;
408  }
409
410  .summary { display: block; color: var(--text); }
411  .meta { color: var(--text-mute); font-size: 0.82rem; }
412}
413
414// --- readme ---
415.readme {
416  background: var(--surface);
417  border: 1px solid var(--border);
418  border-radius: 4px;
419  padding: 1.5rem 2rem;
420  // Long URLs and other unbreakable tokens wrap instead of widening the
421  // page; the README is user content so we can't assume it stays narrow.
422  overflow-wrap: break-word;
423
424  // Tighter gutters on phones — 2rem of padding plus the container's own
425  // 1.25rem leaves too little room for prose on a 375px screen.
426  @media (max-width: 540px) { padding: 1.15rem 1.15rem; }
427
428  h1, h2, h3, h4 {
429    font-family: var(--mono);
430    letter-spacing: -0.01em;
431  }
432  h1 { font-size: 1.7rem; margin-top: 0; }
433  h2 { font-size: 1.35rem; border-bottom: 1px solid var(--border); padding-bottom: 0.3rem; }
434  h3 { font-size: 1.1rem; }
435
436  p, ul, ol { line-height: 1.65; }
437  code { background: var(--code-bg); padding: 0.1rem 0.35rem; border-radius: 3px; font-size: 0.92em; }
438  pre {
439    background: var(--code-bg);
440    padding: 1rem;
441    border-radius: 4px;
442    overflow-x: auto;
443    code { background: transparent; padding: 0; }
444  }
445  blockquote {
446    border-left: 3px solid var(--accent);
447    padding-left: 1rem;
448    color: var(--text-mute);
449    margin: 1rem 0;
450  }
451  // A markdown table can easily be wider than a phone screen. `display:
452  // block` turns the <table> itself into a horizontal scroll container
453  // (the rows still lay out as a table inside an anonymous table box), so
454  // a wide table scrolls on its own instead of forcing the whole page
455  // wide. `width: max-content` keeps a narrow table from stretching.
456  table {
457    display: block;
458    width: max-content;
459    max-width: 100%;
460    overflow-x: auto;
461    border-collapse: collapse;
462    margin: 1rem 0;
463    th, td {
464      border: 1px solid var(--border);
465      padding: 0.35rem 0.75rem;
466    }
467    th { background: var(--surface-2); }
468  }
469  hr { border: 0; border-top: 1px solid var(--border); }
470}
471
472// --- log ---
473.log-list {
474  list-style: none;
475  padding: 0;
476  margin: 0;
477}
478
479.log-row {
480  display: flex;
481  gap: 1rem;
482  padding: 0.75rem 0;
483  border-bottom: 1px solid var(--border);
484
485  .sha { color: var(--accent-2); flex-shrink: 0; }
486  .summary { margin: 0; }
487  .meta { margin: 0; color: var(--text-mute); font-size: 0.85rem; }
488}
489
490// --- tree ---
491.tree {
492  width: 100%;
493  border-collapse: collapse;
494  font-family: var(--mono);
495  font-size: 0.92rem;
496
497  tr { border-bottom: 1px solid var(--border); }
498  td { padding: 0.5rem 0.75rem; }
499
500  &__name { width: 100%; overflow-wrap: anywhere; }
501  &__name--tree a::before { content: ""; color: var(--accent-2); }
502  &__name--blob a::before { content: ""; color: var(--text-mute); }
503  &__size { color: var(--text-mute); text-align: right; white-space: nowrap; }
504}
505
506// --- blob view ---
507.blob-head {
508  display: flex;
509  justify-content: space-between;
510  align-items: center;
511  background: var(--surface);
512  border: 1px solid var(--border);
513  border-bottom: 0;
514  padding: 0.5rem 0.75rem;
515  border-radius: 4px 4px 0 0;
516  font-size: 0.85rem;
517  color: var(--text-mute);
518  font-family: var(--mono);
519}
520
521.blob {
522  border: 1px solid var(--border);
523  border-radius: 0 0 4px 4px;
524  overflow-x: auto;
525  background: var(--code-bg);
526
527  .hl {
528    margin: 0;
529    padding: 0.75rem 0;
530    font-family: var(--mono);
531    font-size: 0.85rem;
532    line-height: 1.55;
533  }
534  .ln {
535    display: inline-block;
536    width: 4ch;
537    padding-right: 1rem;
538    text-align: right;
539    color: var(--text-mute);
540    user-select: none;
541    &::before { content: attr(data-n); }
542  }
543  .l { display: inline; }
544}
545
546// --- diff ---
547.commit-head {
548  margin-bottom: 1.5rem;
549
550  h1 {
551    font-family: var(--mono);
552    font-size: 1.35rem;
553    margin: 0 0 0.5rem;
554  }
555  .sha { color: var(--accent-2); }
556  .meta { color: var(--text-mute); margin: 0 0 1rem; }
557}
558
559.commit-msg {
560  background: var(--surface);
561  border: 1px solid var(--border);
562  padding: 0.75rem 1rem;
563  border-radius: 4px;
564  font-size: 0.9rem;
565  white-space: pre-wrap;
566  margin: 0;
567}
568
569.file-diff {
570  margin-top: 1.5rem;
571  border: 1px solid var(--border);
572  border-radius: 4px;
573  overflow: hidden;
574
575  &__head {
576    background: var(--surface);
577    padding: 0.5rem 0.75rem;
578    display: flex;
579    align-items: center;
580    // Let a long file path wrap below the status badge instead of being
581    // clipped by this article's `overflow: hidden`.
582    flex-wrap: wrap;
583    gap: 0.35rem 0.75rem;
584    font-family: var(--mono);
585    font-size: 0.88rem;
586  }
587
588  .path { overflow-wrap: anywhere; min-width: 0; }
589}
590
591.status {
592  font-family: var(--mono);
593  font-size: 0.75rem;
594  text-transform: uppercase;
595  padding: 0.05rem 0.45rem;
596  border-radius: 3px;
597  letter-spacing: 0.05em;
598
599  &--added { background: rgba(125, 184, 140, 0.15); color: var(--add-fg); }
600  &--deleted { background: rgba(196, 112, 85, 0.15); color: var(--del-fg); }
601  &--modified { background: var(--surface-2); color: var(--text-mute); }
602  &--renamed { background: rgba(201, 168, 76, 0.15); color: var(--accent-2); }
603}
604
605.hunk {
606  margin: 0;
607  padding: 0.5rem 0;
608  background: var(--code-bg);
609  font-family: var(--mono);
610  font-size: 0.82rem;
611  line-height: 1.35;
612  overflow-x: auto;
613}
614
615.hunk-hdr {
616  display: block;
617  padding: 0.15rem 0.75rem;
618  color: var(--hunk-fg);
619  background: rgba(158, 197, 210, 0.08);
620}
621
622.line {
623  display: block;
624  padding: 0 0.75rem;
625  white-space: pre;
626
627  &--add { background: var(--add-bg); color: var(--add-fg);
628    &::before { content: "+"; display: inline-block; width: 1.2ch; color: var(--add-fg); opacity: 0.6; }
629  }
630  &--del { background: var(--del-bg); color: var(--del-fg);
631    &::before { content: "-"; display: inline-block; width: 1.2ch; color: var(--del-fg); opacity: 0.6; }
632  }
633  &--ctx { color: var(--text);
634    &::before { content: " "; display: inline-block; width: 1.2ch; }
635  }
636}
637
638// --- footer ---
639.footer {
640  margin-top: 4rem;
641  padding: 4rem 0 3rem;
642  border-top: 1px solid rgba(107, 158, 120, 0.06);
643  background: #090806;
644
645  &__grid {
646    display: grid;
647    gap: 2.5rem;
648    grid-template-columns: minmax(0, 2.4fr) minmax(0, 1fr) minmax(0, 1fr);
649    position: relative;
650    z-index: 1;
651
652    @media (max-width: 760px) {
653      grid-template-columns: minmax(0, 1fr);
654      gap: 2rem;
655    }
656  }
657
658  &__label {
659    font-family: var(--mono);
660    font-size: 0.75rem;
661    font-weight: 600;
662    letter-spacing: 0.12em;
663    text-transform: uppercase;
664    color: var(--accent-2);
665    opacity: 0.7;
666    margin-bottom: 0.9rem;
667  }
668
669  &__about p {
670    color: var(--text-mute);
671    font-size: 0.9rem;
672    line-height: 1.7;
673    margin: 0 0 0.85rem;
674    max-width: 56ch;
675
676    a {
677      color: var(--text);
678      text-decoration: underline;
679      text-underline-offset: 2px;
680      text-decoration-color: var(--border);
681
682      &:hover { color: var(--accent-2); text-decoration-color: var(--accent-2); }
683    }
684  }
685
686  &__tagline {
687    font-style: italic;
688    color: var(--text-mute);
689    opacity: 0.75;
690  }
691
692  &__col {
693    ul {
694      list-style: none;
695      margin: 0;
696      padding: 0;
697    }
698
699    li { margin-bottom: 0.55rem; }
700
701    a {
702      color: var(--text-mute);
703      font-size: 0.9rem;
704      transition: color 150ms ease;
705
706      &:hover { color: var(--accent-2); text-decoration: none; }
707    }
708  }
709
710}
711
712// Slim copyright bar, like blog/status/analytics.
713.footer-bar {
714  background: #050403;
715  border-top: 1px solid rgba(107, 158, 120, 0.04);
716  padding: 0.8rem 0;
717  color: rgba(221, 215, 205, 0.3);
718  font-size: 0.78rem;
719
720  &__row {
721    display: flex;
722    align-items: center;
723    justify-content: space-between;
724    gap: 1rem;
725
726    @media (max-width: 480px) {
727      flex-direction: column;
728      gap: 0.6rem;
729      text-align: center;
730    }
731  }
732
733  &__link {
734    display: inline-flex;
735    color: var(--text-mute);
736    transition: color 150ms ease;
737
738    &:hover { color: var(--accent-2); }
739  }
740}