Initial commit

This commit is contained in:
2026-04-08 19:49:15 +02:00
commit cd1ff989d0
52 changed files with 3277 additions and 0 deletions
+7
View File
@@ -0,0 +1,7 @@
public/
resources/
.hugo_build.lock
node_modules/
.DS_Store
hugo_stats.json
*.local
+33
View File
@@ -0,0 +1,33 @@
# Stage 1: Build
FROM node:22-alpine AS builder
# Install Hugo
RUN apk add --no-cache hugo
# Install pnpm
RUN corepack enable && corepack prepare pnpm@latest --activate
WORKDIR /app
# Copy package files first (for layer caching)
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
# Copy source files
COPY . .
# Build CSS and Hugo site
RUN pnpm build
# Stage 2: Production
FROM nginx:alpine
# Copy built site
COPY --from=builder /app/public /usr/share/nginx/html
# Copy nginx config
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
+595
View File
@@ -0,0 +1,595 @@
@import "tailwindcss";
/* ── Content sources ─────────────────────────────────────────── */
@source "../../layouts/**/*.html";
@source "../../content/**/*.md";
@source "../../assets/js/**/*.js";
/* ── Design tokens — sneaker drop culture ────────────────────── */
@theme {
/* Dark base — deep navy-black */
--color-void: #050510;
--color-ink: #09091A;
--color-concrete: #0F0F22;
--color-zinc: #1C1C38;
--color-smoke: #383860;
--color-fog: #6868A0;
--color-chalk: #B8B8E0;
--color-paper: #EEEEFF;
/* Gradient triad — pink → purple → ice */
--color-heat: #FF1A8C; /* hot pink — primary energy */
--color-pulse: #9B00FF; /* electric purple — mid */
--color-frost: #00C8FF; /* ice blue — cool accent */
--color-ember: #FF4455; /* alert red */
--color-lime: #AAFF00; /* acid lime — highlight */
/* Backward-compat aliases so existing templates still work */
--color-gold: var(--color-heat);
--color-gold-dim: #CC1570;
--color-hot: var(--color-heat);
--color-volt: var(--color-lime);
--color-ice: var(--color-frost);
--color-mist: var(--color-zinc);
--color-ash: var(--color-smoke);
--color-graphite: var(--color-concrete);
--color-charcoal: var(--color-ink);
/* ── Typography ─────────────────────────────────────────────── */
--font-display: "Bebas Neue", "Arial Black", sans-serif;
--font-body: "Barlow", system-ui, sans-serif;
--font-comic: "Unbounded", "Arial Black", sans-serif;
--font-mono: "Share Tech Mono", "Courier New", monospace;
/* ── Easing ─────────────────────────────────────────────────── */
--ease-bounce: cubic-bezier(0.34, 1.56, 0.64, 1);
--ease-sharp: cubic-bezier(0.4, 0, 0.6, 1);
--ease-cinematic: cubic-bezier(0.25, 0.46, 0.45, 0.94);
--ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
}
/* ── Base ─────────────────────────────────────────────────────── */
@layer base {
*, *::before, *::after { box-sizing: border-box; }
[x-cloak] { display: none !important; }
html {
background-color: var(--color-void);
color: var(--color-paper);
font-family: var(--font-body);
font-size: 16px;
scroll-behavior: smooth;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
}
body { min-height: 100vh; }
h1, h2, h3, h4, h5, h6 {
font-family: var(--font-display);
font-weight: 400;
letter-spacing: 0.01em;
line-height: 0.92;
text-transform: uppercase;
}
a {
color: inherit;
text-decoration: none;
transition: color 0.2s var(--ease-sharp);
}
img, video { max-width: 100%; height: auto; display: block; }
::selection {
background-color: var(--color-heat);
color: #fff;
}
:focus-visible {
outline: 2px solid var(--color-heat);
outline-offset: 3px;
}
/* Gradient scrollbar */
::-webkit-scrollbar { width: 4px; height: 4px; }
::-webkit-scrollbar-track { background: var(--color-ink); }
::-webkit-scrollbar-thumb { background: linear-gradient(180deg, var(--color-heat), var(--color-frost)); }
}
/* ── Components ───────────────────────────────────────────────── */
@layer components {
/* ── Gradient text ─────────────────────────────────────────── */
.text-gradient {
background: linear-gradient(90deg, var(--color-heat) 0%, var(--color-pulse) 50%, var(--color-frost) 100%);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
color: transparent;
}
.text-gradient-animated {
background: linear-gradient(90deg, var(--color-heat), var(--color-pulse), var(--color-frost), var(--color-heat));
background-size: 300% 100%;
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
color: transparent;
animation: gradient-shift 5s ease infinite;
}
/* ── Gradient overlays ─────────────────────────────────────── */
.overlay-gradient {
background: linear-gradient(
to top,
rgba(5, 5, 16, 0.98) 0%,
rgba(5, 5, 16, 0.65) 40%,
rgba(5, 5, 16, 0.1) 70%,
transparent 100%
);
}
.overlay-gradient-hero {
background: linear-gradient(
155deg,
rgba(5, 5, 16, 0.1) 0%,
rgba(5, 5, 16, 0.5) 45%,
rgba(5, 5, 16, 0.97) 100%
);
}
/* ── Label — monospace spec-sheet text ─────────────────────── */
.label {
font-family: var(--font-mono);
font-size: 0.65rem;
font-weight: 400;
letter-spacing: 0.16em;
text-transform: uppercase;
}
/* ── Badge — parallelogram sticker ────────────────────────── */
.badge {
display: inline-flex;
align-items: center;
font-family: var(--font-mono);
font-size: 0.65rem;
font-weight: 400;
letter-spacing: 0.14em;
line-height: 1;
background: var(--color-heat);
color: #fff;
padding: 5px 16px;
text-transform: uppercase;
clip-path: polygon(8px 0, 100% 0, calc(100% - 8px) 100%, 0 100%);
transition: transform 0.15s var(--ease-bounce), filter 0.2s;
}
.badge:hover { transform: skewX(-4deg) scale(1.06); filter: brightness(1.2); }
a.badge, a > .badge { cursor: pointer; }
.badge-heat { background: var(--color-heat); color: #fff; }
.badge-pulse { background: var(--color-pulse); color: #fff; }
.badge-frost { background: var(--color-frost); color: var(--color-void); }
.badge-lime { background: var(--color-lime); color: var(--color-void); }
.badge-ember { background: var(--color-ember); color: #fff; }
/* Semantic aliases kept for template compat */
.badge-hot { background: var(--color-heat); color: #fff; }
.badge-volt { background: var(--color-lime); color: var(--color-void); }
.badge-ice { background: var(--color-frost); color: var(--color-void); }
.badge-gradient {
background: linear-gradient(90deg, var(--color-heat), var(--color-pulse), var(--color-frost));
color: #fff;
}
.badge-outline {
background: transparent;
color: var(--color-heat);
border: 1.5px solid var(--color-heat);
padding: 4px 14px;
clip-path: none;
}
/* ── Btn — primary CTA with shimmer sweep ──────────────────── */
.btn {
display: inline-flex;
align-items: center;
gap: 0.5em;
font-family: var(--font-mono);
font-size: 0.7rem;
font-weight: 400;
letter-spacing: 0.18em;
text-transform: uppercase;
padding: 14px 28px;
background: var(--color-heat);
color: #fff;
position: relative;
overflow: hidden;
clip-path: polygon(10px 0, 100% 0, calc(100% - 10px) 100%, 0 100%);
transition:
box-shadow 0.25s var(--ease-sharp),
transform 0.2s var(--ease-bounce);
cursor: pointer;
}
/* shimmer sweep */
.btn::before {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(
105deg,
transparent 35%,
rgba(255, 255, 255, 0.28) 50%,
transparent 65%
);
transform: translateX(-150%);
transition: transform 0.55s var(--ease-out-expo);
pointer-events: none;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 8px 32px rgba(255, 26, 140, 0.5),
0 2px 8px rgba(155, 0, 255, 0.2);
}
.btn:hover::before { transform: translateX(200%); }
.btn:active { transform: translateY(0px); }
.btn-outline {
background: transparent;
border: 2px solid var(--color-heat);
color: var(--color-heat);
clip-path: none;
}
.btn-outline::before { display: none; }
.btn-outline:hover {
background: var(--color-heat);
color: #fff;
box-shadow: 0 8px 28px rgba(255, 26, 140, 0.4);
}
.btn-frost {
background: var(--color-frost);
color: var(--color-void);
}
.btn-frost:hover {
box-shadow: 0 8px 32px rgba(0, 200, 255, 0.45);
}
.btn-ghost {
background: transparent;
border: 1px solid var(--color-zinc);
color: var(--color-chalk);
clip-path: none;
}
.btn-ghost::before { display: none; }
.btn-ghost:hover {
border-color: var(--color-heat);
color: var(--color-heat);
box-shadow: none;
}
/* ── Card media — zoom on hover ────────────────────────────── */
.card-media {
overflow: hidden;
position: relative;
background-color: var(--color-concrete);
}
.card-media img,
.card-media video {
transition: transform 0.7s var(--ease-out-expo);
will-change: transform;
}
.card-media:hover img,
.card-media:hover video {
transform: scale(1.07);
}
/* ── Card — gradient border + graffiti offset shadow ──────── */
.card-comic {
position: relative;
border: 1.5px solid var(--color-zinc);
overflow: hidden;
transition:
border-color 0.2s var(--ease-sharp),
box-shadow 0.3s var(--ease-out-expo),
transform 0.25s var(--ease-out-expo);
}
/* gradient top bar */
.card-comic::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 3px;
background: linear-gradient(90deg, var(--color-heat), var(--color-pulse), var(--color-frost));
opacity: 0;
transition: opacity 0.3s var(--ease-sharp);
z-index: 2;
}
.card-comic:hover {
border-color: rgba(255, 26, 140, 0.55);
transform: translateY(-5px) scale(1.005);
box-shadow:
0 0 0 1.5px rgba(255, 26, 140, 0.4),
0 20px 60px rgba(255, 26, 140, 0.18),
0 8px 24px rgba(155, 0, 255, 0.12),
6px 6px 0 rgba(255, 26, 140, 0.22);
}
.card-comic:hover::after { opacity: 1; }
/* ── Isometric triangle grid — 3 sets of lines at 0° / 60° / -60°
Creates an infinite isometric floor-tile illusion.
Colors: heat (horizontal) · frost (60°) · pulse (-60°)
────────────────────────────────────────────────────────────── */
.speed-lines {
background-image:
repeating-linear-gradient(
-55deg,
rgba(255, 26, 140, 0.14) 0, rgba(255, 26, 140, 0.14) 1px,
transparent 1px, transparent 22px,
rgba(0, 200, 255, 0.08) 22px, rgba(0, 200, 255, 0.08) 23px,
transparent 23px, transparent 44px
);
}
/* ── Diamond crosshatch ──────────────────────────────────── */
.halftone {
background-image:
repeating-linear-gradient(
45deg,
rgba(255, 26, 140, 0.07) 0, rgba(255, 26, 140, 0.07) 1px,
transparent 1px, transparent 24px
),
repeating-linear-gradient(
-45deg,
rgba(0, 200, 255, 0.05) 0, rgba(0, 200, 255, 0.05) 1px,
transparent 1px, transparent 24px
);
}
/* ── Spray dot halftone ──────────────────────────────────── */
.spray-bg {
background-image:
radial-gradient(circle, rgba(255, 26, 140, 0.09) 1px, transparent 1px),
radial-gradient(circle, rgba(0, 200, 255, 0.06) 1px, transparent 1px);
background-size: 18px 18px, 29px 29px;
background-position: 0 0, 9px 9px;
}
/* ── Noise overlay ──────────────────────────────────────────── */
.noise::after {
content: "";
position: absolute;
inset: 0;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.75' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.04'/%3E%3C/svg%3E");
background-size: 200px 200px;
pointer-events: none;
opacity: 0.25;
}
/* ── Graffiti tag — oversized hollow decorative text ────────── */
.graffiti-tag {
font-family: var(--font-display);
font-size: clamp(5rem, 14vw, 13rem);
letter-spacing: 0.02em;
line-height: 0.9;
color: transparent;
-webkit-text-stroke: 1px rgba(255, 26, 140, 0.12);
pointer-events: none;
user-select: none;
}
/* ── Marquee ticker ──────────────────────────────────────────── */
.marquee-track {
display: flex;
white-space: nowrap;
animation: marquee-scroll 32s linear infinite;
}
.marquee-track:hover { animation-play-state: paused; }
/* ── Section rules ──────────────────────────────────────────── */
.section-rule { border: none; border-top: 1px solid var(--color-zinc); }
.section-rule-electric {
border: none;
height: 1px;
background: linear-gradient(90deg, var(--color-heat), var(--color-pulse), var(--color-frost));
opacity: 0.5;
}
/* Thin gradient divider */
.gradient-line {
height: 1px;
background: linear-gradient(90deg, var(--color-heat), var(--color-pulse), var(--color-frost));
opacity: 0.35;
}
/* ── Neon glow utilities ────────────────────────────────────── */
.neon-heat { box-shadow: 0 0 24px rgba(255, 26, 140, 0.35), 0 0 60px rgba(255, 26, 140, 0.08); }
.neon-pulse { box-shadow: 0 0 24px rgba(155, 0, 255, 0.35), 0 0 60px rgba(155, 0, 255, 0.08); }
.neon-frost { box-shadow: 0 0 24px rgba( 0, 200, 255, 0.35), 0 0 60px rgba( 0, 200, 255, 0.08); }
/* ── Prose editorial ────────────────────────────────────────── */
.prose-editorial {
color: var(--color-chalk);
font-family: var(--font-body);
font-size: 1.0625rem;
line-height: 1.8;
max-width: 72ch;
}
.prose-editorial > * + * { margin-top: 1.6em; }
.prose-editorial strong { color: var(--color-paper); font-weight: 600; }
.prose-editorial em { font-style: italic; color: var(--color-paper); }
.prose-editorial a {
color: var(--color-heat);
border-bottom: 1px solid rgba(255, 26, 140, 0.35);
transition: border-color 0.2s, color 0.2s;
}
.prose-editorial a:hover {
color: var(--color-frost);
border-color: var(--color-frost);
}
.prose-editorial blockquote {
border-left: 3px solid var(--color-heat);
padding: 0.4em 0 0.4em 1.5rem;
margin: 2.5em 0;
color: var(--color-paper);
font-family: var(--font-display);
font-size: 1.5rem;
letter-spacing: 0.01em;
line-height: 1.1;
}
.prose-editorial blockquote p { margin: 0; }
.prose-editorial h2 {
font-size: 2.5rem;
color: var(--color-paper);
margin-top: 2.5em;
margin-bottom: 0.3em;
}
.prose-editorial h3 {
font-size: 1.8rem;
color: var(--color-paper);
margin-top: 2em;
margin-bottom: 0.3em;
}
.prose-editorial hr {
border: none;
height: 2px;
background: linear-gradient(90deg, var(--color-heat), var(--color-pulse), transparent);
margin: 3em 0;
width: 8rem;
}
.prose-editorial code {
font-family: var(--font-mono);
font-size: 0.875em;
background: var(--color-zinc);
padding: 0.1em 0.4em;
color: var(--color-frost);
}
.prose-editorial pre {
background: var(--color-ink);
border: 1px solid var(--color-zinc);
padding: 1.25rem 1.5rem;
overflow-x: auto;
}
.prose-editorial pre code {
background: none;
padding: 0;
font-size: 0.875rem;
color: var(--color-chalk);
}
/* ── HTMX progress bar ─────────────────────────────────────── */
#progress-bar {
position: fixed;
top: 0;
left: 0;
height: 4px;
background: linear-gradient(90deg, var(--color-heat), var(--color-pulse), var(--color-frost));
box-shadow: 0 0 12px rgba(255, 26, 140, 0.6);
z-index: 9999;
transition: width 0.4s var(--ease-out-expo), opacity 0.3s ease;
width: 0;
opacity: 0;
}
}
/* ── Utilities ────────────────────────────────────────────────── */
@layer utilities {
.gutter-x {
padding-left: clamp(1rem, 4vw, 3rem);
padding-right: clamp(1rem, 4vw, 3rem);
}
/* Aspect ratios */
.aspect-editorial { aspect-ratio: 3 / 4; }
.aspect-cinema { aspect-ratio: 21 / 9; }
.aspect-portrait { aspect-ratio: 2 / 3; }
/* Text wrapping */
.text-balance { text-wrap: balance; }
.text-pretty { text-wrap: pretty; }
/* Offset shadow */
.shadow-comic { box-shadow: 5px 5px 0 var(--color-heat); }
.shadow-comic-hot { box-shadow: 5px 5px 0 var(--color-heat); }
.shadow-comic-volt { box-shadow: 5px 5px 0 var(--color-lime); }
.shadow-comic-ice { box-shadow: 5px 5px 0 var(--color-frost); }
/* Text stroke — hollow headline */
.text-stroke-gold { -webkit-text-stroke: 1.5px var(--color-heat); color: transparent; }
.text-stroke-paper { -webkit-text-stroke: 1.5px var(--color-paper); color: transparent; }
.text-stroke-heat { -webkit-text-stroke: 1.5px var(--color-heat); color: transparent; }
.text-stroke-frost { -webkit-text-stroke: 1.5px var(--color-frost); color: transparent; }
/* Scrollbar hide */
.scrollbar-hide { scrollbar-width: none; -ms-overflow-style: none; }
.scrollbar-hide::-webkit-scrollbar { display: none; }
}
/* ── Keyframes ────────────────────────────────────────────────── */
@keyframes gradient-shift {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
@keyframes marquee-scroll {
to { transform: translateX(-50%); }
}
@keyframes scroll-line {
0% { transform: translateY(-100%); opacity: 0; }
20% { opacity: 1; }
80% { opacity: 1; }
100% { transform: translateY(100%); opacity: 0; }
}
/* ── Logo glitch animation ───────────────────────────────────── */
@keyframes glitch-clip {
0%, 88%, 100% { transform: translate(0); clip-path: none; }
90% { transform: translate(-3px, 1px); clip-path: polygon(0 15%, 100% 15%, 100% 40%, 0 40%); }
92% { transform: translate(3px, -1px); clip-path: polygon(0 60%, 100% 60%, 100% 78%, 0 78%); }
94% { transform: translate(0); clip-path: none; }
}
.logo-glitch:hover {
animation: glitch-clip 2.5s steps(1) infinite;
}
/* ── View Transitions ────────────────────────────────────────── */
::view-transition-old(main-content) {
animation: 160ms var(--ease-sharp) both page-out;
}
::view-transition-new(main-content) {
animation: 320ms var(--ease-out-expo) both page-in;
}
@keyframes page-out {
to { opacity: 0; transform: translateY(-10px); }
}
@keyframes page-in {
from { opacity: 0; transform: translateY(14px); }
}
+79
View File
@@ -0,0 +1,79 @@
// ── Pivoine — main.js ─────────────────────────────────────────────
// ── HTMX progress bar ─────────────────────────────────────────────
(function () {
const bar = document.getElementById("progress-bar");
if (!bar) return;
document.body.addEventListener("htmx:beforeRequest", () => {
bar.style.width = "0";
bar.style.opacity = "1";
requestAnimationFrame(() => {
bar.style.width = "55%";
});
});
document.body.addEventListener("htmx:afterSettle", () => {
bar.style.width = "100%";
setTimeout(() => {
bar.style.opacity = "0";
setTimeout(() => { bar.style.width = "0"; }, 350);
}, 150);
});
})();
// ── Lazy-load videos (data-src attribute) ─────────────────────────
function initLazyVideos(root) {
if (!("IntersectionObserver" in window)) return;
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (!entry.isIntersecting) return;
const video = entry.target;
if (video.dataset.src) {
video.src = video.dataset.src;
video.load();
}
observer.unobserve(video);
});
},
{ rootMargin: "300px" }
);
(root || document).querySelectorAll("video[data-src]").forEach((v) => observer.observe(v));
}
initLazyVideos();
// ── After HTMX partial swap ────────────────────────────────────────
document.body.addEventListener("htmx:afterSwap", (e) => {
initLazyVideos(e.target);
if (window.Alpine) Alpine.initTree(e.target);
window.scrollTo({ top: 0, behavior: "instant" });
});
// ── After HTMX history restore (back / forward navigation) ────────
document.body.addEventListener("htmx:historyRestore", () => {
// Sync nav active state to the restored URL
if (window.Alpine) Alpine.store("nav").path = window.location.pathname;
// Complete the progress bar (htmx:afterSettle never fires on history restore)
const bar = document.getElementById("progress-bar");
if (bar) {
bar.style.width = "100%";
setTimeout(() => {
bar.style.opacity = "0";
setTimeout(() => { bar.style.width = "0"; }, 350);
}, 150);
}
const main = document.getElementById("main-content");
if (!main) return;
if (window.Alpine) Alpine.initTree(main);
// Reload and replay autoplay videos (autoplay attr is ignored on restore)
main.querySelectorAll("video[autoplay]").forEach((v) => {
v.load();
v.play().catch(() => {});
});
window.scrollTo({ top: 0, behavior: "instant" });
});
+4
View File
@@ -0,0 +1,4 @@
---
title: "Pivoine"
description: "An editorial magazine for AI-generated art and generative aesthetics."
---
+22
View File
@@ -0,0 +1,22 @@
---
title: "About Pivoine"
description: "A magazine dedicated to the intersection of artificial intelligence and aesthetic culture."
---
*Pivoine* is an independent editorial project exploring how machine intelligence reshapes visual culture.
We publish editorial features, artist profiles, and critical essays about AI-generated art — approaching this emerging medium with the same rigour and aesthetic seriousness as any other.
## The Name
The name comes from the French word for **peony** — a flower that is both extravagant and precise, excessive and structured. That tension feels right for AI art: the overwhelming abundance of generated imagery held against the deliberate choices that make something worth looking at.
## What We Publish
We are interested in the **editorial dimension** of AI image-making. Not the tools, not the prompts, not the speed records — but the *work* itself and the questions it raises about authorship, aesthetics, and what it means to make an image in an age when images are free.
Each feature is a collaboration between a human sensibility and a machine process. The artists and authors we publish treat the generator as a medium, not a shortcut.
## Submissions
We accept editorial pitches and artist submissions. Write to us at [valknar@pivoine.art](mailto:valknar@pivoine.art).
+4
View File
@@ -0,0 +1,4 @@
---
title: "Authors"
description: "The artists and writers behind Pivoine's editorial features."
---
Binary file not shown.

After

Width:  |  Height:  |  Size: 716 KiB

+14
View File
@@ -0,0 +1,14 @@
---
title: "Valknar"
name: "Valknar"
bio: "Valknar works with diffusion models as a compositional tool, creating dark surrealist visions that challenge the language of machine vision and disrupt comfortable readings of the generated face."
social:
instagram: "valknarix"
x: "valknar100"
---
Valknar approaches AI generation as a negotiation — between the model's statistical memory of human aesthetics and a deliberate effort to pull those aesthetics toward the unfamiliar. The results are portraits and figures that feel simultaneously recognisable and wrong.
Working primarily with portrait and figure generation, Sable uses seed manipulation, latent space traversal, and post-processing to extract images that resist the smoothness the models default to.
Based in Stuttgart.
+27
View File
@@ -0,0 +1,27 @@
---
title: "Imprint"
description: "Legal information and editorial responsibility."
---
## Responsible for Content
**Pivoine Editorial**
[valknar@pivoine.art](mailto:valknar@pivoine.art)
## Editorial Policy
*Pivoine* is an independent publication. All AI-generated images and videos published on this site are the creative work of the named authors using various generative AI tools. The editorial team curates, commissions, and publishes this work under the publication's name.
## Copyright
Unless otherwise noted, all editorial content, text, and design on this site is © 2026 Pivoine. AI-generated images remain the creative property of their respective named authors.
Reproduction of any content requires written permission from the editorial team.
## Privacy
This site uses [Umami](https://umami.is) for privacy-friendly analytics. Umami is self-hosted at `umami.pivoine.art`, collects no personally identifiable information, sets no cookies, and does not share data with third parties. Standard server logs are retained for operational purposes only.
## Hosting & Technology
Built with [Hugo](https://gohugo.io), styled with [Tailwind CSS](https://tailwindcss.com), and deployed as a static site.
+4
View File
@@ -0,0 +1,4 @@
---
title: "Editorials"
description: "All editorial features published in Pivoine magazine."
---
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 MiB

+15
View File
@@ -0,0 +1,15 @@
---
title: "Hyperloop"
description: "A sleek train glides through a vibrant, swirling tunnel of neon colors. The camera smoothly tracks alongside the train, capturing the reflective surfaces and mesmerizing patterns of the tunnel as it shifts and undulates."
date: 2025-08-17
author: "valknar"
featured: true
categories:
- Surreal
tags:
- futuristic
- vibrant
- urban
- technology
---
+70
View File
@@ -0,0 +1,70 @@
baseURL = "https://pivoine.art/"
languageCode = "en-us"
title = "Pivoine"
copyright = "© 2026 Pivoine Editorial"
[pagination]
pagerSize = 12
enableRobotsTXT = true
enableGitInfo = true # populates .Lastmod for article:modified_time
[sitemap]
changefreq = "weekly"
priority = 0.5
filename = "sitemap.xml"
[params]
description = "An editorial magazine for AI-generated art and generative aesthetics."
tagline = "Where the machine dreams."
author = "Pivoine Editorial"
ogImage = "/images/og-default.jpg"
logoText = "PIVOINE"
copyrightYear = "2026"
twitterHandle = "pivoineArt"
umamiSrc = "https://umami.pivoine.art/script.js"
umamiId = "9190b814-0480-498a-b5fe-1fe1dc162766"
[params.social]
instagram = "https://instagram.com/valknarix"
x = "https://x.com/valknar100"
[taxonomies]
category = "categories"
tag = "tags"
[[menus.main]]
name = "Posts"
url = "/posts/"
weight = 1
[[menus.main]]
name = "Authors"
url = "/authors/"
weight = 2
[[menus.main]]
name = "Categories"
url = "/categories/"
weight = 3
[[menus.main]]
name = "About"
url = "/about/"
weight = 4
[[menus.footer]]
name = "About"
url = "/about/"
weight = 1
[[menus.footer]]
name = "Imprint"
url = "/imprint/"
weight = 2
[markup.goldmark.renderer]
unsafe = true
[markup.highlight]
style = "dracula"
noClasses = true
[build]
writeStats = true
+65
View File
@@ -0,0 +1,65 @@
{{- define "main" -}}
<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>
<!-- 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>
</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 -}}
+52
View File
@@ -0,0 +1,52 @@
<!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"
>
<!-- HTMX transition progress bar -->
<div id="progress-bar" aria-hidden="true"></div>
{{- partial "nav.html" . -}}
{{- block "page-background" . -}}{{- end -}}
<main id="main-content" class="flex-1" style="view-transition-name: main-content">
{{- block "main" . }}{{- end }}
</main>
{{- partial "footer.html" . -}}
<!-- 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 }}
{{- 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>
+34
View File
@@ -0,0 +1,34 @@
{{- 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>
<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>
{{- end -}}
+18
View File
@@ -0,0 +1,18 @@
{{- 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 content -->
<article class="gutter-x py-16 md:py-24 max-w-3xl prose-editorial">
{{ .Content }}
</article>
{{- end -}}
+32
View File
@@ -0,0 +1,32 @@
{{- 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>
<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>
{{- end -}}
</div>
{{- else -}}
<p class="label text-fog text-center py-24">No entries yet.</p>
{{- end -}}
</section>
{{- end -}}
+54
View File
@@ -0,0 +1,54 @@
{{- 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>
<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 -}}
<!-- 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>
{{- end -}}
+97
View File
@@ -0,0 +1,97 @@
{{- define "main" -}}
{{- $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>
{{- 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 hover:bg-heat hover:text-white hover:border-heat transition-all"
>{{ $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>
</div>
</div>
</header>
<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'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 $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 -}}
+26
View File
@@ -0,0 +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>
<!-- 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 -}}
</section>
{{- end -}}
+144
View File
@@ -0,0 +1,144 @@
{{- define "main" -}}
{{- $posts := where .Site.RegularPages "Section" "posts" -}}
{{- $featured := where $posts "Params.featured" true -}}
{{- $hero := index $featured 0 -}}
{{- $secondary := after 1 $featured | first 3 -}}
{{- $latest := complement $featured $posts -}}
<!-- ── 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 -}}
{{- $heroBannerSrc := "" -}}
{{- with $heroBanner }}{{ $heroBannerSrc = .RelPermalink }}{{ else }}{{ with $.Params.background }}{{ $heroBannerSrc = .src }}{{ else }}{{ with $.Params.banner }}{{ $heroBannerSrc = .src }}{{ end }}{{ 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 -}}
<!-- 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-gradient">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 lg:text-[10rem] 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">
{{- .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>
<!-- 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 -}}
<!-- ── SECONDARY FEATURED ────────────────────────────────────── -->
{{- with $secondary -}}
<section class="gutter-x py-14 md:py-20">
<div class="flex items-center gap-5 mb-8">
<span class="badge badge-pulse">Also Featured</span>
<div class="flex-1 gradient-line"></div>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-5">
{{- range . -}}
{{- partial "post-card-large.html" . -}}
{{- end -}}
</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>&nbsp;<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 $latest }} 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">Learn more {{ partial "icon.html" "arrow-right" }}</a>
</div>
</section>
{{- end -}}
+26
View File
@@ -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>
+46
View File
@@ -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">
&copy; {{ .Site.Params.copyrightYear }} {{ .Site.Params.author }}. All rights reserved.
</p>
</footer>
+74
View File
@@ -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">
+11
View File
@@ -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 -}}
+31
View File
@@ -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>
+36
View File
@@ -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 -}}
+132
View File
@@ -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>
+33
View File
@@ -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 -}}
+57
View File
@@ -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>
+63
View File
@@ -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>
+62
View File
@@ -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 -}}
+186
View File
@@ -0,0 +1,186 @@
{{- 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 -}}
{{- 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 hover:bg-heat hover:text-white hover:border-heat transition-all"
>{{ . }}</a>
{{- 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" . -}}
{{- 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>
{{- end -}}
</div>
</header>
<!-- ── 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="max-height: 75vh">
<img class="w-full object-cover" src="{{ $bannerImg.RelPermalink }}" alt="{{ .Title }}" loading="eager" decoding="async">
</div>
{{- else -}}{{- with .Params.banner -}}
<div class="w-full overflow-hidden mb-16 md:mb-24" style="max-height: 75vh">
{{- partial "media.html" (dict "media" . "class" "w-full h-full object-cover" "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 -}}
<!-- ── 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 -}}
<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>
<!-- Numbered png+mp4 pairs from page bundle -->
<div class="columns-1 sm:columns-2 lg:columns-3 gap-5 space-y-5">
{{- range $bundleImages -}}
{{- $stem := .Name | strings.TrimSuffix ".png" -}}
{{- $video := $.Resources.GetMatch (printf "%s.mp4" $stem) -}}
<figure class="break-inside-avoid group card-comic">
<div class="card-media min-h-48">
{{- if $video -}}
<video class="w-full" src="{{ $video.RelPermalink }}" poster="{{ .RelPermalink }}" autoplay loop muted playsinline></video>
{{- else -}}
<img class="w-full" src="{{ .RelPermalink }}" alt="" loading="lazy" decoding="async">
{{- end -}}
</div>
</figure>
{{- end -}}
</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 -}}
<!-- ── 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 hover:bg-heat hover:text-white hover:border-heat transition-all"
># {{ . }}</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">
<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 -}}
+7
View File
@@ -0,0 +1,7 @@
User-agent: *
Allow: /
# Block build artefacts
Disallow: /public/
Sitemap: {{ .Site.BaseURL }}sitemap.xml
+26
View File
@@ -0,0 +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>
<!-- 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 -}}
</section>
{{- end -}}
+51
View File
@@ -0,0 +1,51 @@
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
# Gzip compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml application/json application/rss+xml image/svg+xml;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Static assets with long cache
location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2|ttf|eot|webp|mp3|mp4|webm|ogg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
# HTML files - no cache for fresh content
location ~* \.html$ {
expires -1;
add_header Cache-Control "no-store, no-cache, must-revalidate";
}
# Custom 404 page
error_page 404 /404.html;
# Clean URLs - try files, then directories, then 404
location / {
try_files $uri $uri/ $uri.html =404;
}
# RSS feed
location = /index.xml {
types { application/rss+xml xml; }
}
# Deny access to hidden files
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
}
+16
View File
@@ -0,0 +1,16 @@
{
"name": "pivoine-art",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "hugo server --buildDrafts --disableFastRender",
"build": "NODE_ENV=production hugo --minify"
},
"devDependencies": {
"tailwindcss": "^4.0.0",
"@tailwindcss/postcss": "^4.0.0",
"autoprefixer": "^10.4.0",
"postcss": "^8.5.0",
"postcss-cli": "^11.0.0"
}
}
+892
View File
@@ -0,0 +1,892 @@
lockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
importers:
.:
devDependencies:
'@tailwindcss/postcss':
specifier: ^4.0.0
version: 4.2.2
autoprefixer:
specifier: ^10.4.0
version: 10.4.27(postcss@8.5.8)
postcss:
specifier: ^8.5.0
version: 8.5.8
postcss-cli:
specifier: ^11.0.0
version: 11.0.1(jiti@2.6.1)(postcss@8.5.8)
tailwindcss:
specifier: ^4.0.0
version: 4.2.2
packages:
'@alloc/quick-lru@5.2.0':
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
engines: {node: '>=10'}
'@jridgewell/gen-mapping@0.3.13':
resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
'@jridgewell/remapping@2.3.5':
resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==}
'@jridgewell/resolve-uri@3.1.2':
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
engines: {node: '>=6.0.0'}
'@jridgewell/sourcemap-codec@1.5.5':
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
'@jridgewell/trace-mapping@0.3.31':
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
'@tailwindcss/node@4.2.2':
resolution: {integrity: sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==}
'@tailwindcss/oxide-android-arm64@4.2.2':
resolution: {integrity: sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg==}
engines: {node: '>= 20'}
cpu: [arm64]
os: [android]
'@tailwindcss/oxide-darwin-arm64@4.2.2':
resolution: {integrity: sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg==}
engines: {node: '>= 20'}
cpu: [arm64]
os: [darwin]
'@tailwindcss/oxide-darwin-x64@4.2.2':
resolution: {integrity: sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw==}
engines: {node: '>= 20'}
cpu: [x64]
os: [darwin]
'@tailwindcss/oxide-freebsd-x64@4.2.2':
resolution: {integrity: sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ==}
engines: {node: '>= 20'}
cpu: [x64]
os: [freebsd]
'@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2':
resolution: {integrity: sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ==}
engines: {node: '>= 20'}
cpu: [arm]
os: [linux]
'@tailwindcss/oxide-linux-arm64-gnu@4.2.2':
resolution: {integrity: sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==}
engines: {node: '>= 20'}
cpu: [arm64]
os: [linux]
'@tailwindcss/oxide-linux-arm64-musl@4.2.2':
resolution: {integrity: sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==}
engines: {node: '>= 20'}
cpu: [arm64]
os: [linux]
'@tailwindcss/oxide-linux-x64-gnu@4.2.2':
resolution: {integrity: sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==}
engines: {node: '>= 20'}
cpu: [x64]
os: [linux]
'@tailwindcss/oxide-linux-x64-musl@4.2.2':
resolution: {integrity: sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==}
engines: {node: '>= 20'}
cpu: [x64]
os: [linux]
'@tailwindcss/oxide-wasm32-wasi@4.2.2':
resolution: {integrity: sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==}
engines: {node: '>=14.0.0'}
cpu: [wasm32]
bundledDependencies:
- '@napi-rs/wasm-runtime'
- '@emnapi/core'
- '@emnapi/runtime'
- '@tybys/wasm-util'
- '@emnapi/wasi-threads'
- tslib
'@tailwindcss/oxide-win32-arm64-msvc@4.2.2':
resolution: {integrity: sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ==}
engines: {node: '>= 20'}
cpu: [arm64]
os: [win32]
'@tailwindcss/oxide-win32-x64-msvc@4.2.2':
resolution: {integrity: sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA==}
engines: {node: '>= 20'}
cpu: [x64]
os: [win32]
'@tailwindcss/oxide@4.2.2':
resolution: {integrity: sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg==}
engines: {node: '>= 20'}
'@tailwindcss/postcss@4.2.2':
resolution: {integrity: sha512-n4goKQbW8RVXIbNKRB/45LzyUqN451deQK0nzIeauVEqjlI49slUlgKYJM2QyUzap/PcpnS7kzSUmPb1sCRvYQ==}
ansi-regex@5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'}
ansi-styles@4.3.0:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
engines: {node: '>=8'}
anymatch@3.1.3:
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
engines: {node: '>= 8'}
autoprefixer@10.4.27:
resolution: {integrity: sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==}
engines: {node: ^10 || ^12 || >=14}
hasBin: true
peerDependencies:
postcss: ^8.1.0
baseline-browser-mapping@2.10.12:
resolution: {integrity: sha512-qyq26DxfY4awP2gIRXhhLWfwzwI+N5Nxk6iQi8EFizIaWIjqicQTE4sLnZZVdeKPRcVNoJOkkpfzoIYuvCKaIQ==}
engines: {node: '>=6.0.0'}
hasBin: true
binary-extensions@2.3.0:
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
engines: {node: '>=8'}
braces@3.0.3:
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
engines: {node: '>=8'}
browserslist@4.28.1:
resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==}
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
caniuse-lite@1.0.30001782:
resolution: {integrity: sha512-dZcaJLJeDMh4rELYFw1tvSn1bhZWYFOt468FcbHHxx/Z/dFidd1I6ciyFdi3iwfQCyOjqo9upF6lGQYtMiJWxw==}
chokidar@3.6.0:
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
engines: {node: '>= 8.10.0'}
cliui@8.0.1:
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
engines: {node: '>=12'}
color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
dependency-graph@1.0.0:
resolution: {integrity: sha512-cW3gggJ28HZ/LExwxP2B++aiKxhJXMSIt9K48FOXQkm+vuG5gyatXnLsONRJdzO/7VfjDIiaOOa/bs4l464Lwg==}
engines: {node: '>=4'}
detect-libc@2.1.2:
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
engines: {node: '>=8'}
electron-to-chromium@1.5.329:
resolution: {integrity: sha512-/4t+AS1l4S3ZC0Ja7PHFIWeBIxGA3QGqV8/yKsP36v7NcyUCl+bIcmw6s5zVuMIECWwBrAK/6QLzTmbJChBboQ==}
emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
enhanced-resolve@5.20.1:
resolution: {integrity: sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==}
engines: {node: '>=10.13.0'}
escalade@3.2.0:
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
engines: {node: '>=6'}
fdir@6.5.0:
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
engines: {node: '>=12.0.0'}
peerDependencies:
picomatch: ^3 || ^4
peerDependenciesMeta:
picomatch:
optional: true
fill-range@7.1.1:
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
engines: {node: '>=8'}
fraction.js@5.3.4:
resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==}
fs-extra@11.3.4:
resolution: {integrity: sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==}
engines: {node: '>=14.14'}
fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
get-caller-file@2.0.5:
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
engines: {node: 6.* || 8.* || >= 10.*}
glob-parent@5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'}
graceful-fs@4.2.11:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
is-binary-path@2.1.0:
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
engines: {node: '>=8'}
is-extglob@2.1.1:
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
engines: {node: '>=0.10.0'}
is-fullwidth-code-point@3.0.0:
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
engines: {node: '>=8'}
is-glob@4.0.3:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
engines: {node: '>=0.10.0'}
is-number@7.0.0:
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
engines: {node: '>=0.12.0'}
jiti@2.6.1:
resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
hasBin: true
jsonfile@6.2.0:
resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==}
lightningcss-android-arm64@1.32.0:
resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [android]
lightningcss-darwin-arm64@1.32.0:
resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [darwin]
lightningcss-darwin-x64@1.32.0:
resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [darwin]
lightningcss-freebsd-x64@1.32.0:
resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [freebsd]
lightningcss-linux-arm-gnueabihf@1.32.0:
resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==}
engines: {node: '>= 12.0.0'}
cpu: [arm]
os: [linux]
lightningcss-linux-arm64-gnu@1.32.0:
resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
lightningcss-linux-arm64-musl@1.32.0:
resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
lightningcss-linux-x64-gnu@1.32.0:
resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
lightningcss-linux-x64-musl@1.32.0:
resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
lightningcss-win32-arm64-msvc@1.32.0:
resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [win32]
lightningcss-win32-x64-msvc@1.32.0:
resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [win32]
lightningcss@1.32.0:
resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==}
engines: {node: '>= 12.0.0'}
lilconfig@3.1.3:
resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==}
engines: {node: '>=14'}
magic-string@0.30.21:
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
nanoid@3.3.11:
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
node-releases@2.0.36:
resolution: {integrity: sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==}
normalize-path@3.0.0:
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
engines: {node: '>=0.10.0'}
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
picomatch@2.3.2:
resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==}
engines: {node: '>=8.6'}
picomatch@4.0.4:
resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==}
engines: {node: '>=12'}
pify@2.3.0:
resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
engines: {node: '>=0.10.0'}
postcss-cli@11.0.1:
resolution: {integrity: sha512-0UnkNPSayHKRe/tc2YGW6XnSqqOA9eqpiRMgRlV1S6HdGi16vwJBx7lviARzbV1HpQHqLLRH3o8vTcB0cLc+5g==}
engines: {node: '>=18'}
hasBin: true
peerDependencies:
postcss: ^8.0.0
postcss-load-config@5.1.0:
resolution: {integrity: sha512-G5AJ+IX0aD0dygOE0yFZQ/huFFMSNneyfp0e3/bT05a8OfPC5FUoZRPfGijUdGOJNMewJiwzcHJXFafFzeKFVA==}
engines: {node: '>= 18'}
peerDependencies:
jiti: '>=1.21.0'
postcss: '>=8.0.9'
tsx: ^4.8.1
peerDependenciesMeta:
jiti:
optional: true
postcss:
optional: true
tsx:
optional: true
postcss-reporter@7.1.0:
resolution: {integrity: sha512-/eoEylGWyy6/DOiMP5lmFRdmDKThqgn7D6hP2dXKJI/0rJSO1ADFNngZfDzxL0YAxFvws+Rtpuji1YIHj4mySA==}
engines: {node: '>=10'}
peerDependencies:
postcss: ^8.1.0
postcss-value-parser@4.2.0:
resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
postcss@8.5.8:
resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==}
engines: {node: ^10 || ^12 || >=14}
pretty-hrtime@1.0.3:
resolution: {integrity: sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==}
engines: {node: '>= 0.8'}
read-cache@1.0.0:
resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
readdirp@3.6.0:
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
engines: {node: '>=8.10.0'}
require-directory@2.1.1:
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
engines: {node: '>=0.10.0'}
slash@5.1.0:
resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==}
engines: {node: '>=14.16'}
source-map-js@1.2.1:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
string-width@4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}
strip-ansi@6.0.1:
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
engines: {node: '>=8'}
tailwindcss@4.2.2:
resolution: {integrity: sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==}
tapable@2.3.2:
resolution: {integrity: sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==}
engines: {node: '>=6'}
thenby@1.3.4:
resolution: {integrity: sha512-89Gi5raiWA3QZ4b2ePcEwswC3me9JIg+ToSgtE0JWeCynLnLxNr/f9G+xfo9K+Oj4AFdom8YNJjibIARTJmapQ==}
tinyglobby@0.2.15:
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
engines: {node: '>=12.0.0'}
to-regex-range@5.0.1:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
universalify@2.0.1:
resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
engines: {node: '>= 10.0.0'}
update-browserslist-db@1.2.3:
resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==}
hasBin: true
peerDependencies:
browserslist: '>= 4.21.0'
wrap-ansi@7.0.0:
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
engines: {node: '>=10'}
y18n@5.0.8:
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
engines: {node: '>=10'}
yaml@2.8.3:
resolution: {integrity: sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==}
engines: {node: '>= 14.6'}
hasBin: true
yargs-parser@21.1.1:
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
engines: {node: '>=12'}
yargs@17.7.2:
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
engines: {node: '>=12'}
snapshots:
'@alloc/quick-lru@5.2.0': {}
'@jridgewell/gen-mapping@0.3.13':
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
'@jridgewell/trace-mapping': 0.3.31
'@jridgewell/remapping@2.3.5':
dependencies:
'@jridgewell/gen-mapping': 0.3.13
'@jridgewell/trace-mapping': 0.3.31
'@jridgewell/resolve-uri@3.1.2': {}
'@jridgewell/sourcemap-codec@1.5.5': {}
'@jridgewell/trace-mapping@0.3.31':
dependencies:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.5
'@tailwindcss/node@4.2.2':
dependencies:
'@jridgewell/remapping': 2.3.5
enhanced-resolve: 5.20.1
jiti: 2.6.1
lightningcss: 1.32.0
magic-string: 0.30.21
source-map-js: 1.2.1
tailwindcss: 4.2.2
'@tailwindcss/oxide-android-arm64@4.2.2':
optional: true
'@tailwindcss/oxide-darwin-arm64@4.2.2':
optional: true
'@tailwindcss/oxide-darwin-x64@4.2.2':
optional: true
'@tailwindcss/oxide-freebsd-x64@4.2.2':
optional: true
'@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2':
optional: true
'@tailwindcss/oxide-linux-arm64-gnu@4.2.2':
optional: true
'@tailwindcss/oxide-linux-arm64-musl@4.2.2':
optional: true
'@tailwindcss/oxide-linux-x64-gnu@4.2.2':
optional: true
'@tailwindcss/oxide-linux-x64-musl@4.2.2':
optional: true
'@tailwindcss/oxide-wasm32-wasi@4.2.2':
optional: true
'@tailwindcss/oxide-win32-arm64-msvc@4.2.2':
optional: true
'@tailwindcss/oxide-win32-x64-msvc@4.2.2':
optional: true
'@tailwindcss/oxide@4.2.2':
optionalDependencies:
'@tailwindcss/oxide-android-arm64': 4.2.2
'@tailwindcss/oxide-darwin-arm64': 4.2.2
'@tailwindcss/oxide-darwin-x64': 4.2.2
'@tailwindcss/oxide-freebsd-x64': 4.2.2
'@tailwindcss/oxide-linux-arm-gnueabihf': 4.2.2
'@tailwindcss/oxide-linux-arm64-gnu': 4.2.2
'@tailwindcss/oxide-linux-arm64-musl': 4.2.2
'@tailwindcss/oxide-linux-x64-gnu': 4.2.2
'@tailwindcss/oxide-linux-x64-musl': 4.2.2
'@tailwindcss/oxide-wasm32-wasi': 4.2.2
'@tailwindcss/oxide-win32-arm64-msvc': 4.2.2
'@tailwindcss/oxide-win32-x64-msvc': 4.2.2
'@tailwindcss/postcss@4.2.2':
dependencies:
'@alloc/quick-lru': 5.2.0
'@tailwindcss/node': 4.2.2
'@tailwindcss/oxide': 4.2.2
postcss: 8.5.8
tailwindcss: 4.2.2
ansi-regex@5.0.1: {}
ansi-styles@4.3.0:
dependencies:
color-convert: 2.0.1
anymatch@3.1.3:
dependencies:
normalize-path: 3.0.0
picomatch: 2.3.2
autoprefixer@10.4.27(postcss@8.5.8):
dependencies:
browserslist: 4.28.1
caniuse-lite: 1.0.30001782
fraction.js: 5.3.4
picocolors: 1.1.1
postcss: 8.5.8
postcss-value-parser: 4.2.0
baseline-browser-mapping@2.10.12: {}
binary-extensions@2.3.0: {}
braces@3.0.3:
dependencies:
fill-range: 7.1.1
browserslist@4.28.1:
dependencies:
baseline-browser-mapping: 2.10.12
caniuse-lite: 1.0.30001782
electron-to-chromium: 1.5.329
node-releases: 2.0.36
update-browserslist-db: 1.2.3(browserslist@4.28.1)
caniuse-lite@1.0.30001782: {}
chokidar@3.6.0:
dependencies:
anymatch: 3.1.3
braces: 3.0.3
glob-parent: 5.1.2
is-binary-path: 2.1.0
is-glob: 4.0.3
normalize-path: 3.0.0
readdirp: 3.6.0
optionalDependencies:
fsevents: 2.3.3
cliui@8.0.1:
dependencies:
string-width: 4.2.3
strip-ansi: 6.0.1
wrap-ansi: 7.0.0
color-convert@2.0.1:
dependencies:
color-name: 1.1.4
color-name@1.1.4: {}
dependency-graph@1.0.0: {}
detect-libc@2.1.2: {}
electron-to-chromium@1.5.329: {}
emoji-regex@8.0.0: {}
enhanced-resolve@5.20.1:
dependencies:
graceful-fs: 4.2.11
tapable: 2.3.2
escalade@3.2.0: {}
fdir@6.5.0(picomatch@4.0.4):
optionalDependencies:
picomatch: 4.0.4
fill-range@7.1.1:
dependencies:
to-regex-range: 5.0.1
fraction.js@5.3.4: {}
fs-extra@11.3.4:
dependencies:
graceful-fs: 4.2.11
jsonfile: 6.2.0
universalify: 2.0.1
fsevents@2.3.3:
optional: true
get-caller-file@2.0.5: {}
glob-parent@5.1.2:
dependencies:
is-glob: 4.0.3
graceful-fs@4.2.11: {}
is-binary-path@2.1.0:
dependencies:
binary-extensions: 2.3.0
is-extglob@2.1.1: {}
is-fullwidth-code-point@3.0.0: {}
is-glob@4.0.3:
dependencies:
is-extglob: 2.1.1
is-number@7.0.0: {}
jiti@2.6.1: {}
jsonfile@6.2.0:
dependencies:
universalify: 2.0.1
optionalDependencies:
graceful-fs: 4.2.11
lightningcss-android-arm64@1.32.0:
optional: true
lightningcss-darwin-arm64@1.32.0:
optional: true
lightningcss-darwin-x64@1.32.0:
optional: true
lightningcss-freebsd-x64@1.32.0:
optional: true
lightningcss-linux-arm-gnueabihf@1.32.0:
optional: true
lightningcss-linux-arm64-gnu@1.32.0:
optional: true
lightningcss-linux-arm64-musl@1.32.0:
optional: true
lightningcss-linux-x64-gnu@1.32.0:
optional: true
lightningcss-linux-x64-musl@1.32.0:
optional: true
lightningcss-win32-arm64-msvc@1.32.0:
optional: true
lightningcss-win32-x64-msvc@1.32.0:
optional: true
lightningcss@1.32.0:
dependencies:
detect-libc: 2.1.2
optionalDependencies:
lightningcss-android-arm64: 1.32.0
lightningcss-darwin-arm64: 1.32.0
lightningcss-darwin-x64: 1.32.0
lightningcss-freebsd-x64: 1.32.0
lightningcss-linux-arm-gnueabihf: 1.32.0
lightningcss-linux-arm64-gnu: 1.32.0
lightningcss-linux-arm64-musl: 1.32.0
lightningcss-linux-x64-gnu: 1.32.0
lightningcss-linux-x64-musl: 1.32.0
lightningcss-win32-arm64-msvc: 1.32.0
lightningcss-win32-x64-msvc: 1.32.0
lilconfig@3.1.3: {}
magic-string@0.30.21:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
nanoid@3.3.11: {}
node-releases@2.0.36: {}
normalize-path@3.0.0: {}
picocolors@1.1.1: {}
picomatch@2.3.2: {}
picomatch@4.0.4: {}
pify@2.3.0: {}
postcss-cli@11.0.1(jiti@2.6.1)(postcss@8.5.8):
dependencies:
chokidar: 3.6.0
dependency-graph: 1.0.0
fs-extra: 11.3.4
picocolors: 1.1.1
postcss: 8.5.8
postcss-load-config: 5.1.0(jiti@2.6.1)(postcss@8.5.8)
postcss-reporter: 7.1.0(postcss@8.5.8)
pretty-hrtime: 1.0.3
read-cache: 1.0.0
slash: 5.1.0
tinyglobby: 0.2.15
yargs: 17.7.2
transitivePeerDependencies:
- jiti
- tsx
postcss-load-config@5.1.0(jiti@2.6.1)(postcss@8.5.8):
dependencies:
lilconfig: 3.1.3
yaml: 2.8.3
optionalDependencies:
jiti: 2.6.1
postcss: 8.5.8
postcss-reporter@7.1.0(postcss@8.5.8):
dependencies:
picocolors: 1.1.1
postcss: 8.5.8
thenby: 1.3.4
postcss-value-parser@4.2.0: {}
postcss@8.5.8:
dependencies:
nanoid: 3.3.11
picocolors: 1.1.1
source-map-js: 1.2.1
pretty-hrtime@1.0.3: {}
read-cache@1.0.0:
dependencies:
pify: 2.3.0
readdirp@3.6.0:
dependencies:
picomatch: 2.3.2
require-directory@2.1.1: {}
slash@5.1.0: {}
source-map-js@1.2.1: {}
string-width@4.2.3:
dependencies:
emoji-regex: 8.0.0
is-fullwidth-code-point: 3.0.0
strip-ansi: 6.0.1
strip-ansi@6.0.1:
dependencies:
ansi-regex: 5.0.1
tailwindcss@4.2.2: {}
tapable@2.3.2: {}
thenby@1.3.4: {}
tinyglobby@0.2.15:
dependencies:
fdir: 6.5.0(picomatch@4.0.4)
picomatch: 4.0.4
to-regex-range@5.0.1:
dependencies:
is-number: 7.0.0
universalify@2.0.1: {}
update-browserslist-db@1.2.3(browserslist@4.28.1):
dependencies:
browserslist: 4.28.1
escalade: 3.2.0
picocolors: 1.1.1
wrap-ansi@7.0.0:
dependencies:
ansi-styles: 4.3.0
string-width: 4.2.3
strip-ansi: 6.0.1
y18n@5.0.8: {}
yaml@2.8.3: {}
yargs-parser@21.1.1: {}
yargs@17.7.2:
dependencies:
cliui: 8.0.1
escalade: 3.2.0
get-caller-file: 2.0.5
require-directory: 2.1.1
string-width: 4.2.3
y18n: 5.0.8
yargs-parser: 21.1.1
+6
View File
@@ -0,0 +1,6 @@
module.exports = {
plugins: {
"@tailwindcss/postcss": {},
autoprefixer: {},
},
};
+15
View File
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg viewBox="0 0 96 96" fill="none" xmlns="http://www.w3.org/2000/svg" aria-label="{{ site.Title }}" role="img">
<defs>
<linearGradient id="gradient" 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(#gradient)" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 701 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

+83
View File
@@ -0,0 +1,83 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="630" viewBox="0 0 1200 630">
<defs>
<linearGradient id="logo-grad" gradientUnits="userSpaceOnUse" x1="-37" y1="0" x2="37" y2="0">
<stop offset="0%" stop-color="#FF1A8C" />
<stop offset="50%" stop-color="#9B00FF" />
<stop offset="100%" stop-color="#00C8FF" />
</linearGradient>
<linearGradient id="bar-grad" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stop-color="#FF1A8C" />
<stop offset="50%" stop-color="#9B00FF" />
<stop offset="100%" stop-color="#00C8FF" />
</linearGradient>
<linearGradient id="glow-grad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#9B00FF" stop-opacity="0.18" />
<stop offset="100%" stop-color="#FF1A8C" stop-opacity="0" />
</linearGradient>
<radialGradient id="logo-glow" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#9B00FF" stop-opacity="0.22" />
<stop offset="100%" stop-color="#050510" stop-opacity="0" />
</radialGradient>
</defs>
<!-- Background -->
<rect width="1200" height="630" fill="#050510" />
<!-- Subtle speed-line diagonals -->
<g stroke="#FF1A8C" stroke-opacity="0.03" stroke-width="1">
<line x1="0" y1="0" x2="1200" y2="630" />
<line x1="0" y1="80" x2="1120" y2="630" />
<line x1="0" y1="160" x2="1040" y2="630" />
<line x1="0" y1="240" x2="960" y2="630" />
<line x1="80" y1="0" x2="1200" y2="550" />
<line x1="160" y1="0" x2="1200" y2="470" />
<line x1="240" y1="0" x2="1200" y2="390" />
</g>
<g stroke="#00C8FF" stroke-opacity="0.03" stroke-width="1">
<line x1="1200" y1="0" x2="0" y2="630" />
<line x1="1200" y1="80" x2="80" y2="630" />
<line x1="1200" y1="160" x2="160" y2="630" />
<line x1="1120" y1="0" x2="0" y2="550" />
<line x1="1040" y1="0" x2="0" y2="470" />
</g>
<!-- Right-side purple glow behind logo -->
<ellipse cx="960" cy="315" rx="320" ry="280" fill="url(#logo-glow)" />
<!-- Top gradient bar -->
<rect x="0" y="0" width="1200" height="3" fill="url(#bar-grad)" />
<!-- Bottom gradient bar -->
<rect x="0" y="627" width="1200" height="3" fill="url(#bar-grad)" opacity="0.4" />
<!-- Vertical divider -->
<line x1="740" y1="80" x2="740" y2="550" stroke="#FF1A8C" stroke-opacity="0.12" stroke-width="1" />
<!-- Logo mark — centered at (960, 305), scale 3.8 -->
<g transform="translate(960, 305) scale(3.8)">
<path
d="M 0,-43.328173 -37.551083,43.328173 0,34.662539 H -17.331269 L 0,-5.7770893 17.33127,34.662539 H 0 l 37.5510841,8.665635 z"
fill="url(#logo-grad)" />
</g>
<!-- Left content area -->
<!-- Eyebrow label -->
<text x="80" y="195" font-family="Helvetica, Arial, sans-serif" font-size="12" letter-spacing="7" fill="#FF1A8C"
opacity="0.9">AI ART MAGAZINE</text>
<!-- Gradient accent line under eyebrow -->
<rect x="80" y="207" width="120" height="1.5" fill="url(#bar-grad)" />
<!-- PIVOINE headline -->
<text x="76" y="360" font-family="Helvetica-Bold, Arial Black, sans-serif" font-size="148" font-weight="900"
letter-spacing="-3" fill="#FFFFFF">PIVOINE</text>
<!-- Tagline -->
<text x="82" y="415" font-family="Helvetica, Arial, sans-serif" font-size="21" fill="#8B8FA8"
letter-spacing="0.5">Where the machine dreams.</text>
<!-- Bottom URL -->
<text x="82" y="562" font-family="Helvetica, Arial, sans-serif" font-size="13" letter-spacing="4" fill="#9B00FF"
opacity="0.8">PIVOINE.ART</text>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

+13
View File
@@ -0,0 +1,13 @@
<svg viewBox="0 0 96 96" width="380" height="380" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="g" 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(#g)"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 614 B

+15
View File
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg viewBox="0 0 96 96" fill="none" xmlns="http://www.w3.org/2000/svg" aria-label="{{ site.Title }}" role="img">
<defs>
<linearGradient id="gradient" 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(#gradient)" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 751 B