feat: add Prettier with go-template and toml plugins, format all files
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+62
-56
@@ -1,65 +1,71 @@
|
||||
{{- define "main" -}}
|
||||
<section class="relative min-h-screen flex flex-col items-center justify-center overflow-hidden noise gutter-x py-32 text-center">
|
||||
<section
|
||||
class="relative min-h-screen flex flex-col items-center justify-center overflow-hidden noise gutter-x py-32 text-center"
|
||||
>
|
||||
<!-- Background texture -->
|
||||
<div class="absolute inset-0 speed-lines opacity-60"></div>
|
||||
<div
|
||||
class="absolute inset-0"
|
||||
style="background: radial-gradient(ellipse 80% 60% at 50% 50%, rgba(155,0,255,0.06) 0%, transparent 70%)"
|
||||
></div>
|
||||
|
||||
<!-- Background texture -->
|
||||
<div class="absolute inset-0 speed-lines opacity-60"></div>
|
||||
<div class="absolute inset-0"
|
||||
style="background: radial-gradient(ellipse 80% 60% at 50% 50%, rgba(155,0,255,0.06) 0%, transparent 70%)"></div>
|
||||
|
||||
<!-- Decorative hollow "404" behind everything -->
|
||||
<div class="absolute inset-0 flex items-center justify-center pointer-events-none select-none" aria-hidden="true">
|
||||
<span
|
||||
class="font-display leading-none"
|
||||
style="font-size: clamp(14rem, 40vw, 32rem);
|
||||
<!-- Decorative hollow "404" behind everything -->
|
||||
<div class="absolute inset-0 flex items-center justify-center pointer-events-none select-none" aria-hidden="true">
|
||||
<span
|
||||
class="font-display leading-none"
|
||||
style="font-size: clamp(14rem, 40vw, 32rem);
|
||||
color: transparent;
|
||||
-webkit-text-stroke: 1px rgba(255,26,140,0.07);
|
||||
letter-spacing: -0.02em;">404</span>
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="relative z-10 flex flex-col items-center gap-6 max-w-2xl mx-auto">
|
||||
|
||||
<!-- Eyebrow badge -->
|
||||
<span class="badge badge-gradient" style="animation: mob-link-in 0.5s var(--ease-out-expo) 0ms both">
|
||||
Error 404
|
||||
</span>
|
||||
|
||||
<!-- Main headline -->
|
||||
<h1
|
||||
class="font-display leading-none text-balance"
|
||||
style="font-size: clamp(4rem, 14vw, 10rem);
|
||||
animation: mob-link-in 0.6s var(--ease-out-expo) 80ms both"
|
||||
>
|
||||
<span class="text-stroke-heat">PAGE</span><br>
|
||||
<span class="text-gradient">NOT FOUND</span>
|
||||
</h1>
|
||||
|
||||
<!-- Gradient divider -->
|
||||
<div class="gradient-line w-32 mx-auto" style="animation: mob-link-in 0.4s ease 200ms both"></div>
|
||||
|
||||
<!-- Description -->
|
||||
<p
|
||||
class="text-fog font-body text-base md:text-lg leading-relaxed max-w-md text-pretty"
|
||||
style="animation: mob-link-in 0.5s var(--ease-out-expo) 260ms both"
|
||||
>
|
||||
This drop got pulled. The page you're looking for doesn't exist, was moved, or got lost in the grid.
|
||||
</p>
|
||||
|
||||
<!-- CTA -->
|
||||
<div class="flex flex-wrap gap-4 justify-center"
|
||||
style="animation: mob-link-in 0.5s var(--ease-out-expo) 340ms both">
|
||||
<a href="/" class="btn">Back to the Drop {{ partial "icon.html" "arrow-right" }}</a>
|
||||
<a href="/posts/" class="btn btn-ghost">Browse all posts {{ partial "icon.html" "arrow-right" }}</a>
|
||||
letter-spacing: -0.02em;"
|
||||
>404</span
|
||||
>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- Content -->
|
||||
<div class="relative z-10 flex flex-col items-center gap-6 max-w-2xl mx-auto">
|
||||
<!-- Eyebrow badge -->
|
||||
<span class="badge badge-gradient" style="animation: mob-link-in 0.5s var(--ease-out-expo) 0ms both">
|
||||
Error 404
|
||||
</span>
|
||||
|
||||
<!-- Bottom graffiti tag -->
|
||||
<div
|
||||
class="graffiti-tag absolute -bottom-6 left-1/2 -translate-x-1/2 select-none pointer-events-none whitespace-nowrap"
|
||||
style="-webkit-text-stroke-color: rgba(0,200,255,0.05);"
|
||||
aria-hidden="true"
|
||||
>LOST</div>
|
||||
<!-- Main headline -->
|
||||
<h1
|
||||
class="font-display leading-none text-balance"
|
||||
style="font-size: clamp(4rem, 14vw, 10rem);
|
||||
animation: mob-link-in 0.6s var(--ease-out-expo) 80ms both"
|
||||
>
|
||||
<span class="text-stroke-heat">PAGE</span><br />
|
||||
<span class="text-gradient">NOT FOUND</span>
|
||||
</h1>
|
||||
|
||||
</section>
|
||||
<!-- Gradient divider -->
|
||||
<div class="gradient-line w-32 mx-auto" style="animation: mob-link-in 0.4s ease 200ms both"></div>
|
||||
|
||||
<!-- Description -->
|
||||
<p
|
||||
class="text-fog font-body text-base md:text-lg leading-relaxed max-w-md text-pretty"
|
||||
style="animation: mob-link-in 0.5s var(--ease-out-expo) 260ms both"
|
||||
>
|
||||
This drop got pulled. The page you're looking for doesn't exist, was moved, or got lost in the grid.
|
||||
</p>
|
||||
|
||||
<!-- CTA -->
|
||||
<div
|
||||
class="flex flex-wrap gap-4 justify-center"
|
||||
style="animation: mob-link-in 0.5s var(--ease-out-expo) 340ms both"
|
||||
>
|
||||
<a href="/" class="btn">Back to the Drop {{ partial "icon.html" "arrow-right" }}</a>
|
||||
<a href="/posts/" class="btn btn-ghost">Browse all posts {{ partial "icon.html" "arrow-right" }}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bottom graffiti tag -->
|
||||
<div
|
||||
class="graffiti-tag absolute -bottom-6 left-1/2 -translate-x-1/2 select-none pointer-events-none whitespace-nowrap"
|
||||
style="-webkit-text-stroke-color: rgba(0,200,255,0.05);"
|
||||
aria-hidden="true"
|
||||
>
|
||||
LOST
|
||||
</div>
|
||||
</section>
|
||||
{{- end -}}
|
||||
|
||||
+155
-119
@@ -1,33 +1,34 @@
|
||||
<!doctype html>
|
||||
<html lang="{{ .Site.LanguageCode }}" class="scroll-smooth">
|
||||
<head>
|
||||
{{- partial "head.html" . -}}
|
||||
</head>
|
||||
<body
|
||||
class="bg-void text-paper font-body min-h-screen flex flex-col antialiased"
|
||||
hx-boost="true"
|
||||
hx-select="#main-content"
|
||||
hx-target="#main-content"
|
||||
hx-swap="outerHTML"
|
||||
hx-push-url="true"
|
||||
>
|
||||
<head>
|
||||
{{- partial "head.html" . -}}
|
||||
</head>
|
||||
<body
|
||||
class="bg-void text-paper font-body min-h-screen flex flex-col antialiased"
|
||||
hx-boost="true"
|
||||
hx-select="#main-content"
|
||||
hx-target="#main-content"
|
||||
hx-swap="outerHTML"
|
||||
hx-push-url="true"
|
||||
>
|
||||
<!-- HTMX transition progress bar -->
|
||||
<div id="progress-bar" aria-hidden="true"></div>
|
||||
|
||||
<!-- HTMX transition progress bar -->
|
||||
<div id="progress-bar" aria-hidden="true"></div>
|
||||
{{- partial "nav.html" . -}}
|
||||
|
||||
{{- partial "nav.html" . -}}
|
||||
{{- block "page-background" . -}}{{- end -}}
|
||||
|
||||
{{- block "page-background" . -}}{{- end -}}
|
||||
|
||||
<main id="main-content" class="flex-1" hx-history-elt>
|
||||
{{- block "main" . }}{{- end }}
|
||||
</main>
|
||||
<main id="main-content" class="flex-1" hx-history-elt>
|
||||
{{- block "main" . }}{{- end }}
|
||||
</main>
|
||||
|
||||
{{- partial "footer.html" . -}}
|
||||
{{- partial "footer.html" . -}}
|
||||
|
||||
<!-- ── Global lightbox — lives outside #main-content so HTMX never touches it -->
|
||||
<div
|
||||
x-data="{
|
||||
|
||||
<!-- ── Global lightbox — lives outside #main-content so HTMX never touches it -->
|
||||
<div
|
||||
x-data="{
|
||||
open: false,
|
||||
idx: 0,
|
||||
fill: false,
|
||||
@@ -36,106 +37,141 @@
|
||||
prev() { this.idx = (this.idx - 1 + this.items.length) % this.items.length },
|
||||
next() { this.idx = (this.idx + 1) % this.items.length }
|
||||
}"
|
||||
@lightbox:open.window="items = $event.detail.items; idx = $event.detail.idx; open = true"
|
||||
@lightbox:close.window="close()"
|
||||
@keydown.escape.window="open && close()"
|
||||
@keydown.arrow-left.window="open && prev()"
|
||||
@keydown.arrow-right.window="open && next()"
|
||||
@keydown.f.window="open && (fill = !fill)"
|
||||
x-show="open"
|
||||
x-cloak
|
||||
x-transition:enter="transition duration-200 ease-out"
|
||||
x-transition:enter-start="opacity-0"
|
||||
x-transition:enter-end="opacity-100"
|
||||
x-transition:leave="transition duration-150 ease-in"
|
||||
x-transition:leave-start="opacity-100"
|
||||
x-transition:leave-end="opacity-0"
|
||||
class="fixed inset-0 z-[200] flex items-center justify-center bg-void/95 backdrop-blur-sm"
|
||||
@click.self="close()"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label="Media lightbox"
|
||||
>
|
||||
<!-- Top chrome bar -->
|
||||
<div class="absolute top-0 inset-x-0 z-10 flex items-center justify-between px-2 h-14"
|
||||
style="background: linear-gradient(to bottom, rgba(5,5,16,0.85), transparent)">
|
||||
<div class="absolute top-0 inset-x-0 h-px gradient-line"></div>
|
||||
<div class="w-24"></div>
|
||||
<div class="label text-fog tabular-nums"
|
||||
x-text="items.length ? `${String(idx + 1).padStart(2,'0')} / ${String(items.length).padStart(2,'0')}` : ''"></div>
|
||||
<div class="flex items-center w-24 justify-end gap-1">
|
||||
<button @click="fill = !fill"
|
||||
class="w-10 h-10 flex items-center justify-center label transition-colors"
|
||||
:class="fill ? 'text-heat hover:text-chalk' : 'text-fog hover:text-heat'"
|
||||
:aria-label="fill ? 'Switch to fit mode' : 'Switch to fill mode'"
|
||||
x-text="fill ? 'FIT' : 'FILL'"></button>
|
||||
<button @click="close()"
|
||||
class="w-10 h-10 flex items-center justify-center label text-fog hover:text-chalk transition-colors"
|
||||
aria-label="Close">✕</button>
|
||||
@lightbox:open.window="items = $event.detail.items; idx = $event.detail.idx; open = true"
|
||||
@lightbox:close.window="close()"
|
||||
@keydown.escape.window="open && close()"
|
||||
@keydown.arrow-left.window="open && prev()"
|
||||
@keydown.arrow-right.window="open && next()"
|
||||
@keydown.f.window="open && (fill = !fill)"
|
||||
x-show="open"
|
||||
x-cloak
|
||||
x-transition:enter="transition duration-200 ease-out"
|
||||
x-transition:enter-start="opacity-0"
|
||||
x-transition:enter-end="opacity-100"
|
||||
x-transition:leave="transition duration-150 ease-in"
|
||||
x-transition:leave-start="opacity-100"
|
||||
x-transition:leave-end="opacity-0"
|
||||
class="fixed inset-0 z-[200] flex items-center justify-center bg-void/95 backdrop-blur-sm"
|
||||
@click.self="close()"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label="Media lightbox"
|
||||
>
|
||||
<!-- Top chrome bar -->
|
||||
<div
|
||||
class="absolute top-0 inset-x-0 z-10 flex items-center justify-between px-2 h-14"
|
||||
style="background: linear-gradient(to bottom, rgba(5,5,16,0.85), transparent)"
|
||||
>
|
||||
<div class="absolute top-0 inset-x-0 h-px gradient-line"></div>
|
||||
<div class="w-24"></div>
|
||||
<div
|
||||
class="label text-fog tabular-nums"
|
||||
x-text="items.length ? `${String(idx + 1).padStart(2,'0')} / ${String(items.length).padStart(2,'0')}` : ''"
|
||||
></div>
|
||||
<div class="flex items-center w-24 justify-end gap-1">
|
||||
<button
|
||||
@click="fill = !fill"
|
||||
class="w-10 h-10 flex items-center justify-center label transition-colors"
|
||||
:class="fill ? 'text-heat hover:text-chalk' : 'text-fog hover:text-heat'"
|
||||
:aria-label="fill ? 'Switch to fit mode' : 'Switch to fill mode'"
|
||||
x-text="fill ? 'FIT' : 'FILL'"
|
||||
></button>
|
||||
<button
|
||||
@click="close()"
|
||||
class="w-10 h-10 flex items-center justify-center label text-fog hover:text-chalk transition-colors"
|
||||
aria-label="Close"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Prev -->
|
||||
<button
|
||||
@click="prev()"
|
||||
x-show="items.length > 1"
|
||||
class="absolute left-2 top-1/2 -translate-y-1/2 z-10 w-10 h-10 md:w-12 md:h-12 flex items-center justify-center text-fog hover:text-heat transition-colors text-xl"
|
||||
aria-label="Previous"
|
||||
>
|
||||
{{- partial "icon.html" "arrow-left" -}}
|
||||
</button>
|
||||
<!-- Media -->
|
||||
<div
|
||||
class="absolute inset-0 flex items-center justify-center transition-[padding] duration-300"
|
||||
:class="fill ? 'p-0' : 'pt-14 pb-14 px-14 md:px-20'"
|
||||
>
|
||||
<video
|
||||
x-show="items[idx] && items[idx].video"
|
||||
:src="items[idx] && items[idx].video ? items[idx].video : ''"
|
||||
:poster="items[idx] ? items[idx].img : ''"
|
||||
x-effect="if (open && items[idx] && items[idx].video) { $el.load(); $el.play() }"
|
||||
:class="fill ? 'w-full h-full object-cover' : 'max-w-full max-h-full object-contain'"
|
||||
class="transition-all duration-300"
|
||||
controls
|
||||
loop
|
||||
muted
|
||||
playsinline
|
||||
></video>
|
||||
<img
|
||||
x-show="items[idx] && !items[idx].video"
|
||||
:src="items[idx] && !items[idx].video ? items[idx].img : ''"
|
||||
alt=""
|
||||
:class="fill ? 'w-full h-full object-cover' : 'max-w-full max-h-full object-contain'"
|
||||
class="transition-all duration-300"
|
||||
/>
|
||||
</div>
|
||||
<!-- Next -->
|
||||
<button
|
||||
@click="next()"
|
||||
x-show="items.length > 1"
|
||||
class="absolute right-2 top-1/2 -translate-y-1/2 z-10 w-10 h-10 md:w-12 md:h-12 flex items-center justify-center text-fog hover:text-heat transition-colors text-xl"
|
||||
aria-label="Next"
|
||||
>
|
||||
{{- partial "icon.html" "arrow-right" -}}
|
||||
</button>
|
||||
<!-- Dots -->
|
||||
<div
|
||||
x-show="items.length > 1"
|
||||
class="absolute bottom-0 inset-x-0 z-10 flex items-center justify-center h-14 gap-2"
|
||||
style="background: linear-gradient(to top, rgba(5,5,16,0.85), transparent)"
|
||||
>
|
||||
<div class="absolute bottom-0 inset-x-0 h-px gradient-line"></div>
|
||||
<template x-for="(item, i) in items" :key="i">
|
||||
<button
|
||||
@click="idx = i"
|
||||
class="h-1.5 rounded-full transition-all duration-300"
|
||||
:class="i === idx ? 'w-5 bg-heat' : 'w-1.5 bg-fog hover:bg-chalk'"
|
||||
:aria-label="`Go to item ${i + 1}`"
|
||||
></button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Prev -->
|
||||
<button @click="prev()" x-show="items.length > 1"
|
||||
class="absolute left-2 top-1/2 -translate-y-1/2 z-10 w-10 h-10 md:w-12 md:h-12 flex items-center justify-center text-fog hover:text-heat transition-colors text-xl"
|
||||
aria-label="Previous">{{- partial "icon.html" "arrow-left" -}}</button>
|
||||
<!-- Media -->
|
||||
<div class="absolute inset-0 flex items-center justify-center transition-[padding] duration-300"
|
||||
:class="fill ? 'p-0' : 'pt-14 pb-14 px-14 md:px-20'">
|
||||
<video
|
||||
x-show="items[idx] && items[idx].video"
|
||||
:src="items[idx] && items[idx].video ? items[idx].video : ''"
|
||||
:poster="items[idx] ? items[idx].img : ''"
|
||||
x-effect="if (open && items[idx] && items[idx].video) { $el.load(); $el.play() }"
|
||||
:class="fill ? 'w-full h-full object-cover' : 'max-w-full max-h-full object-contain'"
|
||||
class="transition-all duration-300"
|
||||
controls loop muted playsinline></video>
|
||||
<img
|
||||
x-show="items[idx] && !items[idx].video"
|
||||
:src="items[idx] && !items[idx].video ? items[idx].img : ''"
|
||||
alt=""
|
||||
:class="fill ? 'w-full h-full object-cover' : 'max-w-full max-h-full object-contain'"
|
||||
class="transition-all duration-300">
|
||||
</div>
|
||||
<!-- Next -->
|
||||
<button @click="next()" x-show="items.length > 1"
|
||||
class="absolute right-2 top-1/2 -translate-y-1/2 z-10 w-10 h-10 md:w-12 md:h-12 flex items-center justify-center text-fog hover:text-heat transition-colors text-xl"
|
||||
aria-label="Next">{{- partial "icon.html" "arrow-right" -}}</button>
|
||||
<!-- Dots -->
|
||||
<div x-show="items.length > 1"
|
||||
class="absolute bottom-0 inset-x-0 z-10 flex items-center justify-center h-14 gap-2"
|
||||
style="background: linear-gradient(to top, rgba(5,5,16,0.85), transparent)">
|
||||
<div class="absolute bottom-0 inset-x-0 h-px gradient-line"></div>
|
||||
<template x-for="(item, i) in items" :key="i">
|
||||
<button @click="idx = i"
|
||||
class="h-1.5 rounded-full transition-all duration-300"
|
||||
:class="i === idx ? 'w-5 bg-heat' : 'w-1.5 bg-fog hover:bg-chalk'"
|
||||
:aria-label="`Go to item ${i + 1}`"></button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- HTMX (page transitions & progressive enhancement) -->
|
||||
<script src="https://unpkg.com/htmx.org@2.0.4/dist/htmx.min.js"></script>
|
||||
<!-- Alpine.js store initialisation (must run before alpine:init) -->
|
||||
<script>
|
||||
document.addEventListener('alpine:init', () => {
|
||||
Alpine.store('nav', { path: window.location.pathname, open: false })
|
||||
})
|
||||
</script>
|
||||
<!-- Alpine.js (reactive UI — nav, interactions) -->
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.14.8/dist/cdn.min.js"></script>
|
||||
<!-- Site JS -->
|
||||
{{- $js := resources.Get "js/main.js" -}}
|
||||
{{- if eq hugo.Environment "production" -}}
|
||||
{{- $js = $js | minify | fingerprint "sha256" -}}
|
||||
{{- end -}}
|
||||
<script src="{{ $js.RelPermalink }}"{{ if eq hugo.Environment "production" }} integrity="{{ $js.Data.Integrity }}"{{ end }} defer></script>
|
||||
<!-- HTMX (page transitions & progressive enhancement) -->
|
||||
<script src="https://unpkg.com/htmx.org@2.0.4/dist/htmx.min.js"></script>
|
||||
<!-- Alpine.js store initialisation (must run before alpine:init) -->
|
||||
<script>
|
||||
document.addEventListener("alpine:init", () => {
|
||||
Alpine.store("nav", { path: window.location.pathname, open: false });
|
||||
});
|
||||
</script>
|
||||
<!-- Alpine.js (reactive UI — nav, interactions) -->
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.14.8/dist/cdn.min.js"></script>
|
||||
<!-- Site JS -->
|
||||
{{- $js := resources.Get "js/main.js" -}}
|
||||
{{- if eq hugo.Environment "production" -}}
|
||||
{{- $js = $js | minify | fingerprint "sha256" -}}
|
||||
{{- end -}}
|
||||
<script
|
||||
src="{{ $js.RelPermalink }}"
|
||||
{{ if eq hugo.Environment "production" }}integrity="{{ $js.Data.Integrity }}"{{ end }}
|
||||
defer
|
||||
></script>
|
||||
|
||||
{{- block "scripts" . }}{{- end }}
|
||||
{{- block "scripts" . }}{{- end }}
|
||||
|
||||
{{- if and (eq hugo.Environment "production") .Site.Params.umamiId -}}
|
||||
<!-- Umami analytics — cookie-free, self-hosted -->
|
||||
<script defer src="{{ .Site.Params.umamiSrc }}" data-website-id="{{ .Site.Params.umamiId }}"></script>
|
||||
{{- end -}}
|
||||
</body>
|
||||
{{- if and (eq hugo.Environment "production") .Site.Params.umamiId -}}
|
||||
<!-- Umami analytics — cookie-free, self-hosted -->
|
||||
<script defer src="{{ .Site.Params.umamiSrc }}" data-website-id="{{ .Site.Params.umamiId }}"></script>
|
||||
{{- end -}}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+27
-29
@@ -1,34 +1,32 @@
|
||||
{{- define "main" -}}
|
||||
<!-- Page header -->
|
||||
<header class="gutter-x pt-36 pb-14 border-b border-zinc speed-lines">
|
||||
<div class="max-w-4xl">
|
||||
{{- with .Data.Singular -}}
|
||||
<span class="badge badge-pulse mb-5 inline-block">{{ . | title }}</span>
|
||||
{{- else -}}
|
||||
<span class="badge badge-pulse mb-5 inline-block">{{ .Kind | title }}</span>
|
||||
{{- end -}}
|
||||
<h1 class="font-display text-5xl md:text-7xl text-paper text-balance leading-none">{{ .Title }}</h1>
|
||||
{{- with .Description -}}
|
||||
<p class="text-chalk text-lg mt-5 max-w-xl leading-relaxed text-pretty font-body">{{ . }}</p>
|
||||
{{- end -}}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Page header -->
|
||||
<header class="gutter-x pt-36 pb-14 border-b border-zinc speed-lines">
|
||||
<div class="max-w-4xl">
|
||||
{{- with .Data.Singular -}}
|
||||
<span class="badge badge-pulse mb-5 inline-block">{{ . | title }}</span>
|
||||
<div class="gradient-line"></div>
|
||||
|
||||
<!-- Posts grid -->
|
||||
<section class="gutter-x py-16 md:py-24">
|
||||
{{- if .Paginator.Pages -}}
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-5">
|
||||
{{- range .Paginator.Pages -}}
|
||||
{{- partial "post-card.html" . -}}
|
||||
{{- end -}}
|
||||
</div>
|
||||
{{- partial "pagination.html" . -}}
|
||||
{{- else -}}
|
||||
<span class="badge badge-pulse mb-5 inline-block">{{ .Kind | title }}</span>
|
||||
<p class="label text-fog text-center py-24">No posts yet.</p>
|
||||
{{- end -}}
|
||||
<h1 class="font-display text-5xl md:text-7xl text-paper text-balance leading-none">{{ .Title }}</h1>
|
||||
{{- with .Description -}}
|
||||
<p class="text-chalk text-lg mt-5 max-w-xl leading-relaxed text-pretty font-body">{{ . }}</p>
|
||||
{{- end -}}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="gradient-line"></div>
|
||||
|
||||
<!-- Posts grid -->
|
||||
<section class="gutter-x py-16 md:py-24">
|
||||
{{- if .Paginator.Pages -}}
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-5">
|
||||
{{- range .Paginator.Pages -}}
|
||||
{{- partial "post-card.html" . -}}
|
||||
{{- end -}}
|
||||
</div>
|
||||
{{- partial "pagination.html" . -}}
|
||||
{{- else -}}
|
||||
<p class="label text-fog text-center py-24">No posts yet.</p>
|
||||
{{- end -}}
|
||||
</section>
|
||||
|
||||
</section>
|
||||
{{- end -}}
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
{{- define "main" -}}
|
||||
<!-- Page header -->
|
||||
<header class="gutter-x pt-36 pb-16 border-b border-mist">
|
||||
<div class="max-w-3xl">
|
||||
<h1 class="font-display text-5xl md:text-7xl text-paper text-balance mb-5">{{ .Title }}</h1>
|
||||
{{- with .Description -}}
|
||||
<p class="text-chalk text-xl leading-relaxed text-pretty">{{ . }}</p>
|
||||
{{- end -}}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Page header -->
|
||||
<header class="gutter-x pt-36 pb-16 border-b border-mist">
|
||||
<div class="max-w-3xl">
|
||||
<h1 class="font-display text-5xl md:text-7xl text-paper text-balance mb-5">{{ .Title }}</h1>
|
||||
{{- with .Description -}}
|
||||
<p class="text-chalk text-xl leading-relaxed text-pretty">{{ . }}</p>
|
||||
{{- end -}}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Page content -->
|
||||
<article class="gutter-x py-16 md:py-24 max-w-3xl prose-editorial">
|
||||
{{ .Content }}
|
||||
</article>
|
||||
|
||||
<!-- Page content -->
|
||||
<article class="gutter-x py-16 md:py-24 max-w-3xl prose-editorial">
|
||||
{{ .Content }}
|
||||
</article>
|
||||
{{- end -}}
|
||||
|
||||
+30
-27
@@ -1,32 +1,35 @@
|
||||
{{- define "main" -}}
|
||||
<!-- Taxonomy header -->
|
||||
<header class="gutter-x pt-36 pb-14 border-b border-zinc speed-lines">
|
||||
<div class="max-w-4xl">
|
||||
<span class="badge badge-frost text-void mb-5 inline-block">Browse</span>
|
||||
<h1 class="font-display text-5xl md:text-7xl text-paper leading-none">{{ .Title }}</h1>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Taxonomy header -->
|
||||
<header class="gutter-x pt-36 pb-14 border-b border-zinc speed-lines">
|
||||
<div class="max-w-4xl">
|
||||
<span class="badge badge-frost text-void mb-5 inline-block">Browse</span>
|
||||
<h1 class="font-display text-5xl md:text-7xl text-paper leading-none">{{ .Title }}</h1>
|
||||
</div>
|
||||
</header>
|
||||
<div class="gradient-line"></div>
|
||||
|
||||
<div class="gradient-line"></div>
|
||||
|
||||
<!-- Terms badge cloud -->
|
||||
<section class="gutter-x py-16 md:py-24">
|
||||
{{- if .Data.Terms -}}
|
||||
<div class="flex flex-wrap gap-4">
|
||||
{{- range .Data.Terms.Alphabetical -}}
|
||||
<a
|
||||
href="{{ .Page.RelPermalink }}"
|
||||
class="group card-comic flex items-baseline gap-3 bg-concrete px-5 py-4 hover:border-heat transition-all"
|
||||
>
|
||||
<span class="font-display text-2xl text-paper group-hover:text-heat transition-colors leading-none">{{ .Page.Title }}</span>
|
||||
<span class="badge badge-outline text-xs ml-1 group-hover:bg-heat group-hover:text-white group-hover:border-heat transition-all">{{ .Count }}</span>
|
||||
</a>
|
||||
<!-- Terms badge cloud -->
|
||||
<section class="gutter-x py-16 md:py-24">
|
||||
{{- if .Data.Terms -}}
|
||||
<div class="flex flex-wrap gap-4">
|
||||
{{- range .Data.Terms.Alphabetical -}}
|
||||
<a
|
||||
href="{{ .Page.RelPermalink }}"
|
||||
class="group card-comic flex items-baseline gap-3 bg-concrete px-5 py-4 hover:border-heat transition-all"
|
||||
>
|
||||
<span class="font-display text-2xl text-paper group-hover:text-heat transition-colors leading-none"
|
||||
>{{ .Page.Title }}</span
|
||||
>
|
||||
<span
|
||||
class="badge badge-outline text-xs ml-1 group-hover:bg-heat group-hover:text-white group-hover:border-heat transition-all"
|
||||
>{{ .Count }}</span
|
||||
>
|
||||
</a>
|
||||
{{- end -}}
|
||||
</div>
|
||||
{{- else -}}
|
||||
<p class="label text-fog text-center py-24">No entries yet.</p>
|
||||
{{- end -}}
|
||||
</div>
|
||||
{{- else -}}
|
||||
<p class="label text-fog text-center py-24">No entries yet.</p>
|
||||
{{- end -}}
|
||||
</section>
|
||||
|
||||
</section>
|
||||
{{- end -}}
|
||||
|
||||
+52
-47
@@ -1,54 +1,59 @@
|
||||
{{- define "main" -}}
|
||||
<!-- Header -->
|
||||
<header class="gutter-x pt-36 pb-14 border-b border-zinc speed-lines">
|
||||
<span class="badge badge-frost text-void mb-5 inline-block">Contributors</span>
|
||||
<h1 class="font-display text-5xl md:text-7xl text-paper leading-none">Authors</h1>
|
||||
</header>
|
||||
|
||||
<!-- Header -->
|
||||
<header class="gutter-x pt-36 pb-14 border-b border-zinc speed-lines">
|
||||
<span class="badge badge-frost text-void mb-5 inline-block">Contributors</span>
|
||||
<h1 class="font-display text-5xl md:text-7xl text-paper leading-none">Authors</h1>
|
||||
</header>
|
||||
<div class="gradient-line"></div>
|
||||
|
||||
<div class="gradient-line"></div>
|
||||
<!-- Authors grid -->
|
||||
<section class="gutter-x py-16 md:py-24">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{{- range .Pages -}}
|
||||
<a href="{{ .RelPermalink }}" class="group card-comic flex flex-col items-start gap-5 p-6 bg-concrete">
|
||||
<!-- Avatar — bundle avatar.* first, fall back to .Params.avatar -->
|
||||
{{- $avatarRes := .Resources.GetMatch "avatar.*" -}}
|
||||
{{- $avatarSrc := "" -}}
|
||||
{{- with $avatarRes }}
|
||||
{{ $avatarSrc = .RelPermalink }}
|
||||
{{ else }}
|
||||
{{ with $.Params.avatar }}{{ $avatarSrc = . }}{{ end }}
|
||||
{{ end -}}
|
||||
{{- if $avatarSrc -}}
|
||||
<div class="w-20 h-20 overflow-hidden border border-zinc group-hover:border-heat transition-colors">
|
||||
<img
|
||||
src="{{ $avatarSrc }}"
|
||||
alt="{{ .Params.name | default .Title }}"
|
||||
class="w-full h-full object-cover transition-transform duration-700 group-hover:scale-105"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
{{- else -}}
|
||||
<div class="w-20 h-20 bg-zinc border border-zinc flex items-center justify-center">
|
||||
<span class="font-display text-2xl text-fog">{{ substr (.Params.name | default .Title) 0 1 }}</span>
|
||||
</div>
|
||||
{{- end -}}
|
||||
|
||||
<!-- Authors grid -->
|
||||
<section class="gutter-x py-16 md:py-24">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{{- range .Pages -}}
|
||||
<a href="{{ .RelPermalink }}" class="group card-comic flex flex-col items-start gap-5 p-6 bg-concrete">
|
||||
|
||||
<!-- Avatar — bundle avatar.* first, fall back to .Params.avatar -->
|
||||
{{- $avatarRes := .Resources.GetMatch "avatar.*" -}}
|
||||
{{- $avatarSrc := "" -}}
|
||||
{{- with $avatarRes }}{{ $avatarSrc = .RelPermalink }}{{ else }}{{ with $.Params.avatar }}{{ $avatarSrc = . }}{{ end }}{{ end -}}
|
||||
{{- if $avatarSrc -}}
|
||||
<div class="w-20 h-20 overflow-hidden border border-zinc group-hover:border-heat transition-colors">
|
||||
<img
|
||||
src="{{ $avatarSrc }}"
|
||||
alt="{{ .Params.name | default .Title }}"
|
||||
class="w-full h-full object-cover transition-transform duration-700 group-hover:scale-105"
|
||||
loading="lazy"
|
||||
>
|
||||
</div>
|
||||
{{- else -}}
|
||||
<div class="w-20 h-20 bg-zinc border border-zinc flex items-center justify-center">
|
||||
<span class="font-display text-2xl text-fog">{{ substr (.Params.name | default .Title) 0 1 }}</span>
|
||||
</div>
|
||||
<!-- Info -->
|
||||
<div class="flex-1">
|
||||
<h2 class="font-display text-xl text-paper group-hover:text-heat transition-colors mb-2 leading-none">
|
||||
{{ .Params.name | default .Title }}
|
||||
</h2>
|
||||
{{- with .Params.bio -}}
|
||||
<p class="text-fog text-sm leading-relaxed line-clamp-3 mt-2 font-body">{{ . }}</p>
|
||||
{{- end -}}
|
||||
</div>
|
||||
|
||||
<span
|
||||
class="badge badge-outline group-hover:bg-heat group-hover:text-white group-hover:border-heat transition-all mt-auto"
|
||||
>
|
||||
View works
|
||||
{{ partial "icon.html" "arrow-right" }}
|
||||
</span>
|
||||
</a>
|
||||
{{- end -}}
|
||||
|
||||
<!-- Info -->
|
||||
<div class="flex-1">
|
||||
<h2 class="font-display text-xl text-paper group-hover:text-heat transition-colors mb-2 leading-none">
|
||||
{{ .Params.name | default .Title }}
|
||||
</h2>
|
||||
{{- with .Params.bio -}}
|
||||
<p class="text-fog text-sm leading-relaxed line-clamp-3 mt-2 font-body">{{ . }}</p>
|
||||
{{- end -}}
|
||||
</div>
|
||||
|
||||
<span class="badge badge-outline group-hover:bg-heat group-hover:text-white group-hover:border-heat transition-all mt-auto">
|
||||
View works {{ partial "icon.html" "arrow-right" }}
|
||||
</span>
|
||||
</a>
|
||||
{{- end -}}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
{{- end -}}
|
||||
|
||||
+91
-83
@@ -1,97 +1,105 @@
|
||||
{{- define "main" -}}
|
||||
{{- $slug := .File.Dir | strings.TrimSuffix "/" | path.Base -}}
|
||||
{{- $posts := where .Site.RegularPages "Params.author" $slug -}}
|
||||
{{- $slug := .File.Dir | strings.TrimSuffix "/" | path.Base -}}
|
||||
{{- $posts := where .Site.RegularPages "Params.author" $slug -}}
|
||||
|
||||
<!-- ── PROFILE HEADER ────────────────────────────────────────── -->
|
||||
<header class="gutter-x pt-36 pb-20 border-b border-zinc speed-lines">
|
||||
<div class="max-w-5xl mx-auto">
|
||||
<div class="flex flex-col md:flex-row gap-10 md:gap-16 items-start">
|
||||
|
||||
<!-- Avatar — page bundle avatar.* takes precedence, falls back to .Params.avatar -->
|
||||
{{- $avatarRes := .Resources.GetMatch "avatar.*" -}}
|
||||
{{- $avatarSrc := "" -}}
|
||||
{{- with $avatarRes }}{{ $avatarSrc = .RelPermalink }}{{ else }}{{ with $.Params.avatar }}{{ $avatarSrc = . }}{{ end }}{{ end -}}
|
||||
{{- if $avatarSrc -}}
|
||||
<div class="flex-shrink-0 w-36 h-36 md:w-48 md:h-48 overflow-hidden border-2 border-heat neon-heat">
|
||||
<img
|
||||
src="{{ $avatarSrc }}"
|
||||
alt="{{ .Params.name | default .Title }}"
|
||||
class="w-full h-full object-cover"
|
||||
loading="lazy"
|
||||
>
|
||||
</div>
|
||||
{{- else -}}
|
||||
<div class="flex-shrink-0 w-36 h-36 md:w-48 md:h-48 bg-concrete border-2 border-zinc flex items-center justify-center">
|
||||
<span class="font-display text-5xl text-smoke">{{ substr (.Params.name | default .Title) 0 1 }}</span>
|
||||
</div>
|
||||
{{- end -}}
|
||||
|
||||
<!-- Info -->
|
||||
<div class="flex-1 min-w-0">
|
||||
<span class="badge badge-gradient mb-5 inline-block">Artist</span>
|
||||
<h1 class="font-display text-4xl md:text-6xl text-paper text-balance mb-4 leading-none">
|
||||
{{ .Params.name | default .Title }}
|
||||
</h1>
|
||||
{{- with .Params.bio -}}
|
||||
<p class="text-chalk text-lg max-w-2xl leading-relaxed text-pretty mb-8 font-body font-light">{{ . }}</p>
|
||||
<!-- ── PROFILE HEADER ────────────────────────────────────────── -->
|
||||
<header class="gutter-x pt-36 pb-20 border-b border-zinc speed-lines">
|
||||
<div class="max-w-5xl mx-auto">
|
||||
<div class="flex flex-col md:flex-row gap-10 md:gap-16 items-start">
|
||||
<!-- Avatar — page bundle avatar.* takes precedence, falls back to .Params.avatar -->
|
||||
{{- $avatarRes := .Resources.GetMatch "avatar.*" -}}
|
||||
{{- $avatarSrc := "" -}}
|
||||
{{- with $avatarRes }}
|
||||
{{ $avatarSrc = .RelPermalink }}
|
||||
{{ else }}
|
||||
{{ with $.Params.avatar }}{{ $avatarSrc = . }}{{ end }}
|
||||
{{ end -}}
|
||||
{{- if $avatarSrc -}}
|
||||
<div class="flex-shrink-0 w-36 h-36 md:w-48 md:h-48 overflow-hidden border-2 border-heat neon-heat">
|
||||
<img
|
||||
src="{{ $avatarSrc }}"
|
||||
alt="{{ .Params.name | default .Title }}"
|
||||
class="w-full h-full object-cover"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
{{- else -}}
|
||||
<div
|
||||
class="flex-shrink-0 w-36 h-36 md:w-48 md:h-48 bg-concrete border-2 border-zinc flex items-center justify-center"
|
||||
>
|
||||
<span class="font-display text-5xl text-smoke">{{ substr (.Params.name | default .Title) 0 1 }}</span>
|
||||
</div>
|
||||
{{- end -}}
|
||||
|
||||
<!-- Social links — values may be full URLs or bare slugs -->
|
||||
{{- with .Params.social -}}
|
||||
{{- $bases := dict "instagram" "https://instagram.com/" "x" "https://x.com/" "twitter" "https://x.com/" "github" "https://github.com/" "behance" "https://www.behance.net/" "artstation" "https://www.artstation.com/" -}}
|
||||
<div class="flex flex-wrap gap-3">
|
||||
{{- range $platform, $handle := . -}}
|
||||
{{- $href := cond (hasPrefix $handle "http") $handle (printf "%s%s" (index $bases $platform | default "#") $handle) -}}
|
||||
<a
|
||||
href="{{ $href }}"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="badge badge-outline"
|
||||
>{{ $platform | upper }} {{ partial "icon.html" "arrow-external" }}</a>
|
||||
|
||||
<!-- Info -->
|
||||
<div class="flex-1 min-w-0">
|
||||
<span class="badge badge-gradient mb-5 inline-block">Artist</span>
|
||||
<h1 class="font-display text-4xl md:text-6xl text-paper text-balance mb-4 leading-none">
|
||||
{{ .Params.name | default .Title }}
|
||||
</h1>
|
||||
{{- with .Params.bio -}}
|
||||
<p class="text-chalk text-lg max-w-2xl leading-relaxed text-pretty mb-8 font-body font-light">{{ . }}</p>
|
||||
{{- end -}}
|
||||
|
||||
|
||||
<!-- Social links — values may be full URLs or bare slugs -->
|
||||
{{- with .Params.social -}}
|
||||
{{- $bases := dict "instagram" "https://instagram.com/" "x" "https://x.com/" "twitter" "https://x.com/" "github" "https://github.com/" "behance" "https://www.behance.net/" "artstation" "https://www.artstation.com/" -}}
|
||||
<div class="flex flex-wrap gap-3">
|
||||
{{- range $platform, $handle := . -}}
|
||||
{{- $href := cond (hasPrefix $handle "http") $handle (printf "%s%s" (index $bases $platform | default "#") $handle) -}}
|
||||
<a href="{{ $href }}" target="_blank" rel="noopener noreferrer" class="badge badge-outline"
|
||||
>{{ $platform | upper }} {{ partial "icon.html" "arrow-external" }}</a
|
||||
>
|
||||
{{- end -}}
|
||||
</div>
|
||||
{{- end -}}
|
||||
|
||||
|
||||
<!-- Post count -->
|
||||
{{- if $posts -}}
|
||||
<p class="label text-fog mt-6">
|
||||
{{ len $posts }} {{ if eq (len $posts) 1 }}editorial{{ else }}editorials{{ end }}
|
||||
</p>
|
||||
{{- end -}}
|
||||
</div>
|
||||
{{- end -}}
|
||||
|
||||
<!-- Post count -->
|
||||
{{- if $posts -}}
|
||||
<p class="label text-fog mt-6">{{ len $posts }} {{ if eq (len $posts) 1 }}editorial{{ else }}editorials{{ end }}</p>
|
||||
{{- end -}}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
</header>
|
||||
|
||||
<div class="gradient-line"></div>
|
||||
<div class="gradient-line"></div>
|
||||
|
||||
<!-- ── AUTHOR BIO (Markdown body) ────────────────────────────── -->
|
||||
{{- with .Content -}}
|
||||
<div class="gutter-x py-12 border-b border-zinc">
|
||||
<div class="max-w-5xl mx-auto prose-editorial">{{ . }}</div>
|
||||
</div>
|
||||
{{- end -}}
|
||||
<!-- ── AUTHOR BIO (Markdown body) ────────────────────────────── -->
|
||||
{{- with .Content -}}
|
||||
<div class="gutter-x py-12 border-b border-zinc">
|
||||
<div class="max-w-5xl mx-auto prose-editorial">{{ . }}</div>
|
||||
</div>
|
||||
{{- end -}}
|
||||
|
||||
<!-- ── AUTHOR'S POSTS ─────────────────────────────────────────── -->
|
||||
{{- if $posts -}}
|
||||
<section class="gutter-x py-16 md:py-24">
|
||||
<div class="max-w-5xl mx-auto">
|
||||
<div class="flex items-center gap-5 mb-10">
|
||||
<h2 class="font-display text-3xl md:text-5xl text-paper leading-none">
|
||||
Works by <span class="text-gradient">{{ .Params.name | default .Title }}</span>
|
||||
</h2>
|
||||
<div class="flex-1 gradient-line ml-2"></div>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-5">
|
||||
{{- range first 4 ($posts.ByDate.Reverse) -}}
|
||||
{{- partial "post-card.html" . -}}
|
||||
{{- end -}}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{{- else -}}
|
||||
<div class="gutter-x py-24 text-center">
|
||||
<p class="label text-fog">No editorials published yet.</p>
|
||||
</div>
|
||||
{{- end -}}
|
||||
|
||||
<!-- ── AUTHOR'S POSTS ─────────────────────────────────────────── -->
|
||||
{{- if $posts -}}
|
||||
<section class="gutter-x py-16 md:py-24">
|
||||
<div class="max-w-5xl mx-auto">
|
||||
<div class="flex items-center gap-5 mb-10">
|
||||
<h2 class="font-display text-3xl md:text-5xl text-paper leading-none">
|
||||
Works by <span class="text-gradient">{{ .Params.name | default .Title }}</span>
|
||||
</h2>
|
||||
<div class="flex-1 gradient-line ml-2"></div>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-5">
|
||||
{{- range first 4 ($posts.ByDate.Reverse) -}}
|
||||
{{- partial "post-card.html" . -}}
|
||||
{{- end -}}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{{- else -}}
|
||||
<div class="gutter-x py-24 text-center">
|
||||
<p class="label text-fog">No editorials published yet.</p>
|
||||
</div>
|
||||
{{- end -}}
|
||||
|
||||
{{- end -}}
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
{{- define "main" -}}
|
||||
<!-- Category header -->
|
||||
<header class="gutter-x pt-36 pb-16 border-b border-mist">
|
||||
<div class="max-w-4xl">
|
||||
<span class="label text-ash block mb-4">Category</span>
|
||||
<h1 class="font-display text-5xl md:text-7xl text-paper text-balance">{{ .Title }}</h1>
|
||||
<p class="text-ash label mt-5">
|
||||
{{ len .Pages }} {{ if eq (len .Pages) 1 }}editorial{{ else }}editorials{{ end }}
|
||||
</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Category header -->
|
||||
<header class="gutter-x pt-36 pb-16 border-b border-mist">
|
||||
<div class="max-w-4xl">
|
||||
<span class="label text-ash block mb-4">Category</span>
|
||||
<h1 class="font-display text-5xl md:text-7xl text-paper text-balance">{{ .Title }}</h1>
|
||||
<p class="text-ash label mt-5">{{ len .Pages }} {{ if eq (len .Pages) 1 }}editorial{{ else }}editorials{{ end }}</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Posts grid -->
|
||||
<section class="gutter-x py-16 md:py-24">
|
||||
{{- if .Paginator.Pages -}}
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-x-5 gap-y-12">
|
||||
{{- range .Paginator.Pages -}}
|
||||
{{- partial "post-card.html" . -}}
|
||||
<!-- Posts grid -->
|
||||
<section class="gutter-x py-16 md:py-24">
|
||||
{{- if .Paginator.Pages -}}
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-x-5 gap-y-12">
|
||||
{{- range .Paginator.Pages -}}
|
||||
{{- partial "post-card.html" . -}}
|
||||
{{- end -}}
|
||||
</div>
|
||||
{{- partial "pagination.html" . -}}
|
||||
{{- else -}}
|
||||
<p class="text-ash label text-center py-24">No posts in this category yet.</p>
|
||||
{{- end -}}
|
||||
</div>
|
||||
{{- partial "pagination.html" . -}}
|
||||
{{- else -}}
|
||||
<p class="text-ash label text-center py-24">No posts in this category yet.</p>
|
||||
{{- end -}}
|
||||
</section>
|
||||
|
||||
</section>
|
||||
{{- end -}}
|
||||
|
||||
+146
-122
@@ -1,134 +1,158 @@
|
||||
{{- define "main" -}}
|
||||
{{- $posts := where .Site.RegularPages "Section" "posts" -}}
|
||||
{{- $featured := where $posts "Params.featured" true -}}
|
||||
{{- $hero := index $featured 0 -}}
|
||||
{{- $latest := where $posts "Permalink" "ne" $hero.Permalink -}}
|
||||
{{- $posts := where .Site.RegularPages "Section" "posts" -}}
|
||||
{{- $featured := where $posts "Params.featured" true -}}
|
||||
{{- $hero := index $featured 0 -}}
|
||||
{{- $latest := where $posts "Permalink" "ne" $hero.Permalink -}}
|
||||
|
||||
<!-- ── HERO ──────────────────────────────────────────────────── -->
|
||||
{{- with $hero -}}
|
||||
<section class="relative min-h-screen flex items-end overflow-hidden noise">
|
||||
|
||||
<!-- Background media — bundle banner.png first, fallback to front matter -->
|
||||
{{- $heroBanner := .Resources.GetMatch "banner.png" -}}
|
||||
{{- if not $heroBanner -}}{{- $heroBanner = .Resources.GetMatch "banner.*" -}}{{- end -}}
|
||||
{{- if $heroBanner -}}
|
||||
<div class="absolute inset-0">
|
||||
{{- partial "img.html" (dict "res" $heroBanner "widths" (slice 1200 1920 2560) "sizes" "100vw" "class" "w-full h-full object-cover" "alt" "" "loading" "eager") -}}
|
||||
</div>
|
||||
{{- else -}}
|
||||
{{- $heroBannerSrc := "" -}}
|
||||
{{- with .Params.background }}{{ $heroBannerSrc = .src }}{{ else }}{{ with $.Params.banner }}{{ $heroBannerSrc = .src }}{{ end }}{{ end -}}
|
||||
{{- with $heroBannerSrc -}}
|
||||
<div class="absolute inset-0">
|
||||
<img src="{{ . }}" alt="" class="w-full h-full object-cover" loading="eager" decoding="async">
|
||||
</div>
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
<!-- Cinematic overlay -->
|
||||
<div class="absolute inset-0 overlay-gradient-hero"></div>
|
||||
|
||||
<!-- Hero content -->
|
||||
<div class="relative z-10 gutter-x pb-20 md:pb-32 w-full">
|
||||
<div class="max-w-5xl">
|
||||
|
||||
<!-- Drop label + category badges -->
|
||||
<div class="flex flex-wrap items-center gap-3 mb-6">
|
||||
<span class="badge badge-pulse">Featured Drop</span>
|
||||
{{- with .Params.categories -}}
|
||||
{{- range first 1 . -}}
|
||||
<a href="/categories/{{ . | urlize }}/" class="badge badge-outline">{{ . }}</a>
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
</div>
|
||||
|
||||
<!-- Title -->
|
||||
<h1 class="font-display text-6xl md:text-8xl text-paper text-balance mb-6 leading-none">
|
||||
{{- .Title -}}
|
||||
</h1>
|
||||
|
||||
<!-- Description -->
|
||||
<p class="text-chalk text-base md:text-lg max-w-2xl mb-10 leading-relaxed text-pretty font-body opacity-90 line-clamp-3">
|
||||
{{- .Description -}}
|
||||
</p>
|
||||
|
||||
<!-- CTA + author -->
|
||||
<div class="flex flex-wrap items-center gap-6">
|
||||
<a href="{{ .RelPermalink }}" class="btn btn-outline">View Editorial {{ partial "icon.html" "arrow-right" }}</a>
|
||||
{{- with .Params.author -}}
|
||||
{{- $author := site.GetPage (printf "authors/%s" .) -}}
|
||||
{{- with $author -}}
|
||||
<div class="flex items-center gap-3">
|
||||
{{- $avRes := .Resources.GetMatch "avatar.*" -}}
|
||||
{{- $avSrc := "" -}}
|
||||
{{- with $avRes }}{{ $avSrc = .RelPermalink }}{{ else }}{{ with $author.Params.avatar }}{{ $avSrc = . }}{{ end }}{{ end -}}
|
||||
{{- with $avSrc -}}
|
||||
<img src="{{ . }}" alt="{{ $author.Params.name | default $author.Title }}"
|
||||
class="w-8 h-8 object-cover border border-heat/50">
|
||||
{{- end -}}
|
||||
<span class="label text-chalk">
|
||||
By <a href="{{ .RelPermalink }}" class="text-heat hover:text-frost transition-colors">{{ .Params.name | default .Title }}</a>
|
||||
</span>
|
||||
<!-- ── HERO ──────────────────────────────────────────────────── -->
|
||||
{{- with $hero -}}
|
||||
<section class="relative min-h-screen flex items-end overflow-hidden noise">
|
||||
<!-- Background media — bundle banner.png first, fallback to front matter -->
|
||||
{{- $heroBanner := .Resources.GetMatch "banner.png" -}}
|
||||
{{- if not $heroBanner -}}{{- $heroBanner = .Resources.GetMatch "banner.*" -}}{{- end -}}
|
||||
{{- if $heroBanner -}}
|
||||
<div class="absolute inset-0">
|
||||
{{- partial "img.html" (dict "res" $heroBanner "widths" (slice 1200 1920 2560) "sizes" "100vw" "class" "w-full h-full object-cover" "alt" "" "loading" "eager") -}}
|
||||
</div>
|
||||
{{- else -}}
|
||||
{{- $heroBannerSrc := "" -}}
|
||||
{{- with .Params.background }}
|
||||
{{ $heroBannerSrc = .src }}
|
||||
{{ else }}
|
||||
{{ with $.Params.banner }}{{ $heroBannerSrc = .src }}{{ end }}
|
||||
{{ end -}}
|
||||
{{- with $heroBannerSrc -}}
|
||||
<div class="absolute inset-0">
|
||||
<img src="{{ . }}" alt="" class="w-full h-full object-cover" loading="eager" decoding="async" />
|
||||
</div>
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
|
||||
<!-- Cinematic overlay -->
|
||||
<div class="absolute inset-0 overlay-gradient-hero"></div>
|
||||
|
||||
<!-- Hero content -->
|
||||
<div class="relative z-10 gutter-x pb-20 md:pb-32 w-full">
|
||||
<div class="max-w-5xl">
|
||||
<!-- Drop label + category badges -->
|
||||
<div class="flex flex-wrap items-center gap-3 mb-6">
|
||||
<span class="badge badge-pulse">Featured Drop</span>
|
||||
{{- with .Params.categories -}}
|
||||
{{- range first 1 . -}}
|
||||
<a href="/categories/{{ . | urlize }}/" class="badge badge-outline">{{ . }}</a>
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
</div>
|
||||
|
||||
<!-- Title -->
|
||||
<h1 class="font-display text-6xl md:text-8xl text-paper text-balance mb-6 leading-none">
|
||||
{{- .Title -}}
|
||||
</h1>
|
||||
|
||||
<!-- Description -->
|
||||
<p
|
||||
class="text-chalk text-base md:text-lg max-w-2xl mb-10 leading-relaxed text-pretty font-body opacity-90 line-clamp-3"
|
||||
>
|
||||
{{- .Description -}}
|
||||
</p>
|
||||
|
||||
<!-- CTA + author -->
|
||||
<div class="flex flex-wrap items-center gap-6">
|
||||
<a href="{{ .RelPermalink }}" class="btn btn-outline"
|
||||
>View Editorial {{ partial "icon.html" "arrow-right" }}</a
|
||||
>
|
||||
{{- with .Params.author -}}
|
||||
{{- $author := site.GetPage (printf "authors/%s" .) -}}
|
||||
{{- with $author -}}
|
||||
<div class="flex items-center gap-3">
|
||||
{{- $avRes := .Resources.GetMatch "avatar.*" -}}
|
||||
{{- $avSrc := "" -}}
|
||||
{{- with $avRes }}
|
||||
{{ $avSrc = .RelPermalink }}
|
||||
{{ else }}
|
||||
{{ with $author.Params.avatar }}{{ $avSrc = . }}{{ end }}
|
||||
{{ end -}}
|
||||
{{- with $avSrc -}}
|
||||
<img
|
||||
src="{{ . }}"
|
||||
alt="{{ $author.Params.name | default $author.Title }}"
|
||||
class="w-8 h-8 object-cover border border-heat/50"
|
||||
/>
|
||||
{{- end -}}
|
||||
<span class="label text-chalk">
|
||||
By
|
||||
<a href="{{ .RelPermalink }}" class="text-heat hover:text-frost transition-colors"
|
||||
>{{ .Params.name | default .Title }}</a
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Scroll indicator -->
|
||||
<div class="absolute bottom-8 right-8 z-10 hidden md:flex flex-col items-center gap-2">
|
||||
<span class="label text-fog" style="writing-mode: vertical-lr; letter-spacing: 0.25em;">SCROLL</span>
|
||||
<div class="w-px h-12 bg-zinc relative overflow-hidden">
|
||||
<div class="absolute top-0 left-0 right-0 h-full"
|
||||
style="background: linear-gradient(var(--color-heat), var(--color-frost));
|
||||
animation: scroll-line 1.8s ease-in-out infinite;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
{{- end -}}
|
||||
|
||||
<div class="gradient-line" style="opacity:0.2"></div>
|
||||
|
||||
<!-- ── LATEST POSTS ──────────────────────────────────────────── -->
|
||||
<section class="gutter-x py-16 md:py-24 relative overflow-hidden">
|
||||
|
||||
<!-- Decorative graffiti tag behind heading -->
|
||||
<div class="graffiti-tag absolute -top-6 -right-4 opacity-100 select-none pointer-events-none"
|
||||
style="-webkit-text-stroke-color: rgba(0,200,255,0.07);" aria-hidden="true">DROPS</div>
|
||||
|
||||
<div class="relative z-10 flex flex-wrap items-end justify-between gap-4 mb-10">
|
||||
<h2 class="font-display text-5xl md:text-7xl leading-none">
|
||||
<span class="text-stroke-heat">LATEST</span> <span class="text-gradient">DROPS</span>
|
||||
</h2>
|
||||
<a href="/posts/" class="btn btn-ghost">All posts {{ partial "icon.html" "arrow-right" }}</a>
|
||||
</div>
|
||||
|
||||
<div class="relative z-10 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-5">
|
||||
{{- range first 8 $latest -}}
|
||||
{{- partial "post-card.html" . -}}
|
||||
{{- end -}}
|
||||
</div>
|
||||
|
||||
{{- if gt (len $latest) 8 -}}
|
||||
<div class="relative z-10 mt-14 text-center">
|
||||
<a href="/posts/" class="btn btn-outline">View All {{ len $posts }} Posts {{ partial "icon.html" "arrow-right" }}</a>
|
||||
</div>
|
||||
<!-- Scroll indicator -->
|
||||
<div class="absolute bottom-8 right-8 z-10 hidden md:flex flex-col items-center gap-2">
|
||||
<span class="label text-fog" style="writing-mode: vertical-lr; letter-spacing: 0.25em;">SCROLL</span>
|
||||
<div class="w-px h-12 bg-zinc relative overflow-hidden">
|
||||
<div
|
||||
class="absolute top-0 left-0 right-0 h-full"
|
||||
style="background: linear-gradient(var(--color-heat), var(--color-frost));
|
||||
animation: scroll-line 1.8s ease-in-out infinite;"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{{- end -}}
|
||||
|
||||
</section>
|
||||
|
||||
<!-- ── ABOUT STRIP ───────────────────────────────────────────── -->
|
||||
<section class="bg-concrete border-t border-b border-zinc gutter-x py-16 md:py-20 spray-bg relative overflow-hidden">
|
||||
<div class="max-w-3xl mx-auto text-center relative z-10">
|
||||
<span class="badge badge-frost text-void mb-6 inline-block">About the Magazine</span>
|
||||
<p class="font-display text-3xl md:text-5xl text-paper leading-tight text-balance mb-10">
|
||||
{{ .Site.Params.description }}
|
||||
</p>
|
||||
<a href="/about/" class="btn btn-outline">About the magazine {{ partial "icon.html" "arrow-right" }}</a>
|
||||
</div>
|
||||
</section>
|
||||
<div class="gradient-line" style="opacity:0.2"></div>
|
||||
|
||||
<!-- ── LATEST POSTS ──────────────────────────────────────────── -->
|
||||
<section class="gutter-x py-16 md:py-24 relative overflow-hidden">
|
||||
<!-- Decorative graffiti tag behind heading -->
|
||||
<div
|
||||
class="graffiti-tag absolute -top-6 -right-4 opacity-100 select-none pointer-events-none"
|
||||
style="-webkit-text-stroke-color: rgba(0,200,255,0.07);"
|
||||
aria-hidden="true"
|
||||
>
|
||||
DROPS
|
||||
</div>
|
||||
|
||||
<div class="relative z-10 flex flex-wrap items-end justify-between gap-4 mb-10">
|
||||
<h2 class="font-display text-5xl md:text-7xl leading-none">
|
||||
<span class="text-stroke-heat">LATEST</span> <span class="text-gradient">DROPS</span>
|
||||
</h2>
|
||||
<a href="/posts/" class="btn btn-ghost">All posts {{ partial "icon.html" "arrow-right" }}</a>
|
||||
</div>
|
||||
|
||||
<div class="relative z-10 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-5">
|
||||
{{- range first 8 $latest -}}
|
||||
{{- partial "post-card.html" . -}}
|
||||
{{- end -}}
|
||||
</div>
|
||||
|
||||
{{- if gt (len $latest) 8 -}}
|
||||
<div class="relative z-10 mt-14 text-center">
|
||||
<a href="/posts/" class="btn btn-outline"
|
||||
>View All {{ len $posts }} Posts {{ partial "icon.html" "arrow-right" }}</a
|
||||
>
|
||||
</div>
|
||||
{{- end -}}
|
||||
|
||||
</section>
|
||||
|
||||
<!-- ── ABOUT STRIP ───────────────────────────────────────────── -->
|
||||
<section class="bg-concrete border-t border-b border-zinc gutter-x py-16 md:py-20 spray-bg relative overflow-hidden">
|
||||
<div class="max-w-3xl mx-auto text-center relative z-10">
|
||||
<span class="badge badge-frost text-void mb-6 inline-block">About the Magazine</span>
|
||||
<p class="font-display text-3xl md:text-5xl text-paper leading-tight text-balance mb-10">
|
||||
{{ .Site.Params.description }}
|
||||
</p>
|
||||
<a href="/about/" class="btn btn-outline">About the magazine {{ partial "icon.html" "arrow-right" }}</a>
|
||||
</div>
|
||||
</section>
|
||||
{{- end -}}
|
||||
|
||||
@@ -1,24 +1,39 @@
|
||||
{{- /* Inline author byline for post single pages.
|
||||
Expects the author page as context (.).
|
||||
*/ -}}
|
||||
Expects the author page as context (.).
|
||||
*/
|
||||
-}}
|
||||
<div class="flex items-center gap-4 py-6 border-t border-b border-zinc my-8 max-w-2xl">
|
||||
{{- $avatarRes := .Resources.GetMatch "avatar.*" -}}
|
||||
{{- if $avatarRes -}}
|
||||
{{- $av := $avatarRes.Resize "96x webp" -}}
|
||||
<a href="{{ $.RelPermalink }}" class="flex-shrink-0">
|
||||
<img src="{{ $av.RelPermalink }}" alt="{{ $.Params.name | default $.Title }}" class="w-16 h-16 object-cover border border-zinc hover:border-heat transition-colors" loading="lazy" decoding="async">
|
||||
<img
|
||||
src="{{ $av.RelPermalink }}"
|
||||
alt="{{ $.Params.name | default $.Title }}"
|
||||
class="w-16 h-16 object-cover border border-zinc hover:border-heat transition-colors"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
</a>
|
||||
{{- else -}}{{- with .Params.avatar -}}
|
||||
<a href="{{ $.RelPermalink }}" class="flex-shrink-0">
|
||||
<img src="{{ . }}" alt="{{ $.Params.name | default $.Title }}" class="w-16 h-16 object-cover border border-zinc hover:border-heat transition-colors" loading="lazy" decoding="async">
|
||||
</a>
|
||||
{{- end -}}{{- end -}}
|
||||
{{- else -}}
|
||||
{{- with .Params.avatar -}}
|
||||
<a href="{{ $.RelPermalink }}" class="flex-shrink-0">
|
||||
<img
|
||||
src="{{ . }}"
|
||||
alt="{{ $.Params.name | default $.Title }}"
|
||||
class="w-16 h-16 object-cover border border-zinc hover:border-heat transition-colors"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
</a>
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
<div class="min-w-0">
|
||||
<a href="{{ .RelPermalink }}" class="text-base font-display text-heat hover:text-frost transition-colors block">
|
||||
{{ .Params.name | default .Title }}
|
||||
</a>
|
||||
{{- with .Params.bio -}}
|
||||
<p class="text-fog text-sm leading-relaxed line-clamp-2 font-body">{{ . }}</p>
|
||||
<p class="text-fog text-sm leading-relaxed line-clamp-2 font-body">{{ . }}</p>
|
||||
{{- end -}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<footer class="relative overflow-hidden border-t border-zinc gutter-x py-14 mt-auto bg-ink speed-lines">
|
||||
|
||||
<!-- Decorative background text -->
|
||||
<div
|
||||
class="graffiti-tag absolute -bottom-6 -right-4 select-none pointer-events-none"
|
||||
style="-webkit-text-stroke-color: rgba(255,26,140,0.07); opacity: 1;"
|
||||
aria-hidden="true"
|
||||
>PIVOINE</div>
|
||||
>
|
||||
PIVOINE
|
||||
</div>
|
||||
|
||||
<div class="gradient-line mb-10 relative z-10"></div>
|
||||
<div class="relative z-10 flex flex-col md:flex-row md:items-start justify-between gap-10">
|
||||
|
||||
<!-- Brand -->
|
||||
<div>
|
||||
<a href="/" class="block mb-4 hover:opacity-90 transition-opacity w-fit" aria-label="{{ .Site.Title }} Home">
|
||||
@@ -22,17 +22,23 @@
|
||||
<nav aria-label="Footer navigation">
|
||||
<ul class="flex flex-wrap gap-x-8 gap-y-4">
|
||||
{{- range .Site.Menus.footer -}}
|
||||
<li>
|
||||
<a href="{{ .URL }}" class="label text-fog hover:text-heat transition-colors">{{ .Name }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ .URL }}" class="label text-fog hover:text-heat transition-colors">{{ .Name }}</a>
|
||||
</li>
|
||||
{{- end -}}
|
||||
{{- with .Site.Params.social -}}
|
||||
{{- range $platform, $url := . -}}
|
||||
<li>
|
||||
<a href="{{ $url }}" target="_blank" rel="noopener noreferrer" class="label text-fog hover:text-frost transition-colors">
|
||||
{{- $platform | upper -}} {{ partial "icon.html" "arrow-external" }}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="{{ $url }}"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="label text-fog hover:text-frost transition-colors"
|
||||
>
|
||||
{{- $platform | upper -}}
|
||||
{{ partial "icon.html" "arrow-external" }}
|
||||
</a>
|
||||
</li>
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
</ul>
|
||||
|
||||
+96
-44
@@ -1,81 +1,133 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="htmx-config" content='{"globalViewTransitions":true,"scrollBehavior":"smooth"}'>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="theme-color" content="#FF1A8C">
|
||||
<meta charset="utf-8" />
|
||||
<meta name="htmx-config" content='{"globalViewTransitions":true,"scrollBehavior":"smooth"}' />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#FF1A8C" />
|
||||
|
||||
<title>
|
||||
{{- if .IsHome -}}
|
||||
{{- .Site.Title }} — {{ .Site.Params.tagline -}}
|
||||
{{- .Site.Title }} —
|
||||
{{ .Site.Params.tagline -}}
|
||||
{{- else -}}
|
||||
{{- .Title }} — {{ .Site.Title -}}
|
||||
{{- .Title }} —
|
||||
{{ .Site.Title -}}
|
||||
{{- end -}}
|
||||
</title>
|
||||
|
||||
<meta name="description" content="{{ with .Description }}{{ . }}{{ else }}{{ .Site.Params.description }}{{ end }}">
|
||||
<meta name="author" content="{{ with .Params.author }}{{ . }}{{ else }}{{ .Site.Params.author }}{{ end }}">
|
||||
{{- if .Site.Params.robots }}<meta name="robots" content="{{ .Site.Params.robots }}">{{ end }}
|
||||
<meta name="description" content="{{ with .Description }}{{ . }}{{ else }}{{ .Site.Params.description }}{{ end }}" />
|
||||
<meta name="author" content="{{ with .Params.author }}{{ . }}{{ else }}{{ .Site.Params.author }}{{ end }}" />
|
||||
{{- if .Site.Params.robots }}<meta name="robots" content="{{ .Site.Params.robots }}" />{{ end }}
|
||||
|
||||
|
||||
<!-- Open Graph -->
|
||||
<meta property="og:site_name" content="{{ .Site.Title }}">
|
||||
<meta property="og:title" content="{{ if .IsHome }}{{ .Site.Title }} — {{ .Site.Params.tagline }}{{ else }}{{ .Title }}{{ end }}">
|
||||
<meta property="og:description" content="{{ with .Description }}{{ . }}{{ else }}{{ .Site.Params.description }}{{ end }}">
|
||||
<meta property="og:url" content="{{ .Permalink }}">
|
||||
<meta property="og:type" content="{{ if .IsPage }}article{{ else }}website{{ end }}">
|
||||
<meta property="og:locale" content="{{ .Site.LanguageCode | default "en_US" | replaceRE "-" "_" }}">
|
||||
<meta property="og:site_name" content="{{ .Site.Title }}" />
|
||||
<meta
|
||||
property="og:title"
|
||||
content="{{ if .IsHome }}
|
||||
{{ .Site.Title }} —
|
||||
{{ .Site.Params.tagline }}
|
||||
{{ else }}
|
||||
{{ .Title }}
|
||||
{{ end }}"
|
||||
/>
|
||||
<meta
|
||||
property="og:description"
|
||||
content="{{ with .Description }}
|
||||
{{ . }}
|
||||
{{ else }}
|
||||
{{ .Site.Params.description }}
|
||||
{{ end }}"
|
||||
/>
|
||||
<meta property="og:url" content="{{ .Permalink }}" />
|
||||
<meta property="og:type" content="{{ if .IsPage }}article{{ else }}website{{ end }}" />
|
||||
<meta property="og:locale" content="{{ .Site.LanguageCode | default "en_US" | replaceRE "-" "_" }}" />
|
||||
{{- $ogImage := .Site.Params.ogImage -}}
|
||||
{{- $ogImageAlt := .Site.Title -}}
|
||||
{{- with .Params.banner -}}
|
||||
{{- $ogImage = .src -}}
|
||||
{{- $ogImageAlt = $.Title -}}
|
||||
{{- end -}}
|
||||
<meta property="og:image" content="{{ $ogImage | absURL }}">
|
||||
<meta property="og:image:alt" content="{{ $ogImageAlt }}">
|
||||
<meta property="og:image:width" content="1200">
|
||||
<meta property="og:image:height" content="630">
|
||||
<meta property="og:image" content="{{ $ogImage | absURL }}" />
|
||||
<meta property="og:image:alt" content="{{ $ogImageAlt }}" />
|
||||
<meta property="og:image:width" content="1200" />
|
||||
<meta property="og:image:height" content="630" />
|
||||
|
||||
{{- if .IsPage }}
|
||||
<meta property="article:published_time" content="{{ .Date.Format "2006-01-02T15:04:05Z07:00" }}">
|
||||
<meta property="article:modified_time" content="{{ .Lastmod.Format "2006-01-02T15:04:05Z07:00" }}">
|
||||
{{- with .Params.author }}<meta property="article:author" content="{{ . }}">{{ end }}
|
||||
{{- range .Params.categories }}<meta property="article:section" content="{{ . }}">{{ end }}
|
||||
{{- range .Params.tags }}<meta property="article:tag" content="{{ . }}">{{ end }}
|
||||
<meta property="article:published_time" content="{{ .Date.Format "2006-01-02T15:04:05Z07:00" }}" />
|
||||
<meta property="article:modified_time" content="{{ .Lastmod.Format "2006-01-02T15:04:05Z07:00" }}" />
|
||||
{{- with .Params.author }}<meta property="article:author" content="{{ . }}" />{{ end }}
|
||||
{{- range .Params.categories }}<meta property="article:section" content="{{ . }}" />{{ end }}
|
||||
{{- range .Params.tags }}<meta property="article:tag" content="{{ . }}" />{{ end }}
|
||||
{{- end }}
|
||||
|
||||
|
||||
<!-- Twitter / X Card -->
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
{{- with .Site.Params.twitterHandle }}
|
||||
<meta name="twitter:site" content="@{{ . }}">
|
||||
<meta name="twitter:site" content="@{{ . }}" />
|
||||
{{- end }}
|
||||
<meta name="twitter:title" content="{{ if .IsHome }}{{ .Site.Title }} — {{ .Site.Params.tagline }}{{ else }}{{ .Title }}{{ end }}">
|
||||
<meta name="twitter:description" content="{{ with .Description }}{{ . }}{{ else }}{{ .Site.Params.description }}{{ end }}">
|
||||
<meta name="twitter:image" content="{{ $ogImage | absURL }}">
|
||||
<meta name="twitter:image:alt" content="{{ $ogImageAlt }}">
|
||||
<meta
|
||||
name="twitter:title"
|
||||
content="{{ if .IsHome }}
|
||||
{{ .Site.Title }} —
|
||||
{{ .Site.Params.tagline }}
|
||||
{{ else }}
|
||||
{{ .Title }}
|
||||
{{ end }}"
|
||||
/>
|
||||
<meta
|
||||
name="twitter:description"
|
||||
content="{{ with .Description }}
|
||||
{{ . }}
|
||||
{{ else }}
|
||||
{{ .Site.Params.description }}
|
||||
{{ end }}"
|
||||
/>
|
||||
<meta name="twitter:image" content="{{ $ogImage | absURL }}" />
|
||||
<meta name="twitter:image:alt" content="{{ $ogImageAlt }}" />
|
||||
|
||||
<!-- Canonical -->
|
||||
<link rel="canonical" href="{{ .Permalink }}">
|
||||
<link rel="canonical" href="{{ .Permalink }}" />
|
||||
|
||||
<!-- JSON-LD structured data -->
|
||||
{{- partial "schema.html" . -}}
|
||||
|
||||
|
||||
<!-- Fonts — Bebas Neue + Barlow + Share Tech Mono (non-render-blocking) -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link rel="preload" as="style" href="https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Barlow:ital,wght@0,300;0,400;0,500;0,600;1,400&family=Share+Tech+Mono&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Barlow:ital,wght@0,300;0,400;0,500;0,600;1,400&family=Share+Tech+Mono&display=swap" media="print" onload="this.media='all'">
|
||||
<noscript><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Barlow:ital,wght@0,300;0,400;0,500;0,600;1,400&family=Share+Tech+Mono&display=swap"></noscript>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
rel="preload"
|
||||
as="style"
|
||||
href="https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Barlow:ital,wght@0,300;0,400;0,500;0,600;1,400&family=Share+Tech+Mono&display=swap"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Barlow:ital,wght@0,300;0,400;0,500;0,600;1,400&family=Share+Tech+Mono&display=swap"
|
||||
media="print"
|
||||
onload="this.media='all'"
|
||||
/>
|
||||
<noscript
|
||||
><link
|
||||
rel="stylesheet"
|
||||
href="https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Barlow:ital,wght@0,300;0,400;0,500;0,600;1,400&family=Share+Tech+Mono&display=swap"
|
||||
/></noscript>
|
||||
|
||||
<!-- CSS via Hugo Pipes + PostCSS + Tailwind v4 -->
|
||||
{{- $css := resources.Get "css/main.css" | css.PostCSS -}}
|
||||
{{- if eq hugo.Environment "production" -}}
|
||||
{{- $css = $css | minify | fingerprint "sha256" -}}
|
||||
{{- end -}}
|
||||
<link rel="stylesheet" href="{{ $css.RelPermalink }}"{{ if eq hugo.Environment "production" }} integrity="{{ $css.Data.Integrity }}"{{ end }}>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="{{ $css.RelPermalink }}"
|
||||
{{ if eq hugo.Environment "production" }}integrity="{{ $css.Data.Integrity }}"{{ end }}
|
||||
/>
|
||||
|
||||
<!-- Favicon & PWA -->
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
|
||||
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
||||
<link rel="icon" href="/favicon.ico" sizes="any">
|
||||
<link rel="manifest" href="/site.webmanifest">
|
||||
<meta name="msapplication-TileColor" content="#050510">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
|
||||
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
|
||||
<link rel="icon" href="/favicon.ico" sizes="any" />
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
<meta name="msapplication-TileColor" content="#050510" />
|
||||
|
||||
@@ -1,11 +1,55 @@
|
||||
{{- /* Inline SVG icon. Usage: {{ partial "icon.html" "arrow-right" }}
|
||||
Renders at 1em × 1em, inherits currentColor, aria-hidden.
|
||||
*/ -}}
|
||||
Renders at 1em × 1em, inherits currentColor, aria-hidden. */ -}}
|
||||
{{- $name := . -}}
|
||||
{{- if eq $name "arrow-right" -}}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="1em" height="1em" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="display:inline-block;vertical-align:-0.1em"><path d="M2 8h12"/><path d="M9 3l5 5-5 5"/></svg>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
width="1em"
|
||||
height="1em"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
aria-hidden="true"
|
||||
style="display:inline-block;vertical-align:-0.1em"
|
||||
>
|
||||
<path d="M2 8h12" />
|
||||
<path d="M9 3l5 5-5 5" />
|
||||
</svg>
|
||||
{{- else if eq $name "arrow-left" -}}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="1em" height="1em" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="display:inline-block;vertical-align:-0.1em"><path d="M14 8H2"/><path d="M7 3L2 8l5 5"/></svg>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
width="1em"
|
||||
height="1em"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
aria-hidden="true"
|
||||
style="display:inline-block;vertical-align:-0.1em"
|
||||
>
|
||||
<path d="M14 8H2" />
|
||||
<path d="M7 3L2 8l5 5" />
|
||||
</svg>
|
||||
{{- else if eq $name "arrow-external" -}}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="1em" height="1em" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="display:inline-block;vertical-align:-0.1em"><path d="M4 12L12 4"/><path d="M6 4h6v6"/></svg>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
width="1em"
|
||||
height="1em"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
aria-hidden="true"
|
||||
style="display:inline-block;vertical-align:-0.1em"
|
||||
>
|
||||
<path d="M4 12L12 4" />
|
||||
<path d="M6 4h6v6" />
|
||||
</svg>
|
||||
{{- end -}}
|
||||
|
||||
+11
-10
@@ -2,15 +2,16 @@
|
||||
Renders a Hugo image resource as an optimised WebP <img> with responsive srcset.
|
||||
|
||||
Parameters:
|
||||
res — Hugo image resource (required)
|
||||
widths — slice of pixel widths to generate, e.g. (slice 600 1200)
|
||||
default: (slice 800 1600)
|
||||
sizes — value for the <img sizes> attribute
|
||||
default: "100vw"
|
||||
class — CSS classes applied to <img>
|
||||
alt — alt text (default "")
|
||||
loading — "lazy" or "eager" (default "lazy")
|
||||
*/ -}}
|
||||
res — Hugo image resource (required)
|
||||
widths — slice of pixel widths to generate, e.g. (slice 600 1200)
|
||||
default: (slice 800 1600)
|
||||
sizes — value for the <img sizes> attribute
|
||||
default: "100vw"
|
||||
class — CSS classes applied to <img>
|
||||
alt — alt text (default "")
|
||||
loading — "lazy" or "eager" (default "lazy")
|
||||
*/
|
||||
-}}
|
||||
{{- $res := .res -}}
|
||||
{{- $widths := .widths | default (slice 800 1600) -}}
|
||||
{{- $sizes := .sizes | default "100vw" -}}
|
||||
@@ -35,4 +36,4 @@
|
||||
height="{{ $primary.Height }}"
|
||||
loading="{{ $loading }}"
|
||||
decoding="async"
|
||||
>
|
||||
/>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
{{- /*
|
||||
Logo image.
|
||||
Usage: {{ partial "logo.html" (dict "class" "h-8 w-auto") }}
|
||||
Params:
|
||||
class — CSS classes on the <img> element (default: "h-9 w-auto")
|
||||
*/ -}}
|
||||
Usage: {{ partial "logo.html" (dict "class" "h-8 w-auto")
|
||||
}}
|
||||
Params: class — CSS classes on the <img /> element (default: "h-9 w-auto") */ -}}
|
||||
{{- $class := .class | default "h-9 w-auto" -}}
|
||||
<img src="/logo.svg" class="{{ $class }} logo-img" alt="{{ site.Title }}" width="512" height="512">
|
||||
<img src="/logo.svg" class="{{ $class }} logo-img" alt="{{ site.Title }}" width="512" height="512" />
|
||||
|
||||
+19
-22
@@ -2,35 +2,32 @@
|
||||
Renders an image or video from a front matter map.
|
||||
|
||||
Usage:
|
||||
{{ partial "media.html" (dict "media" .Params.banner "class" "w-full h-full object-cover") }}
|
||||
|
||||
Parameters:
|
||||
media — map with keys: type (image|video), src, alt
|
||||
class — CSS classes applied to the img/video element
|
||||
lazy — set to false to disable lazy loading (default true)
|
||||
*/ -}}
|
||||
{{ partial "media.html" (dict "media" .Params.banner "class" "w-full h-full object-cover")
|
||||
}}
|
||||
|
||||
Parameters: media — map with keys: type (image|video), src, alt class — CSS classes applied to the img/video element
|
||||
lazy — set to false to disable lazy loading (default true) */ -}}
|
||||
{{- $media := .media -}}
|
||||
{{- $class := .class | default "" -}}
|
||||
{{- $lazy := .lazy | default true -}}
|
||||
|
||||
{{- with $media -}}
|
||||
{{- if eq .type "video" -}}
|
||||
<video
|
||||
class="{{ $class }}"
|
||||
src="{{ .src }}"
|
||||
autoplay
|
||||
loop
|
||||
muted
|
||||
playsinline
|
||||
{{- with .alt }} aria-label="{{ . }}"{{ end }}
|
||||
></video>
|
||||
<video
|
||||
class="{{ $class }}"
|
||||
src="{{ .src }}"
|
||||
autoplay
|
||||
loop
|
||||
muted
|
||||
playsinline
|
||||
{{- with .alt }}aria-label="{{ . }}"{{ end }}
|
||||
></video>
|
||||
{{- else -}}
|
||||
<img
|
||||
class="{{ $class }}"
|
||||
src="{{ .src }}"
|
||||
alt="{{ .alt | default "" }}"
|
||||
{{- if $lazy }} loading="lazy" decoding="async"{{ end }}
|
||||
>
|
||||
<img
|
||||
class="{{ $class }}"
|
||||
src="{{ .src }}"
|
||||
alt="{{ .alt | default "" }}"
|
||||
{{- if $lazy }}loading="lazy" decoding="async"{{ end }}
|
||||
/>
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
+88
-53
@@ -10,7 +10,11 @@
|
||||
:class="$store.nav.open || scrolled ? 'bg-ink/95 backdrop-blur-md shadow-[0_1px_0_var(--color-zinc)]' : ''"
|
||||
>
|
||||
<!-- Logotype -->
|
||||
<a href="/" class="flex items-center gap-3 hover:opacity-90 transition-opacity" aria-label="{{ .Site.Params.logoText }} Home">
|
||||
<a
|
||||
href="/"
|
||||
class="flex items-center gap-3 hover:opacity-90 transition-opacity"
|
||||
aria-label="{{ .Site.Params.logoText }} Home"
|
||||
>
|
||||
{{- partial "logo.html" (dict "id" "nav" "class" "h-12 w-auto flex-shrink-0") -}}
|
||||
<span class="logo-glitch font-display text-2xl md:text-3xl leading-none tracking-wide">
|
||||
{{- .Site.Params.logoText -}}
|
||||
@@ -20,19 +24,19 @@
|
||||
<!-- Desktop navigation -->
|
||||
<nav class="hidden md:flex items-center gap-8" aria-label="Primary navigation">
|
||||
{{- range .Site.Menus.main -}}
|
||||
{{- $href := .URL -}}
|
||||
<a
|
||||
href="{{ $href }}"
|
||||
class="label relative transition-colors"
|
||||
:class="($store.nav.path === '{{ $href }}' || ('{{ $href }}' !== '/' && $store.nav.path.startsWith('{{ $href }}'))) ? 'text-heat' : 'text-fog hover:text-chalk'"
|
||||
>
|
||||
{{ .Name }}
|
||||
<span
|
||||
class="absolute -bottom-0.5 left-0 h-px bg-heat transition-all duration-300 ease-out"
|
||||
:style="($store.nav.path === '{{ $href }}' || ('{{ $href }}' !== '/' && $store.nav.path.startsWith('{{ $href }}'))) ? 'width: 100%' : 'width: 0'"
|
||||
style="width: 0"
|
||||
></span>
|
||||
</a>
|
||||
{{- $href := .URL -}}
|
||||
<a
|
||||
href="{{ $href }}"
|
||||
class="label relative transition-colors"
|
||||
:class="($store.nav.path === '{{ $href }}' || ('{{ $href }}' !== '/' && $store.nav.path.startsWith('{{ $href }}'))) ? 'text-heat' : 'text-fog hover:text-chalk'"
|
||||
>
|
||||
{{ .Name }}
|
||||
<span
|
||||
class="absolute -bottom-0.5 left-0 h-px bg-heat transition-all duration-300 ease-out"
|
||||
:style="($store.nav.path === '{{ $href }}' || ('{{ $href }}' !== '/' && $store.nav.path.startsWith('{{ $href }}'))) ? 'width: 100%' : 'width: 0'"
|
||||
style="width: 0"
|
||||
></span>
|
||||
</a>
|
||||
{{- end -}}
|
||||
</nav>
|
||||
|
||||
@@ -45,12 +49,18 @@
|
||||
aria-controls="mobile-menu"
|
||||
aria-label="Toggle navigation"
|
||||
>
|
||||
<span class="block h-px w-6 bg-current transition-all duration-300 origin-center"
|
||||
:class="$store.nav.open ? 'rotate-45 translate-y-[7px]' : ''"></span>
|
||||
<span class="block h-px w-4 bg-current transition-all duration-200"
|
||||
:class="$store.nav.open ? 'opacity-0' : 'opacity-100'"></span>
|
||||
<span class="block h-px w-6 bg-current transition-all duration-300 origin-center"
|
||||
:class="$store.nav.open ? '-rotate-45 -translate-y-[7px]' : ''"></span>
|
||||
<span
|
||||
class="block h-px w-6 bg-current transition-all duration-300 origin-center"
|
||||
:class="$store.nav.open ? 'rotate-45 translate-y-[7px]' : ''"
|
||||
></span>
|
||||
<span
|
||||
class="block h-px w-4 bg-current transition-all duration-200"
|
||||
:class="$store.nav.open ? 'opacity-0' : 'opacity-100'"
|
||||
></span>
|
||||
<span
|
||||
class="block h-px w-6 bg-current transition-all duration-300 origin-center"
|
||||
:class="$store.nav.open ? '-rotate-45 -translate-y-[7px]' : ''"
|
||||
></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -73,51 +83,70 @@
|
||||
<div class="gradient-line flex-none"></div>
|
||||
|
||||
<!-- Decorative background text -->
|
||||
<div class="graffiti-tag absolute -bottom-4 -right-4 select-none pointer-events-none"
|
||||
style="-webkit-text-stroke-color: rgba(155,0,255,0.07);" aria-hidden="true">MENU</div>
|
||||
<div
|
||||
class="graffiti-tag absolute -bottom-4 -right-4 select-none pointer-events-none"
|
||||
style="-webkit-text-stroke-color: rgba(155,0,255,0.07);"
|
||||
aria-hidden="true"
|
||||
>
|
||||
MENU
|
||||
</div>
|
||||
|
||||
<!-- Main nav links -->
|
||||
<nav class="flex-1 flex flex-col justify-center gutter-x py-10" aria-label="Mobile navigation">
|
||||
<div class="flex flex-col gap-0">
|
||||
{{- $i := 0 -}}
|
||||
{{- range .Site.Menus.main -}}
|
||||
{{- $href := .URL -}}
|
||||
<a
|
||||
href="{{ $href }}"
|
||||
class="group relative leading-none py-3 overflow-hidden"
|
||||
style="font-family: var(--font-display); font-size: clamp(2.8rem, 11vw, 6.5rem); animation: mob-link-in 0.5s var(--ease-out-expo) {{ mul $i 70 }}ms both"
|
||||
@click="$store.nav.open = false"
|
||||
>
|
||||
<!-- Hover sweep -->
|
||||
<span class="absolute inset-y-0 left-0 w-0 group-hover:w-full transition-all duration-300 ease-out"
|
||||
style="background: linear-gradient(90deg, rgba(255,26,140,0.07), transparent);"></span>
|
||||
<!-- Link text -->
|
||||
<span class="relative transition-colors duration-200 text-paper group-hover:text-heat uppercase"
|
||||
:class="($store.nav.path === '{{ $href }}' || ('{{ $href }}' !== '/' && $store.nav.path.startsWith('{{ $href }}'))) ? 'text-gradient' : ''"
|
||||
>{{ .Name }}</span>
|
||||
<!-- Index number -->
|
||||
<span class="absolute right-0 top-1/2 -translate-y-1/2 label text-smoke group-hover:text-heat transition-colors duration-200"
|
||||
style="animation: mob-link-in 0.4s var(--ease-out-expo) {{ add (mul $i 70) 100 }}ms both"
|
||||
>0{{ add $i 1 }}</span>
|
||||
</a>
|
||||
{{- $i = add $i 1 -}}
|
||||
{{- $href := .URL -}}
|
||||
<a
|
||||
href="{{ $href }}"
|
||||
class="group relative leading-none py-3 overflow-hidden"
|
||||
style="font-family: var(--font-display); font-size: clamp(2.8rem, 11vw, 6.5rem); animation: mob-link-in 0.5s var(--ease-out-expo) {{ mul $i 70 }}ms both"
|
||||
@click="$store.nav.open = false"
|
||||
>
|
||||
<!-- Hover sweep -->
|
||||
<span
|
||||
class="absolute inset-y-0 left-0 w-0 group-hover:w-full transition-all duration-300 ease-out"
|
||||
style="background: linear-gradient(90deg, rgba(255,26,140,0.07), transparent);"
|
||||
></span>
|
||||
<!-- Link text -->
|
||||
<span
|
||||
class="relative transition-colors duration-200 text-paper group-hover:text-heat uppercase"
|
||||
:class="($store.nav.path === '{{ $href }}' || ('{{ $href }}' !== '/' && $store.nav.path.startsWith('{{ $href }}'))) ? 'text-gradient' : ''"
|
||||
>{{ .Name }}</span
|
||||
>
|
||||
<!-- Index number -->
|
||||
<span
|
||||
class="absolute right-0 top-1/2 -translate-y-1/2 label text-smoke group-hover:text-heat transition-colors duration-200"
|
||||
style="animation: mob-link-in 0.4s var(--ease-out-expo) {{ add (mul $i 70) 100 }}ms both"
|
||||
>0{{ add $i 1 }}</span
|
||||
>
|
||||
</a>
|
||||
{{- $i = add $i 1 -}}
|
||||
{{- end -}}
|
||||
</div>
|
||||
|
||||
<div class="gradient-line my-8"
|
||||
style="animation: mob-link-in 0.4s ease {{ mul $i 70 }}ms both"></div>
|
||||
<div class="gradient-line my-8" style="animation: mob-link-in 0.4s ease {{ mul $i 70 }}ms both"></div>
|
||||
|
||||
<div class="flex flex-wrap gap-x-8 gap-y-3"
|
||||
style="animation: mob-link-in 0.4s ease {{ add (mul $i 70) 40 }}ms both">
|
||||
<div
|
||||
class="flex flex-wrap gap-x-8 gap-y-3"
|
||||
style="animation: mob-link-in 0.4s ease {{ add (mul $i 70) 40 }}ms both"
|
||||
>
|
||||
{{- range .Site.Menus.footer -}}
|
||||
<a href="{{ .URL }}" class="label text-fog hover:text-heat transition-colors" @click="$store.nav.open = false">{{ .Name }}</a>
|
||||
<a
|
||||
href="{{ .URL }}"
|
||||
class="label text-fog hover:text-heat transition-colors"
|
||||
@click="$store.nav.open = false"
|
||||
>{{ .Name }}</a
|
||||
>
|
||||
{{- end -}}
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Bottom bar -->
|
||||
<div class="flex-none gutter-x pb-8 pt-4 border-t border-zinc"
|
||||
style="animation: mob-link-in 0.4s ease 420ms both">
|
||||
<div
|
||||
class="flex-none gutter-x pb-8 pt-4 border-t border-zinc"
|
||||
style="animation: mob-link-in 0.4s ease 420ms both"
|
||||
>
|
||||
<p class="label text-smoke">{{ .Site.Params.tagline }}</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -125,8 +154,14 @@
|
||||
</header>
|
||||
|
||||
<style>
|
||||
@keyframes mob-link-in {
|
||||
from { opacity: 0; transform: translateY(14px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
@keyframes mob-link-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(14px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,33 +1,40 @@
|
||||
{{- /* Custom pagination component.
|
||||
Call as: {{ partial "pagination.html" . }}
|
||||
Call as: {{ partial "pagination.html" .
|
||||
}}
|
||||
*/ -}}
|
||||
{{- $paginator := .Paginator -}}
|
||||
{{- if gt $paginator.TotalPages 1 -}}
|
||||
<nav class="flex items-center justify-center gap-2 py-16" aria-label="Pagination">
|
||||
<nav class="flex items-center justify-center gap-2 py-16" aria-label="Pagination">
|
||||
{{- if $paginator.HasPrev -}}
|
||||
<a
|
||||
href="{{ $paginator.Prev.URL }}"
|
||||
class="label border border-mist text-ash px-5 py-3 hover:border-gold hover:text-gold transition-all duration-300"
|
||||
aria-label="Previous page"
|
||||
>{{ partial "icon.html" "arrow-left" }} Prev</a
|
||||
>
|
||||
{{- end -}}
|
||||
|
||||
{{- if $paginator.HasPrev -}}
|
||||
<a
|
||||
href="{{ $paginator.Prev.URL }}"
|
||||
class="label border border-mist text-ash px-5 py-3 hover:border-gold hover:text-gold transition-all duration-300"
|
||||
aria-label="Previous page"
|
||||
>{{ partial "icon.html" "arrow-left" }} Prev</a>
|
||||
{{- end -}}
|
||||
{{- range $paginator.Pagers -}}
|
||||
<a
|
||||
href="{{ .URL }}"
|
||||
class="label border px-4 py-3 transition-all duration-300 {{ if eq . $paginator }}
|
||||
border-gold text-gold
|
||||
{{ else }}
|
||||
border-mist text-ash hover:border-gold hover:text-gold
|
||||
{{ end }}"
|
||||
{{- if eq . $paginator }}aria-current="page"{{ end }}
|
||||
>{{ .PageNumber }}</a
|
||||
>
|
||||
{{- end -}}
|
||||
|
||||
{{- range $paginator.Pagers -}}
|
||||
<a
|
||||
href="{{ .URL }}"
|
||||
class="label border px-4 py-3 transition-all duration-300 {{ if eq . $paginator }}border-gold text-gold{{ else }}border-mist text-ash hover:border-gold hover:text-gold{{ end }}"
|
||||
{{- if eq . $paginator }} aria-current="page"{{ end }}
|
||||
>{{ .PageNumber }}</a>
|
||||
{{- end -}}
|
||||
{{- if $paginator.HasNext -}}
|
||||
<a
|
||||
href="{{ $paginator.Next.URL }}"
|
||||
class="label border border-mist text-ash px-5 py-3 hover:border-gold hover:text-gold transition-all duration-300"
|
||||
aria-label="Next page"
|
||||
>Next {{ partial "icon.html" "arrow-right" }}</a
|
||||
>
|
||||
{{- end -}}
|
||||
|
||||
{{- if $paginator.HasNext -}}
|
||||
<a
|
||||
href="{{ $paginator.Next.URL }}"
|
||||
class="label border border-mist text-ash px-5 py-3 hover:border-gold hover:text-gold transition-all duration-300"
|
||||
aria-label="Next page"
|
||||
>Next {{ partial "icon.html" "arrow-right" }}</a>
|
||||
{{- end -}}
|
||||
|
||||
</nav>
|
||||
</nav>
|
||||
{{- end -}}
|
||||
|
||||
@@ -1,50 +1,56 @@
|
||||
{{- /* Large overlay card for featured/secondary slots.
|
||||
Expects a post page as context (.).
|
||||
*/ -}}
|
||||
Expects a post page as context (.).
|
||||
*/
|
||||
-}}
|
||||
{{- $post := . -}}
|
||||
<article class="group relative overflow-hidden aspect-[3/4] md:aspect-[4/5] card-comic">
|
||||
<a href="{{ $post.RelPermalink }}" class="block w-full h-full" aria-label="{{ $post.Title }}">
|
||||
|
||||
<!-- Background media — bundle banner.png → 01.png → .Params.banner -->
|
||||
{{- $thumb := $post.Resources.GetMatch "banner.png" -}}
|
||||
{{- if not $thumb -}}{{- $thumb = $post.Resources.GetMatch "01.png" -}}{{- end -}}
|
||||
<div class="card-media w-full h-full absolute inset-0">
|
||||
{{- if $thumb -}}
|
||||
{{- partial "img.html" (dict "res" $thumb "widths" (slice 800 1200) "sizes" "(max-width: 768px) 100vw, 50vw" "class" "w-full h-full object-cover" "alt" $post.Title) -}}
|
||||
{{- else -}}{{- with $post.Params.banner -}}
|
||||
{{- partial "media.html" (dict "media" . "class" "w-full h-full object-cover") -}}
|
||||
{{- else -}}
|
||||
<div class="w-full h-full bg-concrete flex items-center justify-center halftone">
|
||||
<span class="label text-smoke">PIVOINE</span>
|
||||
</div>
|
||||
{{- end -}}{{- end -}}
|
||||
{{- with $post.Params.banner -}}
|
||||
{{- partial "media.html" (dict "media" . "class" "w-full h-full object-cover") -}}
|
||||
{{- else -}}
|
||||
<div class="w-full h-full bg-concrete flex items-center justify-center halftone">
|
||||
<span class="label text-smoke">PIVOINE</span>
|
||||
</div>
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
</div>
|
||||
|
||||
<!-- Gradient overlay -->
|
||||
<div class="absolute inset-0 overlay-gradient transition-opacity duration-500 group-hover:opacity-90"></div>
|
||||
|
||||
<!-- Diagonal heat accent -->
|
||||
<div class="absolute bottom-16 left-0 w-28 h-[3px] opacity-0 group-hover:opacity-90 transition-opacity duration-300"
|
||||
style="background: linear-gradient(90deg, var(--color-heat), var(--color-pulse)); transform: skewX(-20deg) translateX(-4px);"></div>
|
||||
<div
|
||||
class="absolute bottom-16 left-0 w-28 h-[3px] opacity-0 group-hover:opacity-90 transition-opacity duration-300"
|
||||
style="background: linear-gradient(90deg, var(--color-heat), var(--color-pulse)); transform: skewX(-20deg) translateX(-4px);"
|
||||
></div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="absolute bottom-0 left-0 right-0 p-5 md:p-6">
|
||||
{{- with $post.Params.categories -}}
|
||||
<div class="flex flex-wrap gap-2 mb-3">
|
||||
{{- range first 1 . -}}
|
||||
<span class="badge badge-outline">{{ . }}</span>
|
||||
{{- end -}}
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2 mb-3">
|
||||
{{- range first 1 . -}}
|
||||
<span class="badge badge-outline">{{ . }}</span>
|
||||
{{- end -}}
|
||||
</div>
|
||||
{{- end -}}
|
||||
<h3 class="font-display text-xl md:text-2xl text-paper text-balance leading-none group-hover:text-heat transition-colors mb-3">
|
||||
<h3
|
||||
class="font-display text-xl md:text-2xl text-paper text-balance leading-none group-hover:text-heat transition-colors mb-3"
|
||||
>
|
||||
{{- $post.Title -}}
|
||||
</h3>
|
||||
<div class="flex items-center gap-2">
|
||||
{{- with $post.Params.author -}}
|
||||
{{- $authorPage := site.GetPage (printf "authors/%s" .) -}}
|
||||
{{- with $authorPage -}}
|
||||
<span class="label text-chalk opacity-70">{{ .Params.name | default .Title }}</span>
|
||||
<span class="text-smoke opacity-60" aria-hidden="true">/</span>
|
||||
<span class="label text-chalk opacity-70">{{ .Params.name | default .Title }}</span>
|
||||
<span class="text-smoke opacity-60" aria-hidden="true">/</span>
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
<time class="label text-chalk opacity-70" datetime="{{ $post.Date.Format "2006-01-02" }}">
|
||||
@@ -52,6 +58,5 @@
|
||||
</time>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</a>
|
||||
</article>
|
||||
|
||||
@@ -1,36 +1,41 @@
|
||||
{{- /* Compact post card for grids.
|
||||
Expects a post page as context (.).
|
||||
*/ -}}
|
||||
Expects a post page as context (.).
|
||||
*/
|
||||
-}}
|
||||
{{- $post := . -}}
|
||||
<article class="group card-comic flex flex-col bg-concrete">
|
||||
<a href="{{ $post.RelPermalink }}" class="block flex-1 flex flex-col">
|
||||
|
||||
<!-- Thumbnail — bundle banner.png → 01.png → .Params.banner -->
|
||||
{{- $thumb := $post.Resources.GetMatch "banner.png" -}}
|
||||
{{- if not $thumb -}}{{- $thumb = $post.Resources.GetMatch "01.png" -}}{{- end -}}
|
||||
<div class="card-media aspect-editorial">
|
||||
{{- if $thumb -}}
|
||||
{{- partial "img.html" (dict "res" $thumb "widths" (slice 600 1000) "sizes" "(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 25vw" "class" "w-full h-full object-cover" "alt" $post.Title) -}}
|
||||
{{- else -}}{{- with $post.Params.banner -}}
|
||||
{{- partial "media.html" (dict "media" . "class" "w-full h-full object-cover") -}}
|
||||
{{- else -}}
|
||||
<div class="w-full h-full flex items-center justify-center halftone">
|
||||
<span class="label text-smoke text-xs">PIVOINE</span>
|
||||
</div>
|
||||
{{- end -}}{{- end -}}
|
||||
{{- with $post.Params.banner -}}
|
||||
{{- partial "media.html" (dict "media" . "class" "w-full h-full object-cover") -}}
|
||||
{{- else -}}
|
||||
<div class="w-full h-full flex items-center justify-center halftone">
|
||||
<span class="label text-smoke text-xs">PIVOINE</span>
|
||||
</div>
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
</div>
|
||||
|
||||
<!-- Category badge — parallelogram -->
|
||||
{{- with $post.Params.categories -}}
|
||||
<div class="flex flex-wrap gap-2 px-4 pt-4">
|
||||
{{- range first 1 . -}}
|
||||
<span class="badge">{{ . }}</span>
|
||||
{{- end -}}
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2 px-4 pt-4">
|
||||
{{- range first 1 . -}}
|
||||
<span class="badge">{{ . }}</span>
|
||||
{{- end -}}
|
||||
</div>
|
||||
{{- end -}}
|
||||
|
||||
|
||||
<!-- Title -->
|
||||
<h3 class="font-display text-base md:text-lg text-paper text-balance leading-none group-hover:text-heat transition-colors px-4 pt-3 pb-2 flex-1">
|
||||
<h3
|
||||
class="font-display text-base md:text-lg text-paper text-balance leading-none group-hover:text-heat transition-colors px-4 pt-3 pb-2 flex-1"
|
||||
>
|
||||
{{- $post.Title -}}
|
||||
</h3>
|
||||
|
||||
@@ -42,10 +47,26 @@
|
||||
{{- $avRes := .Resources.GetMatch "avatar.*" -}}
|
||||
{{- if $avRes -}}
|
||||
{{- $av := $avRes.Resize "64x webp" -}}
|
||||
<img src="{{ $av.RelPermalink }}" alt="{{ $authorPage.Params.name | default $authorPage.Title }}" class="w-5 h-5 object-cover border border-smoke flex-shrink-0" width="{{ $av.Width }}" height="{{ $av.Height }}" loading="lazy" decoding="async">
|
||||
{{- else -}}{{- with .Params.avatar -}}
|
||||
<img src="{{ . }}" alt="{{ $authorPage.Params.name | default $authorPage.Title }}" class="w-5 h-5 object-cover border border-smoke flex-shrink-0" loading="lazy" decoding="async">
|
||||
{{- end -}}{{- end -}}
|
||||
<img
|
||||
src="{{ $av.RelPermalink }}"
|
||||
alt="{{ $authorPage.Params.name | default $authorPage.Title }}"
|
||||
class="w-5 h-5 object-cover border border-smoke flex-shrink-0"
|
||||
width="{{ $av.Width }}"
|
||||
height="{{ $av.Height }}"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
{{- else -}}
|
||||
{{- with .Params.avatar -}}
|
||||
<img
|
||||
src="{{ . }}"
|
||||
alt="{{ $authorPage.Params.name | default $authorPage.Title }}"
|
||||
class="w-5 h-5 object-cover border border-smoke flex-shrink-0"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
<span class="label text-fog">{{ .Params.name | default .Title }}</span>
|
||||
<span class="text-smoke" aria-hidden="true">/</span>
|
||||
{{- end -}}
|
||||
@@ -54,6 +75,5 @@
|
||||
{{- $post.Date.Format "Jan 2006" -}}
|
||||
</time>
|
||||
</div>
|
||||
|
||||
</a>
|
||||
</article>
|
||||
|
||||
@@ -20,10 +20,10 @@
|
||||
}
|
||||
</script>
|
||||
{{- else if and .IsPage (eq .Section "posts") -}}
|
||||
{{- $authorPage := "" -}}
|
||||
{{- with .Params.author -}}
|
||||
{{- $authorPage = site.GetPage (printf "authors/%s" .) -}}
|
||||
{{- end -}}
|
||||
{{- $authorPage := "" -}}
|
||||
{{- with .Params.author -}}
|
||||
{{- $authorPage = site.GetPage (printf "authors/%s" .) -}}
|
||||
{{- end -}}
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
|
||||
+234
-208
@@ -1,235 +1,261 @@
|
||||
{{- define "page-background" -}}
|
||||
{{- with .Params.background -}}
|
||||
<div class="fixed inset-0 -z-10 overflow-hidden" aria-hidden="true">
|
||||
{{- partial "media.html" (dict "media" . "class" "w-full h-full object-cover opacity-[0.15] blur-md scale-110" "lazy" false) -}}
|
||||
<div class="absolute inset-0" style="background: rgba(5,5,16,0.7)"></div>
|
||||
</div>
|
||||
{{- end -}}
|
||||
{{- with .Params.background -}}
|
||||
<div class="fixed inset-0 -z-10 overflow-hidden" aria-hidden="true">
|
||||
{{- partial "media.html" (dict "media" . "class" "w-full h-full object-cover opacity-[0.15] blur-md scale-110" "lazy" false) -}}
|
||||
<div class="absolute inset-0" style="background: rgba(5,5,16,0.7)"></div>
|
||||
</div>
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "main" -}}
|
||||
|
||||
<!-- ── ARTICLE HEADER ────────────────────────────────────────── -->
|
||||
<header class="gutter-x pt-36 pb-12 max-w-5xl mx-auto relative overflow-hidden">
|
||||
|
||||
<!-- Faded decorative category text -->
|
||||
{{- with index .Params.categories 0 -}}
|
||||
<div class="graffiti-tag absolute -top-2 -right-4 select-none pointer-events-none"
|
||||
style="-webkit-text-stroke-color: rgba(155,0,255,0.08);" aria-hidden="true">{{ . }}</div>
|
||||
{{- end -}}
|
||||
|
||||
<!-- Categories -->
|
||||
{{- with .Params.categories -}}
|
||||
<div class="flex flex-wrap gap-2 mb-6">
|
||||
{{- range . -}}
|
||||
<a
|
||||
href="/categories/{{ . | urlize }}/"
|
||||
class="badge badge-outline"
|
||||
>{{ . }}</a>
|
||||
<!-- ── ARTICLE HEADER ────────────────────────────────────────── -->
|
||||
<header class="gutter-x pt-36 pb-12 max-w-5xl mx-auto relative overflow-hidden">
|
||||
<!-- Faded decorative category text -->
|
||||
{{- with index .Params.categories 0 -}}
|
||||
<div
|
||||
class="graffiti-tag absolute -top-2 -right-4 select-none pointer-events-none"
|
||||
style="-webkit-text-stroke-color: rgba(155,0,255,0.08);"
|
||||
aria-hidden="true"
|
||||
>
|
||||
{{ . }}
|
||||
</div>
|
||||
{{- end -}}
|
||||
</div>
|
||||
{{- end -}}
|
||||
|
||||
<!-- Title -->
|
||||
<h1 class="font-display text-5xl md:text-7xl lg:text-8xl text-paper text-balance mb-8 leading-none">
|
||||
{{- .Title -}}
|
||||
</h1>
|
||||
|
||||
<!-- Description -->
|
||||
{{- with .Description -}}
|
||||
<p class="text-chalk text-xl md:text-2xl max-w-2xl mb-10 leading-relaxed text-pretty font-body font-light">
|
||||
{{ . }}
|
||||
</p>
|
||||
{{- end -}}
|
||||
|
||||
<!-- Author byline -->
|
||||
{{- with .Params.author -}}
|
||||
{{- $authorPage := site.GetPage (printf "authors/%s" .) -}}
|
||||
{{- with $authorPage -}}
|
||||
{{- partial "author-card.html" . -}}
|
||||
<!-- Categories -->
|
||||
{{- with .Params.categories -}}
|
||||
<div class="flex flex-wrap gap-2 mb-6">
|
||||
{{- range . -}}
|
||||
<a href="/categories/{{ . | urlize }}/" class="badge badge-outline">{{ . }}</a>
|
||||
{{- end -}}
|
||||
</div>
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
<!-- Date + reading info -->
|
||||
<div class="flex flex-wrap items-center gap-6">
|
||||
<time class="label text-fog" datetime="{{ .Date.Format "2006-01-02" }}">
|
||||
{{- .Date.Format "January 2, 2006" -}}
|
||||
</time>
|
||||
{{- $imgCount := len (.Resources.Match "[0-9]*.png") -}}
|
||||
{{- $itemCount := $imgCount -}}
|
||||
{{- if and (eq $imgCount 0) .Params.items -}}{{- $itemCount = len .Params.items -}}{{- end -}}
|
||||
{{- if gt $itemCount 0 -}}
|
||||
<span class="label text-fog">{{ $itemCount }} {{ if eq $itemCount 1 }}Work{{ else }}Works{{ end }}</span>
|
||||
|
||||
<!-- Title -->
|
||||
<h1 class="font-display text-5xl md:text-7xl lg:text-8xl text-paper text-balance mb-8 leading-none">
|
||||
{{- .Title -}}
|
||||
</h1>
|
||||
|
||||
<!-- Description -->
|
||||
{{- with .Description -}}
|
||||
<p class="text-chalk text-xl md:text-2xl max-w-2xl mb-10 leading-relaxed text-pretty font-body font-light">
|
||||
{{ . }}
|
||||
</p>
|
||||
{{- end -}}
|
||||
</div>
|
||||
|
||||
</header>
|
||||
|
||||
<!-- ── GRADIENT DIVIDER ──────────────────────────────────────── -->
|
||||
<div class="gradient-line mb-0"></div>
|
||||
<!-- Author byline -->
|
||||
{{- with .Params.author -}}
|
||||
{{- $authorPage := site.GetPage (printf "authors/%s" .) -}}
|
||||
{{- with $authorPage -}}
|
||||
{{- partial "author-card.html" . -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
<!-- ── BANNER MEDIA — bundle banner.png → 01.png → .Params.banner ── -->
|
||||
{{- $bannerImg := .Resources.GetMatch "banner.png" -}}
|
||||
{{- if not $bannerImg -}}{{- $bannerImg = .Resources.GetMatch "01.png" -}}{{- end -}}
|
||||
{{- if $bannerImg -}}
|
||||
<div class="w-full overflow-hidden mb-16 md:mb-24" style="height: clamp(320px, 60vh, 75vh)">
|
||||
{{- partial "img.html" (dict "res" $bannerImg "widths" (slice 1200 1800) "sizes" "100vw" "class" "w-full h-full object-cover object-center" "alt" .Title "loading" "eager") -}}
|
||||
</div>
|
||||
{{- else -}}{{- with .Params.banner -}}
|
||||
<div class="w-full overflow-hidden mb-16 md:mb-24" style="height: clamp(320px, 60vh, 75vh)">
|
||||
{{- partial "media.html" (dict "media" . "class" "w-full h-full object-cover object-center" "lazy" false) -}}
|
||||
</div>
|
||||
{{- end -}}{{- end -}}
|
||||
|
||||
<!-- ── PROSE BODY ────────────────────────────────────────────── -->
|
||||
{{- with .Content -}}
|
||||
<article class="gutter-x mb-20 md:mb-28">
|
||||
<div class="max-w-5xl mx-auto">
|
||||
<div class="max-w-3xl prose-editorial">
|
||||
{{ . }}
|
||||
<!-- Date + reading info -->
|
||||
<div class="flex flex-wrap items-center gap-6">
|
||||
<time class="label text-fog" datetime="{{ .Date.Format "2006-01-02" }}">
|
||||
{{- .Date.Format "January 2, 2006" -}}
|
||||
</time>
|
||||
{{- $imgCount := len (.Resources.Match "[0-9]*.png") -}}
|
||||
{{- $itemCount := $imgCount -}}
|
||||
{{- if and (eq $imgCount 0) .Params.items -}}{{- $itemCount = len .Params.items -}}{{- end -}}
|
||||
{{- if gt $itemCount 0 -}}
|
||||
<span class="label text-fog">{{ $itemCount }} {{ if eq $itemCount 1 }}Work{{ else }}Works{{ end }}</span>
|
||||
{{- end -}}
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
{{- end -}}
|
||||
</header>
|
||||
|
||||
{{- /* ── GENERATION NOTES (disabled) ─────────────────────────────
|
||||
<!-- ── GRADIENT DIVIDER ──────────────────────────────────────── -->
|
||||
<div class="gradient-line mb-0"></div>
|
||||
|
||||
<!-- ── BANNER MEDIA — bundle banner.png → 01.png → .Params.banner ── -->
|
||||
{{- $bannerImg := .Resources.GetMatch "banner.png" -}}
|
||||
{{- if not $bannerImg -}}{{- $bannerImg = .Resources.GetMatch "01.png" -}}{{- end -}}
|
||||
{{- if $bannerImg -}}
|
||||
<div class="w-full overflow-hidden mb-16 md:mb-24" style="height: clamp(320px, 60vh, 75vh)">
|
||||
{{- partial "img.html" (dict "res" $bannerImg "widths" (slice 1200 1800) "sizes" "100vw" "class" "w-full h-full object-cover object-center" "alt" .Title "loading" "eager") -}}
|
||||
</div>
|
||||
{{- else -}}
|
||||
{{- with .Params.banner -}}
|
||||
<div class="w-full overflow-hidden mb-16 md:mb-24" style="height: clamp(320px, 60vh, 75vh)">
|
||||
{{- partial "media.html" (dict "media" . "class" "w-full h-full object-cover object-center" "lazy" false) -}}
|
||||
</div>
|
||||
{{- end -}}
|
||||
|
||||
{{- end -}}
|
||||
|
||||
|
||||
<!-- ── PROSE BODY ────────────────────────────────────────────── -->
|
||||
{{- with .Content -}}
|
||||
<article class="gutter-x mb-20 md:mb-28">
|
||||
<div class="max-w-5xl mx-auto">
|
||||
<div class="max-w-3xl prose-editorial">
|
||||
{{ . }}
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
{{- end -}}
|
||||
|
||||
|
||||
<!-- ── GENERATION NOTES (disabled — remove comment tags to enable)
|
||||
{{- with .Params.prompt -}}
|
||||
<section class="gutter-x mb-20 md:mb-28">
|
||||
<div class="max-w-5xl mx-auto">
|
||||
<div class="flex items-center gap-5 mb-8">
|
||||
<span class="badge badge-outline">Generation Notes</span>
|
||||
<div class="flex-1 gradient-line"></div>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-px bg-zinc">
|
||||
{{- with .scene -}}
|
||||
<div class="bg-concrete p-6 md:p-8">
|
||||
<p class="label text-fog mb-4">Scene</p>
|
||||
<p class="font-body text-sm text-chalk leading-relaxed">{{ . }}</p>
|
||||
</div>
|
||||
{{- end -}}
|
||||
{{- with .environment -}}
|
||||
<div class="bg-concrete p-6 md:p-8">
|
||||
<p class="label text-fog mb-4">Environment</p>
|
||||
<p class="font-body text-sm text-chalk leading-relaxed">{{ . }}</p>
|
||||
</div>
|
||||
{{- end -}}
|
||||
{{- with .style -}}
|
||||
<div class="bg-concrete p-6 md:p-8">
|
||||
<p class="label text-fog mb-4">Lighting & Style</p>
|
||||
<p class="font-body text-sm text-chalk leading-relaxed">{{ . }}</p>
|
||||
</div>
|
||||
{{- end -}}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{{- end -}}
|
||||
*/ -}}
|
||||
|
||||
<!-- ── GALLERY ───────────────────────────────────────────────── -->
|
||||
{{- $bundleImages := .Resources.Match "[0-9]*.png" -}}
|
||||
{{- $legacyItems := slice -}}
|
||||
{{- with .Params.items -}}{{- $legacyItems = . -}}{{- end -}}
|
||||
{{- if or $bundleImages $legacyItems -}}
|
||||
<section class="gutter-x pb-24 md:pb-32" aria-label="Gallery">
|
||||
<div class="max-w-5xl mx-auto">
|
||||
|
||||
{{- if $bundleImages -}}
|
||||
{{- /* Pre-compute lightbox items so Alpine gets a static JSON array */ -}}
|
||||
{{- $lbItems := slice -}}
|
||||
{{- range $bundleImages -}}
|
||||
{{- $stem := .Name | strings.TrimSuffix ".png" -}}
|
||||
{{- $video := $.Resources.GetMatch (printf "%s.mp4" $stem) -}}
|
||||
{{- $webp := .Resize "1200x webp" -}}
|
||||
{{- $entry := dict "img" $webp.RelPermalink "video" "" -}}
|
||||
{{- if $video -}}{{- $entry = dict "img" $webp.RelPermalink "video" $video.RelPermalink -}}{{- end -}}
|
||||
{{- $lbItems = $lbItems | append $entry -}}
|
||||
{{- end -}}
|
||||
|
||||
<div x-data="{ items: {{ $lbItems | jsonify }} }">
|
||||
|
||||
<div class="flex items-center gap-5 mb-10">
|
||||
<span class="badge badge-gradient">Gallery</span>
|
||||
<span class="label text-fog">{{ len $bundleImages }} {{ if eq (len $bundleImages) 1 }}work{{ else }}works{{ end }}</span>
|
||||
<div class="flex-1 gradient-line ml-2"></div>
|
||||
</div>
|
||||
|
||||
<div class="columns-1 sm:columns-2 lg:columns-3 gap-5 space-y-5">
|
||||
{{- range $i, $img := $bundleImages -}}
|
||||
{{- $stem := $img.Name | strings.TrimSuffix ".png" -}}
|
||||
{{- $video := $.Resources.GetMatch (printf "%s.mp4" $stem) -}}
|
||||
<figure class="break-inside-avoid group card-comic cursor-zoom-in"
|
||||
@click="$dispatch('lightbox:open', { items, idx: {{ $i }} })">
|
||||
<div class="card-media min-h-48">
|
||||
{{- if $video -}}
|
||||
<video class="w-full pointer-events-none" src="{{ $video.RelPermalink }}" poster="{{ $img.RelPermalink }}" autoplay loop muted playsinline></video>
|
||||
{{- else -}}
|
||||
{{- partial "img.html" (dict "res" $img "widths" (slice 800 1200) "sizes" "(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw" "class" "w-full pointer-events-none") -}}
|
||||
<section class="gutter-x mb-20 md:mb-28">
|
||||
<div class="max-w-5xl mx-auto">
|
||||
<div class="flex items-center gap-5 mb-8">
|
||||
<span class="badge badge-outline">Generation Notes</span>
|
||||
<div class="flex-1 gradient-line"></div>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-px bg-zinc">
|
||||
{{- with .scene -}}
|
||||
<div class="bg-concrete p-6 md:p-8">
|
||||
<p class="label text-fog mb-4">Scene</p>
|
||||
<p class="font-body text-sm text-chalk leading-relaxed">{{ . }}</p>
|
||||
</div>
|
||||
{{- end -}}
|
||||
{{- with .environment -}}
|
||||
<div class="bg-concrete p-6 md:p-8">
|
||||
<p class="label text-fog mb-4">Environment</p>
|
||||
<p class="font-body text-sm text-chalk leading-relaxed">{{ . }}</p>
|
||||
</div>
|
||||
{{- end -}}
|
||||
{{- with .style -}}
|
||||
<div class="bg-concrete p-6 md:p-8">
|
||||
<p class="label text-fog mb-4">Lighting & Style</p>
|
||||
<p class="font-body text-sm text-chalk leading-relaxed">{{ . }}</p>
|
||||
</div>
|
||||
{{- end -}}
|
||||
</div>
|
||||
</figure>
|
||||
{{- end -}}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{{- else -}}
|
||||
<!-- Legacy front-matter items -->
|
||||
<div class="flex items-center gap-5 mb-10">
|
||||
<span class="badge badge-gradient">Gallery</span>
|
||||
<span class="label text-fog">{{ len $legacyItems }} {{ if eq (len $legacyItems) 1 }}work{{ else }}works{{ end }}</span>
|
||||
<div class="flex-1 gradient-line ml-2"></div>
|
||||
</div>
|
||||
<div x-data="{ selected: null }" class="columns-1 sm:columns-2 lg:columns-3 gap-5 space-y-5">
|
||||
{{- range $i, $item := $legacyItems -}}
|
||||
<figure class="break-inside-avoid group cursor-pointer card-comic" @click="selected = selected === {{ $i }} ? null : {{ $i }}">
|
||||
<div class="card-media">
|
||||
{{- partial "media.html" (dict "media" $item "class" "w-full") -}}
|
||||
</div>
|
||||
{{- with $item.description -}}
|
||||
<figcaption class="px-4 py-3 text-fog text-sm leading-relaxed font-body transition-colors group-hover:text-chalk">{{ . }}</figcaption>
|
||||
{{- end -}}
|
||||
</figure>
|
||||
{{- end -}}
|
||||
</div>
|
||||
</section>
|
||||
{{- end -}}
|
||||
-->
|
||||
|
||||
<!-- ── GALLERY ───────────────────────────────────────────────── -->
|
||||
{{- $bundleImages := .Resources.Match "[0-9]*.png" -}}
|
||||
{{- $legacyItems := slice -}}
|
||||
{{- with .Params.items -}}{{- $legacyItems = . -}}{{- end -}}
|
||||
{{- if or $bundleImages $legacyItems -}}
|
||||
<section class="gutter-x pb-24 md:pb-32" aria-label="Gallery">
|
||||
<div class="max-w-5xl mx-auto">
|
||||
{{- if $bundleImages -}}
|
||||
{{- /* Pre-compute lightbox items so Alpine gets a static JSON array */ -}}
|
||||
{{- $lbItems := slice -}}
|
||||
{{- range $bundleImages -}}
|
||||
{{- $stem := .Name | strings.TrimSuffix ".png" -}}
|
||||
{{- $video := $.Resources.GetMatch (printf "%s.mp4" $stem) -}}
|
||||
{{- $webp := .Resize "1200x webp" -}}
|
||||
{{- $entry := dict "img" $webp.RelPermalink "video" "" -}}
|
||||
{{- if $video -}}{{- $entry = dict "img" $webp.RelPermalink "video" $video.RelPermalink -}}{{- end -}}
|
||||
{{- $lbItems = $lbItems | append $entry -}}
|
||||
{{- end -}}
|
||||
|
||||
|
||||
<div x-data="{ items: {{ $lbItems | jsonify }} }">
|
||||
<div class="flex items-center gap-5 mb-10">
|
||||
<span class="badge badge-gradient">Gallery</span>
|
||||
<span class="label text-fog"
|
||||
>{{ len $bundleImages }} {{ if eq (len $bundleImages) 1 }}work{{ else }}works{{ end }}</span
|
||||
>
|
||||
<div class="flex-1 gradient-line ml-2"></div>
|
||||
</div>
|
||||
|
||||
<div class="columns-1 sm:columns-2 lg:columns-3 gap-5 space-y-5">
|
||||
{{- range $i, $img := $bundleImages -}}
|
||||
{{- $stem := $img.Name | strings.TrimSuffix ".png" -}}
|
||||
{{- $video := $.Resources.GetMatch (printf "%s.mp4" $stem) -}}
|
||||
<figure
|
||||
class="break-inside-avoid group card-comic cursor-zoom-in"
|
||||
@click="$dispatch('lightbox:open', { items, idx: {{ $i }} })"
|
||||
>
|
||||
<div class="card-media min-h-48">
|
||||
{{- if $video -}}
|
||||
<video
|
||||
class="w-full pointer-events-none"
|
||||
src="{{ $video.RelPermalink }}"
|
||||
poster="{{ $img.RelPermalink }}"
|
||||
autoplay
|
||||
loop
|
||||
muted
|
||||
playsinline
|
||||
></video>
|
||||
{{- else -}}
|
||||
{{- partial "img.html" (dict "res" $img "widths" (slice 800 1200) "sizes" "(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw" "class" "w-full pointer-events-none") -}}
|
||||
{{- end -}}
|
||||
</div>
|
||||
</figure>
|
||||
{{- end -}}
|
||||
</div>
|
||||
</div>
|
||||
{{- else -}}
|
||||
<!-- Legacy front-matter items -->
|
||||
<div class="flex items-center gap-5 mb-10">
|
||||
<span class="badge badge-gradient">Gallery</span>
|
||||
<span class="label text-fog"
|
||||
>{{ len $legacyItems }} {{ if eq (len $legacyItems) 1 }}work{{ else }}works{{ end }}</span
|
||||
>
|
||||
<div class="flex-1 gradient-line ml-2"></div>
|
||||
</div>
|
||||
<div x-data="{ selected: null }" class="columns-1 sm:columns-2 lg:columns-3 gap-5 space-y-5">
|
||||
{{- range $i, $item := $legacyItems -}}
|
||||
<figure
|
||||
class="break-inside-avoid group cursor-pointer card-comic"
|
||||
@click="selected = selected === {{ $i }} ? null : {{ $i }}"
|
||||
>
|
||||
<div class="card-media">
|
||||
{{- partial "media.html" (dict "media" $item "class" "w-full") -}}
|
||||
</div>
|
||||
{{- with $item.description -}}
|
||||
<figcaption
|
||||
class="px-4 py-3 text-fog text-sm leading-relaxed font-body transition-colors group-hover:text-chalk"
|
||||
>
|
||||
{{ . }}
|
||||
</figcaption>
|
||||
{{- end -}}
|
||||
</figure>
|
||||
{{- end -}}
|
||||
</div>
|
||||
{{- end -}}
|
||||
|
||||
</div>
|
||||
</section>
|
||||
{{- end -}}
|
||||
|
||||
</div>
|
||||
</section>
|
||||
{{- end -}}
|
||||
|
||||
<!-- ── TAGS ─────────────────────────────────────────────────── -->
|
||||
{{- with .Params.tags -}}
|
||||
<footer class="gutter-x border-t border-zinc pt-10 pb-20">
|
||||
<div class="max-w-5xl mx-auto flex flex-wrap gap-3 items-center">
|
||||
<span class="label text-fog">Tagged:</span>
|
||||
{{- range . -}}
|
||||
<a
|
||||
href="/tags/{{ . | urlize }}/"
|
||||
class="badge badge-outline"
|
||||
># {{ . }}</a>
|
||||
{{- end -}}
|
||||
</div>
|
||||
</footer>
|
||||
{{- end -}}
|
||||
<!-- ── TAGS ─────────────────────────────────────────────────── -->
|
||||
{{- with .Params.tags -}}
|
||||
<footer class="gutter-x border-t border-zinc pt-10 pb-20">
|
||||
<div class="max-w-5xl mx-auto flex flex-wrap gap-3 items-center">
|
||||
<span class="label text-fog">Tagged:</span>
|
||||
{{- range . -}}
|
||||
<a href="/tags/{{ . | urlize }}/" class="badge badge-outline"># {{ . }}</a>
|
||||
{{- end -}}
|
||||
</div>
|
||||
</footer>
|
||||
{{- end -}}
|
||||
|
||||
<!-- ── RELATED POSTS ─────────────────────────────────────────── -->
|
||||
{{- $related := .Site.RegularPages.Related . | first 3 -}}
|
||||
{{- with $related -}}
|
||||
<section class="bg-concrete border-t border-zinc gutter-x py-16 md:py-24 spray-bg">
|
||||
<div class="max-w-5xl mx-auto">
|
||||
<div class="flex items-center gap-5 mb-10">
|
||||
<h2 class="font-display text-3xl md:text-4xl text-paper leading-none">
|
||||
More <span class="text-gradient">Drops</span>
|
||||
</h2>
|
||||
<div class="flex-1 gradient-line ml-2"></div>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-3 gap-5">
|
||||
{{- range . -}}
|
||||
{{- partial "post-card.html" . -}}
|
||||
{{- end -}}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{{- end -}}
|
||||
|
||||
<!-- ── RELATED POSTS ─────────────────────────────────────────── -->
|
||||
{{- $related := .Site.RegularPages.Related . | first 3 -}}
|
||||
{{- with $related -}}
|
||||
<section class="bg-concrete border-t border-zinc gutter-x py-16 md:py-24 spray-bg">
|
||||
<div class="max-w-5xl mx-auto">
|
||||
<div class="flex items-center gap-5 mb-10">
|
||||
<h2 class="font-display text-3xl md:text-4xl text-paper leading-none">
|
||||
More <span class="text-gradient">Drops</span>
|
||||
</h2>
|
||||
<div class="flex-1 gradient-line ml-2"></div>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-3 gap-5">
|
||||
{{- range . -}}
|
||||
{{- partial "post-card.html" . -}}
|
||||
{{- end -}}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{{- end -}}
|
||||
|
||||
{{- end -}}
|
||||
|
||||
+22
-22
@@ -1,26 +1,26 @@
|
||||
{{- define "main" -}}
|
||||
<!-- Tag header -->
|
||||
<header class="gutter-x pt-36 pb-16 border-b border-mist">
|
||||
<div class="max-w-4xl">
|
||||
<span class="label text-ash block mb-4">Tag</span>
|
||||
<h1 class="font-display text-5xl md:text-7xl text-paper text-balance"># {{ .Title }}</h1>
|
||||
<p class="text-ash label mt-5">
|
||||
{{ len .Pages }} {{ if eq (len .Pages) 1 }}editorial{{ else }}editorials{{ end }}
|
||||
</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Tag header -->
|
||||
<header class="gutter-x pt-36 pb-16 border-b border-mist">
|
||||
<div class="max-w-4xl">
|
||||
<span class="label text-ash block mb-4">Tag</span>
|
||||
<h1 class="font-display text-5xl md:text-7xl text-paper text-balance"># {{ .Title }}</h1>
|
||||
<p class="text-ash label mt-5">{{ len .Pages }} {{ if eq (len .Pages) 1 }}editorial{{ else }}editorials{{ end }}</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Posts grid -->
|
||||
<section class="gutter-x py-16 md:py-24">
|
||||
{{- if .Paginator.Pages -}}
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-x-5 gap-y-12">
|
||||
{{- range .Paginator.Pages -}}
|
||||
{{- partial "post-card.html" . -}}
|
||||
<!-- Posts grid -->
|
||||
<section class="gutter-x py-16 md:py-24">
|
||||
{{- if .Paginator.Pages -}}
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-x-5 gap-y-12">
|
||||
{{- range .Paginator.Pages -}}
|
||||
{{- partial "post-card.html" . -}}
|
||||
{{- end -}}
|
||||
</div>
|
||||
{{- partial "pagination.html" . -}}
|
||||
{{- else -}}
|
||||
<p class="text-ash label text-center py-24">No posts with this tag yet.</p>
|
||||
{{- end -}}
|
||||
</div>
|
||||
{{- partial "pagination.html" . -}}
|
||||
{{- else -}}
|
||||
<p class="text-ash label text-center py-24">No posts with this tag yet.</p>
|
||||
{{- end -}}
|
||||
</section>
|
||||
|
||||
</section>
|
||||
{{- end -}}
|
||||
|
||||
Reference in New Issue
Block a user