From f96f25aee9e49fedc013bc8b61a77fd0ea4b2d29 Mon Sep 17 00:00:00 2001 From: kayjaydee Date: Wed, 22 Apr 2026 00:40:21 +0200 Subject: [PATCH] docs(06): UI design contract MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 6 Blog Pages — contrat visuel/interaction pour listing /blog et article /blog/[slug]. Tokens hérités Phase 5 (prose, brand-*, colorMode), inventaire composants (BlogCard, BlogToc, BlogPrevNext), i18n keys blog.* + nav.blog, pattern hero + cards inspiré /projects. Co-Authored-By: Claude Sonnet 4.6 --- .planning/phases/06-blog-pages/06-UI-SPEC.md | 402 +++++++++++++++++++ 1 file changed, 402 insertions(+) create mode 100644 .planning/phases/06-blog-pages/06-UI-SPEC.md diff --git a/.planning/phases/06-blog-pages/06-UI-SPEC.md b/.planning/phases/06-blog-pages/06-UI-SPEC.md new file mode 100644 index 0000000..cf4dec3 --- /dev/null +++ b/.planning/phases/06-blog-pages/06-UI-SPEC.md @@ -0,0 +1,402 @@ +--- +phase: 6 +slug: blog-pages +status: draft +shadcn_initialized: false +preset: none +created: 2026-04-22 +--- + +# Phase 6 — UI Design Contract +## Blog Pages — Listing `/blog` + Article `/blog/[slug]` + +> Contrat visuel et d'interaction pour les deux pages blog SSR bilingues. +> Hérite des tokens de Phase 5 (prose, Shiki, MDC). Génère deux nouvelles pages + trois nouveaux composants. +> Généré par gsd-ui-researcher — à valider par gsd-ui-checker. + +--- + +## Design System + +| Property | Value | Source | +|----------|-------|--------| +| Tool | Nuxt UI v3 (pas de shadcn) | `nuxt.config.ts` + 05-UI-SPEC | +| Preset | not applicable | — | +| Component library | Nuxt UI v3 (`@nuxt/ui`) | CONTEXT D-05/D-07 (UDrawer, UBreadcrumb, UBadge, UButton, UIcon) | +| Icon library | Lucide via Nuxt UI (`i-lucide-*`) | `AppHeader.vue`, `ProjectCard.vue`, `projects.vue` usage existant | +| Font | Hérité (system-ui via Nuxt UI) + mono pour sloganeuse `// blog` | `app/assets/css/main.css` | +| CSS | Tailwind v4 + `@theme` tokens `--color-brand-*` | `app/assets/css/main.css` | +| Typography plugin | `@tailwindcss/typography` (hérité Phase 5) | `main.css` + 05-UI-SPEC | +| Theme | `colorMode` cookie-based (SSR-safe), dark default | `nuxt.config.ts` | + +> La shadcn gate ne s'applique pas — stack Nuxt UI. La vetting gate registry tiers ne s'applique pas non plus. + +--- + +## Spacing Scale + +Échelle 8-points standard (multiples de 4). Tailwind v4 fournit ces valeurs via les utilitaires `p-*`, `m-*`, `gap-*`. + +| Token | Value | Usage dans cette phase | +|-------|-------|------------------------| +| xs | 4px | Gap icône/texte dans meta article (date + reading time) | +| sm | 8px | Gap entre badges UBadge dans une rangée de tags | +| md | 16px | Padding interne des cards (entêtes, content spacing) | +| lg | 24px | Gap entre cards de la grille, padding BlogCard `p-5 sm:p-6` | +| xl | 32px | Espace vertical entre header article et body markdown | +| 2xl | 48px | Marge verticale de la section hero (pt-20 pb-16 pattern projects) | +| 3xl | 64px | `pt-20 pb-16` du hero listing, espace entre sections de page | + +Exceptions : +- Section listing content `py-16 md:py-20` (64→80px responsive) — conforme pattern `/projects` +- Sticky TOC offset top : `top-24` (96px = header 64px + 32px breathing) — multiple de 8, conforme +- Cover hero article `aspect-[21/9]` — ratio uniquement, pas une valeur de spacing +- Grille listing `gap-5 lg:gap-6` (20→24px) — `gap-5` = 20px est hors échelle stricte 8-points ; aligné avec le pattern existant `/projects` pour cohérence visuelle ; le checker doit accepter cette exception documentée +- Prev/Next cards `p-5` (20px) — idem exception alignée sur l'existant + +--- + +## Typography + +Le corps de l'article reste géré par `@tailwindcss/typography` via `prose dark:prose-invert` (hérité Phase 5, inchangé). +Le chrome de la page (hero, cards, header article, TOC, prev/next) utilise les valeurs ci-dessous. + +| Role | Size | Weight | Line Height | Usage | +|------|------|--------|-------------|-------| +| Display (hero H1) | 36→48→60px (`text-4xl sm:text-5xl lg:text-6xl`) | 700 (bold) | 1.1 (`leading-tight` implicite) | H1 gradient de la section hero `/blog` | +| Heading (card title, article H1) | 18→20px (`text-lg` card / `text-3xl sm:text-4xl` article header) | 700 (bold) | 1.2 | Titres BlogCard + titre article `[slug]` | +| Body (subtitle, description) | 16→20px (`text-lg sm:text-xl` subtitle / `text-sm` card desc) | 400 (regular) | 1.5 (`leading-relaxed`) | Subtitle hero, descriptions cards | +| Meta (date, reading time, slogan) | 12→14px (`text-xs`/`text-sm`) | 400 (regular) | 1.5 | Date ISO mono, reading time, slogan `// blog` | + +Règles Phase 6 : +- **2 poids uniquement** : regular (400) + bold (700). Pas de medium/semibold pour éviter la pollution typographique. +- **Mono réservée** : classe `font-mono` uniquement pour le slogan `// blog` et la date `datetime` attribut dans les cards (cohérence avec `ProjectCard.vue`). +- **Gradient text** : le H1 du hero hérite du gradient `from-gray-900 via-gray-800 to-gray-600 dark:from-white dark:via-gray-200 dark:to-gray-500` — identique `projects.vue` pour cohérence. +- **Body article** (prose) : 16px / 400 / 1.75 — inchangé Phase 5. + +--- + +## Color + +Dark mode par défaut, light mode synchronisé via cookie. Le palette `--color-brand-*` est déjà déclaré dans `main.css`. + +| Role | Value | Usage | +|------|-------|-------| +| Dominant (60%) | `bg-white` light / `bg-gray-950` dark (Tailwind) | Fond page, body article, surface hero dégradée | +| Secondary (30%) | `bg-gray-50/80` light / `bg-gray-900/40` dark — cards/panels `bg-white/80` / `bg-gray-900/60` | Fond hero section, fond BlogCard, fond prev/next card, fond TOC sidebar, fond drawer TOC | +| Accent (10%) | `--color-brand-500: #85cb85` (light) / `--color-brand-400: #a3d6a3` (dark) | Liens prose, slogan `// blog`, hover border cards, TOC highlight heading actif, CTA empty state (solid), gradient stats numbers | +| Destructive | `color-error` (Nuxt UI token, rouge) | Aucun usage dans cette phase (callouts `danger` déjà réservés Phase 5) | + +Accent `brand-*` réservé EXCLUSIVEMENT à : +1. Slogan mono `// blog` (hero top) — `text-brand-500 dark:text-brand-400` +2. Gradient numérique des stats dans le hero (`from-brand-400 to-brand-600`) — identique pattern `/projects` +3. Hover border de BlogCard (`hover:border-brand-500/40`) +4. Hover title de BlogCard (`group-hover:text-brand-600 dark:group-hover:text-brand-400`) +5. Shadow hover de BlogCard (`hover:shadow-brand-500/10`) +6. Heading actif courant dans la TOC au scroll (`text-brand-500 dark:text-brand-400`) — IntersectionObserver +7. CTA empty state UButton (`color="primary"` mappé sur brand par Nuxt UI) +8. Liens prose (hérité Phase 5 — inchangé) +9. Icônes arrow de prev/next cards au hover (`group-hover:text-brand-500`) + +Accent INTERDIT sur : +- Date, reading time, meta info (gris neutre) +- Tags UBadge (doivent rester en variant `subtle` color `neutral` ou `primary` une seule teinte — voir Registry) +- Breadcrumb inactif (gris) +- Corps de texte général + +--- + +## Copywriting Contract + +Tous les textes passent par `useI18n()` — clés déclarées dans `i18n/locales/{fr,en}.json`. +Les clés `blog.*`, `nav.blog`, `a11y.blogTocToggle` sont déjà listées dans CONTEXT D-21. + +### Hero listing `/blog` + +| Element | FR | EN | i18n key | +|---------|----|----|----------| +| Slogan (mono) | `// blog` | `// blog` | littéral (pas d'i18n) | +| H1 | Blog | Blog | `blog.title` | +| Subtitle | Articles techniques, retours d'expérience et guides pratiques sur le développement de plugins Hytale et l'écosystème web. | Technical articles, experience feedback and practical guides on Hytale plugin development and the web ecosystem. | `blog.subtitle` | +| Stat 1 label | Articles | Articles | `blog.stats.articles` | +| Stat 2 label | Tags | Tags | `blog.stats.tags` | +| Stat 3 label | Langues | Languages | `blog.stats.languages` | + +### BlogCard (listing + prev/next) + +| Element | FR | EN | i18n key | +|---------|----|----|----------| +| Reading time | `{n} min de lecture` | `{n} min read` | `blog.readingTime` (avec variable `{minutes}`) | +| Label prev article | Article précédent | Previous article | `blog.prevArticle` | +| Label next article | Article suivant | Next article | `blog.nextArticle` | + +### Article `/blog/[slug]` chrome + +| Element | FR | EN | i18n key | +|---------|----|----|----------| +| Breadcrumb home | Accueil | Home | `nav.home` (existant) | +| Breadcrumb blog | Blog | Blog | `nav.blog` (nouveau) | +| TOC title | Sommaire | Table of contents | `blog.toc.title` | +| Back to blog | Retour au blog | Back to blog | `blog.backToBlog` | + +### Empty state listing (0 articles non-draft) + +| Element | FR | EN | i18n key | +|---------|----|----|----------| +| Icon | `i-lucide-book-open` | `i-lucide-book-open` | littéral | +| Heading | Bientôt des articles Hytale | Hytale articles coming soon | `blog.emptyState.title` | +| Body | Le blog se prépare. Les premiers articles sur le développement de plugins Hytale arrivent bientôt. | The blog is being prepared. The first articles on Hytale plugin development are coming soon. | `blog.emptyState.description` | +| CTA label (primary) | Me contacter | Contact me | `blog.emptyState.cta` | +| CTA target | `/contact` via `localePath` | `/contact` via `localePath` | — | +| CTA icon | `i-lucide-mail` | `i-lucide-mail` | littéral | + +### Error state (404 article introuvable) + +Utilise `createError({ statusCode: 404 })` côté serveur → rendu via `error.vue` du layout global. Cette phase **n'ajoute pas** d'UI d'erreur custom — l'erreur 404 existante du projet s'applique. Aucune autre erreur visible prévue. + +### Accessibility copy + +| Element | FR | EN | i18n key | +|---------|----|----|----------| +| TOC toggle button aria-label | Afficher le sommaire | Show table of contents | `a11y.blogTocToggle` | +| Prev card aria-label | Article précédent : {titre} | Previous article: {title} | `a11y.blogPrev` (avec `{title}`) | +| Next card aria-label | Article suivant : {titre} | Next article: {title} | `a11y.blogNext` (avec `{title}`) | + +### Nav link AppHeader + +| Element | FR | EN | i18n key | +|---------|----|----|----------| +| Nav label Blog | Blog | Blog | `nav.blog` | + +Position finale AppHeader : Home / Hytale / **Blog** / Projects / About / Contact / Fiverr (CONTEXT D-15). + +### Destructive actions + +Aucune action destructive dans cette phase (lecture seule, pas de suppression, pas de formulaire). + +--- + +## Component Inventory + +Tous nouveaux composants — aucun shadcn, 100% Tailwind + Nuxt UI. + +| Composant | Chemin | Rôle | Base technique | +|-----------|--------|------|----------------| +| `BlogCard.vue` | `app/components/BlogCard.vue` | Card article réutilisable (listing + prev/next) | Tailwind + NuxtImg + UBadge, variant prop `default` / `compact` | +| `BlogToc.vue` | `app/components/BlogToc.vue` | Sommaire sticky desktop + drawer mobile | UDrawer (mobile) + sticky div (desktop) + IntersectionObserver | +| `BlogPrevNext.vue` | `app/components/BlogPrevNext.vue` | Navigation prev/next cards | 2× BlogCard variant `compact` + UIcon flèches | +| Page listing | `app/pages/blog/index.vue` (NEW) | Hero + grille + empty state | queryCollection(`blog_fr`\|`blog_en`) + BlogCard | +| Page article | `app/pages/blog/[slug].vue` (ENRICH) | Breadcrumb + header + body + TOC + prev/next | Existant Phase 5 enrichi | + +### Composants Nuxt UI consommés + +| Composant | Variant / Props | Usage | +|-----------|-----------------|-------| +| `UBadge` | `color="primary"` `variant="subtle"` | Tags dans BlogCard + header article (non-cliquables) | +| `UBreadcrumb` | Items array avec `label` + `to` | Breadcrumb visuel en haut de l'article (D-07) | +| `UDrawer` | `side="right"` | TOC mobile ( date mono text-xs +├── Title h2 text-lg font-bold, group-hover:text-brand-600 +├── Description text-sm line-clamp-2 leading-relaxed +├── Footer row : reading time text-xs gray-400 + tags supplémentaires (+N) pills neutres +└── NuxtLink absolute inset-0 (SEO + a11y) + +variant="compact" (prev/next) +├── Pas d'image cover (D-10) +├── Padding p-5, flex-col gap-2 +├── Label row : UIcon arrow-left|arrow-right + "Article précédent|suivant" text-xs uppercase tracking-wider gray-500 +├── Title h3 text-base font-bold, group-hover:text-brand-500 +├── Date