# Composants Blade partages

Ce document decrit les composants Blade communs a plusieurs pages du site :
`<x-header>`, `<x-footer>`, `<x-sticky-actions>`, `<x-property-card>`,
`<x-modale-lead>` et `<x-formulaire-terrain>`. Ils vivent dans
`resources/views/components/` et sont des portages fideles de composants de
la maquette Claude Design (`maquettes/*.dc.html`).

Chaque page publique utilise le layout `resources/views/layouts/site.blade.php`,
qui insere automatiquement le header et le footer. Le sticky-actions, lui,
est ajoute (ou non) directement dans la vue de chaque page — voir le tableau
en fin de document.

## Header — `<x-header>`

- Fichier : `resources/views/components/header.blade.php`
- Origine maquette : `maquettes/Header.dc.html`
- Insere automatiquement par le layout (`resources/views/layouts/site.blade.php`),
  pas besoin de l'ajouter dans chaque page.

### Props

| Prop | Type | Defaut | Description |
|---|---|---|---|
| `active` | string | `''` | Cle de l'onglet actif dans la navigation |

Valeurs possibles pour `active` (reprises de la logique `DCLogic` de la
maquette) : `''`, `acheter`, `investir`, `terrain`, `about`, `actus`,
`contact`, `emplois`, `partenaires`.

### Structure

Deux zones de navigation :
- **Nav du haut** (`topItems`) : Contact, Emplois, Notre entreprise,
  Partenaires & Sous-traitants — liens texte simples.
- **Nav principale** (`blockDefs`) : ACHETER et VENDRE MON TERRAIN, avec le
  logo Biome au centre.

### Exemple d'usage

Dans une vue de page (via la variable `$active` passee au layout) :

```blade
{{-- resources/views/pages/acheter.blade.php --}}
@extends('layouts.site')
@php($active = 'acheter')
```

Les etats hover/actif sont geres par les classes `.biome-header__top-link` et
`.biome-header__block`, definies dans `public/css/site.css`.

### Mobile/tablette (<=1024px, lot mobile M1)

Sous 1024px, la nav du haut et la nav principale sont masquees
(`.biome-masquer-tablette-mobile`) et remplacees par un bouton burger
(`[data-ouvre-menu-burger]`) qui ouvre un tiroir plein ecran
(`#biome-tiroir-menu` / `[data-tiroir-menu-burger]`), listant les memes
routes que le header desktop dans l'ordre : ACHETER, VENDRE MON TERRAIN
(mis en avant), puis Contact, Emplois, Notre entreprise, Partenaires &
Sous-traitants. Interactivite geree par `initMenuBurger()` dans
`public/js/site.js` (voir plus bas). Desktop strictement inchange. Detail
complet (classes, z-index) : `docs/mobile-strategie.md` section 11.

## Footer — `<x-footer>`

- Fichier : `resources/views/components/footer.blade.php`
- Origine maquette : `maquettes/Footer.dc.html`
- Insere automatiquement par le layout, pas de props.

### Contenu

Le footer est fixe (pas de props) : quatre colonnes de liens (Acheter,
Vendre mon terrain, L'entreprise, Contact) plus une colonne de marque
(logo, description, reseaux sociaux) et une bande de bas de page (copyright,
mentions legales).

Les coordonnees affichees (telephone, email, adresse, reseaux sociaux) ne
sont **pas** codees en dur dans le composant : elles viennent de
`config/site.php`, pour eviter de dupliquer ces valeurs dans plusieurs vues.

```php
// config/site.php
return [
    'telephone' => '+32 (0)61 86 09 15',
    'telephone_lien' => '+3261860915', // format E164 pour les liens tel:
    'email' => 'info@biome.immo',
    'adresse' => ['ligne1' => 'Gribelle 2D', 'ligne2' => 'B-5575 Gedinne'],
    'reseaux' => ['facebook' => '#', 'instagram' => '#', 'linkedin' => '#'],
    'copyright_annee' => date('Y'),
];
```

Pour changer une coordonnee affichee sur le site (telephone, email, adresse),
il suffit de modifier `config/site.php` — aucune vue a toucher.

Les etats hover sont geres par les classes `.biome-footer__social`,
`.biome-footer__link`, `.biome-footer__link-strong` et
`.biome-footer__bottom-link` dans `public/css/site.css`.

## Sticky Actions — `<x-sticky-actions>`

- Fichier : `resources/views/components/sticky-actions.blade.php`
- Origine maquette : `maquettes/StickyActions.dc.html`
- **Pas** insere automatiquement par le layout : chaque page l'ajoute (ou non)
  explicitement dans sa vue, comme dans la maquette d'origine.

Barre d'actions flottante, ancree a droite de l'ecran, position fixe.

### Props

| Prop | Type | Defaut | Effet |
|---|---|---|---|
| `emplois` | bool | `false` | Variante "offre d'emploi" : blocs "Voir les offres" / "Postuler spontanement". Exclusif avec `partner` et la variante standard. |
| `partner` | bool | `false` | Variante "partenaire" : blocs "Devenir partenaire" / "Etre contacte". Exclusif avec `emplois` et la variante standard. |
| `callback` | bool | `false` | Variante standard uniquement : remplace le bloc telephone par "Etre rappele" (ancre `#rappel`). |
| `hide-biens` | bool | `false` | Variante standard uniquement : masque le bloc "Decouvrez nos biens". |
| `hide-terrain` | bool | `false` | Variante standard uniquement : masque le bloc "Estimez votre terrain". |

Regle de la maquette portee telle quelle : la variante standard s'affiche
uniquement si `emplois` et `partner` sont tous les deux `false`
(`showStandard = !emplois && !partner`).

### Blocs de la variante standard (dans l'ordre d'affichage)

1. Telephone (`tel:...`), masque si `callback` est actif.
2. "Etre rappele" (ancre `#rappel`), affiche seulement si `callback` est actif.
3. "Nous contacter" (route `contact`) — toujours present en variante standard.
4. "Decouvrez nos biens" (route `acheter`), masque si `hide-biens` est actif.
5. "Estimez votre terrain" (route `vendre-mon-terrain`), masque si
   `hide-terrain` est actif.

### Note sur les actions "modales" de la maquette

Dans la maquette, les actions `on-apply`, `on-partner`, `on-contact` et
`on-rappel` ouvraient des modales JavaScript cote client (logique `DCLogic`).
Depuis l'ajout de `<x-modale-lead>` (voir plus bas), les blocs "Etre rappele"
(`callback`) et "Etre contacte" (`partner`) ouvrent desormais la modale
generique `<x-modale-lead type="rappel">` via `data-ouvre-modale="rappel"`
(geree par `initModalesLead()` dans `site.js`), a condition que cette modale
soit bien presente sur la page consommatrice. Les blocs "Postuler
spontanement" (`emplois`) et "Devenir partenaire" (`partner`) continuent de
pointer vers une ancre de formulaire sur la page courante (`#postuler`,
`#part-form`), ces formulaires n'etant pas geres en modale.

### Tableau des variantes par page

Releve par page dans les vues `resources/views/pages/*.blade.php` (attribut
`dc-import name="StickyActions"` dans la maquette d'origine) :

| Page | Route | Props utilisees | Variante resultante |
|---|---|---|---|
| Accueil | `accueil` | aucune | Standard complet (telephone + biens + terrain) |
| Actualites | `actualites` | aucune | Standard complet |
| Investir | `investir` | aucune | Standard complet |
| Qui-sommes-nous | `qui-sommes-nous` | aucune | Standard complet |
| Acheter | `acheter` | `callback` `hide-biens` | Standard : rappel (ouvre `<x-modale-lead type="rappel">`) + terrain (sans telephone ni biens) |
| Vendre mon terrain | `vendre-mon-terrain` | `hide-terrain` | Standard : telephone + biens (sans terrain) |
| Contact | `contact` | `callback` | Standard : rappel (ouvre `<x-modale-lead type="rappel">`) + biens + terrain (sans telephone) |
| Offre (detail) | `offre.show` | `emplois` | Variante emplois |
| Offres (liste) | `offres` | `emplois` | Variante emplois |
| Recrutement | `recrutement` | `emplois` | Variante emplois |
| Partenaires | `partenaires` | `partner` | Variante partenaire : "Devenir partenaire" (ancre `#part-form`) + "Etre contacte" (ouvre `<x-modale-lead type="rappel">` via `data-ouvre-modale="rappel"`, Lot 7a) |
| Bien (detail) | `bien.show` | — | **Pas de sticky-actions** (absent de `Bien.dc.html` dans la maquette, comportement volontairement conserve) |

### Exemple d'usage

```blade
{{-- variante standard, sans le bloc "biens" --}}
<x-sticky-actions callback hide-biens />

{{-- variante emplois --}}
<x-sticky-actions emplois />
```

Les etats hover sont geres par les classes `.biome-sticky__item--fonce`,
`.biome-sticky__item--principal` et `.biome-sticky__item--clair` dans
`public/css/site.css`.

### Mobile/tablette (<=1024px, lot mobile M1)

Sous 1024px, le composant devient une barre horizontale fixe en bas de
l'ecran (`.biome-sticky-actions`), limitee a 2 actions par variante (les
autres blocs restent dans le DOM, masques via
`.biome-masquer-tablette-mobile`). Desktop strictement inchange. Detail
complet (tableau des 2 actions retenues par variante, z-index, padding-bottom
du body) : `docs/mobile-strategie.md` section 11.

## PropertyCard — `<x-property-card>`

- Fichier : `resources/views/components/property-card.blade.php`
- Origine maquette : `maquettes/PropertyCard.dc.html`
- Utilise sur la page **Acheter** (grille de resultats + vue carte) et sur la
  page **Bien** (biens similaires). Prevue pour etre reutilisee sur
  **l'Accueil** au lot 3 (section biens en vedette) : le composant est deja
  generique et ne depend d'aucune donnee propre a la page Acheter.

### Props

| Prop | Type | Description |
|---|---|---|
| `bien` | array | Un bien tel que renvoye par `CrmClientInterface::biens()` / `::bien()`. Champs utilises : `id`, `localite`, `lot`, `prix`, `surface_habitable`, `chambres`, `terrain`, `photo_principale`. |

### Contenu et comportements

- Photo principale (ou placeholder neutre si absente), badge/bouton
  **favori** (coeur), titre (`{localite} LOT {lot}`), prix, trois
  caracteristiques (surface habitable, chambres, terrain), puis deux actions :
  lien "Voir le bien" (`route('bien.show')`) et bouton "Planifier" qui ouvre
  la modale de demande de visite du bien concerne (`data-ouvre-modale="visite"`,
  voir `<x-modale-lead>` ci-dessous).
- Le bouton favori est purement cote client : voir "Favoris (localStorage)"
  dans la section JavaScript plus bas. Aucune donnee de favori n'est envoyee
  ni stockee cote serveur.

### Exemple d'usage

```blade
@foreach ($biens as $bien)
    <x-property-card :bien="$bien" />
@endforeach
```

## ModaleLead — `<x-modale-lead>`

- Fichier : `resources/views/components/modale-lead.blade.php`
- Origine maquette : blocs de modale de `maquettes/Acheter.dc.html` (rappel,
  planifier une visite) et `maquettes/Bien.dc.html` (visite, rappel, infos,
  brochure, prix).
- Composant generique : une seule vue Blade couvre les **5 types de
  demande** possibles sur le site, avec des champs et un texte adaptes selon
  le type.

### Props

| Prop | Type | Defaut | Description |
|---|---|---|---|
| `type` | string | requis | `visite` \| `rappel` \| `infos` \| `brochure` \| `prix`. Determine le titre, les champs affiches et l'endpoint de soumission. |
| `bien-id` | string\|null | `null` | Identifiant CRM pre-rempli dans le champ cache `bien_id`. `null` pour un "rappel" generique hors page Bien (ex. page Acheter). |
| `bien-titre` | string\|null | `null` | Libelle du bien affiche (ex. champ lecture seule "Bien concerne" pour `visite`). |
| `avec-souhaits` | bool | `false` | Ajoute les cases a cocher "Je souhaite..." (brochure/visite/rappel), utilisees uniquement par le type `infos` dans `Bien.dc.html`. |

### Endpoints selon le type

| `type` | Route de soumission | Formulaire de validation |
|---|---|---|
| `visite` | `route('leads.visite.store')` | `StoreDemandeVisiteRequest` |
| `rappel`, `infos`, `brochure`, `prix` | `route('leads.info-bien.store')` | `StoreDemandeInfoBienRequest` (champ `type` transmis en `hidden`) |

Voir `docs/architecture-donnees.md` section 2 ter pour le detail des tables
(`demandes_visite`, `demandes_info_bien`) et des ressources Filament de
consultation associees.

### Cas particulier du type `prix`

L'ecran de confirmation du type `prix` propose un bouton "Telecharger le
detail du prix (PDF)" **desactive**, avec un `title` explicatif. La route
cible (`bien.pdf-prix`, `BienController::pdfPrix()`) est un stub qui repond
HTTP 501 : la decision de qui genere ce PDF (le site ou le CRM) n'est pas
encore prise, voir `docs/architecture-donnees.md` section 3. Aucun lien n'est
pose sur le bouton tant que ce point n'est pas tranche.

### Plusieurs modales sur une meme page

Sur la page Bien, les 5 types coexistent ; sur la page Acheter, une modale
`visite` est generee par bien affiche (`data-bien-id` distinct) plus une
modale `rappel` generique. L'ouverture se fait via tout element portant
`data-ouvre-modale="{type}"` (et optionnellement `data-bien-id` /
`data-bien-titre`, ex. le bouton "Planifier" d'une `<x-property-card>`) —
voir "Modales de demande" dans la section JavaScript plus bas.

### Exemple d'usage

```blade
{{-- Modale generique, sans bien (page Acheter) --}}
<x-modale-lead type="rappel" />

{{-- Modale liee a un bien precis --}}
<x-modale-lead type="visite" :bien-id="$bien['id']" :bien-titre="$bien['localite'].' LOT '.$bien['lot']" />

{{-- Les 5 modales de la page Bien --}}
<x-modale-lead type="visite" :bien-id="$bien['id']" :bien-titre="$titre" />
<x-modale-lead type="rappel" :bien-id="$bien['id']" :bien-titre="$titre" />
<x-modale-lead type="infos" :bien-id="$bien['id']" :bien-titre="$titre" />
<x-modale-lead type="brochure" :bien-id="$bien['id']" :bien-titre="$titre" />
<x-modale-lead type="prix" :bien-id="$bien['id']" :bien-titre="$titre" />
```

## FormulaireTerrain — `<x-formulaire-terrain>`

- Fichier : `resources/views/components/formulaire-terrain.blade.php`
- Origine maquette : modale "PROPOSER MON TERRAIN" et section
  `#form-terrain` de `maquettes/Vendre-mon-terrain.dc.html`, branche
  "terrain" et modale "openTerrain" de `maquettes/Contact.dc.html`.
- Composant generique : une seule vue Blade couvre le formulaire "vendre
  mon terrain" en **2 etapes**, reutilise a l'identique (memes champs) a
  **4 endroits** du site, seul l'habillage visuel change. Toutes les
  soumissions POST vers `route('leads.terrain.store')` (`StoreLeadTerrainRequest`,
  voir `docs/architecture-donnees.md` section 2 ter pour le detail table/
  ressource admin).

### Props

| Prop | Type | Defaut | Description |
|---|---|---|---|
| `origine` | string | requis | `contact_section` \| `contact_modale` \| `terrain_section` \| `terrain_modale`. Pose le champ cache `origine` du formulaire et sert a identifier l'instance (reouverture apres erreur, confirmation apres succes). |
| `variante` | string | `modale` | `modale` \| `section-claire` \| `section-sombre`. Pilote uniquement l'habillage visuel (couleurs de champs, presence d'un titre, largeur), jamais la logique. |
| `id-instance` | string | valeur de `origine` | Suffixe d'id HTML pour distinguer plusieurs instances du composant sur une meme page (ex. page Vendre-mon-terrain qui a a la fois la modale et la section). |
| `succes-par-defaut` | bool | `false` | Repli de robustesse : affiche la confirmation si `session('succes')` est present mais `session('succes_origine')` absent (voir plus bas). Utilise sur les 2 instances "section" ; inutile sur les modales (qui n'ont pas vocation a s'ouvrir par defaut). |

### Les 4 instances

| `origine` | Page | Variante | Declenchee par |
|---|---|---|---|
| `terrain_modale` | `/vendre-mon-terrain` | `modale` | Tous les CTA "PROPOSER MON TERRAIN" / "RECEVOIR UNE ANALYSE GRATUITE" de la page (`data-ouvre-modale="terrain-terrain_modale"`) |
| `terrain_section` | `/vendre-mon-terrain` | `section-sombre` | Section `#form-terrain`, toujours visible (fond bleu fonce) |
| `contact_modale` | `/contact` | `modale` | CTA "Vendre mon terrain" du hero et bloc acces rapide "Je souhaite vendre un terrain" (`data-ouvre-modale="terrain-contact_modale"`) |
| `contact_section` | `/contact` | `section-claire` | Branche "terrain" du formulaire adaptatif (bascule via `initBrancheFormulaireContact`, voir plus bas) |

Les 3 variantes visuelles reprennent fidelement les jeux de styles de la
maquette : champs blancs a bordure grise en `modale`/`section-claire`,
champs "flat" sans bordure sur fond bleu fonce en `section-sombre`. Le
libelle "Etape X sur 2" et sa barre de progression ne s'affichent qu'en
`modale` et `section-sombre` (la variante `section-claire`, integree au
formulaire Contact, affiche a la place un texte d'introduction par etape).

### Reouverture apres erreur / confirmation apres succes

Le meme mecanisme que `<x-modale-lead>` (comparaison a `old('origine')`),
complete par un mecanisme de succes propre a ce composant :

- **Erreur de validation** : `$origineCorrespond = old('origine') === $origine`
  determine si cette instance precise doit afficher les messages d'erreur et
  se rouvrir (modale) sur la bonne etape (`data-etape-erreur`, 1 ou 2 selon
  les champs en erreur).
- **Succes (`session('succes_origine')`)** : `LeadTerrainController::store()`
  flashe `succes_origine` = valeur exacte de `origine` du lead cree, en plus
  du message `succes`. Chaque instance compare
  `session('succes_origine') === $origine` : seule l'instance qui a
  effectivement soumis affiche l'ecran de confirmation ("Demande envoyee !")
  — les 3 autres instances de la page restent inchangees. Sans
  `session('succes_origine')` (ancienne session), repli sur la prop
  `succesParDefaut`. Voir `docs/architecture-donnees.md` section 2 ter pour
  le detail complet du mecanisme et son usage sur la page Contact
  (determine aussi quelle branche du formulaire adaptatif rouvrir).

### Exemple d'usage

```blade
{{-- Page Vendre mon terrain : modale + section --}}
<x-formulaire-terrain origine="terrain_modale" variante="modale" id-instance="terrain_modale" />
...
<x-formulaire-terrain origine="terrain_section" variante="section-sombre" id-instance="terrain_section" succes-par-defaut />

{{-- Page Contact : modale + branche section --}}
<x-formulaire-terrain origine="contact_modale" variante="modale" id-instance="contact_modale" />
...
<x-formulaire-terrain origine="contact_section" variante="section-claire" id-instance="contact_section" succes-par-defaut />
```

## JavaScript partage — `public/js/site.js`

Fichier unique, JS vanilla sans etape de build (charge en `<script defer>`
depuis `resources/views/layouts/site.blade.php`). Chaque comportement est
initialise dans une fonction dediee, appelee au `DOMContentLoaded` ; les
fonctions ne font rien si les elements cibles sont absents de la page (donc
sans risque a charger sur toutes les pages du site).

| Fonction | Page(s) concernee(s) | Role |
|---|---|---|
| `initModaleCandidature()` | Offre, Offres, Recrutement | Modale de candidature en 2 etapes (voir `components/modale-candidature.blade.php`). |
| `initAccordeonFaq()` | Offre (detail) | Accordeon FAQ. |
| `initModalesLead()` | Acheter, Bien, Contact, Vendre mon terrain | Ouverture/fermeture des `<x-modale-lead>` **et** des instances `modale` de `<x-formulaire-terrain>` (meme attribut `[data-modale-lead]`), pre-remplissage `bien_id`/`bien_titre`, une seule modale ouverte a la fois, reouverture apres erreur de validation. |
| `initFormulairesTerrainEtapes()` | Contact, Vendre mon terrain | Navigation etape 1/2 de chaque instance de `<x-formulaire-terrain>`, independamment des autres instances presentes sur la page. |
| `initBrancheFormulaireContact()` | Contact | Bascule entre les 3 branches du formulaire adaptatif (maison / terrain / question). |
| `initFavoris()` | Toute page avec `<x-property-card>` | Bouton favori (coeur), persistance **localStorage uniquement**. |
| `initCurseurBudget()` | Acheter | Curseur "Budget max." de la barre de filtres. |
| `initCarteProvinces()` | Acheter | Carte SVG des 5 provinces wallonnes cliquable. |
| `initBasculeVue()` | Acheter | Bascule entre la vue liste et la vue carte des resultats. |
| `initGalerieBien()` | Bien | Lightbox plein ecran de la galerie photo. |
| `initDescriptionExtensible()` | Bien | Bouton "Lire la description complete". |
| `initMenuBurger()` | Toutes (header commun) | Ouverture/fermeture du tiroir de menu mobile/tablette (`components/header.blade.php`, lot mobile M1). |

### Etapes du formulaire terrain (`initFormulairesTerrainEtapes`)

Cible chaque `[data-formulaire-terrain-etapes]` (une par instance de
`<x-formulaire-terrain>` presente sur la page). Pour chaque instance,
gere independamment :
- le passage etape 1 -> 2 (`[data-etape-suivante]`), avec validation
  native HTML des champs de l'etape 1 avant de continuer ;
- le retour etape 2 -> 1 (`[data-etape-precedente]`) ;
- l'etape affichee au chargement, pilotee par `data-etape-erreur` (pose
  cote serveur sur le conteneur de l'instance concernee par une erreur de
  validation — voir `<x-formulaire-terrain>` plus haut).

Si l'instance est une modale, son ouverture/fermeture est geree a part par
`initModalesLead()` (meme attribut `[data-modale-lead]` que
`<x-modale-lead>`) : cette fonction ne s'occupe que de la navigation
interne entre les 2 etapes, pas de l'affichage de la modale elle-meme.

### Bascule de branche du formulaire Contact (`initBrancheFormulaireContact`)

Cible les 3 boutons `[data-besoin-contact="maison|terrain|question"]` de
`pages/contact.blade.php`. Les 3 jeux de champs (branche standard
maison/question + branche terrain, cette derniere etant l'instance
`contact_section` de `<x-formulaire-terrain>`) restent tous dans le DOM ;
seule leur visibilite change au clic (`[data-branche-standard]` /
`[data-branche-terrain]`), ainsi que les sous-champs propres a "maison"
(`[data-champ-maison]`) ou "question" (`[data-champ-question]`) a
l'interieur de la branche standard.

L'etat initial (quelle branche est active au chargement) vient de
l'attribut `data-besoin-initial`, pose cote serveur par
`pages/contact.blade.php` selon la priorite : `session('succes_origine')`
(apres un succes) > `old('origine')` / `old('type_besoin')` (apres une
erreur) > `'maison'` par defaut — voir `docs/architecture-donnees.md`
section 2 ter pour le detail du mecanisme `succes_origine`.

### Favoris (`initFavoris`)

- Cle `localStorage` : `biome.favoris`, tableau JSON d'identifiants de biens
  (`["ach-1", "ach-3", ...]`).
- **Aucune donnee envoyee au serveur** : conforme a la maquette d'origine (le
  "favs" de `PropertyCard.dc.html` vivait uniquement dans un state client) et
  au RGPD (rien a nettoyer/purger cote base, rien a exposer dans l'admin).
- Un evenement custom `biome:favoris-modifies` est emis a chaque changement
  (`detail.favoris` = tableau a jour), pour permettre a une future page
  "Mes favoris" de reagir sans devoir relire le DOM.

### Curseur budget (`initCurseurBudget`)

Cible `[data-curseur-budget]` (l'input range du filtre "Budget max.") et
`[data-budget-affichage]` (le libelle affiche a cote). Formate la valeur en
euros avec separateur de milliers ; au-dela du plafond (1.000.000), affiche
"1.000.000 € et +" — meme convention que `AcheterController::BUDGET_SANS_PLAFOND`.
Purement visuel : la vraie valeur est soumise au rechargement du formulaire
(pas d'AJAX).

### Carte des provinces (`initCarteProvinces`)

Cible `[data-toggle-carte-provinces]` (bouton d'ouverture/fermeture du
panneau) et `[data-panneau-carte-provinces]`. Chaque forme SVG
(`[data-province-forme]`) et chaque etiquette-commune
(`[data-province-bouton]`) piloce la meme case a cocher reelle
(`[data-province-case]`, `name="provinces[]"`), pour rester synchronisees ;
au clic, le formulaire de filtres est resoumis immediatement
(`form.requestSubmit()`) — rechargement serveur, pas d'AJAX.

### Bascule vue (`initBasculeVue`)

Cible les boutons `[data-vue-liste]` / `[data-vue-carte]` et les panneaux
`[data-panneau-vue-liste]` / `[data-panneau-vue-carte]` : simple
affichage/masquage cote client, aucun rechargement de donnees (les deux
panneaux sont deja rendus par le serveur).

### Lightbox (`initGalerieBien`)

Cible `[data-galerie-item]` (photo principale + vignettes de
`pages/bien.blade.php`) et l'overlay `[data-lightbox]`. Navigation au clic,
au clavier (fleches gauche/droite, Echap) et par les boutons precedent/suivant.

### Menu burger (`initMenuBurger`)

Cible `[data-ouvre-menu-burger]` (bouton du header, visible sous 1024px via
`.biome-visible-mobile`) et `[data-tiroir-menu-burger]` (le tiroir plein
ecran, `#biome-tiroir-menu`). A l'ouverture : bloque le scroll du body
(`document.body.style.overflow = 'hidden'`), pose `aria-expanded="true"` sur
le bouton. Fermeture : sur la croix (`[data-ferme-menu-burger]`), sur un
clic sur n'importe quel lien du tiroir, ou la touche Echap ; restaure le
scroll, `aria-expanded="false"`, et rend le focus au bouton burger
(accessibilite clavier). Ne fait rien si le header n'est pas present sur la
page (jamais le cas, le header est insere par le layout, mais la fonction
suit la meme convention defensive que les autres de ce fichier).

## Carrousel Realisations (page Qui-sommes-nous)

Pas un composant Blade partage (pas de `<x-...>`) : bloc inline dans
`resources/views/pages/qui-sommes-nous.blade.php` uniquement, portage fidele
du bloc "REALISATIONS" de `maquettes/Qui-sommes-nous.dc.html`. **CSS pur,
aucun JavaScript** : le defilement en boucle continue vient d'une animation
`@keyframes biomeScroll` (`translateX(0)` -> `translateX(-50%)`) appliquee a
un conteneur flex deux fois plus large que la zone visible. Pour obtenir
cette largeur double sans a-coup a la reprise de boucle, la collection
`$realisations` (venant de `Realisation::query()->ordonnees()->get()`, voir
`docs/architecture-donnees.md`) est simplement dupliquee cote vue
(`$realisations->concat($realisations)`) avant d'etre parcourue en
`@foreach` — meme technique que la maquette source (tableau JS concatene
avec lui-meme).

## Voir aussi

- `docs/design-tokens.md` — tokens CSS utilises par ces composants.
- `docs/architecture-donnees.md` — mapping section maquette / source de donnees,
  et contrat `CrmClientInterface` (dont le decorateur de cache `CrmClientEnCache`).
- `README.md` section "Ou se trouve quoi" et liste des routes publiques.
