Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PIVOINE
Where the machine dreams.
An editorial magazine for AI-generated art and generative aesthetics — built on Hugo, styled with Tailwind v4, animated with Alpine.js and HTMX.
Stack
| Layer | Technology |
|---|---|
| Static site generator | Hugo (extended) |
| CSS | Tailwind CSS v4 via PostCSS |
| Reactivity | Alpine.js v3 |
| Navigation | HTMX v2 (boosted links + View Transitions) |
| Fonts | Bebas Neue · Barlow · Share Tech Mono (Google Fonts) |
| Analytics | Umami (cookie-free) |
| Server | nginx (Docker) → Traefik → Coolify |
Getting Started
Prerequisites: Node.js 22+, pnpm, Hugo extended
pnpm install
pnpm dev # Hugo dev server with drafts, http://localhost:1313
pnpm build # Production build → public/
Project Structure
pivoine.art/
├── assets/
│ ├── css/main.css # Design tokens + all component styles
│ └── js/main.js # HTMX progress bar, lazy-load videos
├── content/
│ ├── _index.md # Homepage front matter
│ ├── about/index.md
│ ├── imprint/index.md
│ ├── authors/
│ │ └── {slug}/
│ │ ├── index.md # Author profile
│ │ └── avatar.png # Page resource (auto-processed to WebP)
│ └── posts/
│ └── {slug}/
│ ├── index.md # Post front matter
│ ├── banner.png # Hero image (or 01.png as fallback)
│ ├── 01.png # Gallery image
│ ├── 01.mp4 # Optional companion video
│ └── 02.png …
├── layouts/
│ ├── _default/ # baseof, list, single, terms
│ ├── partials/ # head, nav, footer, cards, img, schema …
│ ├── posts/single.html # Article template
│ ├── authors/ # Author list + profile
│ ├── categories/ tags/ # Taxonomy pages
│ ├── index.html # Homepage
│ └── 404.html
├── static/
│ ├── images/og-default.jpg
│ └── favicon.* / site.webmanifest
├── Dockerfile
├── nginx.conf
├── hugo.toml
└── postcss.config.js
Content Model
Posts
title: "Hyperloop"
description: "Short editorial summary shown in cards and meta tags."
date: 2025-08-17
author: "valknar" # must match content/authors/{slug}/
featured: true # appears in homepage hero / secondary strip
categories: [Surreal]
tags: [futuristic, vibrant, urban]
# Optional — only needed when not using page-bundle images
banner:
type: image # or "video"
src: "/images/posts/…"
alt: "Alt text"
background: # full-bleed blurred backdrop on article page
type: image
src: "/images/posts/…"
alt: "Alt text"
Page-bundle images (preferred):
Drop numbered images and optional companion videos alongside index.md:
posts/hyperloop/
├── index.md
├── banner.png ← hero (detected automatically)
├── 01.png ← gallery item 1
├── 01.mp4 ← plays instead of 01.png if present
├── 02.png
└── 03.png …
Hugo automatically resizes all bundle images to WebP at multiple widths — no manual optimisation needed.
Authors
title: "Valknar"
name: "Valknar"
bio: "Short biography shown in bylines and author profile."
social:
instagram: "handle"
x: "handle"
Place avatar.png (or .jpg) in the same directory — it is converted to 96 × 96 px WebP automatically.
Image Processing
All page-resource images are handled by layouts/partials/img.html, which converts to WebP and emits responsive srcset:
| Context | Widths generated | sizes hint |
|---|---|---|
| Post banner | 1200w · 1800w | 100vw |
| Gallery image | 800w · 1200w | 33vw on desktop |
| Featured card | 800w · 1200w | 50vw on desktop |
| Grid card thumbnail | 600w · 1000w | 25vw on desktop |
| Author avatar | 96px | — |
Before / after example (hyperloop/banner.png):
| Original PNG | WebP 1800w | WebP 600w |
|---|---|---|
| 7.9 MB | 496 KB | 56 KB |
Processed images are cached in resources/_gen/ (gitignored).
Design System
Colour Palette
/* Base — deep navy-black */
--color-void: #050510
--color-ink: #09091A
--color-concrete: #0F0F22
--color-zinc: #1C1C38
/* Neutrals */
--color-fog: #6868A0
--color-chalk: #B8B8E0
--color-paper: #EEEEFF
/* Accent triad */
--color-heat: #FF1A8C /* hot pink — primary */
--color-pulse: #9B00FF /* electric purple — mid */
--color-frost: #00C8FF /* ice blue — cool */
Typography
| Role | Font | Usage |
|---|---|---|
| Display | Bebas Neue | Headlines, oversized decorative text |
| Body | Barlow 300–600 | Editorial prose, UI copy |
| Label | Share Tech Mono | Metadata, badges, specs |
Component Classes
.btn CTA button — clip-path skew, shimmer sweep on hover
.btn-outline Transparent variant
.btn-frost Ice blue variant
.badge Parallelogram sticker (multiple colour variants)
.badge-outline Bordered, transparent
.card-comic Card with 1.5px border + lift + glow on hover
.card-media Media container — zoom on hover
.gradient-line 1 px heat → pulse → frost divider
.graffiti-tag Oversized hollow decorative text (text-stroke)
.speed-lines Isometric triangle grid background
.halftone Diamond crosshatch background
.spray-bg Radial dot pattern
.text-gradient Static pink → purple → ice gradient text
.text-gradient-animated Looping gradient (5 s)
.label Monospace spec-sheet micro-text
.prose-editorial Long-form article body styles
.gutter-x Responsive horizontal padding (clamp 1 – 3 rem)
Templates
| Template | Route | Description |
|---|---|---|
index.html |
/ |
Hero (featured post) · secondary cards (3) · latest grid (8) · about strip |
posts/single.html |
/posts/{slug}/ |
Article header · author byline · banner · prose · gallery · tags · related |
_default/list.html |
/posts/ |
Paginated post grid (12/page) |
authors/single.html |
/authors/{slug}/ |
Author profile · all posts by author |
authors/list.html |
/authors/ |
Author grid |
categories/single.html |
/categories/{slug}/ |
Posts by category |
_default/terms.html |
/categories/ /tags/ |
Badge cloud of all terms + counts |
404.html |
* |
Styled error page |
Navigation & Interactivity
HTMX — hx-boost on <body> intercepts all internal links, swaps only #main-content, and pushes URL history. Combined with the CSS View Transitions API for smooth page animations.
Alpine.js — $store.nav.path drives active link highlighting. $store.nav.open toggles the mobile fullscreen overlay (teleported to <body> to escape header stacking context).
Mobile menu — animated hamburger → ✕, staggered link slide-in with mob-link-in keyframe.
SEO & Meta
- Open Graph + Twitter Card on every page
- JSON-LD structured data (
WebSiteon home,BlogPostingon articles) robots.txtgenerated by Hugo (enableRobotsTXT = true)X-Robots-Tag: index, followset in nginx- Canonical URLs,
article:modified_timefrom git history - Sitemap at
/sitemap.xml
Deployment
Docker
# Stage 1 — build
FROM node:22-alpine AS builder
# installs Hugo + pnpm, runs pnpm build
# Stage 2 — serve
FROM nginx:alpine
COPY --from=builder /app/public /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
docker build -t pivoine-art .
docker run -p 8080:80 pivoine-art
nginx
- Gzip enabled for text, CSS, JS, SVG, JSON
- Static assets:
Cache-Control: public, immutable· 1 year - HTML:
Cache-Control: no-store, no-cache(always fresh) - Clean URLs via
try_files $uri $uri/ $uri.html
Traefik / Coolify
The container sits behind Traefik (managed by Coolify). A global security-headers middleware in stacks/traefik/dynamic/security.yaml applies HSTS, CSP, and other headers to all services. A separate no-index middleware in the same file can be applied to internal services that should not be indexed.
Adding Content
New post
# Create a leaf bundle
mkdir -p content/posts/my-new-drop
touch content/posts/my-new-drop/index.md
# Drop images alongside index.md
cp ~/renders/banner.png content/posts/my-new-drop/
cp ~/renders/01.png content/posts/my-new-drop/
Fill in index.md front matter, run pnpm dev, and the post appears immediately. Hugo handles WebP conversion, srcset generation, and gallery detection automatically.
New author
mkdir -p content/authors/my-handle
touch content/authors/my-handle/index.md
cp ~/avatar.png content/authors/my-handle/
Reference the author slug in any post's author: field.
Configuration
Key settings in hugo.toml:
[params]
description = "An editorial magazine for AI-generated art …"
tagline = "Where the machine dreams."
author = "Pivoine Editorial"
logoText = "PIVOINE"
twitterHandle = "pivoineArt"
ogImage = "/images/og-default.jpg"
copyrightYear = "2026"
umamiSrc = "https://umami.pivoine.art/script.js"
umamiId = "…"
[params.social]
instagram = "https://instagram.com/…"
x = "https://x.com/…"
Menus are defined as [[menus.main]] and [[menus.footer]] arrays in the same file.
License
© 2026 Pivoine Editorial. All rights reserved.