Add SRI fingerprinting, inline avatar blurhash, and a language switcher icon
CSS and JS assets now go through fingerprint so browsers/CDNs pick up new content immediately instead of caching a stale copy, and the generated hash also gives us a real integrity attribute for free. The avatar blurhash placeholder is now inlined as a data URI instead of served as a separate file, so it paints with the first HTML response rather than racing the real photo over the network. The footer language switcher gets a globe icon and wraps onto its own line, with the site-foot markup reordered so it renders after the store-path row. htmx and hx-preload move from static/ to assets/ so they can be fingerprinted too.
51380a7 parent: 1ce0eef modified
blog/assets/css/style.scss +22 -4 | @@ -985,13 +985,31 @@ blockquote.alert { | ||
| 985 | 985 | // competing for space inline with them. |
| 986 | 986 | .lang-switch { |
| 987 | 987 | display: inline-flex; |
| 988 | + flex-wrap: wrap; | |
| 989 | + align-items: center; | |
| 988 | 990 | justify-content: center; |
| 989 | 991 | flex-basis: 100%; |
| 990 | - gap: 0.5rem; | |
| 991 | - } | |
| 992 | + gap: 0.4rem; | |
| 993 | + | |
| 994 | + // flex-shrink:0 alone isn't enough: a flex item's automatic min-width | |
| 995 | + // still falls back to its min-content size, which for spaceless CJK | |
| 996 | + // text is a single character - so without white-space:nowrap too, a | |
| 997 | + // too-narrow row splits "日本語" across lines instead of wrapping the | |
| 998 | + // whole link as one unit. | |
| 999 | + a, | |
| 1000 | + span { | |
| 1001 | + flex-shrink: 0; | |
| 1002 | + white-space: nowrap; | |
| 1003 | + } | |
| 992 | 1004 | |
| 993 | - .lang-switch__current { | |
| 994 | - color: var(--ink-muted); | |
| 1005 | + // Bigger than the RSS icon's 0.85em - at that size this icon's finer | |
| 1006 | + // detail (two characters, a curved arrow) turns to mush. | |
| 1007 | + svg { | |
| 1008 | + width: 1.4em; | |
| 1009 | + height: 1.4em; | |
| 1010 | + fill: currentColor; | |
| 1011 | + flex-shrink: 0; | |
| 1012 | + } | |
| 995 | 1013 | } |
| 996 | 1014 | |
| 997 | 1015 | // Own row, so the path gets the whole measure rather than whatever is left |
| @@ -985,13 +985,31 @@ blockquote.alert { | |||
| 985 | // competing for space inline with them. | 985 | // competing for space inline with them. |
| 986 | .lang-switch { | 986 | .lang-switch { |
| 987 | display: inline-flex; | 987 | display: inline-flex; |
| 988 | + flex-wrap: wrap; | ||
| 989 | + align-items: center; | ||
| 988 | justify-content: center; | 990 | justify-content: center; |
| 989 | flex-basis: 100%; | 991 | flex-basis: 100%; |
| 990 | - gap: 0.5rem; | 992 | + gap: 0.4rem; |
| 991 | - } | 993 | + |
| 994 | + // flex-shrink:0 alone isn't enough: a flex item's automatic min-width | ||
| 995 | + // still falls back to its min-content size, which for spaceless CJK | ||
| 996 | + // text is a single character - so without white-space:nowrap too, a | ||
| 997 | + // too-narrow row splits "日本語" across lines instead of wrapping the | ||
| 998 | + // whole link as one unit. | ||
| 999 | + a, | ||
| 1000 | + span { | ||
| 1001 | + flex-shrink: 0; | ||
| 1002 | + white-space: nowrap; | ||
| 1003 | + } | ||
| 992 | 1004 | ||
| 993 | - .lang-switch__current { | 1005 | + // Bigger than the RSS icon's 0.85em - at that size this icon's finer |
| 994 | - color: var(--ink-muted); | 1006 | + // detail (two characters, a curved arrow) turns to mush. |
| 1007 | + svg { | ||
| 1008 | + width: 1.4em; | ||
| 1009 | + height: 1.4em; | ||
| 1010 | + fill: currentColor; | ||
| 1011 | + flex-shrink: 0; | ||
| 1012 | + } | ||
| 995 | } | 1013 | } |
| 996 | 1014 | ||
| 997 | // Own row, so the path gets the whole measure rather than whatever is left | 1015 | // Own row, so the path gets the whole measure rather than whatever is left |
deleted
blog/assets/images/avatar-blurhash.png +0 -0 | deleted file mode 100644 | ||
| Binary files a/blog/assets/images/avatar-blurhash.png and /dev/null differ | ||
| deleted file mode 100644 | |||
| Binary files a/blog/assets/images/avatar-blurhash.png and /dev/null differ | Binary files a/blog/assets/images/avatar-blurhash.png and /dev/null differ | ||
renamed
blog/assets/js/htmx.min.js +0 -0 | similarity index 100% | ||
| rename from blog/static/js/htmx.min.js | ||
| rename to blog/assets/js/htmx.min.js |
| similarity index 100% | |||
| rename from blog/static/js/htmx.min.js | |||
| rename to blog/assets/js/htmx.min.js |
renamed
blog/assets/js/hx-preload.min.js +0 -0 | similarity index 100% | ||
| rename from blog/static/js/hx-preload.min.js | ||
| rename to blog/assets/js/hx-preload.min.js |
| similarity index 100% | |||
| rename from blog/static/js/hx-preload.min.js | |||
| rename to blog/assets/js/hx-preload.min.js |
modified
blog/hugo.toml +3 -3 | @@ -58,14 +58,14 @@ noindex = true | ||
| 58 | 58 | # Drop a square photo at blog/assets/images/avatar.jpg (or change this path) |
| 59 | 59 | # to have it show up; the colophon quietly skips itself if the file is missing. |
| 60 | 60 | avatar = "images/avatar.jpg" |
| 61 | -avatarAlt = "mykiwi's GitHub avatar" | |
| 61 | +avatarAlt = "mykiwi's avatar" | |
| 62 | 62 | firstName = "Romain" |
| 63 | 63 | lastName = "GAUTIER" |
| 64 | 64 | atprotoHandle = 'mykiwi.dev' |
| 65 | 65 | # Markdown, rendered as-is in the colophon (links work: [text](url)). Also |
| 66 | 66 | # the source for the <meta name="description"> tag, unless params.description |
| 67 | 67 | # above is set to override it. |
| 68 | -bio = "I'm a software engineer, passionate about technology, sharing knowledge and open source like #PHP, #Symfony, #Docker, #Dagger, #Nix and atproto:// Also [🐕️ & 🐈️](https://instagram.com/apy.leon \"Pictures of Apy & Léon\") dad." | |
| 68 | +bio = "I'm a software engineer, passionate about technology, sharing knowledge and open source like #PHP, #Symfony, #Docker, #Dagger, #Nix, atproto:// and ATmosphere. Also [🐕️ & 🐈️](https://instagram.com/apy.leon \"Pictures of Apy & Léon\") dad." | |
| 69 | 69 | |
| 70 | 70 | [[params.profile.links]] |
| 71 | 71 | key = "Tweets on ATmosphere" |
| @@ -79,7 +79,7 @@ url = "https://mamot.fr/@mykiwi" | ||
| 79 | 79 | |
| 80 | 80 | [[params.profile.links]] |
| 81 | 81 | key = "Tweets on Twitter" |
| 82 | -value = "@mykiwi" | |
| 82 | +value = "@my_kiwi" | |
| 83 | 83 | url = "https://x.com/mykiwi" |
| 84 | 84 | |
| 85 | 85 | [[params.profile.links]] |
| @@ -58,14 +58,14 @@ noindex = true | |||
| 58 | # Drop a square photo at blog/assets/images/avatar.jpg (or change this path) | 58 | # Drop a square photo at blog/assets/images/avatar.jpg (or change this path) |
| 59 | # to have it show up; the colophon quietly skips itself if the file is missing. | 59 | # to have it show up; the colophon quietly skips itself if the file is missing. |
| 60 | avatar = "images/avatar.jpg" | 60 | avatar = "images/avatar.jpg" |
| 61 | -avatarAlt = "mykiwi's GitHub avatar" | 61 | +avatarAlt = "mykiwi's avatar" |
| 62 | firstName = "Romain" | 62 | firstName = "Romain" |
| 63 | lastName = "GAUTIER" | 63 | lastName = "GAUTIER" |
| 64 | atprotoHandle = 'mykiwi.dev' | 64 | atprotoHandle = 'mykiwi.dev' |
| 65 | # Markdown, rendered as-is in the colophon (links work: [text](url)). Also | 65 | # Markdown, rendered as-is in the colophon (links work: [text](url)). Also |
| 66 | # the source for the <meta name="description"> tag, unless params.description | 66 | # the source for the <meta name="description"> tag, unless params.description |
| 67 | # above is set to override it. | 67 | # above is set to override it. |
| 68 | -bio = "I'm a software engineer, passionate about technology, sharing knowledge and open source like #PHP, #Symfony, #Docker, #Dagger, #Nix and atproto:// Also [🐕️ & 🐈️](https://instagram.com/apy.leon \"Pictures of Apy & Léon\") dad." | 68 | +bio = "I'm a software engineer, passionate about technology, sharing knowledge and open source like #PHP, #Symfony, #Docker, #Dagger, #Nix, atproto:// and ATmosphere. Also [🐕️ & 🐈️](https://instagram.com/apy.leon \"Pictures of Apy & Léon\") dad." |
| 69 | 69 | ||
| 70 | [[params.profile.links]] | 70 | [[params.profile.links]] |
| 71 | key = "Tweets on ATmosphere" | 71 | key = "Tweets on ATmosphere" |
| @@ -79,7 +79,7 @@ url = "https://mamot.fr/@mykiwi" | |||
| 79 | 79 | ||
| 80 | [[params.profile.links]] | 80 | [[params.profile.links]] |
| 81 | key = "Tweets on Twitter" | 81 | key = "Tweets on Twitter" |
| 82 | -value = "@mykiwi" | 82 | +value = "@my_kiwi" |
| 83 | url = "https://x.com/mykiwi" | 83 | url = "https://x.com/mykiwi" |
| 84 | 84 | ||
| 85 | [[params.profile.links]] | 85 | [[params.profile.links]] |
modified
blog/layouts/_default/baseof.html +15 -4 | @@ -28,10 +28,17 @@ | ||
| 28 | 28 | <!-- Fonts are self-hosted, so there is no third-party DNS + TLS before first paint. --> |
| 29 | 29 | <link rel="preload" href="/fonts/Newsreader-latin.woff2" as="font" type="font/woff2" crossorigin> |
| 30 | 30 | <link rel="preload" href="/fonts/JetBrainsMono-Regular.woff2" as="font" type="font/woff2" crossorigin> |
| 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 | + */}} | |
| 31 | 38 | {{ with resources.Get "css/style.scss" }} |
| 32 | 39 | {{ $opts := dict "transpiler" "dartsass" "outputStyle" "compressed" }} |
| 33 | - {{ with . | toCSS $opts }} | |
| 34 | - <link rel="stylesheet" type="text/css" href="{{ .RelPermalink }}"> | |
| 40 | + {{ with . | toCSS $opts | fingerprint }} | |
| 41 | + <link rel="stylesheet" type="text/css" href="{{ .RelPermalink }}" integrity="{{ .Data.Integrity }}"> | |
| 35 | 42 | {{ end }} |
| 36 | 43 | {{ end }} |
| 37 | 44 | |
| @@ -58,7 +65,9 @@ | ||
| 58 | 65 | press. |
| 59 | 66 | */}} |
| 60 | 67 | <meta name="htmx-config" content="preload.boostEvent:mouseover"> |
| 61 | - <script src="{{ "js/htmx.min.js" | relURL }}" defer></script> | |
| 68 | + {{ with resources.Get "js/htmx.min.js" | fingerprint }} | |
| 69 | + <script src="{{ .RelPermalink }}" integrity="{{ .Data.Integrity }}" defer></script> | |
| 70 | + {{ end }} | |
| 62 | 71 | {{- /* |
| 63 | 72 | Official htmx extension, vendored from the same htmx.org package as |
| 64 | 73 | htmx.min.js above (node_modules/htmx.org/dist/ext/hx-preload.min.js - |
| @@ -67,7 +76,9 @@ | ||
| 67 | 76 | Needs no hx-ext or other opt-in - just being loaded activates it for |
| 68 | 77 | every boosted anchor. |
| 69 | 78 | */}} |
| 70 | - <script src="{{ "js/hx-preload.min.js" | relURL }}" defer></script> | |
| 79 | + {{ with resources.Get "js/hx-preload.min.js" | fingerprint }} | |
| 80 | + <script src="{{ .RelPermalink }}" integrity="{{ .Data.Integrity }}" defer></script> | |
| 81 | + {{ end }} | |
| 71 | 82 | {{- /* |
| 72 | 83 | htmx's own browser back/forward handling (#restoreHistory) is a |
| 73 | 84 | separate code path from a boosted click - it calls htmx.ajax() directly |
| @@ -28,10 +28,17 @@ | |||
| 28 | <!-- Fonts are self-hosted, so there is no third-party DNS + TLS before first paint. --> | 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> | 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> | 30 | <link rel="preload" href="/fonts/JetBrainsMono-Regular.woff2" as="font" type="font/woff2" crossorigin> |
| 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 | + */}} | ||
| 31 | {{ with resources.Get "css/style.scss" }} | 38 | {{ with resources.Get "css/style.scss" }} |
| 32 | {{ $opts := dict "transpiler" "dartsass" "outputStyle" "compressed" }} | 39 | {{ $opts := dict "transpiler" "dartsass" "outputStyle" "compressed" }} |
| 33 | - {{ with . | toCSS $opts }} | 40 | + {{ with . | toCSS $opts | fingerprint }} |
| 34 | - <link rel="stylesheet" type="text/css" href="{{ .RelPermalink }}"> | 41 | + <link rel="stylesheet" type="text/css" href="{{ .RelPermalink }}" integrity="{{ .Data.Integrity }}"> |
| 35 | {{ end }} | 42 | {{ end }} |
| 36 | {{ end }} | 43 | {{ end }} |
| 37 | 44 | ||
| @@ -58,7 +65,9 @@ | |||
| 58 | press. | 65 | press. |
| 59 | */}} | 66 | */}} |
| 60 | <meta name="htmx-config" content="preload.boostEvent:mouseover"> | 67 | <meta name="htmx-config" content="preload.boostEvent:mouseover"> |
| 61 | - <script src="{{ "js/htmx.min.js" | relURL }}" defer></script> | 68 | + {{ with resources.Get "js/htmx.min.js" | fingerprint }} |
| 69 | + <script src="{{ .RelPermalink }}" integrity="{{ .Data.Integrity }}" defer></script> | ||
| 70 | + {{ end }} | ||
| 62 | {{- /* | 71 | {{- /* |
| 63 | Official htmx extension, vendored from the same htmx.org package as | 72 | Official htmx extension, vendored from the same htmx.org package as |
| 64 | htmx.min.js above (node_modules/htmx.org/dist/ext/hx-preload.min.js - | 73 | htmx.min.js above (node_modules/htmx.org/dist/ext/hx-preload.min.js - |
| @@ -67,7 +76,9 @@ | |||
| 67 | Needs no hx-ext or other opt-in - just being loaded activates it for | 76 | Needs no hx-ext or other opt-in - just being loaded activates it for |
| 68 | every boosted anchor. | 77 | every boosted anchor. |
| 69 | */}} | 78 | */}} |
| 70 | - <script src="{{ "js/hx-preload.min.js" | relURL }}" defer></script> | 79 | + {{ with resources.Get "js/hx-preload.min.js" | fingerprint }} |
| 80 | + <script src="{{ .RelPermalink }}" integrity="{{ .Data.Integrity }}" defer></script> | ||
| 81 | + {{ end }} | ||
| 71 | {{- /* | 82 | {{- /* |
| 72 | htmx's own browser back/forward handling (#restoreHistory) is a | 83 | htmx's own browser back/forward handling (#restoreHistory) is a |
| 73 | separate code path from a boosted click - it calls htmx.ajax() directly | 84 | separate code path from a boosted click - it calls htmx.ajax() directly |
modified
blog/layouts/partials/colophon.html +8 -9 | @@ -9,21 +9,20 @@ | ||
| 9 | 9 | {{- with .avatar }} |
| 10 | 10 | {{- with resources.Get . }} |
| 11 | 11 | {{- /* |
| 12 | - avatar-blurhash.png is a 16x16 decode of the avatar's blurhash | |
| 13 | - (oZL;Z^4m?wng-PoL~XNbOsShRjNGXTxt={xtM{xZo$t8%1a$IVRk%Nxut6W;IoWBj]V[V@RjR+s.), | |
| 14 | - shown via CSS while the real photo loads - | |
| 12 | + Inlined (not a separate resources.Get file) so it paints with the | |
| 13 | + first HTML response instead of racing the real photo over the | |
| 14 | + network. 4x4 decode of the avatar's blurhash | |
| 15 | + (oZL;Z^4m?wng-PoL~XNbOsShRjNGXTxt={xtM{xZo$t8%1a$IVRk%Nxut6W;IoWBj]V[V@RjR+s.) - | |
| 15 | 16 | regenerate it from a new avatar with a blurhash encoder/decoder if |
| 16 | 17 | the avatar ever changes. |
| 17 | 18 | */}} |
| 18 | 19 | <img |
| 19 | - class="colophon__avatar" | |
| 20 | - src="{{ .RelPermalink }}" | |
| 20 | + alt="{{ $profile.avatarAlt | default site.Title }}" | |
| 21 | + style="background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAIAAAAmkwkpAAAAP0lEQVR42gE0AMv/AO3r6u7v+6Sco//9/wDLyqd5hnfju4PHx8QAsLOita+p0bOOycTFALuTaCBoRoeDRaKsnq0HINP5xdMWAAAAAElFTkSuQmCC')" | |
| 21 | 22 | width="{{ .Width }}" |
| 22 | 23 | height="{{ .Height }}" |
| 23 | - alt="{{ $profile.avatarAlt | default site.Title }}" | |
| 24 | - {{- with resources.Get "images/avatar-blurhash.png" }} | |
| 25 | - style="background-image:url('{{ .RelPermalink }}')" | |
| 26 | - {{- end }} | |
| 24 | + class="colophon__avatar" | |
| 25 | + src="{{ .RelPermalink }}" | |
| 27 | 26 | > |
| 28 | 27 | {{- end }} |
| 29 | 28 | {{- end }} |
| @@ -9,21 +9,20 @@ | |||
| 9 | {{- with .avatar }} | 9 | {{- with .avatar }} |
| 10 | {{- with resources.Get . }} | 10 | {{- with resources.Get . }} |
| 11 | {{- /* | 11 | {{- /* |
| 12 | - avatar-blurhash.png is a 16x16 decode of the avatar's blurhash | 12 | + Inlined (not a separate resources.Get file) so it paints with the |
| 13 | - (oZL;Z^4m?wng-PoL~XNbOsShRjNGXTxt={xtM{xZo$t8%1a$IVRk%Nxut6W;IoWBj]V[V@RjR+s.), | 13 | + first HTML response instead of racing the real photo over the |
| 14 | - shown via CSS while the real photo loads - | 14 | + network. 4x4 decode of the avatar's blurhash |
| 15 | + (oZL;Z^4m?wng-PoL~XNbOsShRjNGXTxt={xtM{xZo$t8%1a$IVRk%Nxut6W;IoWBj]V[V@RjR+s.) - | ||
| 15 | regenerate it from a new avatar with a blurhash encoder/decoder if | 16 | regenerate it from a new avatar with a blurhash encoder/decoder if |
| 16 | the avatar ever changes. | 17 | the avatar ever changes. |
| 17 | */}} | 18 | */}} |
| 18 | <img | 19 | <img |
| 19 | - class="colophon__avatar" | 20 | + alt="{{ $profile.avatarAlt | default site.Title }}" |
| 20 | - src="{{ .RelPermalink }}" | 21 | + style="background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAIAAAAmkwkpAAAAP0lEQVR42gE0AMv/AO3r6u7v+6Sco//9/wDLyqd5hnfju4PHx8QAsLOita+p0bOOycTFALuTaCBoRoeDRaKsnq0HINP5xdMWAAAAAElFTkSuQmCC')" |
| 21 | width="{{ .Width }}" | 22 | width="{{ .Width }}" |
| 22 | height="{{ .Height }}" | 23 | height="{{ .Height }}" |
| 23 | - alt="{{ $profile.avatarAlt | default site.Title }}" | 24 | + class="colophon__avatar" |
| 24 | - {{- with resources.Get "images/avatar-blurhash.png" }} | 25 | + src="{{ .RelPermalink }}" |
| 25 | - style="background-image:url('{{ .RelPermalink }}')" | ||
| 26 | - {{- end }} | ||
| 27 | > | 26 | > |
| 28 | {{- end }} | 27 | {{- end }} |
| 29 | {{- end }} | 28 | {{- end }} |
modified
blog/layouts/partials/site-foot.html +15 -8 | @@ -11,21 +11,28 @@ | ||
| 11 | 11 | {{ i18n "rssLabel" }} |
| 12 | 12 | </a> |
| 13 | 13 | </span> |
| 14 | - {{ with site.Params.nixstorepath }} | |
| 15 | - <span class="store-path-row"> | |
| 16 | - <code class="store-path" title="This site was built by Nix">{{ . }}</code> | |
| 17 | - </span> | |
| 18 | - {{ end }} | |
| 19 | 14 | {{- $translations := .AllTranslations }} |
| 20 | 15 | {{- if gt (len $translations) 1 }} |
| 21 | 16 | <span class="lang-switch"> |
| 17 | + {{- /* | |
| 18 | + "Turnstile Language Icon" by Farhat Datta (A' Design Award 2011, | |
| 19 | + languageicon.org), traced coordinates rounded to 2 decimals - already | |
| 20 | + single-color, so fill:currentColor (in CSS, not per-path) is enough | |
| 21 | + to match the RSS icon's convention. Sized larger than that icon (see | |
| 22 | + .lang-switch svg) since its finer detail (the two characters, the | |
| 23 | + arrow) turns to mush at the RSS icon's ~0.85em. | |
| 24 | + */}} | |
| 25 | + <svg viewBox="0 0 841.89 595.28" aria-hidden="true"><polygon points="512.55,488.24 542.37,537.3 558.09,491.72 "/><path d="M277.8,213.22c-1.12-1.1,1.46,8.97,5.04,12.59 c6.35,6.41,11.31,7.23,13.95,7.34c5.84,0.23,13.06-1.46,17.34-3.25 c4.14-1.77,11.4-5.47,14.15-10.88c0.58-1.16,2.17-3.1,1.17-7.89 c-0.76-3.69-3.11-4.98-5.97-4.78c-2.86,0.19-11.53,2.51-15.73,3.79 c-4.19,1.27-12.83,3.9-16.6,4.72 C287.4,215.68,279.12,214.48,277.8,213.22z"/><path d="M383.96,333.55c-1.66-0.6-35.96-14.81-40.83-17.14 c-3.98-1.91-13.74-6.04-18.33-7.91c12.93-19.94,21.09-34.98,22.18-37.28 c2.01-4.19,15.7-30.98,16.02-32.62c0.31-1.67,0.7-7.84,0.4-9.31 c-0.3-1.5-5.32,1.38-12.13,3.69c-6.82,2.3-19.79,10.74-24.8,11.79 c-5.03,1.05-21.09,7.13-29.32,9.86c-8.22,2.73-23.77,7.47-30.17,9.2 c-6.41,1.73-12,1.86-15.58,2.95c0,0,0.48,5.02,1.43,6.52 c0.94,1.5,4.33,5.19,8.27,6.22c3.94,1.04,10.46,0.62,13.44-0.06 c2.97-0.69,8.11-3.2,8.8-4.3c0.7-1.12-0.36-4.55,0.81-5.59 c1.19-1.03,16.84-4.69,22.75-6.47c5.91-1.82,28.54-9.61,31.61-9.21 c-0.97,3.22-19.17,39.28-25.04,50.03 c-5.86,10.75-39.93,58.07-47.18,66.41 c-5.5,6.34-18.84,22.56-23.46,26.22c1.17,0.32,9.42-0.39,10.93-1.32 c9.38-5.78,25-25.22,30.03-31.14 c14.95-17.53,28.08-35.95,38.5-51.75h0.01 c2.03,0.85,18.43,14.21,22.71,17.17c4.28,2.96,21.17,12.39,24.83,13.95 c3.66,1.58,17.72,8.07,18.32,5.87 C388.76,347.12,385.62,334.18,383.96,333.55z"/><path fill-rule="evenodd" clip-rule="evenodd" d="M304.69,512.21 c3.29,2.01,6.39,3.65,9.86,5.3c6.94,3.47,14.79,7.12,22.28,9.86 c10.23,3.83,20.45,6.94,30.68,9.31c5.66,1.28,11.87,2.37,17.9,3.29 c0.55,0,16.8,2.01,20.09,2.01h16.44c6.39-0.55,12.42-0.91,18.81-1.83 c5.11-0.73,10.77-1.64,16.25-2.92c4.02-0.91,8.22-1.83,12.24-3.1 c3.83-1.1,8.22-2.56,12.42-4.02c2.74-0.91,5.66-2.19,8.58-3.29 c2.37-1.1,5.3-2.37,8.04-3.47c3.29-1.46,7.12-3.47,10.77-5.3 c2.92-1.46,6.21-3.29,9.31-5.11c2.37-1.28,7.85-5.48,10.77-5.48 c3.29,0,5.48,2.92,5.48,5.48c0,5.3-7.12,6.94-10.41,9.31 c-3.47,2.37-7.67,4.2-11.32,6.21c-7.3,3.83-14.79,7.12-21.91,9.86 c-9.31,3.47-19.54,6.76-28.67,8.95c-3.47,0.73-6.94,1.64-10.41,2.19 c-1.83,0.37-20.82,3.29-26.11,3.29h-24.11 c-6.39-0.55-13.15-1.28-19.54-2.19c-5.66-0.91-11.69-2.01-17.35-3.29 c-4.38-0.91-9.13-2.19-13.33-3.47c-7.3-2.01-14.43-4.57-21.37-7.3 c-12.6-4.75-25.75-10.96-38.17-19.17c-2.19-1.46-2.37-2.92-2.37-4.57 c0-2.74,2.01-5.3,5.3-5.3C297.75,507.47,303.59,511.67,304.69,512.21z"/><g><path d="M639.84,180.4l-40.77-12.98V48.36 c0-3.47-2.56-5.84-5.84-5.84c-2.56,0-84.92,28.31-91.49,30.68 c-22.33,7.44-86.8,29.73-86.8,29.73l-0,0 c-1.03,0.3-2.64,0.79-4.74,1.45L252.85,48.85 c-1.19-0.42-2.43,0.46-2.43,1.72v2v104.72 c-24.41,8.17-41.81,14.02-42.7,14.34c-1.64,0.55-4.2,0.91-5.66,2.92 c-0.73,0.73-0.91,2.01-1.28,2.92V484.46c0,0.37,0.18,0.55,0.18,0.73 c1.1,2.37,3.1,3.84,5.3,3.84c2.74,0,208.37-69.03,212.93-70.86 c0.22-0.07,0.46-0.24,0.7-0.44l218.84,69.75 c1.18,0.38,2.38-0.5,2.38-1.74V182.14 C641.11,181.35,640.6,180.64,639.84,180.4z M410.97,409.4l-199.05,66.29 V182.04l199.05-66.29V409.4z M587.93,55.67v108.21l-164.49-52.35 L587.93,55.67z M567.69,385.27l-10.52-38.43l-60.52-18.34l-13.01,31.3 l-29.29-8.89l62.18-152.59l28.51,8.64l51.94,187.19L567.69,385.27z"/><polygon points="507.84,300.38 547.77,312.48 529.56,247.88 "/></g></svg> | |
| 22 | 26 | {{- range sort $translations "Language.Weight" }} |
| 23 | - {{- if eq .Lang $.Lang }} | |
| 24 | - <span class="lang-switch__current">{{ .Language.Label }}</span> | |
| 25 | - {{- else }} | |
| 27 | + {{- if ne .Lang $.Lang }} | |
| 26 | 28 | <a href="{{ .Permalink }}" hreflang="{{ .Lang }}" hx-boost="false">{{ .Language.Label }}</a> |
| 27 | 29 | {{- end }} |
| 28 | 30 | {{- end }} |
| 29 | 31 | </span> |
| 30 | 32 | {{- end }} |
| 33 | + {{ with site.Params.nixstorepath }} | |
| 34 | + <span class="store-path-row"> | |
| 35 | + <code class="store-path" title="This site was built by Nix">{{ . }}</code> | |
| 36 | + </span> | |
| 37 | + {{ end }} | |
| 31 | 38 | </footer> |
| @@ -11,21 +11,28 @@ | |||
| 11 | {{ i18n "rssLabel" }} | 11 | {{ i18n "rssLabel" }} |
| 12 | </a> | 12 | </a> |
| 13 | </span> | 13 | </span> |
| 14 | - {{ with site.Params.nixstorepath }} | ||
| 15 | - <span class="store-path-row"> | ||
| 16 | - <code class="store-path" title="This site was built by Nix">{{ . }}</code> | ||
| 17 | - </span> | ||
| 18 | - {{ end }} | ||
| 19 | {{- $translations := .AllTranslations }} | 14 | {{- $translations := .AllTranslations }} |
| 20 | {{- if gt (len $translations) 1 }} | 15 | {{- if gt (len $translations) 1 }} |
| 21 | <span class="lang-switch"> | 16 | <span class="lang-switch"> |
| 17 | + {{- /* | ||
| 18 | + "Turnstile Language Icon" by Farhat Datta (A' Design Award 2011, | ||
| 19 | + languageicon.org), traced coordinates rounded to 2 decimals - already | ||
| 20 | + single-color, so fill:currentColor (in CSS, not per-path) is enough | ||
| 21 | + to match the RSS icon's convention. Sized larger than that icon (see | ||
| 22 | + .lang-switch svg) since its finer detail (the two characters, the | ||
| 23 | + arrow) turns to mush at the RSS icon's ~0.85em. | ||
| 24 | + */}} | ||
| 25 | + <svg viewBox="0 0 841.89 595.28" aria-hidden="true"><polygon points="512.55,488.24 542.37,537.3 558.09,491.72 "/><path d="M277.8,213.22c-1.12-1.1,1.46,8.97,5.04,12.59 c6.35,6.41,11.31,7.23,13.95,7.34c5.84,0.23,13.06-1.46,17.34-3.25 c4.14-1.77,11.4-5.47,14.15-10.88c0.58-1.16,2.17-3.1,1.17-7.89 c-0.76-3.69-3.11-4.98-5.97-4.78c-2.86,0.19-11.53,2.51-15.73,3.79 c-4.19,1.27-12.83,3.9-16.6,4.72 C287.4,215.68,279.12,214.48,277.8,213.22z"/><path d="M383.96,333.55c-1.66-0.6-35.96-14.81-40.83-17.14 c-3.98-1.91-13.74-6.04-18.33-7.91c12.93-19.94,21.09-34.98,22.18-37.28 c2.01-4.19,15.7-30.98,16.02-32.62c0.31-1.67,0.7-7.84,0.4-9.31 c-0.3-1.5-5.32,1.38-12.13,3.69c-6.82,2.3-19.79,10.74-24.8,11.79 c-5.03,1.05-21.09,7.13-29.32,9.86c-8.22,2.73-23.77,7.47-30.17,9.2 c-6.41,1.73-12,1.86-15.58,2.95c0,0,0.48,5.02,1.43,6.52 c0.94,1.5,4.33,5.19,8.27,6.22c3.94,1.04,10.46,0.62,13.44-0.06 c2.97-0.69,8.11-3.2,8.8-4.3c0.7-1.12-0.36-4.55,0.81-5.59 c1.19-1.03,16.84-4.69,22.75-6.47c5.91-1.82,28.54-9.61,31.61-9.21 c-0.97,3.22-19.17,39.28-25.04,50.03 c-5.86,10.75-39.93,58.07-47.18,66.41 c-5.5,6.34-18.84,22.56-23.46,26.22c1.17,0.32,9.42-0.39,10.93-1.32 c9.38-5.78,25-25.22,30.03-31.14 c14.95-17.53,28.08-35.95,38.5-51.75h0.01 c2.03,0.85,18.43,14.21,22.71,17.17c4.28,2.96,21.17,12.39,24.83,13.95 c3.66,1.58,17.72,8.07,18.32,5.87 C388.76,347.12,385.62,334.18,383.96,333.55z"/><path fill-rule="evenodd" clip-rule="evenodd" d="M304.69,512.21 c3.29,2.01,6.39,3.65,9.86,5.3c6.94,3.47,14.79,7.12,22.28,9.86 c10.23,3.83,20.45,6.94,30.68,9.31c5.66,1.28,11.87,2.37,17.9,3.29 c0.55,0,16.8,2.01,20.09,2.01h16.44c6.39-0.55,12.42-0.91,18.81-1.83 c5.11-0.73,10.77-1.64,16.25-2.92c4.02-0.91,8.22-1.83,12.24-3.1 c3.83-1.1,8.22-2.56,12.42-4.02c2.74-0.91,5.66-2.19,8.58-3.29 c2.37-1.1,5.3-2.37,8.04-3.47c3.29-1.46,7.12-3.47,10.77-5.3 c2.92-1.46,6.21-3.29,9.31-5.11c2.37-1.28,7.85-5.48,10.77-5.48 c3.29,0,5.48,2.92,5.48,5.48c0,5.3-7.12,6.94-10.41,9.31 c-3.47,2.37-7.67,4.2-11.32,6.21c-7.3,3.83-14.79,7.12-21.91,9.86 c-9.31,3.47-19.54,6.76-28.67,8.95c-3.47,0.73-6.94,1.64-10.41,2.19 c-1.83,0.37-20.82,3.29-26.11,3.29h-24.11 c-6.39-0.55-13.15-1.28-19.54-2.19c-5.66-0.91-11.69-2.01-17.35-3.29 c-4.38-0.91-9.13-2.19-13.33-3.47c-7.3-2.01-14.43-4.57-21.37-7.3 c-12.6-4.75-25.75-10.96-38.17-19.17c-2.19-1.46-2.37-2.92-2.37-4.57 c0-2.74,2.01-5.3,5.3-5.3C297.75,507.47,303.59,511.67,304.69,512.21z"/><g><path d="M639.84,180.4l-40.77-12.98V48.36 c0-3.47-2.56-5.84-5.84-5.84c-2.56,0-84.92,28.31-91.49,30.68 c-22.33,7.44-86.8,29.73-86.8,29.73l-0,0 c-1.03,0.3-2.64,0.79-4.74,1.45L252.85,48.85 c-1.19-0.42-2.43,0.46-2.43,1.72v2v104.72 c-24.41,8.17-41.81,14.02-42.7,14.34c-1.64,0.55-4.2,0.91-5.66,2.92 c-0.73,0.73-0.91,2.01-1.28,2.92V484.46c0,0.37,0.18,0.55,0.18,0.73 c1.1,2.37,3.1,3.84,5.3,3.84c2.74,0,208.37-69.03,212.93-70.86 c0.22-0.07,0.46-0.24,0.7-0.44l218.84,69.75 c1.18,0.38,2.38-0.5,2.38-1.74V182.14 C641.11,181.35,640.6,180.64,639.84,180.4z M410.97,409.4l-199.05,66.29 V182.04l199.05-66.29V409.4z M587.93,55.67v108.21l-164.49-52.35 L587.93,55.67z M567.69,385.27l-10.52-38.43l-60.52-18.34l-13.01,31.3 l-29.29-8.89l62.18-152.59l28.51,8.64l51.94,187.19L567.69,385.27z"/><polygon points="507.84,300.38 547.77,312.48 529.56,247.88 "/></g></svg> | ||
| 22 | {{- range sort $translations "Language.Weight" }} | 26 | {{- range sort $translations "Language.Weight" }} |
| 23 | - {{- if eq .Lang $.Lang }} | 27 | + {{- if ne .Lang $.Lang }} |
| 24 | - <span class="lang-switch__current">{{ .Language.Label }}</span> | ||
| 25 | - {{- else }} | ||
| 26 | <a href="{{ .Permalink }}" hreflang="{{ .Lang }}" hx-boost="false">{{ .Language.Label }}</a> | 28 | <a href="{{ .Permalink }}" hreflang="{{ .Lang }}" hx-boost="false">{{ .Language.Label }}</a> |
| 27 | {{- end }} | 29 | {{- end }} |
| 28 | {{- end }} | 30 | {{- end }} |
| 29 | </span> | 31 | </span> |
| 30 | {{- end }} | 32 | {{- end }} |
| 33 | + {{ with site.Params.nixstorepath }} | ||
| 34 | + <span class="store-path-row"> | ||
| 35 | + <code class="store-path" title="This site was built by Nix">{{ . }}</code> | ||
| 36 | + </span> | ||
| 37 | + {{ end }} | ||
| 31 | </footer> | 38 | </footer> |