# 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

```bash
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

```yaml
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

```yaml
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

```css
/* 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 (`WebSite` on home, `BlogPosting` on articles)
- `robots.txt` generated by Hugo (`enableRobotsTXT = true`)
- `X-Robots-Tag: index, follow` set in nginx
- Canonical URLs, `article:modified_time` from git history
- Sitemap at `/sitemap.xml`

---

## Deployment

### Docker

```dockerfile
# 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
```

```bash
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

```bash
# 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

```bash
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`:

```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.
