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