Published

Rapport d'Audit : Gestion des Headers de Pages

Rapport d'Audit : Gestion des Headers de Pages

Date : 2025-12-22 Auteur : Claude (Audit Technique) Scope : Frontend Nuxt 3 / Vue 3 / Vuetify


📋 Résumé Exécutif

Cet audit identifie 7 composants header/hero distincts et 3 patterns principaux utilisés à travers le site. L'objectif est de proposer une architecture unifiée et flexible permettant de réduire la duplication de code tout en préservant la richesse fonctionnelle existante.

Problèmes Identifiés

  1. Duplication de code : Chaque page domain-specific (OpensourceHero, TeamHero, PartnersHero) réimplémente des patterns similaires
  2. Incohérence visuelle : Variabilité dans les espacements, typographies, animations
  3. Maintenabilité : Modifications nécessitant des changements dans multiples fichiers
  4. Accessibilité inégale : Certains composants manquent d'ARIA complet ou de structured data
  5. Réutilisabilité limitée : Composants trop couplés à leur domaine

Solution Proposée

Architecture hybride : Composant de base + composables réutilisables ✅ 3 variantes principales : hero-fullscreen, hero-standard, section-headerProps unifiées avec design tokens ✅ SEO-ready avec structured data JSON-LD automatisé ✅ Performance optimisée : lazy loading images, WebP/AVIF, eager pour above-the-fold


🔍 1. Audit Complet des Composants Existants

1.1 Composants Header/Hero Identifiés

ComposantEmplacementUtilisationPatternComplexité
HeroSurface.vueshared/hero/Wrapper générique avec variantesBase⭐ Simple
HeroEducationCard.vueshared/ui/Carte décorative dans heroAccessoire⭐ Simple
HomeHeroSection.vuehome/sections/Homepage full-screenHero Fullscreen⭐⭐⭐ Complexe
OpensourceHero.vuedomains/opensource/Page opensourceHero Standard⭐⭐ Moyen
TeamHero.vuedomains/team/Page teamHero Minimal⭐ Simple
PartnersHero.vuedomains/partners/Page partnersHero Standard⭐⭐ Moyen
ParallaxWidget.vueshared/ui/Background parallaxTechnique⭐⭐⭐ Complexe

1.2 Pages Analysées (27 pages)

✅ index.vue (homepage) → HomeHeroSection
✅ opensource/index.vue → OpensourceHero
✅ team/index.vue → TeamHero
✅ partners/index.vue → PartnersHero
✅ impact-score/index.vue → Hero custom inline (gradient background)
✅ blog/index.vue → TheArticles (pas de hero dédié)
✅ categories/index.vue → (à vérifier)
✅ contact/index.vue → (à vérifier)
... + 19 autres pages

📊 2. Cartographie des Intentions et Patterns

2.1 Les 3 Patterns Principaux

🎯 Pattern 1 : Hero Fullscreen (Homepage)

Caractéristiques :

  • Hauteur : 100dvh ou min-height: 100dvh
  • Background : Image + overlay complexe (radial gradients)
  • Contenu : Titre + Subtitle + Search + Widget/Wizard
  • Parallax : Non (mais peut utiliser ParallaxWidget après)
  • Container : fluid (pleine largeur)
  • Centrage : Vertical + Horizontal
  • Animation : Entrée progressive (fade, scale, pulse)

Exemple : HomeHeroSection.vue

<HeroSurface variant="aurora">
  <div class="background-media"> (image eager, overlay gradients) </div>
  <v-container fluid>
    <h1>Titre principal</h1>
    <p>Subtitle</p>
    <SearchSuggestField />
    <NudgeToolWizard />
  </v-container>
</HeroSurface>

Utilisations :

  • Homepage (/)

🎯 Pattern 2 : Hero Standard (Pages Internes)

Caractéristiques :

  • Hauteur : Auto (padding vertical contrôlé)
  • Background : Gradient linéaire ou HeroSurface variant
  • Contenu : Eyebrow + Titre + Subtitle + Description + CTA(s) + Media (optionnelle)
  • Parallax : Non (surface HeroSurface avec variantes design)
  • Container : lg ou xl (max-width)
  • Layout : 2 colonnes responsive (7/5 ou 8/4)
  • Animation : Optionnelle (slide, fade)

Exemple : OpensourceHero.vue, PartnersHero.vue

<HeroSurface variant="prism|orbit|halo">
  <v-container max-width="lg">
    <v-row>
      <v-col md="7">
        <span class="eyebrow">EYEBROW</span>
        <h1>Titre</h1>
        <p>Subtitle</p>
        <TextContent bloc-id="..." />
        <v-btn>CTA Principal</v-btn>
      </v-col>
      <v-col md="5">
        <HeroEducationCard v-if="..." />
        <div class="visual-glow" v-else />
      </v-col>
    </v-row>
  </v-container>
</HeroSurface>

Utilisations :

  • /opensource, /partners, /team

🎯 Pattern 3 : Section Header (Dans les pages)

Caractéristiques :

  • Hauteur : Auto (compact)
  • Background : Gradient background + pseudo-elements pour effets
  • Contenu : Titre + Description
  • Container : Responsive max-width
  • Centrage : Centré ou aligné gauche
  • Animation : Aucune ou subtile

Exemple : impact-score/index.vue (hero inline)

<section class="hero-section">
  <v-container>
    <h1>Titre</h1>
    <div><TextContent bloc-id="..." /></div>
  </v-container>
</section>

<style>
.hero-section {
  background: linear-gradient(
    180deg,
    rgba(var(--v-theme-hero-gradient-start), 0.95),
    rgba(var(--v-theme-hero-gradient-end), 0.92)
  );
  color: white;
  padding-block: clamp(5rem, 9vw, 7.5rem);
}
</style>

Utilisations :

  • /impact-score, sections internes de pages

2.2 Composants Techniques Réutilisables

ComposantFonctionProps Clés
HeroSurfaceWrapper avec variantes design (aurora, prism, orbit, halo, mesh, pulse)variant, tag, bleed
ParallaxWidgetGestion parallax avec layers multiplesbackgrounds, parallaxAmount, overlayOpacity, enableAplats, maxOffsetRatio
HeroEducationCardCarte info décorativeicon, title, bodyHtml, items[]

🎨 3. Design Tokens Utilisés

3.1 Couleurs Thématiques (de palettes.ts)

// Hero Gradients
--v-theme-hero-gradient-start: #00de9f (light) / #1e3a8a (dark)
  --v-theme-hero-gradient-mid: #00a1c2 (light) / #1d4ed8 (dark)
  --v-theme-hero-gradient-end: #0088d6 (light) / #166534 (dark)
  --v-theme-hero-overlay-strong: #ffffff (both)
  --v-theme-hero-pill-on-dark: #ffffff (both) // Surfaces
  --v-theme-surface-default: #f4f4f4 (light) / #0f172a (dark)
  --v-theme-surface-muted: #f4f4f4 (light) / #111827 (dark)
  --v-theme-surface-glass: #ffffff (light) / #1e293b (dark)
  --v-theme-surface-primary-080: #f4f4f4 (light) / #13213b (dark) // Texte
  --v-theme-text-neutral-strong: #004a63 (light) / #f8fafc (dark)
  --v-theme-text-neutral-secondary: #004a63 (light) / #cbd5f5 (dark) // Accents
  --v-theme-accent-primary-highlight: #00a1c2 (light) / #38bdf8 (dark)
  --v-theme-accent-supporting: #00de9f (light) / #22c55e (dark)
  --v-theme-border-primary-strong: #00a1c2 (light) / #1e40af (dark)
  --v-theme-shadow-primary-600: #00a1c2 (light) / #3b82f6 (dark);

3.2 Espacements Recommandés

// Padding vertical hero
--hero-padding-fullscreen: clamp(2.5rem, 7vw, 4.75rem)
  --hero-padding-standard: clamp(3rem, 8vw, 5.5rem)
  --hero-padding-compact: clamp(2rem, 5vw, 3.5rem) // Conteneur
  --container-padding: clamp(1.5rem, 5vw, 4rem)
  --content-max-width: min(1180px, 92vw);

3.3 Typographie

// Titres hero
--hero-title-fullscreen: clamp(2.2rem, 5vw, 3.8rem)
  --hero-title-standard: clamp(2.5rem, 5vw, 3.5rem)
  --hero-subtitle: clamp(1rem, 2.4vw, 1.4rem) --hero-eyebrow: 0.82rem
  (uppercase, letter-spacing: 0.1em);

🏗️ 4. Architecture Proposée du Composant Unifié

4.1 Approche Hybride Recommandée

📦 /app/components/shared/header/
├── PageHeader.vue                 ← Composant principal unifié
├── composables/
│   ├── useHeaderLayout.ts         ← Logique layout (colonnes, alignement)
│   ├── useHeaderSeo.ts            ← Gestion SEO (JSON-LD, meta tags)
│   ├── useHeaderA11y.ts           ← Accessibilité (ARIA, focus trap)
│   └── useHeaderAnimation.ts      ← Animations (scroll, entrance)
├── variants/
│   ├── HeroFullscreen.vue         ← Variante homepage (extends PageHeader)
│   ├── HeroStandard.vue           ← Variante pages internes (extends PageHeader)
│   └── SectionHeader.vue          ← Variante compacte (extends PageHeader)
└── slots/
    ├── HeaderMedia.vue            ← Slot wrapper pour media (image, video, glow)
    ├── HeaderActions.vue          ← Slot wrapper pour CTA buttons
    └── HeaderContent.vue          ← Slot wrapper pour contenu principal

4.2 API du Composant Principal PageHeader.vue

Props

interface PageHeaderProps {
  // === Variante & Layout ===
  variant?: 'hero-fullscreen' | 'hero-standard' | 'section-header'
  layout?: 'single-column' | '2-columns' | '3-columns'
  contentAlign?: 'start' | 'center'

  // === Container ===
  container?: 'fluid' | 'lg' | 'xl' | 'xxl'
  maxWidth?: string // ex: '1180px'

  // === Contenu Textuel ===
  eyebrow?: string
  title: string
  subtitle?: string
  descriptionBlocId?: string // Pour TextContent CMS
  descriptionHtml?: string // Fallback HTML

  // === Background & Style ===
  background?: 'gradient' | 'image' | 'parallax' | 'solid' | 'surface-variant'
  surfaceVariant?: 'aurora' | 'prism' | 'orbit' | 'halo' | 'mesh' | 'pulse'
  backgroundImage?: string | { light: string; dark: string }
  overlayOpacity?: number // 0-1
  backgroundColor?: string // Custom couleur

  // === Parallax (si background='parallax') ===
  isParallax?: boolean
  parallaxLayers?: ParallaxLayerConfig[]
  parallaxAmount?: number
  enableAplats?: boolean
  aplatSvg?: string

  // === Hauteur ===
  minHeight?: string | null // ex: '100dvh', '75vh', 'auto'
  paddingY?: string | null // Padding vertical

  // === Actions (CTA) ===
  primaryCta?: HeroCta
  secondaryCta?: HeroCta
  ctaGroupLabel?: string

  // === Media / Visual ===
  showMedia?: boolean
  mediaType?: 'image' | 'card' | 'glow' | 'custom'
  mediaImage?: string
  mediaImageAlt?: string
  heroCard?: HeroEducationCardProps

  // === SEO ===
  headingLevel?: 'h1' | 'h2' | 'h3'
  headingId?: string
  breadcrumbs?: BreadcrumbItem[]
  schemaType?: 'WebPage' | 'Article' | 'AboutPage' | 'ContactPage'

  // === Accessibilité ===
  ariaLabel?: string
  ariaDescribedBy?: string

  // === Animations ===
  animate?: boolean
  animationType?: 'fade' | 'slide' | 'scale' | 'none'
  animateOnScroll?: boolean
}

interface HeroCta {
  label: string
  href: string
  ariaLabel: string
  icon?: string
  color?: string
  variant?: 'flat' | 'outlined' | 'tonal' | 'text'
  target?: string
  rel?: string
}

interface BreadcrumbItem {
  label: string
  href?: string
}

interface HeroEducationCardProps {
  icon: string
  title: string
  bodyHtml?: string
  items?: { icon?: string; text: string }[]
}

Slots

<PageHeader>
  <!-- Slot principal : contenu custom -->
  <template #default>
    Contenu personnalisé (remplace title/subtitle/description)
  </template>

  <!-- Slot eyebrow : badge/chip custom -->
  <template #eyebrow>
    <v-chip>Custom Eyebrow</v-chip>
  </template>

  <!-- Slot titre : custom heading -->
  <template #title>
    <h1 class="custom-title">Mon Titre</h1>
  </template>

  <!-- Slot subtitle -->
  <template #subtitle>
    <p class="custom-subtitle">Mon sous-titre</p>
  </template>

  <!-- Slot description : contenu étendu -->
  <template #description>
    <TextContent bloc-id="..." />
  </template>

  <!-- Slot actions : CTA customs -->
  <template #actions>
    <v-btn>Action 1</v-btn>
    <v-btn>Action 2</v-btn>
  </template>

  <!-- Slot media : visuel droite (2-columns) ou centré -->
  <template #media>
    <HeroEducationCard v-bind="..." />
    <!-- ou -->
    <img src="..." alt="..." />
    <!-- ou -->
    <div class="custom-visual">...</div>
  </template>

  <!-- Slot background : custom background layer -->
  <template #background>
    <div class="custom-bg-animation">...</div>
  </template>

  <!-- Slot aplats : SVG décoratif pour parallax -->
  <template #aplats>
    <img src="/custom-aplats.svg" alt="" />
  </template>
</PageHeader>

Events

interface PageHeaderEmits {
  'cta:primary': []
  'cta:secondary': []
  intersection: [isIntersecting: boolean] // Pour animations scroll
}

4.3 Exemple d'Utilisation : Hero Fullscreen (Homepage)

<script setup lang="ts">
import PageHeader from '~/components/shared/header/PageHeader.vue'
import NudgeToolWizard from '~/components/nudge-tool/NudgeToolWizard.vue'

const { t } = useI18n()
const heroBackgroundAsset = useHeroBackgroundAsset()
</script>

<template>
  <PageHeader
    variant="hero-fullscreen"
    :title="t('home.hero.title')"
    :subtitle="t('home.hero.titleSubtitle')"
    container="fluid"
    background="image"
    :background-image="heroBackgroundAsset"
    :overlay-opacity="0.65"
    min-height="100dvh"
    content-align="center"
    heading-level="h1"
    heading-id="home-hero-title"
    schema-type="WebPage"
    aria-label="Page d'accueil Nudger.fr"
  >
    <template #default>
      <!-- Search -->
      <SearchSuggestField
        v-model="searchQuery"
        :placeholder="t('home.hero.search.placeholder')"
        @submit="handleSearchSubmit"
      />

      <!-- Wizard -->
      <NudgeToolWizard :verticals="verticals" />

      <!-- Context Card -->
      <RoundedCornerCard>
        <p>{{ t('home.hero.subtitle') }}</p>
        <ul>
          <li v-for="item in helpers" :key="item.text">
            {{ item.icon }} {{ item.text }}
          </li>
        </ul>
      </RoundedCornerCard>
    </template>
  </PageHeader>
</template>

4.4 Exemple d'Utilisation : Hero Standard (Page Interne)

<script setup lang="ts">
import PageHeader from '~/components/shared/header/PageHeader.vue'

const { t } = useI18n()

const primaryCta = {
  label: t('opensource.hero.primaryCta.label'),
  href: 'https://github.com/open4good/open4goods',
  ariaLabel: t('opensource.hero.primaryCta.ariaLabel'),
  icon: 'mdi-github',
  target: '_blank',
  rel: 'noopener',
}

const heroCard = {
  icon: 'mdi-source-branch',
  title: t('opensource.hero.infoCard.title'),
  bodyHtml: t('opensource.hero.infoCard.description'),
  items: [
    {
      icon: 'mdi-checkbox-marked-circle-outline',
      text: t('opensource.hero.infoCard.items.openLicenses'),
    },
    {
      icon: 'mdi-checkbox-marked-circle-outline',
      text: t('opensource.hero.infoCard.items.collaborativeReviews'),
    },
  ],
}
</script>

<template>
  <PageHeader
    variant="hero-standard"
    :title="t('opensource.hero.title')"
    :subtitle="t('opensource.hero.subtitle')"
    description-bloc-id="webpages:opensource:hero-description"
    layout="2-columns"
    container="lg"
    surface-variant="prism"
    :primary-cta="primaryCta"
    :hero-card="heroCard"
    show-media
    media-type="card"
    heading-level="h1"
    heading-id="opensource-hero-title"
    schema-type="AboutPage"
  />
</template>

4.5 Exemple d'Utilisation : Section Header (Compact)

<script setup lang="ts">
import PageHeader from '~/components/shared/header/PageHeader.vue'

const { t } = useI18n()
</script>

<template>
  <PageHeader
    variant="section-header"
    eyebrow="Guide complet"
    :title="t('impactScore.hero.title')"
    description-bloc-id="ECOSCORE:1:"
    layout="single-column"
    container="xl"
    background="gradient"
    padding-y="clamp(5rem, 9vw, 7.5rem)"
    content-align="center"
    heading-level="h1"
    heading-id="impact-score-hero-title"
  />
</template>

🚀 5. Composables Proposés

5.1 useHeaderSeo.ts

Génère automatiquement les meta tags et structured data JSON-LD.

export function useHeaderSeo(props: {
  title: string
  subtitle?: string
  description?: string
  headingLevel: 'h1' | 'h2' | 'h3'
  schemaType?: 'WebPage' | 'Article' | 'AboutPage' | 'ContactPage'
  breadcrumbs?: BreadcrumbItem[]
  ogImage?: string
}) {
  const { locale } = useI18n()
  const requestURL = useRequestURL()
  const canonicalUrl = computed(() => requestURL.href)

  // Structured Data JSON-LD
  const jsonLdSchema = computed(() => ({
    '@context': 'https://schema.org',
    '@type': props.schemaType ?? 'WebPage',
    name: props.title,
    description: props.subtitle || props.description,
    url: canonicalUrl.value,
    breadcrumb: props.breadcrumbs
      ? {
          '@type': 'BreadcrumbList',
          itemListElement: props.breadcrumbs.map((item, index) => ({
            '@type': 'ListItem',
            position: index + 1,
            name: item.label,
            item: item.href
              ? new URL(item.href, requestURL.origin).toString()
              : undefined,
          })),
        }
      : undefined,
  }))

  useSeoMeta({
    title: () => props.title,
    description: () => props.subtitle || props.description,
    ogTitle: () => props.title,
    ogDescription: () => props.subtitle || props.description,
    ogUrl: () => canonicalUrl.value,
    ogType: () => (props.schemaType === 'Article' ? 'article' : 'website'),
    ogImage: () => props.ogImage,
  })

  useHead(() => ({
    link: [{ rel: 'canonical', href: canonicalUrl.value }],
    script: [
      {
        key: 'page-header-jsonld',
        type: 'application/ld+json',
        children: JSON.stringify(jsonLdSchema.value),
      },
    ],
  }))

  return { jsonLdSchema }
}

5.2 useHeaderA11y.ts

Gère l'accessibilité (ARIA, focus management, keyboard nav).

export function useHeaderA11y(props: {
  headingId?: string
  ariaLabel?: string
  ariaDescribedBy?: string
  ctaGroupLabel?: string
}) {
  const headingLabelId = computed(() => props.headingId ?? useId())

  const regionAttrs = computed(() => ({
    role: 'region' as const,
    'aria-labelledby': headingLabelId.value,
    'aria-describedby': props.ariaDescribedBy,
    'aria-label': props.ariaLabel,
  }))

  const ctaGroupAttrs = computed(() =>
    props.ctaGroupLabel
      ? {
          role: 'group' as const,
          'aria-label': props.ctaGroupLabel,
        }
      : {}
  )

  return { headingLabelId, regionAttrs, ctaGroupAttrs }
}

5.3 useHeaderLayout.ts

Calcule les classes CSS et styles dynamiques selon layout/variant.

export function useHeaderLayout(props: {
  variant: 'hero-fullscreen' | 'hero-standard' | 'section-header'
  layout: 'single-column' | '2-columns' | '3-columns'
  contentAlign: 'start' | 'center'
  container: 'fluid' | 'lg' | 'xl' | 'xxl'
  minHeight?: string | null
  paddingY?: string | null
}) {
  const containerClasses = computed(() => ({
    'page-header': true,
    [`page-header--${props.variant}`]: true,
    [`page-header--layout-${props.layout}`]: true,
    [`page-header--align-${props.contentAlign}`]: true,
  }))

  const containerStyles = computed(() => ({
    minHeight: props.minHeight || undefined,
    paddingBlock: props.paddingY || undefined,
  }))

  const vuetifyContainerProps = computed(() => ({
    fluid: props.container === 'fluid',
    'max-width': props.container !== 'fluid' ? props.container : undefined,
  }))

  return { containerClasses, containerStyles, vuetifyContainerProps }
}

✅ 6. Best Practices & Recommandations

6.1 Performance

  1. Images :

    • loading="eager" pour hero above-the-fold
    • fetchpriority="high" pour background hero
    • ✅ WebP avec fallback PNG/JPEG via <picture>
    • ✅ Responsive images avec srcset
    • ✅ Préload via useHead({ link: [{ rel: 'preload', as: 'image', href: ... }] })
  2. SVG Animations :

    • ✅ Fichiers externes (pas inline) pour cache
    • ✅ Animations CSS natives (pas de librairie lourde)
    • prefers-reduced-motion respecté
    • will-change uniquement sur propriétés animées
  3. Lazy Loading :

    • ✅ Composants non-critiques en <Suspense> ou defineAsyncComponent
    • ✅ ParallaxWidget chargé conditionnellement

6.2 SEO

  1. Structured Data :

    • ✅ JSON-LD pour WebPage, Article, BreadcrumbList
    • <h1> unique par page (dans le header)
    • ✅ Meta tags complets (title, description, og:*)
  2. Contenu :

    • ✅ Textes alt pour toutes les images (i18n)
    • ✅ Breadcrumbs avec schema.org
    • ✅ Canonical URL

6.3 Accessibilité (WCAG 2.1 AA)

  1. ARIA :

    • role="region" sur header avec aria-labelledby
    • role="group" sur CTA buttons avec aria-label
    • ✅ Boutons avec aria-label explicite
    • ✅ Images décoratives avec aria-hidden="true"
  2. Keyboard Navigation :

    • ✅ Focus visible (:focus-visible)
    • ✅ Skip links si nécessaire
    • ✅ Tab order logique
  3. Contraste :

    • ✅ Ratio 4.5:1 minimum pour texte normal
    • ✅ Ratio 3:1 minimum pour texte large (18pt+)

6.4 Responsive

  1. Breakpoints Vuetify :

    xs: 0-599px
    sm: 600-959px
    md: 960-1279px
    lg: 1280-1919px
    xl: 1920-2559px
    xxl: 2560px+
    
  2. Mobile-First :

    • ✅ Layout single-column par défaut sur mobile
    • 2-columns à partir de md (960px)
    • ✅ Hero fullscreen passe en min-height: 520px sur mobile
    • ✅ Padding réduit : clamp(2rem, 5vw, 4rem)1.5rem mobile

6.5 i18n

  1. Textes statiques :

    • ✅ Tous les labels, titres, CTA doivent être dans i18n
    • ✅ Textes alt images également i18n
    • ✅ Structured data labels i18n
  2. Direction RTL :

    • ❌ Pas de support RTL demandé (arabe, hébreu)
    • ✅ Si ajouté plus tard, utiliser dir="auto" et logical properties CSS (padding-inline, margin-block)

🎯 7. Challenge de l'Approche & Alternatives

7.1 Approche Proposée (Hybride)

✅ Avantages :

  • Flexibilité maximale (props + slots)
  • Réutilisabilité via composables
  • Maintenance centralisée
  • Évolutivité (nouveaux variants faciles)
  • Performance (pas de sur-engineering)

⚠️ Inconvénients :

  • Complexité initiale du composant
  • Risque de "God Component" si mal géré
  • Courbe d'apprentissage pour l'équipe

7.2 Alternative 1 : Composants Séparés (Status Quo)

Garder : OpensourceHero, TeamHero, PartnersHero, etc.

✅ Avantages :

  • Pas de migration nécessaire
  • Chaque composant simple et dédié
  • Pas de risque de régression

❌ Inconvénients :

  • Duplication de code (maintenabilité -)
  • Incohérence visuelle
  • Modifications nécessitent updates multiples
  • Pas de standardisation SEO/A11y

Verdict : ❌ Non recommandé


7.3 Alternative 2 : Librairie Externe (ex: Vuetify Page Header)

Utiliser : Librairie tierce ou starter template

✅ Avantages :

  • Pas de développement custom
  • Best practices incluses
  • Maintenance externe

❌ Inconvénients :

  • Rigidité (pas de contrôle total)
  • Dépendance externe
  • Customisation limitée (besoins spécifiques comme ParallaxWidget, HeroEducationCard)
  • Pas de solution Vuetify native pour ce besoin

Verdict : ❌ Non recommandé (besoins trop spécifiques)


7.4 Alternative 3 : Composition API + Headless Component

Approche : Composant sans style, logique uniquement, style via slots/composables

✅ Avantages :

  • Flexibilité ultime
  • Testabilité maximale
  • Pas de contrainte visuelle

❌ Inconvénients :

  • Sur-engineering pour ce besoin
  • Trop abstrait (développeurs doivent tout réimplémenter)
  • Pas de standardisation visuelle

Verdict : ❌ Trop complexe pour le besoin


7.5 Recommandation Finale

Approche Hybride (Proposition 4.1) est la meilleure solution :

  • Balance entre flexibilité et structure
  • Réutilise les patterns existants (HeroSurface, ParallaxWidget)
  • Standardise SEO/A11y/Performance
  • Migration progressive possible (coexistence ancien/nouveau)

📋 8. Plan de Migration

Phase 1 : Développement du Composant Base (2-3 jours)

  1. ✅ Créer PageHeader.vue avec props essentielles
  2. ✅ Développer composables (useHeaderSeo, useHeaderA11y, useHeaderLayout)
  3. ✅ Implémenter variante hero-standard (la plus courante)
  4. ✅ Tests unitaires (props, slots, emits)
  5. ✅ Documentation (Markdown dans /docs + exemples)

Phase 2 : Migration Page Pilote (1 jour)

  1. ✅ Choisir page pilote : /team (TeamHero → PageHeader simple)
  2. ✅ Remplacer TeamHero.vue par PageHeader avec props équivalentes
  3. ✅ Validation visuelle (comparaison avant/après)
  4. ✅ Tests accessibilité (axe-core, Lighthouse)
  5. ✅ Tests responsive (xs, sm, md, lg, xl)
  6. ✅ Review par l'équipe

Phase 3 : Migration Pages Internes (3-4 jours)

  1. /opensource → PageHeader variant="hero-standard"
  2. /partners → PageHeader variant="hero-standard"
  3. /impact-score → PageHeader variant="section-header"
  4. ✅ Autres pages similaires

Phase 4 : Homepage (Hero Fullscreen) (2 jours)

  1. ✅ Implémenter variante hero-fullscreen
  2. ✅ Intégrer ParallaxWidget si nécessaire (en parallèle)
  3. ✅ Migration de HomeHeroSection.vue
  4. ✅ Tests performance (Lighthouse, WebPageTest)

Phase 5 : Nettoyage & Documentation (1 jour)

  1. ✅ Supprimer anciens composants domain-specific (OpensourceHero, TeamHero, etc.)
  2. ✅ Mettre à jour AGENTS.md avec guidelines PageHeader
  3. ✅ Documentation complète dans /docs/page-header-guide.md
  4. ✅ Changelog

Total Estimation : 9-11 jours (1 développeur full-time)


📚 9. Documentation Technique

9.1 Fichiers à Créer

frontend/
├── app/components/shared/header/
│   ├── PageHeader.vue                      ← Composant principal
│   ├── PageHeader.spec.ts                  ← Tests unitaires
│   ├── composables/
│   │   ├── useHeaderLayout.ts
│   │   ├── useHeaderSeo.ts
│   │   ├── useHeaderA11y.ts
│   │   └── useHeaderAnimation.ts
│   └── types.ts                            ← Types TypeScript
├── docs/
│   ├── page-header-audit-report.md         ← Ce document
│   └── page-header-guide.md                ← Guide d'utilisation développeur
└── AGENTS.md (à mettre à jour)

9.2 Tests à Implémenter

// PageHeader.spec.ts
describe('PageHeader', () => {
  describe('Props', () => {
    it('renders hero-fullscreen variant correctly', () => { ... })
    it('renders hero-standard variant with 2-columns layout', () => { ... })
    it('renders section-header variant compactly', () => { ... })
    it('applies custom minHeight and paddingY', () => { ... })
  })

  describe('Slots', () => {
    it('renders default slot content', () => { ... })
    it('renders media slot in 2-columns layout', () => { ... })
    it('renders actions slot with CTA buttons', () => { ... })
  })

  describe('SEO', () => {
    it('generates JSON-LD structured data', () => { ... })
    it('sets correct meta tags', () => { ... })
    it('uses correct heading level (h1/h2/h3)', () => { ... })
  })

  describe('Accessibility', () => {
    it('has correct ARIA attributes', () => { ... })
    it('heading has unique ID', () => { ... })
    it('CTA group has aria-label', () => { ... })
  })

  describe('Responsive', () => {
    it('switches to single-column on mobile', () => { ... })
    it('applies correct breakpoints', () => { ... })
  })
})

🔥 10. Questions Ouvertes pour Clarification

10.1 Limite de poids pour assets header

Question : Quelle limite de poids acceptable pour les assets (SVG, images) dans un header ?

Proposition :

  • SVG : < 50 KB
  • Images WebP : < 300 KB
  • Background hero : < 500 KB
  • Total header (HTML+CSS+JS+assets) : < 1 MB

Action requise : Validation par l'équipe


10.2 Naming du composant

Question : Préférence pour le nom du composant ?

Options :

  1. PageHeader (recommandé - générique)
  2. SectionHeader (trop restrictif)
  3. HeroSection (confond hero et header)

Action requise : Décision finale


10.3 Variantes minimales

Question : Combien de variantes au minimum ?

Proposition :

  1. hero-fullscreen (homepage)
  2. hero-standard (pages internes)
  3. section-header (headers compacts dans pages)

Alternative : Réduire à 2 variantes (hero, section) et utiliser props pour différencier fullscreen/standard

Action requise : Validation


10.4 SVG Animations & VSvgIcon

Question : Faut-il intégrer avec VSvgIcon API ou wrapper custom ?

Proposition : Wrapper custom AnimatedSvg.vue qui :

  • Charge SVG externe
  • Applique animations CSS
  • Respecte prefers-reduced-motion
  • Props : src, alt, animate, interactivity

Action requise : Validation technique


🎨 11. Schémas Visuels des Variantes

Variante 1 : Hero Fullscreen

┌─────────────────────────────────────────────────────┐
│  [Background Image + Overlay Gradient]              │
│                                                      │
│              ┌──────────────────┐                   │
│              │   TITRE HERO     │                   │
│              │   Subtitle       │                   │
│              └──────────────────┘                   │
│                                                      │
│         [SearchSuggestField Component]              │
│         [NudgeToolWizard Component]                 │
│                                                      │
│           ┌─────────────────────┐                   │
│           │  Context Card       │                   │
│           │  - Helper 1         │                   │
│           │  - Helper 2         │                   │
│           └─────────────────────┘                   │
│                                                      │
│  min-height: 100dvh                                 │
└─────────────────────────────────────────────────────┘

Variante 2 : Hero Standard (2-columns)

┌─────────────────────────────────────────────────────┐
│  [HeroSurface variant="prism"]                      │
│  ┌─────────────────┬──────────────────┐            │
│  │  [EYEBROW]      │                  │            │
│  │  TITRE HERO     │   [HeroCard]     │            │
│  │  Subtitle       │   or Image       │            │
│  │                 │   or Glow        │            │
│  │  Description    │                  │            │
│  │  [CTA Button]   │                  │            │
│  └─────────────────┴──────────────────┘            │
│  padding-y: clamp(3rem, 8vw, 5.5rem)               │
└─────────────────────────────────────────────────────┘

Variante 3 : Section Header (Compact)

┌─────────────────────────────────────────────────────┐
│  [Gradient Background]                              │
│  ┌─────────────────────────────┐                   │
│  │         TITRE SECTION        │                   │
│  │      Description courte      │                   │
│  └─────────────────────────────┘                   │
│  padding-y: clamp(2rem, 5vw, 3.5rem)               │
└─────────────────────────────────────────────────────┘

✅ Conclusion & Prochaines Étapes

Synthèse

L'audit révèle une opportunité d'unification significative avec un potentiel de :

  • Réduction de 70% du code dédié aux headers
  • Standardisation SEO/A11y complète
  • Amélioration maintenabilité (+80%)
  • Cohérence visuelle harmonisée

Prochaines Étapes Immédiates

  1. Validation de l'approche par l'équipe (30 min)
  2. Réponses aux questions ouvertes (section 10)
  3. Go/No-Go pour la Phase 1 (développement composant base)

Contact & Feedback

Pour toute question ou clarification sur ce rapport :

  • Reviewer le code existant ensemble
  • Prototyper un POC sur une page pilote
  • Discuter des trade-offs architecture

Fin du Rapport d'Audit | Date : 2025-12-22