Initial commit
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
{{- /* Inline author byline for post single pages.
|
||||
Expects the author page as context (.).
|
||||
*/ -}}
|
||||
<div class="flex items-center gap-4 py-6 border-t border-b border-zinc my-8">
|
||||
{{- $avatarRes := .Resources.GetMatch "avatar.*" -}}
|
||||
{{- $avatarSrc := "" -}}
|
||||
{{- with $avatarRes }}{{ $avatarSrc = .RelPermalink }}{{ else }}{{ with $.Params.avatar }}{{ $avatarSrc = . }}{{ end }}{{ end -}}
|
||||
{{- with $avatarSrc -}}
|
||||
<a href="{{ $.RelPermalink }}" class="flex-shrink-0">
|
||||
<img
|
||||
src="{{ . }}"
|
||||
alt="{{ $.Params.name | default $.Title }}"
|
||||
class="w-12 h-12 object-cover border border-zinc hover:border-heat transition-colors"
|
||||
loading="lazy"
|
||||
>
|
||||
</a>
|
||||
{{- end -}}
|
||||
<div class="min-w-0">
|
||||
<a href="{{ .RelPermalink }}" class="label text-heat hover:text-frost transition-colors block mb-1">
|
||||
{{ .Params.name | default .Title }}
|
||||
</a>
|
||||
{{- with .Params.bio -}}
|
||||
<p class="text-fog text-sm leading-relaxed line-clamp-2 font-body">{{ . }}</p>
|
||||
{{- end -}}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,46 @@
|
||||
<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>
|
||||
|
||||
<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="logo-glitch block mb-4 hover:opacity-90 transition-opacity w-fit" aria-label="{{ .Site.Title }} Home">
|
||||
{{- partial "logo.html" (dict "id" "footer" "class" "h-10 w-auto") -}}
|
||||
</a>
|
||||
<p class="label text-fog max-w-xs leading-loose">{{ .Site.Params.description }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Links -->
|
||||
<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>
|
||||
{{- 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>
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="relative z-10 gradient-line mt-10 mb-6"></div>
|
||||
<p class="relative z-10 label text-smoke">
|
||||
© {{ .Site.Params.copyrightYear }} {{ .Site.Params.author }}. All rights reserved.
|
||||
</p>
|
||||
</footer>
|
||||
@@ -0,0 +1,74 @@
|
||||
<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 -}}
|
||||
{{- else -}}
|
||||
{{- .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 }}
|
||||
|
||||
<!-- 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 "-" "_" }}">
|
||||
{{- $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">
|
||||
|
||||
{{- 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 }}
|
||||
{{- end }}
|
||||
|
||||
<!-- Twitter / X Card -->
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
{{- with .Site.Params.twitterHandle }}
|
||||
<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 }}">
|
||||
|
||||
<!-- Canonical -->
|
||||
<link rel="canonical" href="{{ .Permalink }}">
|
||||
|
||||
<!-- JSON-LD structured data -->
|
||||
{{- partial "schema.html" . -}}
|
||||
|
||||
<!-- Fonts — Bebas Neue + Barlow + Share Tech Mono -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<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">
|
||||
|
||||
<!-- 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 }}>
|
||||
|
||||
<!-- Favicon -->
|
||||
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
||||
<link rel="icon" href="/favicon.ico" sizes="any">
|
||||
@@ -0,0 +1,11 @@
|
||||
{{- /* Inline SVG icon. Usage: {{ partial "icon.html" "arrow-right" }}
|
||||
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>
|
||||
{{- 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>
|
||||
{{- 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>
|
||||
{{- end -}}
|
||||
@@ -0,0 +1,31 @@
|
||||
{{- /*
|
||||
Inline SVG logotype with gradient fill.
|
||||
Usage: {{ partial "logo.html" (dict "id" "nav" "class" "h-9 w-auto") }}
|
||||
Params:
|
||||
id — unique suffix for gradient ID (required to avoid conflicts when used twice)
|
||||
class — CSS classes on the <svg> element (default: "h-9 w-auto")
|
||||
*/ -}}
|
||||
{{- $id := .id | default "logo" -}}
|
||||
{{- $class := .class | default "h-9 w-auto" -}}
|
||||
<svg
|
||||
class="{{ $class }}"
|
||||
viewBox="0 0 96 96"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-label="{{ site.Title }}"
|
||||
role="img"
|
||||
>
|
||||
<defs>
|
||||
<linearGradient id="lg-{{ $id }}" gradientUnits="userSpaceOnUse" x1="10" y1="48" x2="86" y2="48">
|
||||
<stop offset="0%" stop-color="#FF1A8C"/>
|
||||
<stop offset="50%" stop-color="#9B00FF"/>
|
||||
<stop offset="100%" stop-color="#00C8FF"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g transform="translate(48,48)">
|
||||
<path
|
||||
d="M -1.1e-6,-43.328173 -37.551083,43.328173 -1.1e-6,34.662539 H -17.331269 L -1.1e-6,-5.7770893 17.33127,34.662539 H -1.1e-6 l 37.5510841,8.665635 z"
|
||||
fill="url(#lg-{{ $id }})"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
@@ -0,0 +1,36 @@
|
||||
{{- /*
|
||||
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)
|
||||
*/ -}}
|
||||
|
||||
{{- $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>
|
||||
{{- else -}}
|
||||
<img
|
||||
class="{{ $class }}"
|
||||
src="{{ .src }}"
|
||||
alt="{{ .alt | default "" }}"
|
||||
{{- if $lazy }} loading="lazy" decoding="async"{{ end }}
|
||||
>
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
@@ -0,0 +1,132 @@
|
||||
<header
|
||||
x-data="{ scrolled: false }"
|
||||
x-init="window.addEventListener('htmx:pushedIntoHistory', () => { $store.nav.path = window.location.pathname; $store.nav.open = false })"
|
||||
@scroll.window="scrolled = window.scrollY > 80"
|
||||
class="fixed top-0 inset-x-0 z-50"
|
||||
>
|
||||
<!-- Top bar -->
|
||||
<div
|
||||
class="gutter-x flex items-center justify-between py-4 transition-all duration-500"
|
||||
:class="$store.nav.open || scrolled ? 'bg-ink/95 backdrop-blur-md shadow-[0_1px_0_var(--color-zinc)]' : ''"
|
||||
>
|
||||
<!-- Logotype -->
|
||||
<a href="/" class="logo-glitch flex items-center gap-3 hover:opacity-90 transition-opacity" aria-label="{{ .Site.Title }} Home">
|
||||
{{- partial "logo.html" (dict "id" "nav" "class" "h-8 w-auto flex-shrink-0") -}}
|
||||
<span class="text-gradient font-display text-2xl md:text-3xl leading-none tracking-wide">
|
||||
{{- .Site.Params.logoText -}}
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<!-- 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>
|
||||
{{- end -}}
|
||||
</nav>
|
||||
|
||||
<!-- Mobile toggle — animated hamburger → X -->
|
||||
<button
|
||||
class="md:hidden w-10 h-10 flex flex-col items-center justify-center gap-[6px] text-fog hover:text-chalk transition-colors"
|
||||
:class="$store.nav.open ? 'text-heat' : ''"
|
||||
@click="$store.nav.open = !$store.nav.open"
|
||||
:aria-expanded="$store.nav.open.toString()"
|
||||
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>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Fullscreen overlay — teleported to <body> to escape header's stacking context -->
|
||||
<template x-teleport="body">
|
||||
<div
|
||||
id="mobile-menu"
|
||||
x-show="$store.nav.open"
|
||||
x-cloak
|
||||
x-transition:enter="transition duration-300 ease-out"
|
||||
x-transition:enter-start="opacity-0"
|
||||
x-transition:enter-end="opacity-100"
|
||||
x-transition:leave="transition duration-200 ease-in"
|
||||
x-transition:leave-start="opacity-100"
|
||||
x-transition:leave-end="opacity-0"
|
||||
class="fixed inset-0 z-40 flex flex-col bg-void speed-lines overflow-hidden"
|
||||
>
|
||||
<!-- Top spacer (nav bar height) -->
|
||||
<div class="flex-none h-[65px]"></div>
|
||||
<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>
|
||||
|
||||
<!-- 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 -}}
|
||||
{{- end -}}
|
||||
</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">
|
||||
{{- range .Site.Menus.footer -}}
|
||||
<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">
|
||||
<p class="label text-smoke">{{ .Site.Params.tagline }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</header>
|
||||
|
||||
<style>
|
||||
@keyframes mob-link-in {
|
||||
from { opacity: 0; transform: translateY(14px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,33 @@
|
||||
{{- /* Custom pagination component.
|
||||
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">
|
||||
|
||||
{{- 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 -}}
|
||||
|
||||
{{- 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>
|
||||
{{- end -}}
|
||||
@@ -0,0 +1,57 @@
|
||||
{{- /* Large overlay card for featured/secondary slots.
|
||||
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 -}}
|
||||
<img class="w-full h-full object-cover" src="{{ $thumb.RelPermalink }}" alt="{{ $post.Title }}" loading="lazy" decoding="async">
|
||||
{{- 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 -}}
|
||||
</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>
|
||||
|
||||
<!-- 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>
|
||||
{{- end -}}
|
||||
<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>
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
<time class="label text-chalk opacity-70" datetime="{{ $post.Date.Format "2006-01-02" }}">
|
||||
{{- $post.Date.Format "Jan 2006" -}}
|
||||
</time>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</a>
|
||||
</article>
|
||||
@@ -0,0 +1,63 @@
|
||||
{{- /* Compact post card for grids.
|
||||
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 -}}
|
||||
<img class="w-full h-full object-cover" src="{{ $thumb.RelPermalink }}" alt="{{ $post.Title }}" loading="lazy" decoding="async">
|
||||
{{- 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 -}}
|
||||
</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>
|
||||
{{- 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">
|
||||
{{- $post.Title -}}
|
||||
</h3>
|
||||
|
||||
<!-- Author + date -->
|
||||
<div class="flex items-center gap-2 px-4 pb-4 mt-auto pt-3 border-t border-zinc">
|
||||
{{- with $post.Params.author -}}
|
||||
{{- $authorPage := site.GetPage (printf "authors/%s" .) -}}
|
||||
{{- with $authorPage -}}
|
||||
{{- $avRes := .Resources.GetMatch "avatar.*" -}}
|
||||
{{- $avSrc := "" -}}
|
||||
{{- with $avRes }}{{ $avSrc = .RelPermalink }}{{ else }}{{ with $.Params.avatar }}{{ $avSrc = . }}{{ end }}{{ end -}}
|
||||
{{- with $avSrc -}}
|
||||
<img
|
||||
src="{{ . }}"
|
||||
alt="{{ $authorPage.Params.name | default $authorPage.Title }}"
|
||||
class="w-5 h-5 object-cover border border-smoke flex-shrink-0"
|
||||
loading="lazy"
|
||||
>
|
||||
{{- end -}}
|
||||
<span class="label text-fog">{{ .Params.name | default .Title }}</span>
|
||||
<span class="text-smoke" aria-hidden="true">/</span>
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
<time class="label text-fog" datetime="{{ $post.Date.Format "2006-01-02" }}">
|
||||
{{- $post.Date.Format "Jan 2006" -}}
|
||||
</time>
|
||||
</div>
|
||||
|
||||
</a>
|
||||
</article>
|
||||
@@ -0,0 +1,62 @@
|
||||
{{- /* JSON-LD structured data — WebSite on home, BlogPosting on posts */ -}}
|
||||
|
||||
{{- if .IsHome -}}
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "WebSite",
|
||||
"name": {{ .Site.Title | jsonify }},
|
||||
"url": {{ .Site.BaseURL | jsonify }},
|
||||
"description": {{ .Site.Params.description | jsonify }},
|
||||
"publisher": {
|
||||
"@type": "Organization",
|
||||
"name": {{ .Site.Title | jsonify }},
|
||||
"url": {{ .Site.BaseURL | jsonify }},
|
||||
"logo": {
|
||||
"@type": "ImageObject",
|
||||
"url": {{ "/logo.svg" | absURL | jsonify }}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{{- else if and .IsPage (eq .Section "posts") -}}
|
||||
{{- $authorPage := "" -}}
|
||||
{{- with .Params.author -}}
|
||||
{{- $authorPage = site.GetPage (printf "authors/%s" .) -}}
|
||||
{{- end -}}
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "BlogPosting",
|
||||
"headline": {{ .Title | jsonify }},
|
||||
"description": {{ with .Description }}{{ . | jsonify }}{{ else }}{{ .Site.Params.description | jsonify }}{{ end }},
|
||||
"url": {{ .Permalink | jsonify }},
|
||||
"datePublished": {{ .Date.Format "2006-01-02T15:04:05Z07:00" | jsonify }},
|
||||
"dateModified": {{ .Lastmod.Format "2006-01-02T15:04:05Z07:00" | jsonify }},
|
||||
{{- with .Params.banner }}
|
||||
"image": {{ .src | absURL | jsonify }},
|
||||
{{- else }}
|
||||
"image": {{ .Site.Params.ogImage | absURL | jsonify }},
|
||||
{{- end }}
|
||||
"author": {
|
||||
"@type": "Person",
|
||||
"name": {{ with $authorPage }}{{ .Params.name | default .Title | jsonify }}{{ else }}{{ .Site.Params.author | jsonify }}{{ end }}{{ with $authorPage }},
|
||||
"url": {{ .Permalink | jsonify }}{{ end }}
|
||||
},
|
||||
"publisher": {
|
||||
"@type": "Organization",
|
||||
"name": {{ .Site.Title | jsonify }},
|
||||
"url": {{ .Site.BaseURL | jsonify }},
|
||||
"logo": {
|
||||
"@type": "ImageObject",
|
||||
"url": {{ "/logo.svg" | absURL | jsonify }}
|
||||
}
|
||||
},
|
||||
"mainEntityOfPage": {
|
||||
"@type": "WebPage",
|
||||
"@id": {{ .Permalink | jsonify }}
|
||||
}{{ with .Params.tags }},
|
||||
"keywords": {{ . | jsonify }}{{ end }}
|
||||
}
|
||||
</script>
|
||||
{{- end -}}
|
||||
Reference in New Issue
Block a user