orchard
mirrorEvery 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
1{{/* Go requires every {{template}} target to exist, so the optional ones are
2 defined empty here and overridden by whichever page wants them: layouts are
3 parsed before the page, and a later definition of the same name wins. */}}
4
5{{ define "form" }}{{ end }}
6
7{{ define "logo" }}
8<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
9 <g stroke="#6b9e78" stroke-width="1.6" opacity="0.55" stroke-linecap="round">
10 <path d="M46 15 L34 28"/>
11 <path d="M18 47 L29 36"/>
12 <path d="M46 15 L52 40"/>
13 </g>
14 <path d="M32 4 L36.4 26.2 L58 32 L36.4 37.8 L32 60 L27.6 37.8 L6 32 L27.6 26.2 Z"
15 fill="#6b9e78"/>
16 <circle cx="46" cy="15" r="3.4" fill="#c9d9cb"/>
17 <circle cx="18" cy="47" r="2.6" fill="#7eaab8"/>
18 <circle cx="52" cy="40" r="2.2" fill="#c9a84c"/>
19</svg>
20{{ end }}
21
22{{ define "nav" }}
23<nav class="navbar navbar-dark navbar-expand-md">
24 <div class="container-fluid px-4">
25 <a class="navbar-brand" href="{{ if .Authenticated }}/account{{ else }}/{{ end }}">
26 <span class="logo" aria-hidden="true">{{ template "logo" . }}</span>
27 <span class="brand-text">{{ .SiteName }}</span>
28 <span class="brand-dot" aria-hidden="true"></span>
29 </a>
30 {{ if .Authenticated }}
31 <button class="navbar-toggler border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navMain" aria-controls="navMain" aria-expanded="false" aria-label="Toggle navigation">
32 <span class="navbar-toggler-icon"></span>
33 </button>
34 <div class="collapse navbar-collapse" id="navMain">
35 <ul class="navbar-nav ms-3">
36 <li class="nav-item"><a class="nav-link {{ if eq .Path "/account" }}active{{ end }}" href="/account">Account</a></li>
37 <li class="nav-item"><a class="nav-link {{ if eq .Path "/sessions" }}active{{ end }}" href="/sessions">Sessions</a></li>
38 <li class="nav-item"><a class="nav-link {{ if eq .Path "/security" }}active{{ end }}" href="/security">Security</a></li>
39 <li class="nav-item"><a class="nav-link {{ if eq .Path "/activity" }}active{{ end }}" href="/activity">Activity</a></li>
40 </ul>
41 <ul class="navbar-nav ms-auto">
42 <li class="nav-item d-flex align-items-center">
43 <form method="post" action="/logout" class="d-inline">
44 <button type="submit" class="btn btn-sm btn-outline-light">Sign out</button>
45 </form>
46 </li>
47 </ul>
48 </div>
49 {{ end }}
50 </div>
51</nav>
52{{ end }}
53
54{{/* One of the other sites. The whole card is the link, so the hit area is the
55 card rather than the word inside it. */}}
56{{ define "siteCard" }}
57<a class="site-card" href="https://{{ .host }}/">
58 <span class="site-card-name">
59 <span class="site-card-dot" aria-hidden="true"></span>
60 {{ .name }}
61 <span class="site-card-go" aria-hidden="true">→</span>
62 </span>
63 <span class="site-card-desc d-block">{{ .desc }}</span>
64 <span class="site-card-host d-block">{{ .host }}</span>
65</a>
66{{ end }}
67
68{{ define "alerts" }}
69{{ if .Error }}<div class="auth-alert is-error" role="alert">{{ .Error }}</div>{{ end }}
70{{ if .Notice }}<div class="auth-alert is-notice" role="status">{{ .Notice }}</div>{{ end }}
71{{ end }}
72
73{{/* The shell every signed-out page uses, so the four of them cannot drift.
74 Everything in the right hand half is decoration and carries aria-hidden:
75 it is a star field, and there is nothing in it to read. */}}
76{{ define "authShell" }}
77<div class="auth-stage">
78 <div class="auth-sky" aria-hidden="true">
79 <div class="auth-nebula"></div>
80 <canvas class="auth-stars" data-starfield></canvas>
81 <div class="auth-orbits"></div>
82 <div class="auth-vignette"></div>
83 </div>
84
85 <div class="auth-panel">
86 <div class="auth-form">
87 <div class="auth-mark" aria-hidden="true">{{ template "logo" . }}</div>
88 {{ template "form" . }}
89 </div>
90 </div>
91</div>
92{{ end }}
93
94{{ define "footer" }}
95<div class="footer-bar">
96 <div class="container-fluid px-4">
97 <div class="row align-items-center">
98 <div class="col-sm-6 d-flex align-items-center justify-content-center justify-content-sm-start order-1 order-sm-0">
99 <small>© {{ .Year }} {{ .AuthorName }} · All times UTC</small>
100 </div>
101 <div class="col-sm-6 d-flex justify-content-center justify-content-sm-end py-3 py-sm-0">
102 <a href="{{ .SourceURL }}" target="_blank" rel="noopener" class="footer-bar-link">Source</a>
103 </div>
104 </div>
105 </div>
106</div>
107{{ end }}