✨ 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

+264 -176
View File
@@ -44,6 +44,7 @@
--font-weight-bold: 700;
--font-weight-extrabold: 800;
--font-weight-black: 900;
--tracking-tighter: -0.05em;
--tracking-tight: -0.025em;
--tracking-normal: 0em;
--leading-snug: 1.375;
@@ -60,7 +61,6 @@
--blur-sm: 8px;
--blur-xl: 24px;
--blur-2xl: 40px;
--blur-3xl: 64px;
--aspect-video: 16 / 9;
--default-transition-duration: 150ms;
--default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
@@ -276,8 +276,8 @@
.-start-6 {
inset-inline-start: calc(var(--spacing) * -6);
}
.-start-\[2\.1rem\] {
inset-inline-start: calc(2.1rem * -1);
.-start-10 {
inset-inline-start: calc(var(--spacing) * -10);
}
.start-\[calc\(max\(-50vw\,-800px\)\+50\%\)\] {
inset-inline-start: calc(max(-50vw, -800px) + 50%);
@@ -294,9 +294,6 @@
.end-8 {
inset-inline-end: calc(var(--spacing) * 8);
}
.-top-40 {
top: calc(var(--spacing) * -40);
}
.top-0 {
top: 0px;
}
@@ -336,17 +333,14 @@
.left-1\/2 {
left: calc(1 / 2 * 100%);
}
.left-\[calc\(50\%-11rem\)\] {
left: calc(50% - 11rem);
}
.isolate {
isolation: isolate;
}
.-z-10 {
z-index: calc(10 * -1);
}
.z-0 {
z-index: 0;
.-z-20 {
z-index: calc(20 * -1);
}
.z-2 {
z-index: 2;
@@ -423,9 +417,6 @@
.m-auto {
margin: auto;
}
.-mx-2 {
margin-inline: calc(var(--spacing) * -2);
}
.mx-1 {
margin-inline: var(--spacing);
}
@@ -468,9 +459,6 @@
.ms-auto {
margin-inline-start: auto;
}
.-me-48 {
margin-inline-end: calc(var(--spacing) * -48);
}
.me-1 {
margin-inline-end: var(--spacing);
}
@@ -1164,6 +1152,9 @@
.mt-8 {
margin-top: calc(var(--spacing) * 8);
}
.mt-9 {
margin-top: calc(var(--spacing) * 9);
}
.mt-10 {
margin-top: calc(var(--spacing) * 10);
}
@@ -1230,9 +1221,6 @@
.mb-10 {
margin-bottom: calc(var(--spacing) * 10);
}
.mb-12 {
margin-bottom: calc(var(--spacing) * 12);
}
.mb-14 {
margin-bottom: calc(var(--spacing) * 14);
}
@@ -1305,9 +1293,6 @@
.aspect-\[4\/3\] {
aspect-ratio: 4/3;
}
.aspect-\[1155\/678\] {
aspect-ratio: 1155/678;
}
.aspect-auto {
aspect-ratio: auto;
}
@@ -1369,9 +1354,6 @@
.h-40 {
height: calc(var(--spacing) * 40);
}
.h-48 {
height: calc(var(--spacing) * 48);
}
.h-84 {
height: calc(var(--spacing) * 84);
}
@@ -1384,6 +1366,9 @@
.h-\[150px\] {
height: 150px;
}
.h-\[640px\] {
height: 640px;
}
.h-\[800px\] {
height: 800px;
}
@@ -1408,8 +1393,8 @@
.min-h-0 {
min-height: 0px;
}
.min-h-\[104px\] {
min-height: 104px;
.min-h-32 {
min-height: calc(var(--spacing) * 32);
}
.min-h-\[130px\] {
min-height: 130px;
@@ -1468,9 +1453,6 @@
.w-\[30px\] {
width: 30px;
}
.w-\[36rem\] {
width: 36rem;
}
.w-\[min\(22rem\,calc\(100vw-1\.5rem\)\)\] {
width: min(22rem, calc(100vw - 1.5rem));
}
@@ -1619,18 +1601,12 @@
.-rotate-90 {
rotate: calc(90deg * -1);
}
.rotate-\[30deg\] {
rotate: 30deg;
}
.transform {
transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,);
}
.transform\! {
transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,) !important;
}
.transform-gpu {
transform: translateZ(0) var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,);
}
.transform-none {
transform: none;
}
@@ -1659,6 +1635,9 @@
.appearance-none {
appearance: none;
}
.grid-cols-2 {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.grid-rows-\[0fr\] {
grid-template-rows: 0fr;
}
@@ -1686,6 +1665,9 @@
.gap-1 {
gap: var(--spacing);
}
.gap-1\.5 {
gap: calc(var(--spacing) * 1.5);
}
.gap-2 {
gap: calc(var(--spacing) * 2);
}
@@ -1695,6 +1677,9 @@
.gap-4 {
gap: calc(var(--spacing) * 4);
}
.gap-10 {
gap: calc(var(--spacing) * 10);
}
:where(.space-y-2 > :not(:last-child)) {
--tw-space-y-reverse: 0;
margin-block-start: calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));
@@ -1906,6 +1891,12 @@
.border-neutral-300 {
border-color: rgba(var(--color-neutral-300), 1);
}
.border-neutral-300\/80 {
border-color: color-mix(in srgb, rgba(rgba(var(--color-neutral-300), 1), 1) 80%, transparent);
@supports (color: color-mix(in lab, red, red)) {
border-color: color-mix(in oklab, rgba(var(--color-neutral-300), 1) 80%, transparent);
}
}
.border-primary-200 {
border-color: rgba(var(--color-primary-200), 1);
}
@@ -1993,6 +1984,12 @@
.bg-neutral-100 {
background-color: rgba(var(--color-neutral-100), 1);
}
.bg-neutral-100\/50 {
background-color: color-mix(in srgb, rgba(rgba(var(--color-neutral-100), 1), 1) 50%, transparent);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, rgba(var(--color-neutral-100), 1) 50%, transparent);
}
}
.bg-neutral-100\/75 {
background-color: color-mix(in srgb, rgba(rgba(var(--color-neutral-100), 1), 1) 75%, transparent);
@supports (color: color-mix(in lab, red, red)) {
@@ -2017,18 +2014,24 @@
background-color: color-mix(in oklab, rgba(var(--color-neutral), 1) 25%, transparent);
}
}
.bg-neutral\/40 {
background-color: color-mix(in srgb, rgba(rgba(var(--color-neutral), 1), 1) 40%, transparent);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, rgba(var(--color-neutral), 1) 40%, transparent);
}
}
.bg-neutral\/50 {
background-color: color-mix(in srgb, rgba(rgba(var(--color-neutral), 1), 1) 50%, transparent);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, rgba(var(--color-neutral), 1) 50%, transparent);
}
}
.bg-neutral\/60 {
background-color: color-mix(in srgb, rgba(rgba(var(--color-neutral), 1), 1) 60%, transparent);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, rgba(var(--color-neutral), 1) 60%, transparent);
}
}
.bg-neutral\/75 {
background-color: color-mix(in srgb, rgba(rgba(var(--color-neutral), 1), 1) 75%, transparent);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, rgba(var(--color-neutral), 1) 75%, transparent);
}
}
.bg-neutral\/85 {
background-color: color-mix(in srgb, rgba(rgba(var(--color-neutral), 1), 1) 85%, transparent);
@supports (color: color-mix(in lab, red, red)) {
@@ -2041,12 +2044,6 @@
background-color: color-mix(in oklab, rgba(var(--color-neutral), 1) 95%, transparent);
}
}
.bg-primary-50\/70 {
background-color: color-mix(in srgb, rgba(rgba(var(--color-primary-50), 1), 1) 70%, transparent);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, rgba(var(--color-primary-50), 1) 70%, transparent);
}
}
.bg-primary-100 {
background-color: rgba(var(--color-primary-100), 1);
}
@@ -2071,8 +2068,8 @@
.bg-white {
background-color: #fff;
}
.bg-white\/80 {
background-color: color-mix(in oklab, #fff 80%, transparent);
.bg-white\/60 {
background-color: color-mix(in oklab, #fff 60%, transparent);
}
.bg-linear-60 {
--tw-gradient-position: 60deg;
@@ -2093,10 +2090,6 @@
--tw-gradient-position: to top in oklab;
background-image: linear-gradient(var(--tw-gradient-stops));
}
.bg-gradient-to-tr {
--tw-gradient-position: to top right in oklab;
background-image: linear-gradient(var(--tw-gradient-stops));
}
.bg-none {
background-image: none;
}
@@ -2104,10 +2097,6 @@
--tw-gradient-from: rgba(var(--color-neutral), 1);
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
}
.from-primary-400 {
--tw-gradient-from: rgba(var(--color-primary-400), 1);
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
}
.from-primary-500 {
--tw-gradient-from: rgba(var(--color-primary-500), 1);
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
@@ -2123,10 +2112,6 @@
--tw-gradient-to: rgba(var(--color-neutral-100), 1);
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
}
.to-secondary-500 {
--tw-gradient-to: rgba(var(--color-secondary-500), 1);
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
}
.to-secondary-600 {
--tw-gradient-to: rgba(var(--color-secondary-600), 1);
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
@@ -2210,6 +2195,9 @@
.px-\[30px\] {
padding-inline: 30px;
}
.py-0\.5 {
padding-block: calc(var(--spacing) * 0.5);
}
.py-1 {
padding-block: var(--spacing);
}
@@ -2234,12 +2222,18 @@
.py-10 {
padding-block: calc(var(--spacing) * 10);
}
.py-12 {
padding-block: calc(var(--spacing) * 12);
}
.py-16 {
padding-block: calc(var(--spacing) * 16);
}
.py-20 {
padding-block: calc(var(--spacing) * 20);
}
.py-24 {
padding-block: calc(var(--spacing) * 24);
}
.py-\[0\.4rem\] {
padding-block: 0.4rem;
}
@@ -2276,9 +2270,6 @@
.pt-12 {
padding-top: calc(var(--spacing) * 12);
}
.pt-16 {
padding-top: calc(var(--spacing) * 16);
}
.pr-8 {
padding-right: calc(var(--spacing) * 8);
}
@@ -2297,9 +2288,6 @@
.pb-16 {
padding-bottom: calc(var(--spacing) * 16);
}
.pb-32 {
padding-bottom: calc(var(--spacing) * 32);
}
.pl-0 {
padding-left: 0px;
}
@@ -2333,6 +2321,9 @@
.align-top {
vertical-align: top;
}
.font-sans {
font-family: var(--font-sans);
}
.text-2xl {
font-size: var(--text-2xl);
line-height: var(--tw-leading, var(--text-2xl--line-height));
@@ -2372,6 +2363,9 @@
.text-\[0\.6rem\] {
font-size: 0.6rem;
}
.text-\[16rem\] {
font-size: 16rem;
}
.leading-3 {
--tw-leading: calc(var(--spacing) * 3);
line-height: calc(var(--spacing) * 3);
@@ -2384,10 +2378,22 @@
--tw-leading: calc(var(--spacing) * 7);
line-height: calc(var(--spacing) * 7);
}
.leading-8 {
--tw-leading: calc(var(--spacing) * 8);
line-height: calc(var(--spacing) * 8);
}
.leading-\[1\.05\] {
--tw-leading: 1.05;
line-height: 1.05;
}
.leading-\[1\.6\] {
--tw-leading: 1.6;
line-height: 1.6;
}
.leading-none {
--tw-leading: 1;
line-height: 1;
}
.leading-relaxed {
--tw-leading: var(--leading-relaxed);
line-height: var(--leading-relaxed);
@@ -2420,10 +2426,18 @@
--tw-font-weight: var(--font-weight-semibold);
font-weight: var(--font-weight-semibold);
}
.tracking-\[0\.15em\] {
--tw-tracking: 0.15em;
letter-spacing: 0.15em;
}
.tracking-\[0\.18em\] {
--tw-tracking: 0.18em;
letter-spacing: 0.18em;
}
.tracking-\[0\.25em\] {
--tw-tracking: 0.25em;
letter-spacing: 0.25em;
}
.tracking-normal {
--tw-tracking: var(--tracking-normal);
letter-spacing: var(--tracking-normal);
@@ -2432,6 +2446,10 @@
--tw-tracking: var(--tracking-tight);
letter-spacing: var(--tracking-tight);
}
.tracking-tighter {
--tw-tracking: var(--tracking-tighter);
letter-spacing: var(--tracking-tighter);
}
.text-balance {
text-wrap: balance;
}
@@ -2484,6 +2502,12 @@
.text-neutral-900 {
color: rgba(var(--color-neutral-900), 1);
}
.text-neutral-900\/5 {
color: color-mix(in srgb, rgba(rgba(var(--color-neutral-900), 1), 1) 5%, transparent);
@supports (color: color-mix(in lab, red, red)) {
color: color-mix(in oklab, rgba(var(--color-neutral-900), 1) 5%, transparent);
}
}
.text-primary-100 {
color: rgba(var(--color-primary-100), 1);
}
@@ -2578,9 +2602,6 @@
.opacity-0 {
opacity: 0%;
}
.opacity-20 {
opacity: 20%;
}
.opacity-30 {
opacity: 30%;
}
@@ -2634,6 +2655,18 @@
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
}
.shadow-neutral-900\/5 {
--tw-shadow-color: color-mix(in srgb, rgba(rgba(var(--color-neutral-900), 1), 1) 5%, transparent);
@supports (color: color-mix(in lab, red, red)) {
--tw-shadow-color: color-mix(in oklab, color-mix(in oklab, rgba(var(--color-neutral-900), 1) 5%, transparent) var(--tw-shadow-alpha), transparent);
}
}
.shadow-neutral-900\/20 {
--tw-shadow-color: color-mix(in srgb, rgba(rgba(var(--color-neutral-900), 1), 1) 20%, transparent);
@supports (color: color-mix(in lab, red, red)) {
--tw-shadow-color: color-mix(in oklab, color-mix(in oklab, rgba(var(--color-neutral-900), 1) 20%, transparent) var(--tw-shadow-alpha), transparent);
}
}
.shadow-primary-500\/20 {
--tw-shadow-color: color-mix(in srgb, rgba(rgba(var(--color-primary-500), 1), 1) 20%, transparent);
@supports (color: color-mix(in lab, red, red)) {
@@ -2658,10 +2691,6 @@
--tw-blur: blur(8px) !important;
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,) !important;
}
.blur-3xl {
--tw-blur: blur(var(--blur-3xl));
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
}
.drop-shadow {
--tw-drop-shadow-size: drop-shadow(0 1px 2px var(--tw-drop-shadow-color, rgb(0 0 0 / 0.1))) drop-shadow(0 1px 1px var(--tw-drop-shadow-color, rgb(0 0 0 / 0.06)));
--tw-drop-shadow: drop-shadow(0 1px 2px rgb(0 0 0 / 0.1)) drop-shadow( 0 1px 1px rgb(0 0 0 / 0.06));
@@ -2677,11 +2706,6 @@
.filter\! {
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,) !important;
}
.backdrop-blur {
--tw-backdrop-blur: blur(8px);
-webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);
backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);
}
.backdrop-blur-2xl {
--tw-backdrop-blur: blur(var(--blur-2xl));
-webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);
@@ -2769,9 +2793,9 @@
--tw-duration: 300ms;
transition-duration: 300ms;
}
.duration-500 {
--tw-duration: 500ms;
transition-duration: 500ms;
.duration-700 {
--tw-duration: 700ms;
transition-duration: 700ms;
}
.duration-\[350ms\] {
--tw-duration: 350ms;
@@ -2785,6 +2809,10 @@
--tw-ease: cubic-bezier(0,0,0.15,1), cubic-bezier(0,0,0.15,1);
transition-timing-function: cubic-bezier(0,0,0.15,1), cubic-bezier(0,0,0.15,1);
}
.ease-\[cubic-bezier\(0\.22\,1\,0\.36\,1\)\] {
--tw-ease: cubic-bezier(0.22,1,0.36,1);
transition-timing-function: cubic-bezier(0.22,1,0.36,1);
}
.ease-\[cubic-bezier\(0\.25\,0\.1\,0\.25\,1\.0\)\] {
--tw-ease: cubic-bezier(0.25,0.1,0.25,1.0);
transition-timing-function: cubic-bezier(0.25,0.1,0.25,1.0);
@@ -2831,11 +2859,8 @@
--tw-scale-z: 105%;
scale: var(--tw-scale-x) var(--tw-scale-y);
}
.group-hover\:scale-110:is(:where(.group):hover *) {
--tw-scale-x: 110%;
--tw-scale-y: 110%;
--tw-scale-z: 110%;
scale: var(--tw-scale-x) var(--tw-scale-y);
.group-hover\:scale-\[1\.03\]:is(:where(.group):hover *) {
scale: 1.03;
}
.group-hover\:text-primary-500:is(:where(.group):hover *) {
color: rgba(var(--color-primary-500), 1);
@@ -3102,33 +3127,21 @@
}
}
@media (width >= 640px) {
.sm\:-top-80 {
top: calc(var(--spacing) * -80);
}
.sm\:top-5 {
top: calc(var(--spacing) * 5);
}
.sm\:left-\[calc\(50\%-30rem\)\] {
left: calc(50% - 30rem);
}
.sm\:me-7 {
margin-inline-end: calc(var(--spacing) * 7);
}
.sm\:mt-16 {
margin-top: calc(var(--spacing) * 16);
}
.sm\:mb-0 {
margin-bottom: 0px;
}
.sm\:w-1\/2 {
width: calc(1 / 2 * 100%);
}
.sm\:w-\[72rem\] {
width: 72rem;
}
.sm\:grid-cols-2 {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.sm\:grid-cols-3 {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.sm\:flex-row {
flex-direction: row;
}
@@ -3144,15 +3157,15 @@
.sm\:p-6 {
padding: calc(var(--spacing) * 6);
}
.sm\:p-8 {
padding: calc(var(--spacing) * 8);
}
.sm\:px-6 {
padding-inline: calc(var(--spacing) * 6);
}
.sm\:px-14 {
padding-inline: calc(var(--spacing) * 14);
}
.sm\:py-16 {
padding-block: calc(var(--spacing) * 16);
}
.sm\:py-24 {
padding-block: calc(var(--spacing) * 24);
}
@@ -3165,6 +3178,10 @@
.sm\:text-center {
text-align: center;
}
.sm\:text-2xl {
font-size: var(--text-2xl);
line-height: var(--tw-leading, var(--text-2xl--line-height));
}
.sm\:text-7xl {
font-size: var(--text-7xl);
line-height: var(--tw-leading, var(--text-7xl--line-height));
@@ -3173,14 +3190,18 @@
font-size: var(--text-lg);
line-height: var(--tw-leading, var(--text-lg--line-height));
}
.sm\:text-\[26rem\] {
font-size: 26rem;
}
.sm\:leading-9 {
--tw-leading: calc(var(--spacing) * 9);
line-height: calc(var(--spacing) * 9);
}
.sm\:last\:me-0:last-child {
margin-inline-end: 0px;
}
}
@media (width >= 853px) {
.md\:-me-16 {
margin-inline-end: calc(var(--spacing) * -16);
}
.md\:mt-0 {
margin-top: 0px;
}
@@ -3199,15 +3220,15 @@
.md\:h-\[200px\] {
height: 200px;
}
.md\:w-1\/3 {
width: calc(1 / 3 * 100%);
}
.md\:w-auto {
width: auto;
}
.md\:grid-cols-3 {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.md\:grid-cols-4 {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.md\:flex-row {
flex-direction: row;
}
@@ -3222,12 +3243,6 @@
}
}
@media (width >= 1024px) {
.lg\:absolute {
position: absolute;
}
.lg\:relative {
position: relative;
}
.lg\:sticky {
position: sticky;
}
@@ -3240,54 +3255,27 @@
.lg\:order-last {
order: 9999;
}
.lg\:m-0 {
margin: 0px;
}
.lg\:mx-0 {
margin-inline: 0px;
}
.lg\:mx-auto {
margin-inline: auto;
}
.lg\:ms-auto {
margin-inline-start: auto;
}
.lg\:mt-0 {
margin-top: 0px;
}
.lg\:block {
display: block;
}
.lg\:grid {
display: grid;
}
.lg\:hidden {
display: none;
}
.lg\:h-72 {
height: calc(var(--spacing) * 72);
}
.lg\:h-full {
height: 100%;
}
.lg\:w-1\/4 {
width: calc(1 / 4 * 100%);
}
.lg\:w-auto {
width: auto;
}
.lg\:max-w-2xs {
max-width: var(--container-2xs);
}
.lg\:max-w-7xl {
max-width: var(--container-7xl);
}
.lg\:max-w-none {
max-width: none;
}
.lg\:grid-flow-col-dense {
grid-auto-flow: column dense;
}
.lg\:grid-cols-2 {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
@@ -3300,24 +3288,18 @@
.lg\:flex-row {
flex-direction: row;
}
.lg\:gap-24 {
gap: calc(var(--spacing) * 24);
.lg\:gap-16 {
gap: calc(var(--spacing) * 16);
}
.lg\:p-\[12vh\] {
padding: 12vh;
}
.lg\:px-0 {
padding-inline: 0px;
}
.lg\:px-8 {
padding-inline: calc(var(--spacing) * 8);
}
.lg\:px-32 {
padding-inline: calc(var(--spacing) * 32);
}
.lg\:py-16 {
padding-block: calc(var(--spacing) * 16);
}
.lg\:py-32 {
padding-block: calc(var(--spacing) * 32);
}
@@ -3326,12 +3308,12 @@
}
}
@media (width >= 1280px) {
.xl\:w-1\/4 {
width: calc(1 / 4 * 100%);
}
.xl\:grid-cols-4 {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.xl\:grid-cols-5 {
grid-template-columns: repeat(5, minmax(0, 1fr));
}
}
@media (width >= 1536px) {
.\32 xl\:grid-cols-5 {
@@ -3374,6 +3356,12 @@
.dark\:border-neutral-600:is(.dark *) {
border-color: rgba(var(--color-neutral-600), 1);
}
.dark\:border-neutral-600\/80:is(.dark *) {
border-color: color-mix(in srgb, rgba(rgba(var(--color-neutral-600), 1), 1) 80%, transparent);
@supports (color: color-mix(in lab, red, red)) {
border-color: color-mix(in oklab, rgba(var(--color-neutral-600), 1) 80%, transparent);
}
}
.dark\:border-neutral-700:is(.dark *) {
border-color: rgba(var(--color-neutral-700), 1);
}
@@ -3398,12 +3386,6 @@
.dark\:border-primary-900:is(.dark *) {
border-color: rgba(var(--color-primary-900), 1);
}
.dark\:border-primary-900\/70:is(.dark *) {
border-color: color-mix(in srgb, rgba(rgba(var(--color-primary-900), 1), 1) 70%, transparent);
@supports (color: color-mix(in lab, red, red)) {
border-color: color-mix(in oklab, rgba(var(--color-primary-900), 1) 70%, transparent);
}
}
.dark\:border-secondary-600:is(.dark *) {
border-color: rgba(var(--color-secondary-600), 1);
}
@@ -3503,24 +3485,12 @@
.dark\:bg-neutral-900:is(.dark *) {
background-color: rgba(var(--color-neutral-900), 1);
}
.dark\:bg-neutral-900\/30:is(.dark *) {
background-color: color-mix(in srgb, rgba(rgba(var(--color-neutral-900), 1), 1) 30%, transparent);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, rgba(var(--color-neutral-900), 1) 30%, transparent);
}
}
.dark\:bg-neutral-900\/50:is(.dark *) {
background-color: color-mix(in srgb, rgba(rgba(var(--color-neutral-900), 1), 1) 50%, transparent);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, rgba(var(--color-neutral-900), 1) 50%, transparent);
}
}
.dark\:bg-neutral-900\/80:is(.dark *) {
background-color: color-mix(in srgb, rgba(rgba(var(--color-neutral-900), 1), 1) 80%, transparent);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, rgba(var(--color-neutral-900), 1) 80%, transparent);
}
}
.dark\:bg-neutral-900\/99:is(.dark *) {
background-color: color-mix(in srgb, rgba(rgba(var(--color-neutral-900), 1), 1) 99%, transparent);
@supports (color: color-mix(in lab, red, red)) {
@@ -3603,6 +3573,9 @@
.dark\:text-white:is(.dark *) {
color: #fff;
}
.dark\:text-white\/5:is(.dark *) {
color: color-mix(in oklab, #fff 5%, transparent);
}
.dark\:opacity-60:is(.dark *) {
opacity: 60%;
}
@@ -3653,6 +3626,9 @@
display: none;
}
}
.\[\&_\.p-4\]\:p-3 .p-4 {
padding: calc(var(--spacing) * 3);
}
.\[\&_\.translation_\.menuhide_span\]\:text-sm\! .translation .menuhide span {
font-size: var(--text-sm) !important;
line-height: var(--tw-leading, var(--text-sm--line-height)) !important;
@@ -3665,6 +3641,16 @@
font-size: var(--text-base) !important;
line-height: var(--tw-leading, var(--text-base--line-height)) !important;
}
.\[\&_h2\]\:text-base h2 {
font-size: var(--text-base);
line-height: var(--tw-leading, var(--text-base--line-height));
}
.\[\&_img\.logo\]\:max-h-10 img.logo {
max-height: calc(var(--spacing) * 10);
}
.\[\&_img\.logo\]\:max-w-10 img.logo {
max-width: calc(var(--spacing) * 10);
}
.\[\&_span\]\:text-2xl span {
font-size: var(--text-2xl);
line-height: var(--tw-leading, var(--text-2xl--line-height));
@@ -4540,6 +4526,39 @@ body a, body button {
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
}
@keyframes landing-rise {
from {
opacity: 0;
transform: translateY(0.9rem);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.landing-rise {
animation: landing-rise 0.65s cubic-bezier(0.22, 1, 0.36, 1) both;
}
.landing-rise-1 {
animation-delay: 90ms;
}
.landing-rise-2 {
animation-delay: 180ms;
}
.landing-rise-3 {
animation-delay: 270ms;
}
.landing-rise-4 {
animation-delay: 360ms;
}
@media (prefers-reduced-motion: reduce) {
html {
scroll-behavior: auto !important;
}
.landing-rise, .landing-rise-1, .landing-rise-2, .landing-rise-3, .landing-rise-4 {
animation: none;
}
}
button, [role="button"] {
cursor: pointer;
}
@@ -4688,6 +4707,71 @@ pre {
width: 100%;
height: 100%;
}
@page {
margin: 2cm;
}
@media print {
html {
color-scheme: light only;
}
body {
background: #fff !important;
color: #111 !important;
max-width: none !important;
padding: 0 !important;
}
body > header, #background-canvas, .single_hero_background, #scroll-to-top, #bmc-wbtn {
display: none !important;
}
* {
box-shadow: none !important;
text-shadow: none !important;
}
main, article, .prose, .prose *:not(pre):not(pre *) {
color: #111 !important;
background: transparent !important;
}
pre, pre * {
background: #f4f4f5 !important;
color: #111 !important;
white-space: pre-wrap !important;
}
a {
text-decoration: underline;
}
.prose a[href^="http"]::after {
content: " (" attr(href) ")";
font-size: 0.85em;
color: #555;
word-break: break-all;
}
pre, blockquote, table, figure, img {
break-inside: avoid;
}
h1, h2, h3, h4 {
break-after: avoid;
}
img {
max-width: 100% !important;
}
}
.hero-canvas-fade {
-webkit-mask-image: linear-gradient(to bottom, black 20%, transparent 92%);
mask-image: linear-gradient(to bottom, black 20%, transparent 92%);
}
@media (min-width: 640px) and (max-width: 1023.98px) {
.grid-balance > :last-child:nth-child(odd) {
grid-column: 1 / -1;
}
}
@media (min-width: 1024px) {
.grid-balance-3 > :last-child:nth-child(3n + 1) {
grid-column: 1 / -1;
}
.grid-balance-4 > :last-child:nth-child(4n + 1) {
grid-column: 1 / -1;
}
}
.thumbnail_card {
aspect-ratio: var(--thumbnail-aspect-ratio, 1.5);
height: auto;
@@ -4822,11 +4906,15 @@ body:has(#mobile-menu-toggle:checked) {
.menuhide {
position: absolute;
z-index: 1000;
white-space: nowrap;
width: max-content;
max-width: min(20rem, calc(100vw - 2rem));
opacity: 0;
visibility: hidden;
transition: visibility 0.3s, opacity 0.3s ease-in-out;
}
.nested-menu:nth-last-child(-n + 3) .menuhide {
inset-inline-end: 0;
}
.nested-menu:hover .menuhide, .nested-menu:focus-within .menuhide {
opacity: 1;
visibility: visible;
+149 -1
View File
@@ -23,6 +23,53 @@ body button {
@apply transition-colors;
}
/* Staggered entrance for the landing homepage hero. Removed entirely when a
visitor requests reduced motion. */
@keyframes landing-rise {
from {
opacity: 0;
transform: translateY(0.9rem);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.landing-rise {
animation: landing-rise 0.65s cubic-bezier(0.22, 1, 0.36, 1) both;
}
.landing-rise-1 {
animation-delay: 90ms;
}
.landing-rise-2 {
animation-delay: 180ms;
}
.landing-rise-3 {
animation-delay: 270ms;
}
.landing-rise-4 {
animation-delay: 360ms;
}
@media (prefers-reduced-motion: reduce) {
html {
scroll-behavior: auto !important;
}
.landing-rise,
.landing-rise-1,
.landing-rise-2,
.landing-rise-3,
.landing-rise-4 {
animation: none;
}
}
button,
[role="button"] {
cursor: pointer;
@@ -181,6 +228,101 @@ pre {
height: 100%;
}
/* ---- Print ---- */
@page {
margin: 2cm;
}
@media print {
html {
color-scheme: light only;
}
body {
background: #fff !important;
color: #111 !important;
max-width: none !important;
padding: 0 !important;
}
/* Site chrome that makes no sense on paper */
body > header,
#background-canvas,
.single_hero_background,
#scroll-to-top,
#bmc-wbtn {
display: none !important;
}
/* Flatten decoration; dark mode prints light */
* {
box-shadow: none !important;
text-shadow: none !important;
}
main,
article,
.prose,
.prose *:not(pre):not(pre *) {
color: #111 !important;
background: transparent !important;
}
pre,
pre * {
background: #f4f4f5 !important;
color: #111 !important;
white-space: pre-wrap !important;
}
/* Links stay useful on paper */
a {
text-decoration: underline;
}
.prose a[href^="http"]::after {
content: " (" attr(href) ")";
font-size: 0.85em;
color: #555;
word-break: break-all;
}
/* Sensible page flow */
pre,
blockquote,
table,
figure,
img {
break-inside: avoid;
}
h1,
h2,
h3,
h4 {
break-after: avoid;
}
img {
max-width: 100% !important;
}
}
/* Background hero: feature image dissolves into the global background */
.hero-canvas-fade {
-webkit-mask-image: linear-gradient(to bottom, black 20%, transparent 92%);
mask-image: linear-gradient(to bottom, black 20%, transparent 92%);
}
/* Shortcode grids: a lone trailing card stretches across the full row */
@media (min-width: 640px) and (max-width: 1023.98px) {
.grid-balance > :last-child:nth-child(odd) {
grid-column: 1 / -1;
}
}
@media (min-width: 1024px) {
.grid-balance-3 > :last-child:nth-child(3n + 1) {
grid-column: 1 / -1;
}
.grid-balance-4 > :last-child:nth-child(4n + 1) {
grid-column: 1 / -1;
}
}
.thumbnail_card {
aspect-ratio: var(--thumbnail-aspect-ratio, 1.5);
height: auto;
@@ -332,7 +474,8 @@ body:has(#mobile-menu-toggle:checked) {
.menuhide {
position: absolute;
z-index: 1000;
white-space: nowrap;
width: max-content;
max-width: min(20rem, calc(100vw - 2rem));
opacity: 0;
visibility: hidden;
@@ -342,6 +485,11 @@ body:has(#mobile-menu-toggle:checked) {
opacity 0.3s ease-in-out;
}
/* Dropdowns near the end of the bar open leftward so they stay on screen */
.nested-menu:nth-last-child(-n + 3) .menuhide {
inset-inline-end: 0;
}
.nested-menu:hover .menuhide,
.nested-menu:focus-within .menuhide {
opacity: 1;
+37
View File
@@ -0,0 +1,37 @@
/* Burufugu scheme — the mascot's own palette: sky-blue fish, coral glow, indigo night */
:root {
--color-neutral: 255, 255, 255;
/* Indigo night */
--color-neutral-50: 246, 247, 252;
--color-neutral-100: 238, 240, 249;
--color-neutral-200: 221, 224, 240;
--color-neutral-300: 195, 200, 226;
--color-neutral-400: 141, 148, 189;
--color-neutral-500: 98, 106, 150;
--color-neutral-600: 70, 78, 122;
--color-neutral-700: 48, 55, 97;
--color-neutral-800: 26, 33, 74;
--color-neutral-900: 13, 18, 51;
/* Sky blue (logo) */
--color-primary-50: 240, 247, 254;
--color-primary-100: 224, 239, 254;
--color-primary-200: 186, 222, 253;
--color-primary-300: 138, 197, 250;
--color-primary-400: 96, 172, 246;
--color-primary-500: 74, 158, 240;
--color-primary-600: 52, 126, 219;
--color-primary-700: 42, 98, 189;
--color-primary-800: 38, 76, 152;
--color-primary-900: 30, 58, 110;
/* Coral */
--color-secondary-50: 255, 246, 240;
--color-secondary-100: 255, 234, 222;
--color-secondary-200: 255, 211, 187;
--color-secondary-300: 255, 180, 142;
--color-secondary-400: 255, 138, 92;
--color-secondary-500: 240, 112, 90;
--color-secondary-600: 217, 87, 74;
--color-secondary-700: 184, 64, 58;
--color-secondary-800: 148, 50, 50;
--color-secondary-900: 118, 40, 44;
}
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 98c26.5 0 49.4-10.5 61.8-30h55.7c8.5 0 16.6 3.3 22.6 9.4l77.3 77.2c12.5 12.5 12.5 32.8 0 45.3l-39.4 39.4c-11.8 11.8-30.8 12.6-43.5 1.7L376 228.6V416c0 26.5-21.5 48-48 48H184c-26.5 0-48-21.5-48-48V228.6L121.5 241c-12.7 10.9-31.7 10.1-43.5-1.7L38.6 199.9c-12.5-12.5-12.5-32.8 0-45.3l77.3-77.2c6-6.1 14.1-9.4 22.6-9.4h55.7c12.4 19.5 35.3 30 61.8 30z"/></svg>

After

Width:  |  Height:  |  Size: 452 B

+24
View File
@@ -0,0 +1,24 @@
(function () {
var script = document.currentScript;
var target = document.getElementById(script.getAttribute("data-hero-id"));
if (!target) return;
var distance = parseInt(script.getAttribute("data-fade-distance"), 10) || 500;
var ticking = false;
function update() {
ticking = false;
var opacity = Math.max(0, 1 - window.scrollY / distance);
target.style.opacity = opacity;
target.style.visibility = opacity === 0 ? "hidden" : "visible";
}
window.addEventListener(
"scroll",
function () {
if (!ticking) {
ticking = true;
requestAnimationFrame(update);
}
},
{ passive: true }
);
update();
})();
+55
View File
@@ -0,0 +1,55 @@
(function () {
"use strict";
function initLayoutSwitcher() {
document.querySelectorAll("[data-layout-switcher]").forEach(function (lab) {
var buttons = Array.prototype.slice.call(lab.querySelectorAll("[data-layout-switcher-button]"));
var panels = lab.querySelectorAll("[data-layout-switcher-panel]");
var activate = function (layout, focusTab) {
buttons.forEach(function (button) {
var active = button.dataset.layoutSwitcherButton === layout;
button.setAttribute("aria-selected", String(active));
button.setAttribute("tabindex", active ? "0" : "-1");
button.classList.toggle("border-primary-600", active);
button.classList.toggle("bg-primary-600", active);
button.classList.toggle("text-white", active);
button.classList.toggle("dark:border-primary-500", active);
button.classList.toggle("dark:bg-primary-500", active);
if (active && focusTab) button.focus();
});
panels.forEach(function (panel) {
panel.hidden = panel.dataset.layoutSwitcherPanel !== layout;
});
};
buttons.forEach(function (button, index) {
button.addEventListener("click", function () {
activate(button.dataset.layoutSwitcherButton, false);
});
button.addEventListener("keydown", function (event) {
var target = null;
if (event.key === "ArrowRight") {
target = buttons[(index + 1) % buttons.length];
} else if (event.key === "ArrowLeft") {
target = buttons[(index - 1 + buttons.length) % buttons.length];
} else if (event.key === "Home") {
target = buttons[0];
} else if (event.key === "End") {
target = buttons[buttons.length - 1];
}
if (target) {
event.preventDefault();
activate(target.dataset.layoutSwitcherButton, true);
}
});
});
});
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", initLayoutSwitcher);
} else {
initLayoutSwitcher();
}
})();
+89
View File
@@ -0,0 +1,89 @@
(function () {
"use strict";
function syncCheckboxLabels(checkbox) {
var expanded = checkbox.checked ? "true" : "false";
document.querySelectorAll('label[role="button"][for="' + checkbox.id + '"]').forEach(function (label) {
label.setAttribute("aria-expanded", expanded);
});
}
function setupCheckboxLabels() {
var seen = {};
document.querySelectorAll('label[role="button"][for]').forEach(function (label) {
var checkbox = document.getElementById(label.getAttribute("for"));
if (!checkbox || checkbox.type !== "checkbox") return;
// Enter/Space activates the label like a real button
label.addEventListener("keydown", function (event) {
if (event.key === "Enter" || event.key === " " || event.key === "Spacebar") {
event.preventDefault();
label.click();
}
});
if (!seen[checkbox.id]) {
seen[checkbox.id] = true;
checkbox.addEventListener("change", function () {
syncCheckboxLabels(checkbox);
});
syncCheckboxLabels(checkbox);
}
});
}
function setupMobileMenuEscape() {
var toggle = document.getElementById("mobile-menu-toggle");
if (!toggle) return;
document.addEventListener("keydown", function (event) {
if (event.key === "Escape" && toggle.checked) {
toggle.checked = false;
syncCheckboxLabels(toggle);
var opener = document.querySelector('label[role="button"][for="mobile-menu-toggle"]');
if (opener) opener.focus();
}
});
}
function setupDesktopDropdowns() {
document.querySelectorAll(".nested-menu").forEach(function (menu) {
var trigger = menu.querySelector("[aria-haspopup]");
if (!trigger) return;
var setExpanded = function (expanded) {
trigger.setAttribute("aria-expanded", expanded ? "true" : "false");
};
menu.addEventListener("mouseenter", function () {
setExpanded(true);
});
menu.addEventListener("mouseleave", function () {
setExpanded(false);
});
menu.addEventListener("focusin", function () {
setExpanded(true);
});
menu.addEventListener("focusout", function (event) {
if (!menu.contains(event.relatedTarget)) setExpanded(false);
});
menu.addEventListener("keydown", function (event) {
if (event.key === "Escape") {
if (document.activeElement && menu.contains(document.activeElement)) {
document.activeElement.blur();
}
setExpanded(false);
}
});
});
}
function init() {
setupCheckboxLabels();
setupMobileMenuEscape();
setupDesktopDropdowns();
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init);
} else {
init();
}
})();
+42 -1
View File
@@ -11,6 +11,7 @@ var last = output.lastChild;
var searchVisible = false;
var indexed = false;
var hasResults = false;
var previouslyFocused = null;
// Listen for events
showButton ? showButton.addEventListener("click", displaySearch) : null;
@@ -22,6 +23,11 @@ modal.addEventListener("click", function (event) {
event.stopImmediatePropagation();
return false;
});
const shortcutHint = document.getElementById("search-shortcut-hint");
if (shortcutHint && !/Mac|iPhone|iPad/.test(navigator.platform)) {
shortcutHint.textContent = "Ctrl K";
}
document.addEventListener("keydown", function (event) {
// Forward slash to open search wrapper
if (event.key == "/") {
@@ -35,11 +41,40 @@ document.addEventListener("keydown", function (event) {
}
}
// Cmd+K (macOS) / Ctrl+K to toggle search wrapper
if (event.key.toLowerCase() == "k" && (event.metaKey || event.ctrlKey)) {
event.preventDefault();
if (searchVisible) {
hideSearch();
} else {
displaySearch();
}
}
// Esc to close search wrapper
if (event.key == "Escape") {
hideSearch();
}
// Trap Tab / Shift+Tab focus inside the modal while it is open
if (event.key == "Tab" && searchVisible) {
var focusable = modal.querySelectorAll('a[href], button, input, [tabindex="0"]');
if (focusable.length > 0) {
var firstFocusable = focusable[0];
var lastFocusable = focusable[focusable.length - 1];
if (!modal.contains(document.activeElement)) {
event.preventDefault();
firstFocusable.focus();
} else if (event.shiftKey && document.activeElement == firstFocusable) {
event.preventDefault();
lastFocusable.focus();
} else if (!event.shiftKey && document.activeElement == lastFocusable) {
event.preventDefault();
firstFocusable.focus();
}
}
}
// Down arrow to move down results list
if (event.key == "ArrowDown") {
if (searchVisible && hasResults) {
@@ -91,6 +126,7 @@ function displaySearch() {
buildIndex();
}
if (!searchVisible) {
previouslyFocused = document.activeElement;
document.body.style.overflow = "hidden";
wrapper.style.visibility = "visible";
input.focus();
@@ -104,7 +140,12 @@ function hideSearch() {
wrapper.style.visibility = "hidden";
input.value = "";
output.innerHTML = "";
document.activeElement.blur();
if (previouslyFocused && typeof previouslyFocused.focus === "function" && document.contains(previouslyFocused)) {
previouslyFocused.focus();
} else if (document.activeElement) {
document.activeElement.blur();
}
previouslyFocused = null;
searchVisible = false;
}
}
+11
View File
@@ -0,0 +1,11 @@
[
{ "line": "You shall not pass!", "source": "The Fellowship of the Ring (2001)" },
{ "line": "These aren't the droids you're looking for.", "source": "Star Wars (1977)" },
{ "line": "Toto, I've a feeling we're not in Kansas anymore.", "source": "The Wizard of Oz (1939)" },
{ "line": "Houston, we have a problem.", "source": "Apollo 13 (1995)" },
{ "line": "Roads? Where we're going, we don't need roads.", "source": "Back to the Future (1985)" },
{ "line": "It's just a flesh wound.", "source": "Monty Python and the Holy Grail (1975)" },
{ "line": "I'll be back.", "source": "The Terminator (1984)" },
{ "line": "You're gonna need a bigger boat.", "source": "Jaws (1975)" },
{ "line": "E.T. phone home.", "source": "E.T. the Extra-Terrestrial (1982)" }
]
+6 -1
View File
@@ -49,6 +49,7 @@ error:
404_title: "Page Not Found :confused:"
404_error: "Error 404"
404_description: "It seems that the page you've requested does not exist."
404_home: "Take me home"
footer:
dark_appearance: "Switch to dark appearance"
@@ -60,12 +61,16 @@ list:
no_articles: "There are no articles to list here yet."
nav:
close_menu: "Close menu"
footer_menu: "Footer menu"
main_menu: "Main menu"
mobile_menu: "Mobile menu"
scroll_to_top_title: "Scroll to top"
skip_to_main: "Skip to main content"
toggle_submenu: "Toggle submenu"
search:
open_button_title: "Search (/)"
open_button_title: "Search (/ or ⌘K)"
close_button_title: "Close (Esc)"
input_placeholder: "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>