{{-- Page Bien (detail) — portage fidele de maquettes/Bien.dc.html. Note : pas de x-sticky-actions sur cette page, conformement a la maquette (aucun dc-import StickyActions dans Bien.dc.html). Donnees fournies par BienController@show : - $bien : detail du bien (voir app/Services/Crm/fixtures/biens.php pour la liste exacte des champs) - $photos : galerie du bien (vide si le CRM n'en fournit pas encore pour ce bien, cas de tous les biens sauf ach-1 dans les fixtures actuelles) - $biensSimilaires: jusqu'a 3 biens (hors le bien courant) PDF "detail du prix" : route('bien.pdf-prix') sert la fiche poussee par le CRM, mais UNIQUEMENT apres que le visiteur a rempli le formulaire "Recevoir le detail du prix" pour CE bien (deblocage en session, sinon 403 - voir BienController::pdfPrix()). Plans : les fixtures CRM actuelles n'exposent aucun champ "plans" (recherche faite dans app/Services/Crm/fixtures/biens.php : absent). La maquette affichait 3 vignettes "Rez-de-chaussee / Etage / Implantation terrain" toujours en placeholder (image-slot, jamais d'image reelle dans la source non plus). Cette page reproduit donc le meme bloc de secours (placeholders neutres), fidele au comportement de la maquette. A signaler si le CRM doit un jour fournir de vrais plans. Situation (carte) : la maquette utilise un image-slot "Carte - {location}" (jamais d'image reelle non plus dans la source). Reproduit ici en placeholder neutre, conforme a la maquette. --}} @extends('layouts.site') @section('titre', ($bien['titre'] ?? ($bien['localite'].' - Lot '.$bien['lot'])).' - Biome Constructions') @php // Indicateur "vendu" (voir MiroirCrmClient::bien()) : absent chez // FixtureCrmClient, d'ou le repli a false. Le bien reste consultable // pendant un delai apres la vente (CRM_DELAI_VENDU_JOURS), avec un // bandeau discret plutot que le statut de disponibilite habituel. $estVendu = (bool) ($bien['vendu'] ?? false); $titre = $bien['titre'] ?? ($bien['localite'].' - Lot '.$bien['lot']); $dispoLabel = $estVendu ? 'Vendu' : ($bien['disponibilite'] === 'construction' ? 'En construction' : 'Disponible immédiatement'); $badge = $estVendu ? 'Vendu' : ($bien['disponibilite'] === 'construction' ? 'En construction' : 'Nouvelle construction'); $heroFacts = [ ['icone' => 'M3.5 3.5h17v17h-17zM8 8l8 8M8 8h3M8 8v3M16 16h-3M16 16v-3', 'valeur' => $bien['surface_habitable'], 'libelle' => 'Surface habitable'], ['icone' => 'M3 17v-5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v5M3 14h18M6 10V8a1 1 0 0 1 1-1h3', 'valeur' => $bien['chambres'], 'libelle' => 'Chambres'], ['icone' => 'M4 12h16v3a4 4 0 0 1-4 4H8a4 4 0 0 1-4-4v-3ZM6 12V6a2 2 0 0 1 2-2 2 2 0 0 1 2 2', 'valeur' => $bien['salles_de_bain'], 'libelle' => 'Salle de bain'], ['icone' => 'M3 20h18M5 20l3-8 4 4 3-6 4 10', 'valeur' => $bien['terrain'], 'libelle' => 'Superficie terrain'], ['icone' => 'M9 12.5l2 2 4-4.5M12 3l7 3v5c0 4.5-3 8-7 10-4-2-7-5.5-7-10V6l7-3Z', 'valeur' => 'PEB '.$bien['peb'], 'libelle' => 'Très performant'], ]; // Bloc "Pourquoi choisir cette maison". Cartes UNIVERSELLES (vraies pour // toute construction neuve Biome) toujours affichees ; cartes d'EQUIPEMENT // (chauffage par le sol, photovoltaique, cuisine equipee) affichees // UNIQUEMENT si le bien les possede reellement (donnees CRM par bien, voir // MiroirCrmClient / EnregistreurBienMiroir). Le sous-titre PEB s'adapte a la // lettre pour ne pas affirmer "tres basse energie" a tort. $pebLettre = strtoupper((string) ($bien['peb'] ?? '')); $pebBasseEnergie = $pebLettre !== '' && (str_starts_with($pebLettre, 'A') || str_starts_with($pebLettre, 'B')); $atouts = [ ['titre' => 'Construction', 'sous' => 'neuve', 'icone' => 'M3 11.5 11 4.5l8 7M5.2 9.8V20h11.6V9.6M18.5 3.5v4M16.5 5.5h4'], ]; if ($pebLettre !== '') { $atouts[] = ['titre' => 'PEB '.$pebLettre, 'sous' => $pebBasseEnergie ? 'Très basse énergie' : 'Performance énergétique', 'icone' => 'M13 2 4.5 13H11l-1 9 8.5-11H12l1-9Z']; } // Carte chauffage : on affiche le TYPE de chauffage pousse par le CRM // (rubrique CHAUFFAGE de l'API -> technique.chauffage), ex. "Pompe a // chaleur". Repli sur "par le sol" si seul le booleen chauffage_sol est // fourni. e() car le sous-titre est rendu en {!! !!} (valeur CRM = a echapper). $chauffageType = trim((string) ($bien['chauffage'] ?? '')); $iconeChauffage = 'M3 14.5h18v5.5h-18zM9 14.5V20M15 14.5V20M7 11.5V6.5M7 6.5 5.6 7.9M7 6.5l1.4 1.4M12 11V5.5M12 5.5 10.6 6.9M12 5.5l1.4 1.4M17 11.5V6.5M17 6.5l-1.4 1.4M17 6.5l1.4 1.4'; if ($chauffageType !== '') { $atouts[] = ['titre' => 'Chauffage', 'sous' => e($chauffageType), 'icone' => $iconeChauffage]; } elseif ($bien['chauffage_sol'] ?? false) { $atouts[] = ['titre' => 'Chauffage', 'sous' => 'par le sol', 'icone' => $iconeChauffage]; } if ($bien['panneaux_photovoltaiques'] ?? false) { $atouts[] = ['titre' => 'Panneaux', 'sous' => 'photovoltaïques', 'icone' => 'M9.6 6a2.4 2.4 0 1 0 4.8 0 2.4 2.4 0 1 0 -4.8 0M12 1.8v1.2M6.4 6H7.6M16.4 6h1.2M8.1 3.1l.9.9M15.9 3.1l-.9.9M3.5 12h17v8h-17zM3.5 16h17M9 12v8M15 12v8']; } if ($bien['cuisine_equipee'] ?? false) { $atouts[] = ['titre' => 'Cuisine', 'sous' => 'équipée', 'icone' => 'M6 3v6a1.7 1.7 0 0 0 3.4 0V3M7.7 9v12M16.4 3c-1.5.4-2.5 2.3-2.5 4.9 0 2 1 3.3 2.5 3.3M16.4 3v18']; } $atouts[] = ['titre' => 'Garantie', 'sous' => '10 ans', 'icone' => 'M9 12.5l2 2 4-4.5M12 3l7 3v5c0 4.5-3 8-7 10-4-2-7-5.5-7-10V6l7-3Z']; $facadesCourt = str_replace(['Maison ', 'Appartement neuf', 'Penthouse neuf'], ['', 'Appartement', 'Penthouse'], $bien['facades']); $specs = [ ['cle' => 'Type de bien', 'valeur' => ucfirst($bien['type']), 'icone' => 'M3 11.2 12 4l9 7.2M5.4 10.2V20h13.2v-9.8'], ['cle' => 'Façades', 'valeur' => $facadesCourt, 'icone' => 'M4 21V8l8-5 8 5v13M9 21v-6h6v6'], ['cle' => 'Surface habitable', 'valeur' => $bien['surface_habitable'], 'icone' => 'M3.5 3.5h17v17h-17zM8 8l8 8M8 8h3M8 8v3M16 16h-3M16 16v-3'], ['cle' => 'Superficie terrain', 'valeur' => $bien['terrain'], 'icone' => 'M3 20h18M5 20l3-8 4 4 3-6 4 10'], ['cle' => 'Chambres', 'valeur' => $bien['chambres'], 'icone' => 'M3 17v-5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v5M3 14h18'], ['cle' => 'Salle de bain', 'valeur' => $bien['salles_de_bain'], 'icone' => 'M4 12h16v3a4 4 0 0 1-4 4H8a4 4 0 0 1-4-4v-3Z'], ['cle' => 'Année de construction', 'valeur' => $bien['annee'], 'icone' => 'M8 3v4M16 3v4M4 8h16v12H4zM4 11h16'], ['cle' => 'PEB', 'valeur' => $bien['peb'], 'icone' => 'M13 2 4.5 13H11l-1 9 8.5-11H12l1-9Z'], ['cle' => 'Disponibilité', 'valeur' => $dispoLabel, 'icone' => 'M12 8v4l3 2M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18Z'], ['cle' => 'Orientation du terrain', 'valeur' => $bien['orientation'], 'icone' => 'M3 12a9 9 0 1 0 18 0 9 9 0 1 0 -18 0M15.6 8.4 13.3 13.3 8.4 15.6l2.3-4.9 4.9-2.3Z'], ]; // Points d'interet : ceux pousses par le CRM (champ 'situation', mappe en // points_interet {type, duree}) priment ; a defaut (bien sans donnee, ou // fixtures) on affiche un placeholder neutre. Chaque item = libelle (type, // en avant), detail (duree, en gris dessous), icone (chemin SVG). Le CRM ne // fournit pas d'icone : on la choisit selon le type (repli pin generique) ; // ses textes bruts sont echappes (e()) avant l'affichage en {!! !!}. $iconesPoi = [ 'ecole' => 'M12 3 2 8l10 5 10-5-10-5ZM5 11v5l7 4 7-4v-5', 'commerce' => 'M4 7h16l-1 13H5L4 7ZM8 7V5a4 4 0 0 1 8 0v2', 'autoroute' => 'M5 20 9 4h6l4 16M9 9h6M8 14h8', 'gare' => 'M5 16V6a3 3 0 0 1 3-3h8a3 3 0 0 1 3 3v10M5 16h14M5 16l-2 4M19 16l2 4M9 9h6', 'ville' => 'M3 21h18M6 21V8l6-4 6 4v13M10 21v-5h4v5', 'pin' => 'M12 2a7 7 0 0 0-7 7c0 5.25 7 13 7 13s7-7.75 7-13a7 7 0 0 0-7-7Zm0 9.5A2.5 2.5 0 1 1 12 6.5a2.5 2.5 0 0 1 0 5Z', ]; $iconePourType = function (string $type) use ($iconesPoi): string { $t = \App\Services\Crm\FiltreBiens::cleCommune($type); // minuscule + sans accents return match (true) { str_contains($t, 'ecole') || str_contains($t, 'school') || str_contains($t, 'creche') || str_contains($t, 'universit') || str_contains($t, 'college') || str_contains($t, 'athenee') => $iconesPoi['ecole'], str_contains($t, 'commerc') || str_contains($t, 'magasin') || str_contains($t, 'shop') || str_contains($t, 'super') || str_contains($t, 'boutique') => $iconesPoi['commerce'], str_contains($t, 'autoroute') || str_contains($t, 'ring') || str_contains($t, 'sortie') || preg_match('/\be\d/', $t) === 1 || preg_match('/\bn\d/', $t) === 1 => $iconesPoi['autoroute'], str_contains($t, 'gare') || str_contains($t, 'train') || str_contains($t, 'sncb') || str_contains($t, 'station') => $iconesPoi['gare'], str_contains($t, 'centre') || str_contains($t, 'ville') || str_contains($t, 'city') => $iconesPoi['ville'], default => $iconesPoi['pin'], }; }; $nearby = ! empty($bien['points_interet']) ? array_map(fn (array $poi): array => [ 'libelle' => e($poi['type'] !== '' ? $poi['type'] : $poi['duree']), 'detail' => $poi['type'] !== '' ? e($poi['duree']) : '', 'icone' => $iconePourType($poi['type']), ], $bien['points_interet']) : [ ['libelle' => 'École primaire', 'detail' => 'à 3 min', 'icone' => $iconesPoi['ecole']], ['libelle' => 'Commerces', 'detail' => 'à 5 min', 'icone' => $iconesPoi['commerce']], ['libelle' => 'Accès autoroute', 'detail' => 'à 8 min', 'icone' => $iconesPoi['autoroute']], ['libelle' => 'Gare', 'detail' => 'à 10 min', 'icone' => $iconesPoi['gare']], ['libelle' => 'Centre-ville', 'detail' => 'à 12 min', 'icone' => $iconesPoi['ville']], ]; $confiance = [ ['titre' => 'Entreprise locale', 'sous' => 'à taille humaine', 'icone' => 'M3 21h18M5 21V8l7-5 7 5v13M9 21v-6h6v6'], ['titre' => 'Maisons basse énergie', 'sous' => '(PEB A)', 'icone' => 'M13 2 4.5 13H11l-1 9 8.5-11H12l1-9Z'], ['titre' => 'Matériaux de qualité', 'sous' => 'et finitions soignées', 'icone' => 'M12 2l2.4 6.9H22l-5.8 4.3 2.2 6.8L12 16l-6.4 4 2.2-6.8L2 8.9h7.6L12 2Z'], ['titre' => 'Garantie décennale', 'sous' => '', 'icone' => 'M9 12.5l2 2 4-4.5M12 3l7 3v5c0 4.5-3 8-7 10-4-2-7-5.5-7-10V6l7-3Z'], ]; // Description : le vrai texte pousse par le CRM (champ 'descriptif', deja // decoupe en paragraphes surs par MiroirCrmClient) prime ; a defaut (bien // sans descriptif, ou fixtures), on compose un texte generique. $description = ! empty($bien['description']) ? array_values($bien['description']) : [ 'Située dans le charmant secteur de '.$bien['localite'].', cette '.mb_strtolower($bien['facades']).' en cours de réalisation séduira par ses volumes généreux, sa luminosité remarquable et son environnement paisible.', "Implantée sur un terrain orienté ".$bien['orientation'].", elle bénéficie d'une vue dégagée et offre un cadre où calme, nature et confort se rencontrent.", "La maison est vendue entièrement terminée, avec une cuisine équipée, des finitions soignées et des équipements performants répondant aux exigences actuelles de confort et d'économies d'énergie.", ]; // Rendu robuste quel que soit le nombre de paragraphes : les 2 premiers // visibles, le reste sous "Lire la description complete". $descriptionVisibles = array_slice($description, 0, 2); $descriptionCachees = array_slice($description, 2); // Vignettes : TOUTES les photos (le clic sur une vignette la place en grand // format ; la bande defile via les fleches / le swipe tactile). $vignettes = $photos; // Fiche prix (PDF) : telechargeable UNIQUEMENT si le CRM en a pousse une ET // que le visiteur a rempli le formulaire "Recevoir le detail du prix" pour // CE bien (deblocage memorise en session, voir DemandeInfoBienController et // BienController::pdfPrix). Sinon on affiche le declencheur du formulaire. $fichePrixDebloquee = ! empty($bien['fiche_prix_pdf']) && ! empty(((array) session('fiches_prix_debloquees', []))[$bien['id']] ?? null); // Carte de situation. $carte = {lat, lon} fournis par le controleur // (coordonnees du CRM, ou geocodage de l'adresse), null si indisponible. // Integration OpenStreetMap (gratuit, sans cle, RGPD : aucun cookie/tracker) ; // le bouton "Ouvrir dans Google Maps" ouvre l'app Google (par coordonnees si // connues, sinon par adresse). $carteRequete = ($carte !== null) ? $carte['lat'].','.$carte['lon'] : trim((string) (! empty($bien['adresse']) ? $bien['adresse'] : $bien['localite'])); $carteLien = 'https://www.google.com/maps/search/?api=1&query='.urlencode($carteRequete); $carteEmbedOsm = null; if ($carte !== null) { // Petite fenetre (bbox) autour du point + marqueur. $bbox = ($carte['lon'] - 0.014).','.($carte['lat'] - 0.008).','.($carte['lon'] + 0.014).','.($carte['lat'] + 0.008); $carteEmbedOsm = 'https://www.openstreetmap.org/export/embed.html?bbox='.urlencode($bbox) .'&layer=mapnik&marker='.urlencode($carte['lat'].','.$carte['lon']); } @endphp @section('contenu') {{-- MODALES : les 5 demandes possibles sur cette page. --}} {{-- LIGHTBOX GALERIE --}}
{{-- BREADCRUMB --}}
Accueil Nos biens à vendre {{ $bien['localite'] }} {{ $titre }}
{{-- MAIN GRID — .biome-bien-grille-principale (nouvelle classe, lot mobile M3) : ratio 1fr 360px specifique a cette page (pas dans le catalogue .biome-grille-N standard). Decision client 2026-07-03 : la sidebar (prix, CTA, formulaire) passe SOUS le contenu principal en mobile (1 colonne) ; l'ordre DOM (galerie/contenu puis