# Plan d'action — Home, bonnes pratiques (Next 16 / Tailwind v4 / shadcn / SEO)

**Source :** `docs/audits/home-best-practices-2026-08/README.md` (+ rapports `01`–`08`).
**Règles d'exécution :** une branche `feature/home-bp-<phase>` par phase depuis `dev`, **un commit par section ou par sujet** (Conventional Commits, corps citant le n° de constat `0x-…#n`), `bun run lint` à chaque étape, `bun run build` + `bunx playwright test e2e/home.spec.ts` en fin de phase, review Fable avant merge ff dans `dev`. Workers d'implémentation : Opus. Aucun changement visuel non demandé par le client sans le signaler dans le commit.

Effort : S ≤ 1 h · M ≤ ½ j · L > ½ j. Total estimé : ~6 j.

---

## Phase 0 — Décisions (½ h, utilisateur)

Trancher les 10 points de la section 6 du README. Sans 1 (or), 2 (Reveal) et 3 (H1), les phases 3, 5 et 2 restent partielles.

## Phase 1 — Hygiène, zéro impact visuel (½ j)

Branche `feature/home-bp-hygiene`. Commits :

1. `chore(public): drop 18 orphaned assets` — `advance-manufacture-building.png` (4,5 Mo), `home/glimpses/*`, `home/oem/{sampling,sketching}`, `home/success-team`, `production/{cad-printing,enamel}`, `sustainability/rjc-certified-member`, `slider/{az4951,az5332}`, `icon.svg`, `.DS_Store` (+ `.gitignore`). Garder `android-chrome-*` et les brancher (phase 2). Vérifier chaque suppression par `grep -rn <nom> src content`.
2. `chore(deps): remove @tabler/icons-react, move build-time packages to devDependencies` — `@tabler`, `schema-dts`, `shadcn`, `tw-animate-css` ; ajouter `@types/mdx` ; supprimer `package-lock.json` + `.gitignore` ; garder `class-variance-authority` (réutilisé en phase 4 par `button.tsx`).
3. `chore(next): clean optimizePackageImports and add the 1366 device size` — retirer `swiper`, `@tabler` ; `deviceSizes` + `1366`.
4. `chore(knip): make deadcode actionable` — `ignore: [".claude/**", "**/.next/**", "docs/**"]`, **retirer** `src/components/ui/**` ; supprimer `ui/And.tsx`, `ui/breadcrumb.tsx`, les 6 exports `Select*` morts, `isCategoryId`, `articleJsonLd`, type `Frontmatter`, `.pulse-slow` + `@keyframes editorial-pulse`. **Garder `ui/button.tsx`** (phase 4).
5. `fix(seo): declare the real logo size and a brand theme colour` — `site.logo` 436×373, `themeColor` → `#1a1c1b`, manifest : icônes 192/512 + `maskable`.

Vérif : `bun run deadcode` propre, build, e2e home.

## Phase 2 — SEO & accessibilité structurelle (1 j)

Branche `feature/home-bp-seo-a11y`. Commits :

1. `fix(a11y): add the main landmark and a skip link` — `layout.tsx` `<main id="content">`, skip link i18n (`Common.skipToContent`), `aria-labelledby` sur les 7 sections sans nom (h2 existants ; `sr-only` pour Stats).
2. `fix(seo): localise the WebPage JSON-LD url and stop overriding the layout social metas` — `webPageJsonLd` avec pathname localisé ; `buildPageMetadata` complète `openGraph.{type,siteName}` et `twitter.{card,site}`.
3. `feat(seo): describe the organisation as a manufacturer` — `Organization` (pas `JewelryStore`), `name: "Advance Manufacturers"`, `alternateName`, `logo` en `ImageObject` raster, `foundingDate`, `numberOfEmployees` (à valider client), `hasCredential` RJC, `areaServed`, X dans `sameAs`, `primaryImageOfPage` + `about` sur `WebPage`, un seul `@graph` rendu après les sections.
4. `feat(home/about): promote the overline to the section heading` — `Overline as="h2"` (prop polymorphe), texte par locale (décision 0.4) ; Certification : overline ≠ titre.
5. `feat(home/stats): expose the figures as a description list` — `<dl>`, `sr-only` h2, valeur+suffixe avec `aria-label`.
6. `feat(home): link every section to its deep page` — CTA Gallery → `/gallery`, Process → `/production-process`, Certification → `/sustainability` (ancre descriptive) ; footer 8 liens réutilisant `Header.nav.*`, `aria-label` du `<nav>`.
7. `fix(seo): align the English copy on the target spelling and figures` — « jewellery » partout (corps + alts EN), « Thailand/Bangkok » dans le corps, meta description « since 1989 », `site.description`, `LocaleLayout.description`. **FR** : `metaKeywords` et meta description depuis la source FR, H1 selon décision 0.3.
8. `fix(sitemap): stop stamping build time as lastmod` — omettre `lastmod` sur les pages statiques (ou date manuelle par route) ; corriger/retirer le bloc `<image:image>`.
9. `perf(fonts): stop preloading Playfair Display on routes that never use it` — après suppression de `And.tsx`, vérifier `font-serif` hors Home ; sinon retirer Playfair.
10. Investigation (M, sans commit obligatoire) : `loading.tsx` niveau `[locale]` → contenu en `<div hidden>` ; tester la suppression sur les routes 100 % statiques et mesurer le HTML brut.

Vérif : Rich Results Test sur `/` et `/fr`, `curl -s | grep -c '<main'`, axe via Playwright (0 violation critique), e2e.

## Phase 3 — Tokens de design & fin des px arbitraires (1 j)

Branche `feature/home-bp-tokens`. Commits :

1. `feat(theme): add the typographic and rhythm tokens` — bloc `@theme` du README §2 + `@utility shell` + `--color-gold-text` (décision 0.1) + `--color-scrim` ; supprimer les tokens morts `--spacing-gutter/margin-*/section*` (ou les recaler sur 76/104) ; **mettre à jour `docs/brand.md`** (rythme réel 76/104, coins 0px sur les contrôles).
2. Un commit par fichier, dans l'ordre : `refactor(header)`, `refactor(home/hero)`, `refactor(home/stats)`, `refactor(home/about)`, `refactor(home/certification)`, `refactor(home/value-prop)`, `refactor(home/materials)`, `refactor(home/process)`, `refactor(home/gallery)`, `refactor(footer)` — remplacer les valeurs arbitraires par tokens/échelle (tableaux §2 de chaque rapport), corriger au passage : grilles `auto-fit` → `1/2/4`, ombre hover ValueProp supprimée, `rounded-full` → `rounded-none` (contrôles, social), gradient inline → `bg-linear-to-b from-scrim/72 to-scrim/82`, `text-white/…` → tokens `on-inverse-*` (opaques : `strong/muted/hairline`), hero `lg:h` → `lg:min-h`, header `max-w-[1440px]` + `px-gutter`, `text-brushed-steel` du wordmark → `on-surface-variant`, rythme 76/104 partout.
3. `fix(a11y): meet AA contrast on the gold accents` — `text-gold` → `text-gold-text` sur texte et hover (`hover:bg-gold hover:text-foreground` sur les CTA), numéraux, copyright `/45` → `/60`, bordure social `/35` → `/45`.

Vérif : captures avant/après 375/768/1024/1440/1920 EN+FR (Playwright), diff visuel attendu = contraste et coins uniquement ; compteur `grep -c "\[" src/components/home/*.tsx` en baisse ≥ 70 %.

## Phase 4 — Composants partagés (1 j)

Branche `feature/home-bp-components`. Commits :

1. `feat(ui): brand variants on Button` — `cta`, `cta-outline`, `cta-inverse`, `link-arrow` dans le `cva` de `ui/button.tsx` (`rounded-none`, `h-auto`, `text-label`) ; rendu `render={<Link/>}` ; migrer les 6 CTA + `InquireCta` (qui devient serveur).
2. `feat(ui): ArrowLink, Rule, Wordmark` — `ArrowLink` (lucide `ArrowRight`, `focus-visible` unifié) sur les 7 usages ; `Rule` (4 tailles) consommé par `Overline` + 21 filets ; `Wordmark tone` pour `BrandLogo` et `Footer`, clés i18n `Brand.*` uniques.
3. `feat(ui): NumberedCardGrid` — ValueProp, AboutPhilosophy, Materials ; numérotation dérivée de l'index, `aria-hidden` sur les numéraux.
4. `feat(ui): RjcHallmark` (décision 0.6) — extrait de `SustainabilityRjc`, utilisé sur la Home ; suppression de l'image raster.
5. `refactor(icons): lucide only, brand SVG only` — remplacer les 7 SVG inline copiés de lucide et les glyphes `→ ‹ › ↓`, `data-icon` dans les boutons ; `src/components/icons/social.tsx` (SVG client `currentColor`, 24×24, SVGO) ; `LanguageSelect` : variante `bare` de `SelectTrigger`, `SelectValue`, `lang` sur les options, indicateur de sélection visible.
6. `feat(footer): field-based newsletter form` — `bunx shadcn add field input separator` ; `useId`, `required autoComplete inputMode aria-invalid aria-describedby`, `role="status"` permanent, `SubmitButton`, libellés en props, `Separator` ; suppression de l'année ; `rel="me noopener"`.
7. `fix(newsletter): harden the server action` — `z.email().max(254).toLowerCase()`, honeypot, rate-limit par IP, état honnête tant que l'ESP n'est pas branché (décision 0.7) ; même traitement sur `ContactForm`.

Vérif : `Button` importé ≥ 8 fois, plus aucun `→`/`‹`/`›` texte dans `src/`, e2e.

## Phase 5 — Frontière client & performance (1 j)

Branche `feature/home-bp-client`. Commits :

1. `perf(motion): scroll-driven reveal in CSS` (décision 0.2) — `.reveal` visible par défaut, `animation-timeline: view()` sous `@supports` + `prefers-reduced-motion`, stagger par `nth-child`/`--reveal-delay` ; suppression de `Reveal.tsx` (67 usages → `className="reveal"`) ; hook `useInView` partagé pour `CountUp` et le carrousel. Repli : observer singleton.
2. `perf(home/stats): animate CountUp without re-rendering` — ref + rAF, formateur hissé (ou `formatted` côté serveur), `setValue(0)` dans le callback.
3. `refactor(header): CSS underline, single segment subscription` — `HeaderNavBar` sans mesure/resize, focus suivi, `key` par href, `motion-safe` sur les transitions, `will-change` retiré, `ResizeObserver` stable, media query lue depuis `--breakpoint-nav`, items via contexte (1 sérialisation), `MobileMenuPanel` `max-h` sur `var(--header-h)`, `menuTitle` pour le `<nav>`, `aria-label` du logo avec la destination, cibles ≥ 24 px (`py-1.5`), focus visible du sélecteur de langue, timer nettoyé, survol conditionné à `(hover: hover)`.
4. `feat(ui): Crossfade carousel shared by the home and the gallery` — depuis `CollectionSlider` (pause au focus, `tookOver`), fenêtre de 3 slides, `blur` slide 0 uniquement, pas d'`eager`, `useSyncExternalStore` reduced-motion, `document.hidden` inline, `resetKey`, ARIA carrousel (`region`/`roledescription`/live), dots 24 px, `focus-visible`, `sizes` `calc(100vw - 128px)`, `Common.goToSlide` ; suppression de `GallerySlideshow` + `CollectionSlider`.
5. `perf(i18n): scope the client messages` (L, optionnel) — après 4.6, `NextIntlClientProvider messages={pick(...)}`.

Vérif : Lighthouse mobile/desktop avant/après (LCP, INP, TBT), nombre d'`IntersectionObserver` (DevTools) = 0–1, e2e.

## Phase 6 — Images & contenu (dépend du client)

Branche `feature/home-bp-images`. Commits :

1. `fix(home/hero): readable badge and honest alt` — scrim sous « Est. 1989 », `object-[35%_center]` en attendant l'asset recadré, alts EN/FR du README §5, `saturate` cuit dans l'asset.
2. `fix(home): correct the About and Materials alts` — `craftspeople` (asset ou alt), `metal-xray` décorative (`alt=""`, sans blur, source 1920 px), `predesign`.
3. `perf(images): right-size the sizes attributes` — Stats (660 px, variante basse résolution), About (533 px), Process (421 px), Gallery.
4. À la livraison client : 6 photos « How We Work » 4:3, 12 galerie 21:9, photo JCK, icônes sociales — renommage `home-gallery-01-<sujet>-advance-manufacturers.webp`, `docs/modifications-client/photo-requirements.md` fait foi.
5. `feat(seo): product-led OG image` — visuel produit + wordmark + accroche localisée, `alt` localisé.
6. Chantier éditorial (hors code) : volume Home EN/FR selon `~/Documents/Advmfr/website/pages/home/*` ; pages `/oem-jewellery-manufacturer`, `/wholesale-suppliers`, `/categories` prévues par la stratégie SEO et absentes du routing.

---

## Ordre recommandé et dépendances

`1 → 2 → 3 → 4 → 5 → 6`. La phase 3 (tokens) doit précéder la 4 (composants) pour que `Button`/`Rule`/`Wordmark` naissent sur les tokens. La phase 5.4 (carrousel) dépend de 4.5 (icônes). La phase 6 est parallélisable avec 3–5 pour les commits 1–3.

## Garde-fous

- `e2e/home.spec.ts` asserte les h2 « Why Brands Choose Advance », « How We Work », « Gallery », « Join Our Newsletter List » : ne pas changer leur texte/niveau sans mettre le spec à jour (cibler les landmarks plutôt que les libellés).
- Jamais de traduction EN ↔ FR des H1/H2/metas : chaque locale suit sa source (`feedback_i18n_seo_no_translation`).
- Home = Tenor Sans + or : ne pas « corriger » vers Montserrat/teal (`feedback_home_editorial_typography`).
- Le h2 visible de la section About reste retiré (demande client 2026-08-25).
