repos
/ orchard main

orchard

mirror

Every site I host, in one repo, along with the Cloudflare Tunnel and Caddy that front them. It's all Go, Vite, and SQLite, and it runs on a desktop at home with nothing listening on an inbound port.

blogbuncaddycloudflare-tunneldockergogolanghomelabhtml-templatemonorepoself-hostedseosqlitestatic-sitetypstuptime-monitoringviteweb-analytics

15.3 KB · 712 lines · SCSS Raw History
  1@use "variables" as *;
  2
  3// Bootstrap 5.3 writes defaults into `:root,[data-bs-theme=light]`. Use
  4// `html:root` so we outrank the default block and enforce the dark palette.
  5html:root {
  6  --bs-body-bg: #{$body-bg};
  7  --bs-body-color: #{$body-color};
  8  --bs-body-bg-rgb: 14, 13, 10;
  9  --bs-body-color-rgb: 221, 215, 205;
 10  --bs-emphasis-color: #{$white};
 11  --bs-secondary-bg: #1c1a15;
 12  --bs-tertiary-bg: #201d16;
 13  --bs-border-color: rgba(107, 158, 120, 0.42);
 14  --bs-link-color-rgb: 125, 184, 140;
 15  --bs-link-hover-color-rgb: 149, 204, 162;
 16  --bs-heading-color: #{$white};
 17}
 18
 19body {
 20  min-height: 100vh;
 21  display: flex;
 22  flex-direction: column;
 23  -webkit-font-smoothing: antialiased;
 24  -moz-osx-font-smoothing: grayscale;
 25  letter-spacing: 0.01em;
 26  background-color: $body-bg !important;
 27  color: $body-color;
 28
 29  // The structural grid the whole estate sits on. 32px because that is the
 30  // padding rhythm the panels already use, and low enough alpha that it reads
 31  // as texture rather than as a table. Green here where dash is amber, and a
 32  // touch more alpha to match, since green at this value reads fainter.
 33  background-image:
 34    linear-gradient(rgba(107, 158, 120, 0.028) 1px, transparent 1px),
 35    linear-gradient(90deg, rgba(107, 158, 120, 0.028) 1px, transparent 1px);
 36  background-size: 32px 32px;
 37  background-position: center top;
 38}
 39
 40main { flex: 1; }
 41
 42// The keyboard focus ring, and the selection colour. repos has had both since
 43// it was written and the other four had neither, so a tab through a form here
 44// depended on whatever Bootstrap happened to do per component.
 45:focus-visible {
 46  outline: 2px solid $green;
 47  outline-offset: 2px;
 48  border-radius: 2px;
 49}
 50
 51::selection { background: rgba(107, 158, 120, 0.3); }
 52
 53
 54.vh-100 { height: 100vh; }
 55
 56
 57.logo {
 58  display: inline-flex;
 59  align-items: center;
 60  justify-content: center;
 61  width: 28px;
 62  height: 28px;
 63  text-decoration: none !important;
 64  transition: transform 250ms ease;
 65
 66  svg {
 67    width: 100%;
 68    height: 100%;
 69    display: block;
 70  }
 71
 72  &:hover {
 73    transform: translateY(-1px);
 74  }
 75
 76  &.logo-sm {
 77    width: 18px;
 78    height: 18px;
 79  }
 80}
 81
 82// `min-height` rather than `height`, so the bar grows when the mobile collapse
 83// opens. At a fixed height the open menu is painted over by the siblings below.
 84
 85.navbar {
 86  min-height: 52px;
 87  padding: 0;
 88  background: #090806 !important;
 89  border-bottom: 1px solid rgba(107, 158, 120, 0.2);
 90
 91  > .container {
 92    min-height: 52px;
 93  }
 94
 95  .navbar-brand {
 96    display: flex;
 97    align-items: center;
 98    gap: 0.55rem;
 99    // Without this the text item is as tall as the inherited 1.7 line-height,
100    // and centring a 21px box against a 22px mark is what left them looking a
101    // pixel out from each other.
102    line-height: 1;
103    color: $white;
104    font-weight: 600;
105    letter-spacing: 0.1em;
106    text-transform: uppercase;
107    font-size: 0.78rem;
108    text-decoration: none;
109
110    .logo {
111      width: 22px;
112      height: 22px;
113    }
114
115    .brand-text {
116      display: block;
117      color: $white;
118      // All caps has no descenders, so the glyphs sit above the middle of
119      // their own box. Nudging the text rather than the marks keeps the star
120      // and the dot on the true centre line.
121      transform: translateY(0.055em);
122    }
123
124    .brand-dot {
125      display: block;
126      width: 5px;
127      height: 5px;
128      border-radius: 50%;
129      background: $green;
130      flex: none;
131    }
132  }
133
134  .navbar-toggler {
135    color: rgba(221, 215, 205, 0.85);
136    padding: 0.35rem 0.55rem;
137    font-size: 1rem;
138
139    &:focus { box-shadow: none; }
140  }
141
142  .nav-link {
143    font-size: 0.8rem;
144    font-weight: 500;
145    letter-spacing: 0.05em;
146    text-transform: lowercase;
147    padding-left: 1rem !important;
148    padding-right: 1rem !important;
149    color: rgba(221, 215, 205, 0.8);
150    position: relative;
151    transition: color 200ms ease;
152
153    &::after {
154      content: '';
155      position: absolute;
156      bottom: 2px;
157      left: 1rem;
158      right: 1rem;
159      height: 1px;
160      background: $green;
161      transform: scaleX(0);
162      transition: transform 250ms ease;
163    }
164
165    &:hover,
166    &.active { color: $white; }
167
168    &:hover::after,
169    &.active::after { transform: scaleX(1); }
170  }
171
172  @media (max-width: 767.98px) {
173    .navbar-collapse {
174      width: 100%;
175      border-top: 1px solid rgba(107, 158, 120, 0.28);
176      margin-top: 0.25rem;
177      padding: 0.4rem 0 0.6rem;
178    }
179
180    .navbar-nav {
181      margin-left: 0 !important;
182      width: 100%;
183    }
184
185    .nav-link {
186      padding: 0.65rem 0.75rem !important;
187      border-left: 2px solid transparent;
188      border-radius: 2px;
189      transition: background-color 150ms ease, color 150ms ease, border-color 150ms ease;
190
191      &::after { display: none; }
192
193      &:hover {
194        background: rgba(221, 215, 205, 0.09);
195        color: $white;
196      }
197
198      &.active {
199        color: $white;
200        border-left-color: $green;
201        background: rgba(107, 158, 120, 0.16);
202      }
203    }
204
205    .navbar-nav.ms-auto {
206      margin-top: 0.5rem;
207      padding-top: 0.6rem;
208      border-top: 1px solid rgba(107, 158, 120, 0.24);
209
210      .nav-item { width: 100%; }
211
212      .btn,
213      form,
214      .d-inline { width: 100%; }
215
216      .btn { width: 100%; }
217    }
218  }
219}
220
221.breadcrumb-bar {
222  background: rgba(107, 158, 120, 0.05);
223  border-bottom: 1px solid rgba(107, 158, 120, 0.2);
224
225  .breadcrumb { font-family: $font-family-monospace; }
226}
227
228.section-label {
229  font-size: 0.7rem;
230  font-weight: 600;
231  letter-spacing: 0.12em;
232  text-transform: uppercase;
233  color: rgba(201, 168, 76, 0.95);
234  font-family: $font-family-monospace;
235}
236
237.card {
238  background: $card-bg;
239  border: 1px solid $card-border-color;
240  transition: border-color 250ms ease;
241
242  &:hover { border-color: rgba(107, 158, 120, 0.2); }
243}
244
245.status-dot {
246  display: inline-block;
247  width: 8px; height: 8px;
248  border-radius: 50%;
249  margin-right: 0.55rem;
250  vertical-align: middle;
251  flex-shrink: 0;
252
253  &.status-dot-lg {
254    width: 14px; height: 14px;
255    margin-right: 0;
256  }
257
258  &.is-up { background: $green; box-shadow: 0 0 0 3px rgba(107, 158, 120, 0.25); }
259  &.is-down { background: $terracotta; box-shadow: 0 0 0 3px rgba(196, 112, 85, 0.25); }
260  &.is-warn { background: $amber; box-shadow: 0 0 0 3px rgba(201, 168, 76, 0.25); }
261  &.is-idle { background: #4a443c; }
262}
263
264.metric-tile {
265  background: $card-bg;
266  border: 1px solid $card-border-color;
267  border-radius: $border-radius;
268  padding: 1rem 1.15rem;
269  height: 100%;
270  display: flex;
271  flex-direction: column;
272
273  .metric-header {
274    display: flex;
275    align-items: center;
276    gap: 0.5rem;
277    min-height: 1.3rem;
278  }
279
280  .metric-label {
281    flex: 1;
282    min-width: 0;
283    font-size: 0.68rem;
284    font-weight: 600;
285    letter-spacing: 0.12em;
286    text-transform: uppercase;
287    color: #9a9188;
288  }
289
290  .metric-value {
291    font-size: 1.75rem;
292    font-weight: 700;
293    line-height: 1.1;
294    color: $white;
295    margin-top: 0.35rem;
296    letter-spacing: -0.01em;
297    word-break: break-word;
298  }
299
300  .metric-sub {
301    font-size: 0.72rem;
302    color: #9a9188;
303    margin-top: auto;
304    padding-top: 0.35rem;
305    letter-spacing: 0.02em;
306  }
307
308  .metric-delta {
309    flex-shrink: 0;
310    font-family: $font-family-monospace;
311    font-size: 0.75rem;
312    font-weight: 600;
313    letter-spacing: 0.04em;
314    padding: 0.22rem 0.5rem;
315    border-radius: 2px;
316    border: 1px solid transparent;
317    line-height: 1;
318
319    &.is-up {
320      color: $green-bright;
321      background: $green-dim;
322      border-color: $green-border;
323    }
324    &.is-down {
325      color: #e38871;
326      background: $terracotta-dim;
327      border-color: $terracotta-border;
328    }
329    &.is-flat {
330      color: #9a9188;
331      background: rgba(221, 215, 205, 0.09);
332      border-color: rgba(221, 215, 205, 0.28);
333    }
334  }
335
336  &.metric-accent-green {
337    border-color: $green-border;
338    .metric-value { color: $green-bright; }
339  }
340  &.metric-accent-amber {
341    border-color: $amber-border;
342    .metric-value { color: #ddc06a; }
343  }
344  &.metric-accent-danger {
345    border-color: $terracotta-border;
346    .metric-value { color: #d88870; }
347  }
348}
349
350.chip {
351  display: inline-flex;
352  align-items: center;
353  gap: 0.4rem;
354  font-family: $font-family-monospace;
355  font-size: 0.7rem;
356  font-weight: 600;
357  letter-spacing: 0.05em;
358  text-transform: uppercase;
359  padding: 0.3rem 0.6rem;
360  border-radius: 2px;
361  border: 1px solid transparent;
362  line-height: 1;
363
364  &.chip-ok {
365    background: $green-dim;
366    color: $green-bright;
367    border-color: $green-border;
368  }
369  &.chip-down {
370    background: $terracotta-dim;
371    color: #e38871;
372    border-color: $terracotta-border;
373  }
374  &.chip-warn {
375    background: $amber-dim;
376    color: #ddc06a;
377    border-color: $amber-border;
378  }
379  &.chip-info {
380    background: rgba(126, 170, 184, 0.16);
381    color: #9ec5d2;
382    border-color: rgba(126, 170, 184, 0.5);
383  }
384  &.chip-muted {
385    background: rgba(221, 215, 205, 0.09);
386    color: #9a9188;
387    border-color: rgba(221, 215, 205, 0.28);
388  }
389}
390
391footer {
392  padding-top: 4rem;
393  padding-bottom: 3rem;
394  position: relative;
395  background: #090806;
396  border-top: 1px solid rgba(107, 158, 120, 0.24);
397
398  .h5 {
399    font-size: 0.7rem;
400    letter-spacing: 0.12em;
401    text-transform: uppercase;
402    color: rgba(201, 168, 76, 0.85);
403    font-weight: 600;
404  }
405
406  p {
407    color: rgba(221, 215, 205, 0.68);
408    font-size: 0.85rem;
409    line-height: 1.75;
410
411    a {
412      color: rgba(221, 215, 205, 0.88);
413      text-decoration: underline;
414      text-underline-offset: 2px;
415
416      &:hover { color: $white; }
417    }
418  }
419
420  .links {
421    padding-top: 2rem;
422    padding-bottom: 1rem;
423  }
424}
425
426.footer-bar {
427  background: #050403;
428  border-top: 1px solid rgba(107, 158, 120, 0.2);
429  padding: 0.8rem 0;
430  color: rgba(221, 215, 205, 0.58);
431  font-size: 0.78rem;
432
433  small { color: rgba(221, 215, 205, 0.58); }
434
435  &-link {
436    color: rgba(221, 215, 205, 0.58);
437    transition: color 200ms ease;
438
439    &:hover { color: $white; }
440  }
441}
442
443.terminal-block {
444  background: rgba(9, 8, 6, 0.7);
445  border: 1px solid rgba(107, 158, 120, 0.4);
446  border-radius: $border-radius;
447  font-family: $font-family-monospace;
448  font-size: 0.8rem;
449  color: $green-bright;
450  padding: 1rem 1.2rem;
451  line-height: 1.8;
452
453  .t-line { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
454  .t-prompt { color: $amber; margin-right: 0.4rem; }
455  .t-out { color: $gray-200; }
456  .t-comment { color: #8a8279; }
457  .t-key { color: $amber; }
458  .t-val { color: $green-bright; }
459  .t-cursor {
460    display: inline-block; width: 7px; height: 0.9em; vertical-align: -2px;
461    background: $green-bright;
462    animation: t-blink 1s steps(1) infinite;
463  }
464}
465
466@keyframes t-blink {
467  0%, 50% { opacity: 1; }
468  51%, 100% { opacity: 0; }
469}
470
471.search-form {
472  position: relative;
473
474  .search-icon {
475    position: absolute;
476    left: 0.9rem;
477    top: 50%;
478    transform: translateY(-50%);
479    color: #8a8279;
480    pointer-events: none;
481    z-index: 2;
482  }
483
484  .search-input {
485    padding-left: 2.5rem;
486    font-size: 0.85rem;
487    border: 1px solid rgba(107, 158, 120, 0.4);
488    background: rgba(19, 18, 14, 0.7);
489    color: $body-color;
490    transition: border-color 200ms ease;
491
492    &::placeholder {
493      color: #8a8279;
494      font-style: normal;
495    }
496
497    &:focus {
498      border-color: rgba(107, 158, 120, 0.4);
499      box-shadow: 0 0 0 2px rgba(107, 158, 120, 0.1);
500      background: $card-bg;
501    }
502  }
503}
504
505.dashboard-toolbar {
506  display: flex;
507  gap: 0.5rem;
508  align-items: stretch;
509
510  .form-control {
511    height: 38px;
512    font-size: 0.85rem;
513  }
514
515  .btn {
516    height: 38px;
517    padding: 0 1.1rem;
518    display: inline-flex;
519    align-items: center;
520    white-space: nowrap;
521  }
522}
523
524.chart-panel {
525  background: $card-bg;
526  border: 1px solid $card-border-color;
527  border-radius: $border-radius;
528  padding: 1rem 1.15rem;
529
530  .chart-panel-header {
531    display: flex;
532    align-items: center;
533    justify-content: space-between;
534    margin-bottom: 0.75rem;
535    gap: 0.75rem;
536  }
537
538  .chart-panel-title {
539    font-size: 0.68rem;
540    font-weight: 600;
541    letter-spacing: 0.12em;
542    text-transform: uppercase;
543    color: rgba(201, 168, 76, 0.95);
544    font-family: $font-family-monospace;
545  }
546
547  .chart-panel-body {
548    position: relative;
549  }
550}
551
552
553.rank-list {
554  background: $card-bg;
555  border: 1px solid $card-border-color;
556  border-radius: $border-radius;
557  overflow: hidden;
558  height: 100%;
559  display: flex;
560  flex-direction: column;
561
562  .rank-list-header {
563    display: flex;
564    justify-content: space-between;
565    align-items: center;
566    background: rgba(107, 158, 120, 0.1);
567    padding: 0.6rem 1rem;
568    border-bottom: 1px solid rgba(107, 158, 120, 0.28);
569
570    .rank-list-title {
571      font-size: 0.68rem;
572      font-weight: 600;
573      letter-spacing: 0.12em;
574      text-transform: uppercase;
575      color: rgba(201, 168, 76, 0.95);
576      font-family: $font-family-monospace;
577    }
578
579    .rank-list-col {
580      font-size: 0.62rem;
581      font-weight: 600;
582      letter-spacing: 0.1em;
583      text-transform: uppercase;
584      color: #8a8279;
585      font-family: $font-family-monospace;
586    }
587  }
588
589  .rank-list-body {
590    display: flex;
591    flex-direction: column;
592    flex: 1 1 auto;
593  }
594
595  .rank-list-row {
596    display: grid;
597    grid-template-columns: minmax(0, 1fr) auto;
598    gap: 0.75rem;
599    align-items: center;
600    padding: 0.5rem 1rem;
601    border-bottom: 1px solid rgba(107, 158, 120, 0.2);
602    font-size: 0.85rem;
603
604    &:last-child { border-bottom: none; }
605
606    .rank-label {
607      min-width: 0;
608      overflow: hidden;
609      text-overflow: ellipsis;
610      white-space: nowrap;
611      color: $gray-200;
612
613      a {
614        color: inherit;
615        text-decoration: none;
616
617        &:hover {
618          color: $green-bright;
619          text-decoration: underline;
620        }
621      }
622    }
623
624    .rank-count {
625      font-family: $font-family-monospace;
626      font-size: 0.78rem;
627      color: $amber;
628      font-weight: 600;
629    }
630  }
631
632}
633
634// Not nested inside .rank-list. The same class marks the empty states inside
635// .log-body, which a nested selector never reaches.
636.rank-list-empty {
637  padding: 1.25rem 1rem;
638  color: $gray-300;
639  font-size: 0.82rem;
640  text-align: center;
641}
642
643.auth-shell {
644  min-height: calc(100vh - 52px);
645  display: grid;
646  grid-template-columns: 1fr 1fr;
647
648  @media (max-width: 991px) {
649    grid-template-columns: 1fr;
650  }
651
652  .auth-form-col {
653    padding: 3rem 2rem;
654    display: flex;
655    align-items: center;
656    justify-content: center;
657  }
658
659  .auth-visual-col {
660    position: relative;
661    overflow: hidden;
662    background: linear-gradient(145deg, #090806 0%, #0e0d0a 60%, #13120e 100%);
663    border-left: 1px solid rgba(107, 158, 120, 0.28);
664
665    @media (max-width: 991px) { display: none; }
666  }
667
668  .auth-form-wrap {
669    width: 100%;
670    max-width: 380px;
671  }
672}
673
674
675
676// `danger` above is the same colour under the shared name, kept for markup
677// that still asks for it. slate is for a number that is neither good nor bad.
678.metric-tile {
679  &.metric-accent-terracotta {
680    border-color: $terracotta-border;
681    .metric-value { color: #d88870; }
682  }
683  &.metric-accent-slate {
684    border-color: rgba(126, 170, 184, 0.5);
685    .metric-value { color: #9ec5d2; }
686  }
687}
688
689.text-terracotta { color: #d88870 !important; }
690
691// Absolutely positioned behind the row's text rather than sized as a grid
692// column, so a long label keeps the full width and the count is never pushed off.
693.rank-list .rank-list-row {
694  position: relative;
695
696  .rank-bar {
697    position: absolute;
698    left: 0;
699    top: 0;
700    bottom: 0;
701    background: rgba(107, 158, 120, 0.16);
702    pointer-events: none;
703    z-index: 0;
704  }
705
706  .rank-label,
707  .rank-count {
708    position: relative;
709    z-index: 1;
710  }
711}