✨ v3 theme: glass UI, landing layout, a11y, perf, print styles

Floating glass header with ⌘K search, landing homepage layout with
hero image and GitHub-stars CTA, background canvas with dissolving
heroes, burufugu colour scheme, movie-quote 404, taxonomy pills,
keyboard/ARIA accessibility across menus, search and tabs, idle-
loaded third-party scripts, and dedicated print styles.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Nuno CoraçãoandClaude Fable 5 authored and Nuno C. committed 2026-08-17 20:19:02 +01:00
1 parent 5b0cd34d5b
commit 09f0573749
47 files changed
+1098 -375

No files matched your search

+45 -7
View File
@@ -1,9 +1,47 @@
{{ define "main" }}
<h1 class="mb-3 text-4xl font-extrabold">{{ i18n "error.404_title" | emojify }}</h1>
<p class="mt-8 mb-12 text-neutral-400 dark:text-neutral-500">
{{ i18n "error.404_error" | emojify }}
</p>
<div class="prose dark:prose-invert">
<p>{{ i18n "error.404_description" | emojify }}</p>
</div>
<section class="relative isolate flex grow flex-col items-center justify-center overflow-hidden py-24 text-center">
<div
aria-hidden="true"
class="pointer-events-none absolute inset-0 -z-10 flex select-none items-center justify-center">
<span class="text-[16rem] font-black leading-none tracking-tighter text-neutral-900/5 dark:text-white/5 sm:text-[26rem]">404</span>
</div>
<p class="landing-rise mb-5 text-sm font-semibold uppercase tracking-[0.25em] text-primary-600 dark:text-primary-400">
{{ i18n "error.404_error" | emojify }}
</p>
<h1 class="landing-rise landing-rise-1 text-balance text-5xl font-black leading-[1.05] tracking-tighter text-neutral-900 dark:text-white sm:text-7xl">
{{ i18n "error.404_title" | emojify }}
</h1>
<figure class="landing-rise landing-rise-2 mx-auto mt-8 max-w-2xl px-6">
<blockquote>
<p id="quote-404-line" class="text-balance text-xl italic leading-8 text-neutral-600 dark:text-neutral-300 sm:text-2xl sm:leading-9">
{{ i18n "error.404_description" | emojify }}
</p>
</blockquote>
<figcaption
id="quote-404-source"
class="mt-3 hidden text-sm font-semibold uppercase tracking-[0.15em] text-neutral-400 dark:text-neutral-500"></figcaption>
</figure>
<div class="landing-rise landing-rise-3 mt-10">
{{ partial "cta-button.html" (dict "url" site.Home.RelPermalink "label" (i18n "error.404_home")) }}
</div>
{{ with hugo.Data.quotes404 }}
{{ $json := . | jsonify }}
<script>
(function () {
var quotes = JSON.parse({{ $json }});
if (!quotes.length) return;
var pick = quotes[Math.floor(Math.random() * quotes.length)];
var line = document.getElementById("quote-404-line");
var source = document.getElementById("quote-404-source");
line.textContent = "“" + pick.line + "”";
source.textContent = "— " + pick.source;
source.classList.remove("hidden");
})();
</script>
{{ end }}
</section>
{{ end }}
+48 -11
View File
@@ -39,18 +39,55 @@
{{- partial "search.html" . -}}
{{ end }}
</div>
{{/* Rendered after the main block so page heroes that paint their own
fixed background can suppress the site-wide canvas for their page.
The layer is fixed, so its position in the DOM has no visual effect. */}}
{{ if and (site.Params.backgroundCanvas | default false) (not (.Store.Get "suppressBackgroundCanvas")) }}
{{ $default := site.Store.Get "defaultBackgroundImage" }}
{{ $canvasURL := $default.url }}
{{ if not $canvasURL }}
{{ with $default.obj }}{{ $canvasURL = .RelPermalink }}{{ end }}
{{ end }}
{{ with $canvasURL }}
<div id="background-canvas" aria-hidden="true" class="pointer-events-none fixed inset-0 -z-20">
<img
src="{{ . }}"
role="presentation"
loading="eager"
decoding="async"
class="nozoom h-full w-full object-cover" />
<div class="absolute inset-0 bg-neutral/75 dark:bg-neutral-800/60"></div>
</div>
{{ end }}
{{ end }}
</body>
{{ if site.Params.buymeacoffee.globalWidget | default false }}
<script
data-name="BMC-Widget"
data-cfasync="false"
src="https://cdnjs.buymeacoffee.com/1.0.0/widget.prod.min.js"
data-id="{{ site.Params.buymeacoffee.identifier }}"
data-description="Support me on Buy me a coffee!"
{{ with site.Params.buymeacoffee.globalWidgetMessage }}data-message="{{ . }}"{{ end }}
{{ with site.Params.buymeacoffee.globalWidgetColor | default `#FFDD00` }}data-color="{{ . }}"{{ end }}
{{ with site.Params.buymeacoffee.globalWidgetPosition }}data-position="{{ . }}"{{ end }}
data-x_margin="18"
data-y_margin="18"></script>
{{/* Inject the third-party widget after the page is idle so it never
competes with rendering-critical resources. */}}
<script>
(function () {
var load = function () {
var s = document.createElement("script");
s.src = "https://cdnjs.buymeacoffee.com/1.0.0/widget.prod.min.js";
s.setAttribute("data-name", "BMC-Widget");
s.setAttribute("data-cfasync", "false");
s.setAttribute("data-id", {{ site.Params.buymeacoffee.identifier }});
s.setAttribute("data-description", "Support me on Buy me a coffee!");
{{ with site.Params.buymeacoffee.globalWidgetMessage }}s.setAttribute("data-message", {{ . }});{{ end }}
s.setAttribute("data-color", {{ site.Params.buymeacoffee.globalWidgetColor | default `#FFDD00` }});
{{ with site.Params.buymeacoffee.globalWidgetPosition }}s.setAttribute("data-position", {{ . }});{{ end }}
s.setAttribute("data-x_margin", "18");
s.setAttribute("data-y_margin", "18");
document.body.appendChild(s);
};
if ("requestIdleCallback" in window) {
requestIdleCallback(load, { timeout: 4000 });
} else {
window.addEventListener("load", function () {
setTimeout(load, 1500);
});
}
})();
</script>
{{ end }}
</html>
+1 -1
View File
@@ -28,7 +28,7 @@
{{/* Description (markdown content) */}}
{{ $tocMargin := cond $showToc "mt-12" "mt-0" }}
{{ $topClass := cond (hasPrefix site.Params.header.layout "fixed") "lg:top-[140px]" "lg:top-10" }}
{{ $topClass := cond (or (hasPrefix site.Params.header.layout "fixed") (eq site.Params.header.layout "floating")) "lg:top-[140px]" "lg:top-10" }}
<section class="{{ $tocMargin }} prose flex max-w-full flex-col dark:prose-invert lg:flex-row mb-10">
{{ if $showToc }}
<div class="order-first lg:ms-auto px-0 lg:order-last lg:ps-8 lg:max-w-2xs">
+1 -1
View File
@@ -41,7 +41,7 @@
{{ $enableToc := site.Params.article.showTableOfContents | default false }}
{{ $enableToc = .Params.showTableOfContents | default $enableToc }}
{{ $showToc := and $enableToc (in .TableOfContents "<ul") }}
{{ $topClass := cond (hasPrefix site.Params.header.layout "fixed") "lg:top-[140px]" "lg:top-10" }}
{{ $topClass := cond (or (hasPrefix site.Params.header.layout "fixed") (eq site.Params.header.layout "floating")) "lg:top-[140px]" "lg:top-10" }}
{{ if $showToc }}
<div class="order-first lg:ms-auto px-0 lg:order-last lg:ps-8 lg:max-w-2xs">
<div class="toc ps-5 print:hidden lg:sticky {{ $topClass }}">
+1 -1
View File
@@ -36,7 +36,7 @@
</section>
{{ else }}
<section class="flex flex-wrap max-w-prose -mx-2 overflow-hidden">
<section class="mt-6 flex max-w-prose flex-wrap gap-2">
{{ range .Data.Terms }}
{{ partial "term-link/text.html" . }}
{{ end }}
-2
View File
@@ -1,8 +1,6 @@
{{ define "main" }}
{{ if .Site.Params.homepage.layoutSwitcher | default false }}
{{ partial "home/layout-switcher.html" . }}
{{ $jsHome := resources.Get "js/home.js" | resources.Minify | resources.Fingerprint (.Site.Params.fingerprintAlgorithm | default "sha512") }}
<script defer type="text/javascript" src="{{ $jsHome.RelPermalink }}" integrity="{{ $jsHome.Data.Integrity }}"></script>
{{ else }}
{{ $partial := print "home/" .Site.Params.homepage.layout ".html" }}
{{ if templates.Exists ( printf "partials/%s" $partial ) }}
@@ -98,7 +98,7 @@
loading="lazy"
decoding="async"
fetchpriority="low"
class="not-prose absolute inset-0 h-full w-full object-cover transition duration-500 {{ if $featureImageHover }}group-hover:scale-105{{ end }}"
class="not-prose absolute inset-0 h-full w-full object-cover transition-transform duration-700 ease-[cubic-bezier(0.22,1,0.36,1)] {{ if $featureImageHover }}group-hover:scale-[1.03]{{ end }}"
{{ with $featureImagePosition }}style="object-position: {{ . }};"{{ end }} />
</div>
{{ end }}
@@ -74,7 +74,7 @@
loading="lazy"
decoding="async"
fetchpriority="low"
class="not-prose absolute inset-0 h-full w-full object-cover transition duration-500 {{ if $featureImageHover }}group-hover:scale-105{{ end }}"
class="not-prose absolute inset-0 h-full w-full object-cover transition-transform duration-700 ease-[cubic-bezier(0.22,1,0.36,1)] {{ if $featureImageHover }}group-hover:scale-[1.03]{{ end }}"
{{ with $featureImagePosition }}style="object-position: {{ . }};"{{ end }} />
</div>
{{ end }}
+2 -2
View File
@@ -67,7 +67,7 @@
<article
class="article-link--card group relative min-h-full min-w-full overflow-hidden rounded-lg border border-neutral-300 dark:border-neutral-600">
class="article-link--card group relative min-h-full min-w-full overflow-hidden rounded-lg border border-neutral-300/80 bg-white/60 backdrop-blur-xl dark:border-neutral-600/80 dark:bg-neutral-900/50">
{{ with $featuredURL }}
<div
class="flex-none relative overflow-hidden thumbnail_card"
@@ -78,7 +78,7 @@
loading="lazy"
decoding="async"
fetchpriority="low"
class="not-prose absolute inset-0 h-full w-full object-cover transition duration-500 {{ if $featureImageHover }}group-hover:scale-105{{ end }}"
class="not-prose absolute inset-0 h-full w-full object-cover transition-transform duration-700 ease-[cubic-bezier(0.22,1,0.36,1)] {{ if $featureImageHover }}group-hover:scale-[1.03]{{ end }}"
{{ with $featureImagePosition }}style="object-position: {{ . }};"{{ end }} />
</div>
{{ end }}
+1 -1
View File
@@ -91,7 +91,7 @@
loading="lazy"
decoding="async"
fetchpriority="low"
class="not-prose absolute inset-0 h-full w-full object-cover transition duration-500 {{ if $featureImageHover }}group-hover:scale-105{{ end }}"
class="not-prose absolute inset-0 h-full w-full object-cover transition-transform duration-700 ease-[cubic-bezier(0.22,1,0.36,1)] {{ if $featureImageHover }}group-hover:scale-[1.03]{{ end }}"
{{ with $featureImagePosition }}style="object-position: {{ . }};"{{ end }} />
</div>
{{ end }}
+1 -1
View File
@@ -24,7 +24,7 @@
title="{{ .username }} ({{ .contributions }} contributions)"
aria-label="Contributor: {{ .username }}">
<img
class="nozoom h-10 w-10 rounded-full transition-all group-hover:scale-110 group-hover:ring-2 group-hover:ring-primary-500"
class="nozoom h-10 w-10 rounded-full transition-all duration-300 ease-[cubic-bezier(0.22,1,0.36,1)] group-hover:scale-105 group-hover:ring-2 group-hover:ring-primary-500"
src="{{ .avatar_url }}"
alt="{{ .username }}"
width="40"
+35
View File
@@ -0,0 +1,35 @@
{{- $url := .url | default "#" -}}
{{- $label := .label | default "Learn more" -}}
{{- $style := .style | default "primary" -}}
{{- $github := .github -}}
{{- $stars := "" -}}
{{- with $github -}}
{{- with try (resources.GetRemote (printf "https://api.github.com/repos/%s" .)) -}}
{{- with .Value -}}
{{- with .Content | transform.Unmarshal -}}
{{- $count := int .stargazers_count -}}
{{- if ge $count 1000 -}}
{{- $stars = replace (printf "%.1fk" (div (float $count) 1000)) ".0k" "k" -}}
{{- else -}}
{{- $stars = printf "%d" $count -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
<a
class="not-prose inline-flex items-center gap-2 rounded-xl px-5 py-3 font-semibold transition focus-visible:outline-2 focus-visible:outline-offset-2 {{ if eq $style "outline" }}border border-neutral-300 bg-white text-neutral-800 hover:border-primary-400 hover:text-primary-600 dark:border-neutral-700 dark:bg-neutral-900 dark:text-neutral-100 dark:hover:text-primary-400{{ else }}bg-primary-600 text-white shadow-lg shadow-primary-500/20 hover:-translate-y-0.5 hover:bg-primary-500 focus-visible:outline-primary-600{{ end }}"
href="{{ $url | safeURL }}">
{{- if $github }}
{{ partial "icon.html" "github" }}
{{ end -}}
{{ $label | markdownify }}
{{- if $stars }}
<span class="inline-flex items-center gap-1 rounded-full border border-neutral-300/80 px-2 py-0.5 text-xs font-bold text-neutral-500 dark:border-neutral-600/80 dark:text-neutral-400">
{{ partial "icon.html" "star" }}
{{ $stars }}
</span>
{{- else }}
{{ partial "icon.html" "link" }}
{{- end }}
</a>
+1 -1
View File
@@ -12,7 +12,7 @@
{{ $navClass := printf "flex flex-row pb-4 text-base font-medium text-neutral-500 dark:text-neutral-400 %s" (cond $onlyIcon "overflow-x-auto py-2" "") }}
{{ $ulClass := printf "flex list-none %s" (cond $onlyIcon "flex-row" "flex-col sm:flex-row") }}
{{ $liClass := printf "flex mb-1 text-end sm:mb-0 sm:me-7 sm:last:me-0 %s" (cond $onlyIcon "me-4" "") }}
<nav class="{{ $navClass }}">
<nav aria-label="{{ i18n "nav.footer_menu" }}" class="{{ $navClass }}">
<ul class="{{ $ulClass }}">
{{ range .Site.Menus.footer }}
<li class=" {{ $liClass }}">
+2 -1
View File
@@ -156,7 +156,7 @@
{{ end }}
{{ if not .Site.Params.disableImageZoom | default true }}
{{ $zoomJS := resources.Get "lib/zoom/zoom.min.umd.js" | resources.Fingerprint $alg }}
<script src="{{ $zoomJS.RelPermalink }}" integrity="{{ $zoomJS.Data.Integrity }}"></script>
<script defer src="{{ $zoomJS.RelPermalink }}" integrity="{{ $zoomJS.Data.Integrity }}"></script>
{{ end }}
{{ $showReadingProgress := site.Params.article.showReadingProgress | default false }}
{{ if isset .Params "showreadingprogress" }}
@@ -189,6 +189,7 @@
{{ if .Page.HasShortcode "katex" }}
{{ $jsResources = $jsResources | append (resources.Get "js/katex-render.js") }}
{{ end }}
{{ $jsResources = $jsResources | append (resources.Get "js/menu-a11y.js") }}
{{ $jsResources = $jsResources | append (resources.Get "js/print-support.js") }}
{{ $jsResources = $jsResources | append (resources.Get "js/email.js") }}
{{ if $jsResources }}
@@ -1,4 +1,4 @@
<nav class="flex items-center gap-x-5 h-12">
<nav aria-label="{{ i18n "nav.main_menu" }}" class="flex items-center gap-x-5 h-12">
{{ if .Site.Menus.main }}
{{ range .Site.Menus.main }}
{{ template "DesktopMenu" . }}
@@ -10,23 +10,13 @@
{{ partial "header/components/a11y.html" (dict "prefix" "desktop-") }}
{{ end }}
{{ if .Site.Params.enableSearch | default false }}
<button
id="search-button"
aria-label="Search"
class="text-base bf-icon-color-hover"
title="{{ i18n "search.open_button_title" }}">
{{ partial "icon.html" "search" }}
</button>
{{ end }}
{{ if .Site.Params.footer.showAppearanceSwitcher | default false }}
<div class="flex items-center">
<button
id="appearance-switcher"
aria-label="{{ i18n "footer.dark_appearance" }}"
type="button"
class="text-base bf-icon-color-hover"
class="text-sm bf-icon-color-hover"
title="{{ i18n "footer.dark_appearance" }}">
<div class="flex items-center justify-center dark:hidden">
{{ partial "icon.html" "moon" }}
@@ -37,6 +27,22 @@
</button>
</div>
{{ end }}
{{ if .Site.Params.enableSearch | default false }}
<button
id="search-button"
aria-label="{{ i18n "search.open_button_title" }}"
class="flex items-center gap-2 rounded-lg border bf-border-color bf-border-color-hover bg-neutral-100/50 px-2 py-1 text-sm text-neutral-500 transition-colors hover:text-primary-600 dark:bg-neutral-800/50 dark:text-neutral-400 dark:hover:text-primary-400"
title="{{ i18n "search.open_button_title" }}">
{{ partial "icon.html" "search" }}
<kbd
id="search-shortcut-hint"
class="pointer-events-none font-sans text-xs text-neutral-400 dark:text-neutral-500"
aria-hidden="true">
⌘K
</kbd>
</button>
{{ end }}
</nav>
{{ define "DesktopMenu" }}
@@ -53,14 +59,16 @@
tabindex="0"
{{ end }}
{{ with or .Name .Pre }}aria-label="{{ . }}"{{ end }}
class="flex items-center text-base font-medium bf-icon-color-hover"
aria-haspopup="true"
aria-expanded="false"
class="flex items-center text-sm font-medium bf-icon-color-hover"
title="{{ .Title }}">
{{ if .Pre }}
<span {{ if and .Pre .Name }}class="mr-1"{{ end }}>
{{ partial "icon.html" .Pre }}
</span>
{{ end }}
<span class="text-bg font-bg break-normal" title="{{ .Title }}">
<span class="text-sm font-medium break-normal" title="{{ .Title }}">
{{ .Name | markdownify }}
</span>
<span>
@@ -69,7 +77,7 @@
</a>
</div>
<div class="menuhide">
<div class="pt-2 p-5 mt-2 rounded-xl backdrop-blur shadow-2xl bg-neutral/25 dark:bg-neutral-800/25">
<div class="pt-2 p-5 mt-2 rounded-xl border border-neutral-200/80 bg-neutral/95 shadow-2xl shadow-neutral-900/20 dark:border-neutral-700/80 dark:bg-neutral-800/95">
<div class="flex flex-col space-y-3">
{{ range .Children }}
<a
@@ -108,7 +116,7 @@
</span>
{{ end }}
{{ if .Name }}
<span class="text-base font-medium break-normal">
<span class="text-sm font-medium break-normal">
{{ .Name | markdownify }}
</span>
{{ end }}
@@ -1,15 +1,5 @@
{{ $mobileMenuStyle := .Site.Params.header.mobileMenuStyle | default "fullscreen" }}
<div class="relative flex items-center h-14 gap-4">
{{ if .Site.Params.enableSearch | default false }}
<button
id="search-button-mobile"
aria-label="Search"
class="flex items-center justify-center bf-icon-color-hover"
title="{{ i18n "search.open_button_title" }}">
{{ partial "icon.html" "search" }}
</button>
{{ end }}
{{ if .Site.Params.footer.showAppearanceSwitcher | default false }}
<button
id="appearance-switcher-mobile"
@@ -26,33 +16,59 @@
</button>
{{ end }}
{{ if .Site.Params.enableSearch | default false }}
<button
id="search-button-mobile"
aria-label="{{ i18n "search.open_button_title" }}"
class="flex items-center justify-center bf-icon-color-hover"
title="{{ i18n "search.open_button_title" }}">
{{ partial "icon.html" "search" }}
</button>
{{ end }}
{{ if or
.Site.Menus.main
.Site.Menus.subnavigation
.Site.Params.enableA11y
}}
<input type="checkbox" id="mobile-menu-toggle" autocomplete="off" class="hidden peer">
<label for="mobile-menu-toggle" class="flex items-center justify-center cursor-pointer bf-icon-color-hover">
<label
for="mobile-menu-toggle"
tabindex="0"
role="button"
aria-expanded="false"
aria-controls="mobile-menu-dialog"
aria-label="{{ i18n "nav.main_menu" }}"
class="flex items-center justify-center cursor-pointer bf-icon-color-hover">
{{ partial "icon.html" "bars" }}
</label>
<div
id="mobile-menu-dialog"
role="dialog"
aria-modal="true"
aria-label="{{ i18n "nav.main_menu" }}"
style="scrollbar-gutter: stable;"
class="z-50 invisible overflow-y-auto opacity-0 transition-[opacity,visibility] duration-300 peer-checked:visible peer-checked:opacity-100
{{ if eq $mobileMenuStyle "dropdown" }}
absolute end-0 top-[calc(100%+0.75rem)] w-[min(22rem,calc(100vw-1.5rem))] rounded-2xl border border-neutral-200/80 bg-neutral/95 p-5 shadow-2xl shadow-neutral-950/20 backdrop-blur-xl dark:border-neutral-700/80 dark:bg-neutral-800/95
absolute end-0 top-[calc(100%+0.75rem)] z-10 w-[min(22rem,calc(100vw-1.5rem))] rounded-2xl border border-neutral-200/80 bg-neutral/95 p-5 shadow-2xl shadow-neutral-900/20 dark:border-neutral-700/80 dark:bg-neutral-800/95
{{ else }}
fixed inset-0 px-6 py-20 bg-neutral-50/97 dark:bg-neutral-900/99
{{ end }}
{{ if site.Params.enableStyledScrollbar | default true }}bf-scrollbar{{ end }}">
<label
for="mobile-menu-toggle"
tabindex="0"
role="button"
aria-expanded="false"
aria-controls="mobile-menu-dialog"
class="{{ if eq $mobileMenuStyle "dropdown" }}absolute end-3 top-3 h-10 w-10 rounded-xl{{ else }}fixed end-8 top-5 z-50 h-12 w-12 rounded-full{{ end }} flex items-center justify-center cursor-pointer select-none bf-icon-color-hover border bf-border-color bf-border-color-hover bg-neutral-50 dark:bg-neutral-900">
<span class="sr-only">{{ i18n "nav.close_menu" }}</span>
{{ partial "icon.html" "xmark" }}
</label>
<nav class="{{ if eq $mobileMenuStyle "dropdown" }}mt-10 space-y-3{{ else }}mx-auto max-w-md space-y-6{{ end }}">
<nav
aria-label="{{ i18n "nav.mobile_menu" }}"
class="{{ if eq $mobileMenuStyle "dropdown" }}mt-10 space-y-3{{ else }}mx-auto max-w-md space-y-6{{ end }}">
{{ template "mobile-main-menu" . }}
{{ template "mobile-subnavigation" . }}
{{ template "mobile-footer-components" . }}
@@ -65,29 +81,35 @@
{{ range .Site.Menus.main }}
{{ $submenuId := printf "fullscreen-submenu-%s" (.Identifier | default .Name | anchorize) }}
<div class="px-2">
<a
href="{{ .URL }}"
{{ with or .Name .Pre }}aria-label="{{ . }}"{{ end }}
{{ if or (strings.HasPrefix .URL "http:" ) (strings.HasPrefix .URL "https:" ) }}
target="_blank"
{{ end }}
class="flex items-center gap-4 group bf-icon-color-hover text-neutral-700 dark:text-neutral-200">
{{ if .Pre }}
<span class="flex items-center justify-center h-8 w-8 text-2xl">
{{ partial "icon.html" .Pre }}
<div class="flex items-center gap-4">
<a
href="{{ .URL }}"
{{ with or .Name .Pre }}aria-label="{{ . }}"{{ end }}
{{ if or (strings.HasPrefix .URL "http:" ) (strings.HasPrefix .URL "https:" ) }}
target="_blank"
{{ end }}
class="flex grow items-center gap-4 group bf-icon-color-hover text-neutral-700 dark:text-neutral-200">
{{ if .Pre }}
<span class="flex items-center justify-center h-8 w-8 text-2xl">
{{ partial "icon.html" .Pre }}
</span>
{{ end }}
<span title="{{ .Title }}" class="text-2xl font-bold tracking-tight">
{{ .Name | markdownify }}
</span>
{{ end }}
<span title="{{ .Title }}" class="text-2xl font-bold tracking-tight">
{{ .Name | markdownify }}
</span>
</a>
{{ if .HasChildren }}
<label
for="{{ $submenuId }}"
tabindex="0"
role="button"
aria-expanded="true"
aria-label="{{ i18n "nav.toggle_submenu" }}"
class="ms-auto flex items-center justify-center h-10 w-10 cursor-pointer rounded-lg bf-icon-color-hover border bf-border-color bf-border-color-hover">
{{ partial "icon.html" "chevron-down" }}
</label>
{{ end }}
</a>
</div>
{{ if .HasChildren }}
<input checked type="checkbox" id="{{ $submenuId }}" autocomplete="off" class="hidden peer/full">
@@ -9,7 +9,7 @@
</span>
</button>
<ul class="menuhide">
<li class="rounded-xl backdrop-blur shadow-2xl p-2 flex flex-col gap-1">
<li class="rounded-xl border border-neutral-200/80 bg-neutral/95 shadow-2xl shadow-neutral-900/20 dark:border-neutral-700/80 dark:bg-neutral-800/95 p-2 flex flex-col gap-1">
{{ range .AllTranslations }}
<a href="{{ .RelPermalink }}" class="flex items-center bf-icon-color-hover px-3 py-1">
<span class="text-sm font-sm" title="{{ .Title }}">
+4 -4
View File
@@ -1,6 +1,6 @@
<div class="min-h-[104px]"></div>
<header class="fixed inset-x-0 top-3 z-100 px-3 sm:top-5 sm:px-6">
<div class="mx-auto max-w-7xl rounded-2xl border border-neutral-200/80 bg-neutral/85 px-3 shadow-lg shadow-neutral-950/5 backdrop-blur-xl dark:border-neutral-700/80 dark:bg-neutral-800/85">
<div class="min-h-32"></div>
<div class="fixed inset-x-0 top-3 z-100 px-3 sm:top-5 sm:px-6">
<div class="mx-auto max-w-7xl rounded-2xl border border-neutral-200/80 bg-neutral/60 px-3 shadow-lg shadow-neutral-900/5 backdrop-blur-xl dark:border-neutral-700/80 dark:bg-neutral-800/60 [&_img.logo]:max-h-10 [&_img.logo]:max-w-10">
{{ partial "header/basic.html" . }}
</div>
</header>
</div>
+13 -16
View File
@@ -25,7 +25,9 @@
{{ end }}
{{ end }}
{{ if not (or $featured $featuredURL) }}
{{/* When the site-wide background canvas is active it already renders the
default background image, so only fall back to it here when disabled. */}}
{{ if and (not (or $featured $featuredURL)) (not (site.Params.backgroundCanvas | default false)) }}
{{ $default := site.Store.Get "defaultBackgroundImage" }}
{{ if $default.url }}
{{ $featuredURL = $default.url }}
@@ -67,7 +69,9 @@
{{ if $shouldAddHeaderSpace | default true }}
<div id="hero" class="h-[150px] md:h-[200px]"></div>
{{ end }}
<div class="fixed inset-x-0 top-0 h-[800px] single_hero_background nozoom">
<div
id="hero-background"
class="hero-canvas-fade fixed inset-x-0 top-0 h-[800px] single_hero_background nozoom">
{{ $style := "" }}
{{ $defaultPosition := cond $useDefault site.Params.imagePosition false }}
{{ with $.Params.imagePosition | default $defaultPosition }}
@@ -82,24 +86,17 @@
fetchpriority="high"
class="absolute inset-0 w-full h-full object-cover"
{{ if $style }}style="{{ $style | safeCSS }}"{{ end }} />
<div
class="absolute inset-0 bg-gradient-to-t from-neutral dark:from-neutral-800 to-transparent mix-blend-normal"></div>
<div
class="absolute inset-0 opacity-60 bg-gradient-to-t from-neutral dark:from-neutral-800 to-neutral-100 dark:to-neutral-800 mix-blend-normal"></div>
<div class="absolute inset-0 bg-neutral/60 dark:bg-neutral-800/50"></div>
</div>
{{ if $shouldBlur | default false }}
<div
id="background-blur"
class="fixed opacity-0 inset-x-0 top-0 h-full single_hero_background nozoom backdrop-blur-xl bg-neutral-100/75 dark:bg-neutral-800/60"></div>
{{ $backgroundBlur := resources.Get "js/background-blur.js" }}
{{ $backgroundBlur = $backgroundBlur | resources.Minify | resources.Fingerprint ($.Site.Params.fingerprintAlgorithm | default "sha512") }}
{{ $heroFade := resources.Get "js/hero-scroll-fade.js" }}
{{ $heroFade = $heroFade | resources.Minify | resources.Fingerprint ($.Site.Params.fingerprintAlgorithm | default "sha512") }}
<script
type="text/javascript"
src="{{ $backgroundBlur.RelPermalink }}"
integrity="{{ $backgroundBlur.Data.Integrity }}"
data-blur-id="background-blur"
data-image-id="background-image"
data-image-url="{{ . }}"></script>
src="{{ $heroFade.RelPermalink }}"
integrity="{{ $heroFade.Data.Integrity }}"
data-hero-id="hero-background"
data-fade-distance="500"></script>
{{ end }}
{{- end -}}
@@ -6,7 +6,9 @@
{{ $background := $images.GetMatch "*background*" }}
{{ $backgroundURL := "" }}
{{ if not (or $background $backgroundURL) }}
{{/* When the site-wide background canvas is active it already renders the
default background image, so only fall back to it here when disabled. */}}
{{ if and (not (or $background $backgroundURL)) (not (site.Params.backgroundCanvas | default false)) }}
{{ $default := site.Store.Get "defaultBackgroundImage" }}
{{ if $default.url }}
{{ $backgroundURL = $default.url }}
@@ -102,6 +104,11 @@
{{ end }}
{{/* When the site-wide background canvas is active and this hero has no
background image of its own, skip the fixed backdrop so the canvas shows
through instead of the fade-to-neutral gradients. */}}
{{ if or $backgroundURL (not (site.Params.backgroundCanvas | default false)) }}
{{ $.Store.Set "suppressBackgroundCanvas" true }}
<div class="single_hero_background nozoom fixed inset-x-0 top-0 h-[800px]">
{{ with $backgroundURL }}
{{ $style := "" }}
@@ -124,6 +131,7 @@
<div
class="from-neutral to-neutral absolute inset-0 bg-gradient-to-t opacity-30 mix-blend-normal dark:from-neutral-800 dark:to-neutral-800 dark:opacity-60"></div>
</div>
{{ end }}
{{ if $shouldBlur | default false }}
<div
+8 -1
View File
@@ -4,7 +4,7 @@
<div class="absolute inset-x-0 bottom-0 h-1/2"></div>
<div class="mx-auto max-w-7xl p-0">
<div class="relative sm:overflow-hidden">
<div class="pointer-events-none fixed inset-x-0 top-0 z-0">
<div class="pointer-events-none fixed inset-x-0 top-0 -z-10">
{{ $useDefault := true }}
{{ $homepageImage := "" }}
{{ with .Site.Params.defaultBackgroundImage }}
@@ -23,7 +23,14 @@
{{ $useDefault = false }}
{{ end }}
{{ end }}
{{/* The site-wide background canvas already renders the default image. */}}
{{ if and $useDefault (site.Params.backgroundCanvas | default false) }}
{{ $homepageImage = "" }}
{{ end }}
{{ if $homepageImage }}
{{/* This layout paints its own fixed page background, so the
site-wide background canvas must not render behind it. */}}
{{ $.Store.Set "suppressBackgroundCanvas" true }}
{{ $style := "" }}
{{ $defaultPosition := cond $useDefault site.Params.imagePosition false }}
{{ with $.Params.imagePosition | default $defaultPosition }}
+43 -47
View File
@@ -1,50 +1,46 @@
<div class="relative pt-16 pb-32">
<div aria-hidden="true" class="absolute inset-x-0 top-0 h-48 bg-gradient-to-b"></div>
<div class="relative">
<div class="lg:mx-auto lg:grid lg:max-w-7xl lg:grid-flow-col-dense lg:grid-cols-2 lg:gap-24 lg:px-8">
<div class="mx-auto max-w-xl px-4 sm:px-6 lg:mx-0 lg:max-w-none lg:py-16 lg:px-0">
<article class="max-w-full prose dark:prose-invert">
{{ with .Title }}
<header>
<h1>{{ . | emojify }}</h1>
</header>
{{ end }}
<section class="w-full">{{ .Content }}</section>
</article>
</div>
<div class="mt-6 sm:mt-16 lg:mt-0 mx-auto max-w-xl px-4 sm:px-6 lg:mx-0 lg:max-w-none lg:py-16 lg:px-0">
<div class="-me-48 md:-me-16 lg:relative lg:m-0 lg:h-full lg:px-0 w-full">
{{ $useDefault := true }}
{{ $homepageImage := "" }}
{{ with .Site.Params.defaultBackgroundImage }}
{{ if or (strings.HasPrefix . "http:") (strings.HasPrefix . "https:") }}
{{ $homepageImage = resources.GetRemote . }}
{{ else }}
{{ $homepageImage = resources.Get . }}
{{ end }}
{{ end }}
{{ with .Site.Params.homepage.homepageImage }}
{{ if or (strings.HasPrefix . "http:") (strings.HasPrefix . "https:") }}
{{ $homepageImage = resources.GetRemote . }}
{{ $useDefault = false }}
{{ else }}
{{ $homepageImage = resources.Get . }}
{{ $useDefault = false }}
{{ end }}
{{ end }}
{{ if $homepageImage }}
{{ $style := "" }}
{{ $defaultPosition := cond $useDefault site.Params.imagePosition false }}
{{ with $.Params.imagePosition | default $defaultPosition }}
{{ $style = printf "object-position: %s;" . }}
{{ end }}
<img
class="w-full rounded-xl shadow-xl lg:absolute lg:h-full lg:w-auto lg:max-w-none"
src="{{ $homepageImage.RelPermalink }}"
{{ if $style }}style="{{ $style | safeCSS }}"{{ end }}>
{{ end }}
</div>
</div>
<div class="py-12 sm:py-16">
<div class="mx-auto grid max-w-7xl items-center gap-10 px-4 sm:px-6 lg:grid-cols-2 lg:gap-16 lg:px-8">
<div class="mx-auto w-full max-w-xl lg:mx-0 lg:max-w-none">
<article class="max-w-full prose dark:prose-invert">
{{ with .Title }}
<header>
<h1>{{ . | emojify }}</h1>
</header>
{{ end }}
<section class="w-full">{{ .Content }}</section>
</article>
</div>
<div class="mx-auto w-full max-w-xl lg:mx-0 lg:max-w-none">
{{ $useDefault := true }}
{{ $homepageImage := "" }}
{{ with .Site.Params.defaultBackgroundImage }}
{{ if or (strings.HasPrefix . "http:") (strings.HasPrefix . "https:") }}
{{ $homepageImage = resources.GetRemote . }}
{{ else }}
{{ $homepageImage = resources.Get . }}
{{ end }}
{{ end }}
{{ with .Site.Params.homepage.homepageImage }}
{{ if or (strings.HasPrefix . "http:") (strings.HasPrefix . "https:") }}
{{ $homepageImage = resources.GetRemote . }}
{{ $useDefault = false }}
{{ else }}
{{ $homepageImage = resources.Get . }}
{{ $useDefault = false }}
{{ end }}
{{ end }}
{{ if $homepageImage }}
{{ $style := "" }}
{{ $defaultPosition := cond $useDefault site.Params.imagePosition false }}
{{ with $.Params.imagePosition | default $defaultPosition }}
{{ $style = printf "object-position: %s;" . }}
{{ end }}
<img
class="nozoom aspect-[4/3] w-full rounded-2xl border border-neutral-200/80 object-cover shadow-lg shadow-neutral-900/5 dark:border-neutral-700/80"
src="{{ $homepageImage.RelPermalink }}"
role="presentation"
{{ if $style }}style="{{ $style | safeCSS }}"{{ end }}>
{{ end }}
</div>
</div>
</div>
+53 -8
View File
@@ -1,17 +1,62 @@
<article class="relative isolate overflow-hidden">
<div aria-hidden="true" class="absolute inset-x-0 -top-40 -z-10 transform-gpu overflow-hidden blur-3xl sm:-top-80">
<div class="relative left-[calc(50%-11rem)] aspect-[1155/678] w-[36rem] -translate-x-1/2 rotate-[30deg] bg-gradient-to-tr from-primary-400 to-secondary-500 opacity-20 sm:left-[calc(50%-30rem)] sm:w-[72rem]"></div>
</div>
{{ $heroImage := .Params.heroImage | default site.Params.homepage.homepageImage }}
{{ $heroImg := "" }}
{{ with $heroImage }}
{{ if or (strings.HasPrefix . "http:") (strings.HasPrefix . "https:") }}
{{ $heroImg = resources.GetRemote . }}
{{ else }}
{{ $heroImg = resources.Get . }}
{{ end }}
{{ end }}
{{ with $heroImg }}
<div
id="landing-hero-background"
class="hero-canvas-fade nozoom fixed inset-x-0 top-0 -z-10 h-[640px]">
<img
class="absolute inset-0 h-full w-full object-cover"
src="{{ .RelPermalink }}"
role="presentation"
loading="eager"
decoding="async"
fetchpriority="high" />
<div class="absolute inset-0 bg-neutral/60 dark:bg-neutral-800/50"></div>
</div>
{{ $heroFade := resources.Get "js/hero-scroll-fade.js" }}
{{ $heroFade = $heroFade | resources.Minify | resources.Fingerprint (site.Params.fingerprintAlgorithm | default "sha512") }}
<script
type="text/javascript"
src="{{ $heroFade.RelPermalink }}"
integrity="{{ $heroFade.Data.Integrity }}"
data-hero-id="landing-hero-background"
data-fade-distance="500"></script>
{{ end }}
<section class="mx-auto max-w-7xl px-4 pb-16 pt-12 sm:px-6 sm:pb-24 sm:pt-20 lg:px-8">
<div class="mx-auto max-w-4xl text-center">
{{ with .Site.Params.Author.name }}
<p class="mb-5 text-sm font-bold uppercase tracking-[0.18em] text-primary-600 dark:text-primary-400">{{ . }}</p>
{{ with .Params.heroCaption | default .Site.Params.Author.name }}
<p class="landing-rise mb-5 text-sm font-semibold uppercase tracking-[0.25em] text-primary-600 dark:text-primary-400">
{{ . | markdownify }}
</p>
{{ end }}
{{ with .Title }}
<h1 class="text-balance text-5xl font-black tracking-tight text-neutral-950 dark:text-white sm:text-7xl">{{ . | emojify }}</h1>
<h1 class="landing-rise landing-rise-1 text-balance text-5xl font-black leading-[1.05] tracking-tighter text-neutral-900 dark:text-white sm:text-7xl">
{{ . | emojify }}
</h1>
{{ end }}
<section class="prose prose-lg mx-auto mt-6 max-w-3xl text-left dark:prose-invert sm:text-center">{{ .Content }}</section>
{{ with .Params.heroLead }}
<p class="landing-rise landing-rise-2 mx-auto mt-6 max-w-3xl text-balance text-xl leading-8 text-neutral-600 dark:text-neutral-300 sm:text-2xl sm:leading-9">
{{ . | markdownify }}
</p>
{{ end }}
{{ with .Params.heroButtons }}
<div class="landing-rise landing-rise-3 mt-9 flex flex-wrap items-center justify-center gap-4">
{{ range . }}
{{ partial "cta-button.html" (dict "url" .url "label" .label "style" .style "github" .github) }}
{{ end }}
</div>
{{ end }}
<section class="landing-rise landing-rise-4 prose prose-lg mx-auto mt-10 max-w-3xl text-left dark:prose-invert sm:text-center">
{{ .Content }}
</section>
</div>
</section>
</article>
+19 -6
View File
@@ -1,27 +1,40 @@
{{/* Opt-in preview for Blowfish's built-in homepage layouts. */}}
{{ $layouts := slice "landing" "background" "hero" "profile" "page" "card" }}
<section data-layout-switcher>
<div class="rounded-3xl border border-neutral-200 bg-neutral/40 p-4 shadow-sm dark:border-neutral-800 dark:bg-neutral-900/30 sm:p-8">
<div>
{{ range $index, $layout := $layouts }}
<div data-layout-switcher-panel="{{ $layout }}" role="tabpanel"{{ if ne $index 0 }} hidden{{ end }}>
{{ partial (printf "home/%s.html" $layout) $ }}
{{ $html := partial (printf "home/%s.html" $layout) $ }}
{{ if ne $index 0 }}
{{/* Hidden panels must not eagerly fetch their imagery. */}}
{{ $html = replace $html `loading="eager"` `loading="lazy"` }}
{{ $html = replace $html `fetchpriority="high"` `fetchpriority="low"` }}
{{ end }}
<div id="layout-panel-{{ $layout }}" data-layout-switcher-panel="{{ $layout }}" role="tabpanel" aria-labelledby="layout-tab-{{ $layout }}" tabindex="0"{{ if ne $index 0 }} hidden{{ end }}>
{{ $html | safeHTML }}
</div>
{{ end }}
</div>
<div class="mb-8 mt-8 rounded-3xl border border-primary-200 bg-primary-50/70 p-5 shadow-sm dark:border-primary-900/70 dark:bg-primary-950/30 sm:p-6">
<div class="mb-8 mt-8 rounded-3xl border border-neutral-200/80 bg-neutral/85 p-5 shadow-lg shadow-neutral-900/5 backdrop-blur-xl dark:border-neutral-700/80 dark:bg-neutral-800/85 sm:p-6">
<div class="flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between">
<div>
<p class="text-sm font-bold uppercase tracking-[0.18em] text-primary-600 dark:text-primary-400">Theme playground</p>
<h2 class="mt-1 text-2xl font-black tracking-tight text-neutral-950 dark:text-white">Try a homepage layout</h2>
<h2 class="mt-1 text-2xl font-black tracking-tight text-neutral-900 dark:text-white">Try a homepage layout</h2>
<p class="mt-1 max-w-2xl text-sm text-neutral-600 dark:text-neutral-300">Same content, six built-in Blowfish layouts. Pick one to preview it in place.</p>
</div>
<a class="text-sm font-bold text-primary-600 underline decoration-primary-300 underline-offset-4 hover:text-primary-500 dark:text-primary-400" href="{{ `docs/homepage-layout/` | relLangURL }}">Layout documentation</a>
</div>
<div class="mt-5 flex flex-wrap gap-2" role="tablist" aria-label="Homepage layouts">
{{ range $index, $layout := $layouts }}
<button type="button" class="rounded-full border border-neutral-300 bg-white px-4 py-2 text-sm font-bold text-neutral-700 shadow-sm transition hover:border-primary-400 hover:text-primary-600 dark:border-neutral-700 dark:bg-neutral-900 dark:text-neutral-200 dark:hover:text-primary-400 {{ if eq $index 0 }}border-primary-600 bg-primary-600 text-white hover:bg-primary-500 hover:text-white dark:border-primary-500 dark:bg-primary-500{{ end }}" data-layout-switcher-button="{{ $layout }}" role="tab" aria-selected="{{ cond (eq $index 0) `true` `false` }}">{{ $layout | title }}</button>
<button type="button" id="layout-tab-{{ $layout }}" class="rounded-full border border-neutral-300 bg-white px-4 py-2 text-sm font-bold text-neutral-700 shadow-sm transition hover:border-primary-400 hover:text-primary-600 dark:border-neutral-700 dark:bg-neutral-900 dark:text-neutral-200 dark:hover:text-primary-400 {{ if eq $index 0 }}border-primary-600 bg-primary-600 text-white hover:bg-primary-500 hover:text-white dark:border-primary-500 dark:bg-primary-500{{ end }}" data-layout-switcher-button="{{ $layout }}" role="tab" aria-selected="{{ cond (eq $index 0) `true` `false` }}" aria-controls="layout-panel-{{ $layout }}" tabindex="{{ cond (eq $index 0) `0` `-1` }}">{{ $layout | title }}</button>
{{ end }}
</div>
</div>
{{ $jsSwitcher := resources.Get "js/layout-switcher.js" | resources.Minify | resources.Fingerprint (site.Params.fingerprintAlgorithm | default "sha512") }}
<script
defer
type="text/javascript"
src="{{ $jsSwitcher.RelPermalink }}"
integrity="{{ $jsSwitcher.Data.Integrity }}"></script>
</section>
+4
View File
@@ -1,5 +1,8 @@
<div
id="search-wrapper"
role="dialog"
aria-modal="true"
aria-label="{{ i18n "search.input_placeholder" }}"
class="invisible fixed inset-0 flex h-screen w-screen cursor-default flex-col bg-neutral-500/50 p-4 backdrop-blur-sm dark:bg-neutral-900/50 sm:p-6 md:p-[10vh] lg:p-[12vh] z-500"
data-url="{{ "" | relLangURL }}">
<div
@@ -13,6 +16,7 @@
<input
type="search"
id="search-query"
aria-label="{{ i18n "search.input_placeholder" }}"
class="flex flex-auto h-12 mx-1 bg-transparent appearance-none focus:outline-dotted focus:outline-2 focus:outline-transparent"
placeholder="{{ i18n "search.input_placeholder" }}"
tabindex="0">
+1 -1
View File
@@ -24,7 +24,7 @@
title="{{ .username }}"
aria-label="Sponsor: {{ .username }}">
<img
class="nozoom h-16 w-16 rounded-full ring-2 ring-primary-500 transition-transform group-hover:scale-110"
class="nozoom h-16 w-16 rounded-full ring-2 ring-primary-500 transition-transform duration-300 ease-[cubic-bezier(0.22,1,0.36,1)] group-hover:scale-105"
src="{{ .avatar_url }}"
alt="{{ .username }}"
width="64"
+1 -1
View File
@@ -49,7 +49,7 @@
loading="lazy"
decoding="async"
fetchpriority="low"
class="not-prose absolute inset-0 h-full w-full object-cover transition duration-500 {{ if $featureImageHover }}group-hover:scale-105{{ end }}"
class="not-prose absolute inset-0 h-full w-full object-cover transition-transform duration-700 ease-[cubic-bezier(0.22,1,0.36,1)] {{ if $featureImageHover }}group-hover:scale-[1.03]{{ end }}"
{{ with $featureImagePosition }}style="object-position: {{ . }};"{{ end }} />
</figure>
{{ end }}
+8 -15
View File
@@ -1,15 +1,8 @@
<article class="w-full px-2 my-3 overflow-hidden sm:w-1/2 md:w-1/3 lg:w-1/4 xl:w-1/4">
<h2 class="flex items-center">
<a
class="text-xl font-medium decoration-primary-500 hover:underline hover:underline-offset-2"
href="{{ .Page.RelPermalink }}"
>{{ .Page.Title }}</a
>
{{ if site.Params.taxonomy.showTermCount | default true }}
<span class="px-2 text-base text-primary-500">&middot;</span>
<span class="text-base text-neutral-400">
{{ .Count }}
</span>
{{ end }}
</h2>
</article>
<a
class="not-prose inline-flex items-center gap-1.5 rounded-full border border-neutral-200/80 bg-white/60 px-3 py-1.5 text-sm font-medium text-neutral-700 backdrop-blur-xl transition-colors hover:border-primary-400 hover:text-primary-600 dark:border-neutral-700/80 dark:bg-neutral-900/50 dark:text-neutral-200 dark:hover:text-primary-400"
href="{{ .Page.RelPermalink }}">
{{ .Page.Title }}
{{ if site.Params.taxonomy.showTermCount | default true }}
<span class="text-xs text-neutral-500 dark:text-neutral-400">{{ .Count }}</span>
{{ end }}
</a>
+2 -2
View File
@@ -8,7 +8,7 @@
</summary>
<div
class="min-w-[220px] py-2 border-dotted border-s-1 -ms-5 ps-5 dark:border-neutral-600">
{{ .TableOfContents | emojify }}
{{ replace (.TableOfContents | emojify) `id="TableOfContents"` (printf `id="TableOfContents" aria-label="%s"` (i18n "article.table_of_contents")) | safeHTML }}
</div>
</details>
<details class="toc-inside mt-0 overflow-hidden rounded-lg -ms-5 ps-5 lg:hidden">
@@ -18,7 +18,7 @@
</summary>
<div
class="py-2 border-dotted border-neutral-300 border-s-1 -ms-5 ps-5 dark:border-neutral-600">
{{ .TableOfContents | emojify }}
{{ replace (.TableOfContents | emojify) `id="TableOfContents"` (printf `id="TableOfContentsMobile" aria-label="%s"` (i18n "article.table_of_contents")) | safeHTML }}
</div>
</details>
+17 -1
View File
@@ -156,7 +156,23 @@
{{ if site.Params.firebase.apiKey }}
{{ $firebase := resources.Get "js/firebase.js" }}
{{ $firebase = $firebase | resources.Minify | resources.Fingerprint (site.Params.fingerprintAlgorithm | default "sha512") }}
<script type="module" src="{{ $firebase.RelPermalink }}" integrity="{{ $firebase.Data.Integrity }}"></script>
<link rel="preconnect" href="https://www.gstatic.com" crossorigin>
{{/* Load the Firebase module (and its ~300KB of gstatic imports) after the
page is idle so it never competes with rendering-critical resources. */}}
<script>
(function () {
var load = function () {
import("{{ $firebase.RelPermalink }}");
};
if ("requestIdleCallback" in window) {
requestIdleCallback(load, { timeout: 4000 });
} else {
window.addEventListener("load", function () {
setTimeout(load, 1500);
});
}
})();
</script>
{{ if in (slice "page" "section") .Kind }}
{{ $translations := .AllTranslations }}
+1 -9
View File
@@ -1,9 +1 @@
{{ $url := .Get "url" | default "#" }}
{{ $label := .Get "label" | default "Learn more" }}
{{ $style := .Get "style" | default "primary" }}
<a
class="not-prose inline-flex items-center gap-2 rounded-xl px-5 py-3 font-semibold transition focus-visible:outline-2 focus-visible:outline-offset-2 {{ if eq $style "outline" }}border border-neutral-300 bg-white text-neutral-800 hover:border-primary-400 hover:text-primary-600 dark:border-neutral-700 dark:bg-neutral-900 dark:text-neutral-100 dark:hover:text-primary-400{{ else }}bg-primary-600 text-white shadow-lg shadow-primary-500/20 hover:-translate-y-0.5 hover:bg-primary-500 focus-visible:outline-primary-600{{ end }}"
href="{{ $url | safeURL }}">
{{ $label | markdownify }}
{{ partial "icon.html" "link" }}
</a>
{{ partial "cta-button.html" (dict "url" (.Get "url") "label" (.Get "label") "style" (.Get "style")) }}
+1 -1
View File
@@ -1,4 +1,4 @@
{{ $columns := .Get "columns" | default "3" }}
<div class="not-prose my-8 grid gap-4 sm:grid-cols-2 {{ if eq $columns "4" }}lg:grid-cols-4{{ else }}lg:grid-cols-3{{ end }}">
<div class="not-prose my-8 grid gap-4 sm:grid-cols-2 grid-balance {{ if eq $columns "4" }}lg:grid-cols-4 grid-balance-4{{ else }}lg:grid-cols-3 grid-balance-3{{ end }}">
{{ .Inner }}
</div>
+6 -2
View File
@@ -2,12 +2,16 @@
{{ $title := .Get "title" | default "Feature" }}
{{ $url := .Get "url" }}
{{ $label := .Get "label" | default "Learn more" }}
{{ $headingLevel := .Get "headingLevel" | default "h2" }}
{{ if not (in (slice "h1" "h2" "h3" "h4" "h5" "h6") $headingLevel) }}
{{ $headingLevel = "h2" }}
{{ end }}
<article class="group rounded-2xl border border-neutral-200 bg-white p-6 shadow-sm transition duration-200 hover:-translate-y-1 hover:border-primary-300 hover:shadow-xl hover:shadow-primary-500/10 dark:border-neutral-800 dark:bg-neutral-900 dark:hover:border-primary-700">
<article class="group rounded-2xl border border-neutral-200/80 bg-white/60 p-6 shadow-sm backdrop-blur-xl transition duration-200 hover:-translate-y-1 hover:border-primary-300 hover:shadow-xl hover:shadow-primary-500/10 dark:border-neutral-800 dark:bg-neutral-900/50 dark:hover:border-primary-700">
<span class="mb-5 inline-flex rounded-xl bg-primary-100 p-3 text-primary-600 dark:bg-primary-950 dark:text-primary-400">
{{ partial "icon.html" $icon }}
</span>
<h3 class="text-lg font-bold text-neutral-950 dark:text-white">{{ $title | markdownify }}</h3>
{{ printf `<%s class="text-lg font-bold text-neutral-900 dark:text-white">` $headingLevel | safeHTML }}{{ $title | markdownify }}{{ printf `</%s>` $headingLevel | safeHTML }}
<div class="mt-2 text-sm leading-6 text-neutral-600 dark:text-neutral-400">{{ .Inner | markdownify }}</div>
{{ with $url }}
<a class="mt-5 inline-flex items-center gap-2 text-sm font-bold text-primary-600 hover:text-primary-500 dark:text-primary-400" href="{{ . | safeURL }}">
+10
View File
@@ -25,6 +25,16 @@
{{ end -}}
{{ end -}}
{{/* images without alternative text get an explicit empty alt so screen
readers skip them instead of announcing the file name */}}
{{ range findRE `<img\s+[^>]*>` $content -}}
{{ $imgTag := . -}}
{{ if not (findRE `\salt=` $imgTag) -}}
{{ $newTag := replaceRE `^<img\s` `<img alt="" ` $imgTag -}}
{{ $content = replace $content $imgTag $newTag -}}
{{ end -}}
{{ end -}}
{{ if not .Parent }}<div class="width-patch"></div>{{ end }}
<div id="{{- $id -}}" class="gallery">
{{ $content | safeHTML -}}
+2 -2
View File
@@ -1,7 +1,7 @@
{{ $value := .Get "value" | default "0" }}
{{ $label := .Get "label" | default "Metric" }}
<div class="rounded-2xl border border-neutral-200 bg-white/80 p-6 text-center shadow-sm dark:border-neutral-800 dark:bg-neutral-900/80">
<div class="rounded-2xl border border-neutral-200/80 bg-white/60 p-6 text-center shadow-sm backdrop-blur-xl dark:border-neutral-800 dark:bg-neutral-900/50">
<p class="text-4xl font-black tracking-tight text-primary-600 dark:text-primary-400">{{ $value | markdownify }}</p>
<p class="mt-2 font-bold text-neutral-950 dark:text-white">{{ $label | markdownify }}</p>
<p class="mt-2 font-bold text-neutral-900 dark:text-white">{{ $label | markdownify }}</p>
{{ with .Inner }}<div class="mt-2 text-sm leading-6 text-neutral-600 dark:text-neutral-400">{{ . | markdownify }}</div>{{ end }}
</div>
+1 -1
View File
@@ -1,4 +1,4 @@
{{ $columns := .Get "columns" | default "3" }}
<div class="not-prose my-10 grid gap-4 sm:grid-cols-2 {{ if eq $columns "4" }}lg:grid-cols-4{{ else }}lg:grid-cols-3{{ end }}">
<div class="not-prose my-10 grid gap-4 sm:grid-cols-2 grid-balance {{ if eq $columns "4" }}lg:grid-cols-4 grid-balance-4{{ else }}lg:grid-cols-3 grid-balance-3{{ end }}">
{{ .Inner }}
</div>
+2 -2
View File
@@ -1,7 +1,7 @@
{{ $title := .Get "title" | default "Step" }}
{{ $number := .Get "number" }}
<li class="relative">
<span class="absolute -start-[2.1rem] top-0 flex h-8 w-8 items-center justify-center rounded-full bg-primary-600 text-sm font-black text-white ring-4 ring-neutral dark:ring-neutral-800">{{ $number | default "•" }}</span>
<h3 class="text-lg font-bold text-neutral-950 dark:text-white">{{ $title | markdownify }}</h3>
<span class="absolute -start-10 top-0 flex h-8 w-8 items-center justify-center rounded-full bg-primary-600 text-sm font-black text-white ring-4 ring-neutral dark:ring-neutral-800">{{ $number | default "•" }}</span>
<h3 class="text-lg font-bold text-neutral-900 dark:text-white">{{ $title | markdownify }}</h3>
<div class="mt-1 text-neutral-600 dark:text-neutral-400">{{ .Inner | markdownify }}</div>
</li>