{{-- Rendu public generique d'un formulaire personnalise multi-etapes (voir App\Models\Form et App\Http\Controllers\FormulairePublicController). REFONTE (Lot 1) : reproduction A L'IDENTIQUE de la maquette de reference fournie par le client (carte blanche centree sur fond degrade bleu, fichier Biome_Formulaire_Qualification_V2.html), mais rendue en GENERIQUE : cette vue s'appuie uniquement sur schema_json / settings_json, jamais sur des champs codes en dur. Palette et espacements exacts de la maquette, voir variables --fp-* posees sur .formulaire-public__carte ci-dessous (vars LOCALES a cette page, ne touchent pas public/css/tokens.css). Deviations documentees par rapport a la maquette source (voir contrat de reproduction transmis par le chef de projet) : - Police Segoe UI -> Poppins/Mulish (Google Fonts, coherent avec le reste du site) ; icones Tabler (CDN) -> SVG inline (aucune dependance CDN, contrainte CLAUDE.md/hebergement OVH). - Icones decoratives specifiques a la maquette (whatsapp/messenger/phone colores, badge "Verifie BCE") non reproduites : elles n'ont pas de sens pour un formulaire generique piloté par schema_json. - L'asterisque "*" des champs obligatoires n'est PAS colore en orange (mockup : .req) : il est genere par les 16 partiels resources/views/formulaires/champs/*.blade.php en texte brut concatene au label, sans span dedie. Le contrat de cette refonte interdit de modifier ces partiels (marquage partage avec le moteur conditionnel JS et le controleur) : l'asterisque reste donc dans la couleur du label. A valider avec le chef de projet si un span dedie doit etre ajoute dans un lot ulterieur. - .formulaire-public__carte-choix (partage par radio-cartes.blade.php ET checkbox-groupe.blade.php) est recolore a la charte du mockup, mais garde une mise en page en grille (pas de disposition empilee pleine largeur façon .radio-group) pour les cases a cocher multiples : seule la variante radio (un seul input[type=radio] par groupe) repasse en colonne pleine largeur façon maquette, via un selecteur CSS :has() cible sur le type d'input (voir plus bas). Choix documente ici plutot qu'improvise silencieusement. CONTRAT DE NOMMAGE (voir FormulairePublicController) — NE PAS CASSER : - reponses[] (scalaire) / reponses[][] (choix multiple) - fichiers[][] (toujours en tableau, y compris mono-fichier) - fichiers[upload_final][] (upload final) - utm[source|medium|campaign|content|term] (caches) - site_web (honeypot, cache, laisse strictement tel quel) Attributs JS de navigation (voir public/js/formulaires-public.js) — NE PAS RENOMMER : data-formulaire-public, data-formulaire-etapes, data-etape="N" (1-based dans le DOM), data-etape-suivante, data-etape-precedente, data-libelle-etape, data-barre-progression, data-rouvrir, data-etape-erreur (0-based, pose par le controleur). Nouveaux attributs (Lot 1) pour la progression visuelle façon maquette, lus par formulaires-public.js::afficherEtape() : data-prog-label (un par libelle d'etape, dans l'ordre) et data-prog-dot (un par pastille, meme ordre) — voir public/js/formulaires-public.js. Variables recues du controleur : $formulaire (App\Models\Form), $schema (array, deja resolu : version courante ou brouillon en apercu), $apercu (bool), $valeursUtm (array). --}} @extends('layouts.formulaire') @section('titre', ($formulaire->title ?? 'Formulaire').' - Biome Constructions') @php $etapes = (array) ($schema['etapes'] ?? []); $settings = (array) ($formulaire->settings_json ?? []); $uploadFinal = (array) ($settings['upload_final'] ?? []); $confirmation = (array) ($settings['confirmation'] ?? []); $etapeErreur = (int) session('etape_erreur', 0); $doitRouvrir = $errors->any() ? '1' : '0'; $totalEtapes = count($etapes) + ((bool) ($uploadFinal['actif'] ?? false) ? 1 : 0); $afficheConfirmation = (bool) session('succes'); // Logique conditionnelle (Phase 2) : seuls les champs portant AU MOINS // UNE condition sont transmis au moteur JS (voir // public/js/formulaires-public.js et App\Services\Formulaires\ // EvaluateurConditions pour la semantique complete, partagee avec le // serveur). Un champ absent de cette carte reste toujours visible. $conditionsParChamp = []; foreach ($etapes as $etapeConditions) { foreach ((array) ($etapeConditions['champs'] ?? []) as $champConditions) { $conditions = (array) ($champConditions['conditions'] ?? []); if (empty($conditions) || empty($champConditions['cle'])) { continue; } $conditionsParChamp[$champConditions['cle']] = [ 'obligatoireBase' => (bool) ($champConditions['obligatoire'] ?? false), 'conditions' => array_map(static fn (array $condition): array => [ 'champ' => $condition['champ'] ?? null, 'operateur' => $condition['operateur'] ?? 'egal', 'valeur' => $condition['valeur'] ?? null, 'action' => $condition['action'] ?? null, ], $conditions), ]; } } // Image d'en-tete optionnelle (Option A, alimentee par un lot ulterieur // du builder) : affichee au-dessus du bandeau degrade si renseignee. $entete = (array) ($settings['entete'] ?? []); $imageEntete = ! empty($entete['image']) ? \Illuminate\Support\Facades\Storage::disk('public')->url($entete['image']) : null; // Libelles de progression : un par etape du schema, plus l'etape // d'upload final optionnelle si active (contrainte : le nombre // d'etapes/dots/libelles doit inclure cette derniere etape). $libellesProgression = array_map( static fn (array $etape): string => (string) ($etape['titre'] ?? ''), $etapes ); if ($uploadFinal['actif'] ?? false) { $libellesProgression[] = (string) ($uploadFinal['label'] ?? 'Photos et documents'); } @endphp @push('styles') @endpush @section('contenu')
@if ($apercu)
Mode apercu{{ $formulaire->status !== 'published' ? ' — ce formulaire n\'est pas encore publie, la soumission est desactivee.' : ' — vous visualisez le brouillon de travail.' }}
@endif
@if ($imageEntete) @endif
Biome Constructions
Bâtir l'avenir en Wallonie

{{ $formulaire->title }}

@if ($formulaire->description)

{{ $formulaire->description }}

@endif
Etape {{ $afficheConfirmation ? $totalEtapes : 1 }} sur {{ $totalEtapes }} @if (count($libellesProgression) > 0)
@foreach ($libellesProgression as $index => $libelle) {{ $libelle }} @endforeach
@endif
@if (count($libellesProgression) > 0)
@foreach ($libellesProgression as $index => $libelle) @endforeach
@endif @if ($afficheConfirmation)

{{ $confirmation['titre'] ?? 'Merci !' }}

{{ session('succes') }}

Retour a l'accueil
@else
@csrf {{-- Configuration de la logique conditionnelle (voir public/js/formulaires-public.js), transmise au moteur JS. Ne contient QUE les champs portant des conditions. --}} {{-- Honeypot : champ invisible pour un humain, piege a robots (voir contrat de nommage dans FormulairePublicController). --}} {{-- Parametres UTM captes a l'affichage, retransmis a la soumission. --}} @foreach ($etapes as $indexEtape => $etape)
0) style="display:none;" @endif>

{{ $etape['titre'] ?? '' }}

@foreach ((array) ($etape['champs'] ?? []) as $champ) @include('formulaires.champs.'.($champ['type'] ?? 'texte'), ['champ' => $champ]) @endforeach
@if ($indexEtape > 0) @endif @if ($indexEtape < count($etapes) - 1 || ($uploadFinal['actif'] ?? false)) @else @endif
@endforeach @if ($uploadFinal['actif'] ?? false)

{{ $uploadFinal['label'] ?? 'Photos et documents' }}

Formats acceptes : {{ $uploadFinal['formats'] ?? '—' }} ({{ $uploadFinal['max_mo'] ?? 10 }} Mo max par fichier, {{ $uploadFinal['max_fichiers'] ?? 10 }} fichiers max) @error('fichiers.upload_final'){{ $message }}@enderror @error('fichiers.upload_final.*'){{ $message }}@enderror
@endif
@endif
@endsection @push('scripts') @endpush