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

7.2 KB · 184 lines · HTML Raw History
  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>