mirror of
https://github.com/nunocoracao/blowfish.git
synced 2026-10-10 22:09:31 +00:00
✨ 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:
1 parent
5b0cd34d5b
commit
09f0573749
47 files changed
+1098
-375
No files matched your search
+45
-7
@@ -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 }}
|
||||
@@ -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>
|
||||
@@ -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">
|
||||
|
||||
@@ -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 }}">
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
@@ -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 }}">
|
||||
|
||||
@@ -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 }}">
|
||||
|
||||
@@ -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>
|
||||
@@ -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
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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">
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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">·</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>
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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 +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,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>
|
||||
@@ -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 }}">
|
||||
|
||||
@@ -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 -}}
|
||||
|
||||
@@ -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,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>
|
||||
@@ -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>
|
||||
Reference in new issue
Block a user