# Blog — Prismic-style Layout (sub-nav + featured/thematic index) — Design Spec

- **Date**: 2026-06-05
- **Branch**: `feat/blog-prismic-layout` (from `dev`)
- **Status**: approved design, pending implementation plan
- **Builds on**: the merged MDX blog + `/blog` index/search (`2026-06-05-mdx-blog-design.md`, `2026-06-05-blog-index-search-design.md`)
- **Reference**: prismic.io/blog (sticky secondary blog nav, featured zone, thematic groupings, rich cards)

## Goal

Adopt the Prismic blog patterns the user liked: a **sticky secondary blog navigation** (category tabs + search, persistent on `/blog` and articles), a **featured + thematic index** for `/blog`, and **richer post cards** (author + avatar + date). Newsletter is deferred; a "popular" section is dropped (no metrics).

## Decisions (locked)

1. Scope: (A) secondary blog nav, (B) index redesign = featured zone + per-category thematic sections, (C) enriched cards (author + avatar). **Deferred**: newsletter. **Dropped**: "popular" (no analytics; featured/thematic + article "related" cover discovery).
2. `localePrefix: "as-needed"` respected; categories remain filters (`/blog?category=<slug>`), article URLs unchanged.
3. `/blog` stays **static** (SSG): all search-param-dependent behaviour lives in client islands under `<Suspense>`.
4. Author avatar: a **placeholder** for now, **configured in `src/config/site.ts`** (swappable later) — not hardcoded.

## Content model

- **Frontmatter** (`src/lib/blog/schema.ts`): add `featured?: boolean` (Zod, default `false`).
- **Authors** (`src/config/blog/authors.ts`): add optional `avatar?: string`. `advance.avatar` is sourced from `site.ts` (see below) — keep author identity data here, the placeholder asset path central in `site.ts`.
- **`src/config/site.ts`**: add a small config holding the default/author avatar placeholder, e.g.:
  ```ts
  blog: {
    authorAvatar: "/blog/authors/placeholder.webp", // TODO: replace with real author avatar
  },
  ```
  `authors.ts` reads `site.blog.authorAvatar` for `advance.avatar`.
- **Asset**: create a neutral placeholder avatar at `public/blog/authors/placeholder.webp` (simple brand-tinted square / monogram). Swap later.
- **`src/lib/blog`**: add `getFeatured(locale, n = 3)` → posts with `featured === true` (date desc), fallback to latest `n` if none flagged. Reuse `getPostsByCategory`, `getRelated`, `getAllPosts`.

## A. Secondary blog nav — `BlogSubNav`

- **Client component** (`src/components/blog/BlogSubNav.tsx`) — interactive leaf; wrapped in `<Suspense>` by consumers so `/blog` stays static.
- **Placement**: a sticky bar directly under the fixed site header, on **`/blog` and article pages** (rendered explicitly in `/blog/page.tsx` and the article page — articles are not under `/blog`, so no shared layout). Sticky `top-[<header height>]`, hairline `border-b border-outline-soft`, `bg-background/95 backdrop-blur` (matches the ghost pattern), full-width inner `max-w-7xl`.
- **Tabs**: "All" → `Link href="/blog"`; each category → `Link href={{ pathname: "/blog", query: { category: <localized slug> } }}`. Active state:
  - On `/blog`: read `useSearchParams().get("category")` → highlight matching tab ("All" when none).
  - On an article: pass `activeCategory={post.category}` prop (overrides search-param logic).
- **Search**: a small form/input; on submit → navigate (`useRouter().push`) to `/blog?q=<term>` (+ preserve current `category` if set). Localized placeholder.
- Tokens-only, sharp corners, Montserrat, horizontal scroll on mobile (`overflow-x-auto`).

## B. Index `/blog` — `BlogIndexView`

- **Client component** (`src/components/blog/BlogIndexView.tsx`), receives `posts: PostMeta[]` (serializable), wrapped in `<Suspense>`. SSR'd during SSG with empty search params → default view is in the prerendered HTML (SEO-safe).
- **Default view** (no `?q` and no `?category`):
  - **Featured zone**: `getFeatured` result (computed client-side from `posts` via the `featured` flag, fallback latest) — 1 large + 2 secondary, or a 3-up row. Reuse/extend `PostCard` (a `featured` variant optional).
  - **Thematic sections**: for each category with ≥1 post, a section: category label (heading) + top 3 `PostCard` + "View all {category}" link → `/blog?category=<slug>`. Skip empty categories.
- **Filtered/search view** (`?q` or `?category` present): the existing Fuse search + filter grid (current `BlogSearch` logic folded in) — initial `query` from `?q`, initial category from `?category`. Hide featured/thematic.
- `BlogSearch` (current) is absorbed into `BlogIndexView` (the filtered branch) OR kept and reused; plan picks one to avoid duplication. The category **chips** previously in `BlogSearch` are replaced by the `BlogSubNav` tabs (remove the in-page chips to avoid double controls).
- `/blog/page.tsx` server shell: H1 (`indexTitle`) + intro, `Blog` JSON-LD + Breadcrumb (existing), `BlogSubNav`, `<Suspense><BlogIndexView posts={...} /></Suspense>`.

## C. Cards & article byline

- **`PostCard`**: add date + author (name + avatar) row. Avatar via `next/image` from `authors[post.author].avatar` (placeholder). Keep category overline, title, excerpt, reading time. Tokens, sharp.
- **`PostLayout`** byline: add the author avatar next to the name; keep date/reading time. Add `BlogSubNav` (sticky, `activeCategory={post.category}`) at the top.

## i18n (`BlogPage`)

Re-add `allCategories` ("All" / "Tous") for the sub-nav tab; add `featured` ("Featured" / "À la une"), `viewAll` ("View all {category}" / "Voir tout : {category}"). Reuse `categories.*`, `search.placeholder`, `readingTime`, `byAuthor`.

## Routing / SEO

- No new routes; `/blog` + `?category=`/`?q=` only. Canonical `/blog` unchanged (query views canonicalize to `/blog`). `Blog` JSON-LD unchanged.
- `/blog` must remain SSG (verify in build route list). Sub-nav + index interactivity = client islands under `<Suspense>`.

## Dependencies

None (Fuse.js already present). No new deps.

## Success criteria

- `bun run lint` clean; `bun run build` green; `/blog` + `/fr/blog` still **SSG**; articles SSG.
- Sticky sub-nav on `/blog` and articles; tabs deep-link to `/blog?category=`; active tab reflects `?category` (/blog) or `post.category` (article); sub-nav search routes to `/blog?q=`.
- `/blog` default view = featured + thematic (in prerendered HTML); `?category`/`?q` → filtered grid; no duplicate category controls.
- Cards + article byline show author name + (placeholder) avatar + date; avatar path comes from `site.ts`.
- Frontmatter `featured` validated; missing/extra fields fail build.

## Open items (deferred)

- Newsletter (Server Action + ESP) — separate branch when a provider is chosen.
- Real author avatar(s) — replace the `site.ts` placeholder.
- Header desktop density (7 nav items) — design pass.
- Visual polish of featured/thematic/cards.
