1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
|
<!doctype html>
<html lang="{{ site.Language.Lang }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light dark">
<meta name="theme-color" content="#f5f2ec" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#15171a" media="(prefers-color-scheme: dark)">
{{ if site.Params.noindex }}
<meta name="robots" content="noindex, nofollow">
{{ end }}
<title>{{ if .IsHome }}{{ site.Title }}{{ else }}{{ .Title }} · {{ site.Title }}{{ end }}</title>
{{- /*
Priority: this page's own front matter > an explicit site-wide
params.description override > params.profile.bio, stripped of markdown
down to plain text. Set params.description only when the SEO snippet
should say something different from the colophon bio; otherwise bio is
the single source of truth for both.
*/}}
{{- $desc := .Description }}
{{- if not $desc }}{{ $desc = site.Params.description }}{{ end }}
{{- if not $desc }}{{ with site.Params.profile.bio }}{{ $desc = . | markdownify | plainify }}{{ end }}{{ end }}
{{ with $desc -}}
<meta name="description" content="{{ . }}">
{{- end }}
<!-- Fonts are self-hosted, so there is no third-party DNS + TLS before first paint. -->
<link rel="preload" href="/fonts/Newsreader-latin.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/fonts/JetBrainsMono-Regular.woff2" as="font" type="font/woff2" crossorigin>
{{- /*
fingerprint appends a content hash to the filename (style.abc123.css)
and gives us a real Subresource Integrity hash for free - without it
this was the one asset with no cache-busting at all: same URL forever,
so a browser (or CDN) holding a stale cached copy from before a
deploy has no way to know a new one exists.
*/}}
{{ with resources.Get "css/style.scss" }}
{{ $opts := dict "transpiler" "dartsass" "outputStyle" "compressed" }}
{{ with . | toCSS $opts | fingerprint }}
<link rel="stylesheet" type="text/css" href="{{ .RelPermalink }}" integrity="{{ .Data.Integrity }}">
{{ end }}
{{ end }}
<link rel="alternate" type="application/rss+xml" title="{{ site.Title }}" href="{{ "index.xml" | absLangURL }}">
{{- /* One hreflang tag per translated version of this page, so search
engines serve readers the right language instead of always the one
that happens to rank. x-default points at the lowest-weight (English)
translation, since that's the site's default language. */}}
{{- $translations := sort .AllTranslations "Language.Weight" }}
{{- range $translations }}
<link rel="alternate" hreflang="{{ .Language.Lang }}" href="{{ .Permalink }}">
{{- end }}
{{- with index $translations 0 }}
<link rel="alternate" hreflang="x-default" href="{{ .Permalink }}">
{{- end }}
{{- /*
Declarative, not htmx.config.preload = {...} in a script: a <meta> tag
is parsed with the rest of the document, before any script (deferred or
not) runs, so htmx's constructor sees it synchronously and correctly
every time - no dependency on inline-script/defer/DOMContentLoaded
ordering at all. boostEvent switches hx-preload's trigger from the
default mousedown/touchstart to mouseover, so the fetch for a boosted
link starts the instant the cursor touches it rather than waiting for a
press.
*/}}
<meta name="htmx-config" content="preload.boostEvent:mouseover">
{{ with resources.Get "js/htmx.min.js" | fingerprint }}
<script src="{{ .RelPermalink }}" integrity="{{ .Data.Integrity }}" defer></script>
{{ end }}
{{- /*
Official htmx extension, vendored from the same htmx.org package as
htmx.min.js above (node_modules/htmx.org/dist/ext/hx-preload.min.js -
re-copy it from there on a version bump). Prefetches a boosted link's
GET response, reused automatically if the click follows within 5s.
Needs no hx-ext or other opt-in - just being loaded activates it for
every boosted anchor.
*/}}
{{ with resources.Get "js/hx-preload.min.js" | fingerprint }}
<script src="{{ .RelPermalink }}" integrity="{{ .Data.Integrity }}" defer></script>
{{ end }}
{{- /*
htmx's own browser back/forward handling (#restoreHistory) is a
separate code path from a boosted click - it calls htmx.ajax() directly
with swap:"outerSync" hardcoded, a full body replace, not our
innerMorph below. Left alone, a link click morphed smoothly but
back/forward tore the masthead down and rebuilt it. Preempt it with the
same config used everywhere else (kept as one Hugo value, $swapSpec,
rather than duplicated literals, so the two can't drift apart), and
abort a still-in-flight restore before starting the next one - the same
guard htmx's own implementation has - so mashing back/forward can't let
a slow, stale response land after a faster, newer one.
`defer` only affects scripts with a `src` - htmx.min.js and
hx-preload.min.js above genuinely wait until after parsing, but this
inline script has no `src` and runs immediately in place during
parsing, defer or not. It must not touch `htmx` at its top level, only
from inside a listener that fires later - hence DOMContentLoaded, which
is guaranteed to fire only after every deferred script, htmx.min.js and
hx-preload.min.js included, has already run.
*/}}
{{- $swapSpec := dict "swap" "innerMorph" "transition" true }}
<script>
document.addEventListener("DOMContentLoaded", () => {
let restoreAbort;
document.addEventListener("htmx:before:history:restore", (e) => {
e.preventDefault();
restoreAbort?.abort();
restoreAbort = new AbortController();
htmx.ajax("GET", e.detail.path, {
target: document.body,
swap: "{{ $swapSpec.swap }}",
transition: {{ $swapSpec.transition }},
request: {headers: {}, signal: restoreAbort.signal},
});
});
});
</script>
{{ block "head" . }}{{ end }}
</head>
{{- /*
Boosts every same-origin link/form into an AJAX navigation, inherited from
body down (htmx 4 requires the ":inherited" suffix explicitly - it no
longer inherits hx-* attributes implicitly). Boosted elements default to
targeting the whole <body>; innerMorph diffs the new body onto the old
one node-by-node instead of replacing it wholesale, so unchanged nodes
(the masthead's "mykiwi.blog" link) are patched in place rather than
torn down and re-flashed, while changed ones - the per-page signal SVG
path, the swapped #main content - update normally. transition:true wraps
that diff in a native View Transition for a soft cross-fade.
*/}}
<body hx-boost:inherited="swap:{{ $swapSpec.swap }} transition:{{ $swapSpec.transition }}">
<a class="skip-link" href="#main">{{ i18n "skipToContent" }}</a>
<div class="wrap">
{{ partial "masthead.html" . }}
<main id="main">{{ block "main" . }}{{ end }}</main>
{{ partial "site-foot.html" . }}
</div>
</body>
</html>
|