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<!doctype html>
2<html lang="en">
3 <head>
4 <meta charset="utf-8" />
5 <meta name="viewport" content="width=device-width, initial-scale=1" />
6
7 <!-- Blocking and inline, since whether the curtain plays has to be settled
8 before the first paint. Keyed on the build hash, which is what says
9 whether this browser still has this build's bytes in its cache. -->
10 <script>
11 (function () {
12 // Set before first paint, so CSS can scope anything that only makes
13 // sense once JS runs. Rules that start an element at opacity 0 and rely
14 // on a script to reveal it must be gated on this, or a throw anywhere in
15 // the module leaves that content permanently invisible.
16 document.documentElement.dataset.js = "true";
17 var build = "{{ .Script }}";
18 var week = 7 * 24 * 60 * 60 * 1000;
19 try {
20 var seen = JSON.parse(localStorage.getItem("ib.build") || "null");
21 if (seen && seen.b === build && Date.now() - seen.t < week) {
22 document.documentElement.dataset.returning = "true";
23 }
24 // Stamped on every load, so the window is "a week since you were
25 // last here" rather than "a week since you first arrived".
26 localStorage.setItem(
27 "ib.build",
28 JSON.stringify({ b: build, t: Date.now() }),
29 );
30 } catch (e) {}
31 })();
32 </script>
33
34 <title>{{ .Title }}</title>
35 <meta name="description" content="{{ .Description }}" />
36 <link rel="canonical" href="{{ .Canonical }}" />
37 {{- if .Staging }}
38 <!-- The test hostname must never be indexed: it would compete with the
39 real site as a byte-for-byte duplicate. -->
40 <meta name="robots" content="noindex, nofollow" />
41 {{- end }}
42
43 <meta name="theme-color" content="{{ .ThemeColor }}" />
44 <link rel="icon" type="image/svg+xml" href="/static/favicon.svg" />
45 <link rel="icon" href="/favicon.ico" />
46 <link rel="manifest" href="/manifest.json" />
47
48 <meta property="og:type" content="website" />
49 <meta property="og:site_name" content="Isaac Bythewood" />
50 <meta property="og:title" content="{{ .Title }}" />
51 <meta property="og:description" content="{{ .Description }}" />
52 <meta property="og:url" content="{{ .Canonical }}" />
53 <!-- A PNG, not the SVG favicon. Every social platform refuses
54 image/svg+xml here, so a vector card is a card nobody sees. -->
55 <meta property="og:image" content="{{ .OGImage }}" />
56 <meta property="og:image:width" content="1200" />
57 <meta property="og:image:height" content="630" />
58 <meta property="og:image:alt" content="Isaac Bythewood, Senior Solutions Architect" />
59
60 <meta name="twitter:card" content="summary_large_image" />
61 <meta name="twitter:title" content="{{ .Title }}" />
62 <meta name="twitter:description" content="{{ .Description }}" />
63 <meta name="twitter:image" content="{{ .OGImage }}" />
64
65 {{- if .JSONLD }}
66 <script type="application/ld+json">{{ .JSONLD }}</script>
67 {{- end }}
68
69 {{- range .Styles }}
70 <link rel="stylesheet" href="{{ . }}" />
71 {{- end }}
72 <script type="module" src="{{ .Script }}"></script>
73 {{- if .Analytics }}
74 <script>
75 (function (m, e, t, r, i, c, s) {
76 m.collectorQueue = m.collectorQueue || r;
77 m.collectorServer = c;
78 m.collectorId = s;
79 m.collectorScript = e.createElement(t);
80 m.collectorScript.src = c + i;
81 e.head.appendChild(m.collectorScript);
82 })(window, document, "script", [], "/static/collector.js",
83 "https://analytics.bythewood.me",
84 "30e69c06-9beb-4283-8919-8c7a686ab013");
85 </script>
86 {{- end }}
87 </head>
88
89 <body data-page="{{ .Page }}">
90 <!-- The custom cursor. Hidden under 1024px, where there is no pointer to
91 replace. -->
92 <div class="cursor">
93 <svg
94 width="16"
95 height="16"
96 viewBox="0 0 16 16"
97 fill="#ff2d2d"
98 fill-opacity="0.9"
99 xmlns="http://www.w3.org/2000/svg"
100 >
101 <circle cx="8" cy="8" r="8" />
102 </svg>
103 </div>
104 <div class="cursor-circle"></div>
105
106 <div class="loader" {{ if .LoaderWaits }}data-wait="true"{{ end }} aria-hidden="true">
107 <div class="loader-column"></div>
108 <div class="loader-column"></div>
109 <div class="loader-column"></div>
110 <div class="loader-column"></div>
111 <div class="loader-column"></div>
112 <div class="loader-column"></div>
113 <div class="loader-indicator">
114 <span class="loader-dot"></span>
115 <span class="loader-dot"></span>
116 <span class="loader-dot"></span>
117 </div>
118 </div>
119
120 <nav class="nav">
121 <a href="/" class="nav-logo" aria-label="Back to home"><span aria-hidden="true"></span></a>
122 <a href="/contact" class="nav-contact">Get in touch</a>
123 <div class="nav-current">{{ .Num }}</div>
124 </nav>
125
126 <button class="menu-hamburger" aria-label="Menu" aria-expanded="false" aria-controls="menu-overlay" type="button">
127 <div class="menu-patty" style="width: 15px"></div>
128 <div class="menu-patty"></div>
129 <div class="menu-patty" style="width: 20px"></div>
130 </button>
131
132 <!-- The overlay is hidden by z-index and a transform rather than display
133 or visibility, so its links stay in the tab order. inert takes them out
134 without disturbing the z-index the slide-out depends on. -->
135 <div class="menu-overlay" id="menu-overlay" inert>
136 <div class="menu-overlayGrid">
137 <div class="menu-overlayGridLeft">
138 <div class="menu-topBar">
139 {{- range .TopLinks }}
140 <a class="menu-topLink" href="{{ .Href }}" target="_blank" rel="noopener noreferrer">{{ .Label }}</a>
141 {{- end }}
142 <div class="menu-bar"></div>
143 </div>
144 {{- range .NavPages }}
145 <a class="menu-overlayLink" href="{{ .Href }}" data-text="{{ .Title }}">{{ .Title }}</a>
146 {{- end }}
147 <div class="menu-bottomBar">
148 <img src="{{ .AvatarSrc }}" alt="Isaac Bythewood" width="50" height="50" loading="lazy" decoding="async" />
149 <div class="menu-bar"></div>
150 </div>
151 </div>
152 <div class="menu-overlayGridRight">
153 <!-- Loaded on first open. loading="lazy" does not help, because the
154 overlay is a full-screen fixed element that intersects the
155 viewport, so the browser fetches the image anyway. -->
156 <img
157 data-src="{{ .MenuSrc }}"
158 data-srcset="{{ .MenuSrcset }}"
159 sizes="(max-width: 767.98px) 100vw, 40vw"
160 alt="#006 Molten Copper"
161 decoding="async"
162 />
163 </div>
164 </div>
165 </div>
166
167 <div class="transition">
168 <div class="layout-lines" aria-hidden="true">
169 <div class="layout-line" style="grid-column: 1"></div>
170 <div class="layout-line" style="grid-column: 2"></div>
171 <div class="layout-line" style="grid-column: 3"></div>
172 <div class="layout-line" style="grid-column: 4"></div>
173 <div class="layout-line" style="grid-column: 5"></div>
174 <div class="layout-line" style="grid-column: 6"></div>
175 </div>
176 <div class="layout-area">
177 <main class="page-main" style="grid-area: {{ .GridArea }}">
178 {{- template "content" . }}
179 </main>
180 </div>
181 </div>
182 </body>
183</html>