@extends('layouts.site') @php // Une page « builder » contient les classes de grille du thème WordPress d'origine. $isBuilder = $type === 'page' && \Illuminate\Support\Str::contains($item->content, ['content-section', 'col-sm-', 'gridContainer']); // Tous les articles ont un visuel (à la une, sinon image de marque via display_image). // Articles dont le corps embarquait une image externe morte -> illustration promo locale. $hasHero = $type === 'post'; $usesPromo = $type === 'post' && \App\Support\BlogImages::hasExternalImage($item->content); $rawHero = $type === 'post' ? ($usesPromo ? \App\Support\BlogImages::forSlug($item->slug.'-hero') : $item->display_image) : null; $heroImg = $rawHero ? (\Illuminate\Support\Str::startsWith($rawHero, 'http') ? $rawHero : url($rawHero)) : null; // Titre nettoyé : retire l'emoji de tête (🏙️, etc.) et les espaces - titres modernes, sans pictos démodés. $displayTitle = trim(preg_replace('/^[\x{1F000}-\x{1FAFF}\x{2190}-\x{27BF}\x{2B00}-\x{2BFF}\x{FE00}-\x{FE0F}\x{200D}\s]+/u', '', $item->title)) ?: $item->title; // Icônes Lucide (trait fin) pour la navigation « Les trucs en + ». $tIco = fn($p) => ''.$p.''; // Navigation « Les trucs en + » (bas de page) - [icône Lucide, libellé, url] $trucs = [ [$tIco(''),'La plage','/plage-du-moulleau'], [$tIco(''),'La jetée','/jetee-du-moulleau-navette-cap-ferret'], [$tIco(''),'Restaurants','/quelques-adresse-de-restaurant'], [$tIco(''),'Boutiques','/quelques-boutiques'], [$tIco(''),'Balades à vélo','/les-meilleures-balades-a-velo-autour-du-moulleau'], [$tIco(''),'Spots sportifs','/quelques-spots-pour-faire-du-sport-autour-du-moulleau'], [$tIco(''),'Activités','/quelques-activites'], [$tIco(''),'Incontournables','/quelques-lieux-incontournables-a-visiter-autour-du-moulleau'], [$tIco(''),'Loueurs de vélos','/quelques-adresse-de-loueurs-de-velos'], [$tIco(''),'Les villes','/les-villes-aux-alentours'], ]; @endphp @section('title', ($item->seo_title ?: $displayTitle).' - JavaPax') @section('meta_description', $item->seo_description ?: \Illuminate\Support\Str::limit(trim(strip_tags($item->content)), 155)) {{-- Consolidation du contenu dupliqué : si canonical_url est défini, on pointe le canonical vers la page de référence et on passe la page en noindex. --}} @if(!empty($item->canonical_url)) @section('canonical', url($item->canonical_url)) @section('robots', 'noindex, follow') @endif @if($type === 'post') @push('jsonld') @php $ld = array_filter([ '@context' => 'https://schema.org', '@type' => 'BlogPosting', 'headline' => $displayTitle, 'datePublished' => optional($item->published_at)->toAtomString(), 'dateModified' => optional($item->updated_at)->toAtomString(), 'image' => $heroImg, 'author' => ['@type' => 'Organization', 'name' => 'JavaPax'], 'publisher' => ['@type' => 'Organization', 'name' => 'JavaPax'], 'mainEntityOfPage' => url('/'.$item->slug), ]); @endphp @endpush @endif {{-- Fil d'Ariane structuré (BreadcrumbList) --}} @push('jsonld') @php $crumbs = [['name' => 'Accueil', 'url' => url('/')]]; if ($type === 'post') { $crumbs[] = ['name' => 'Les trucs en +', 'url' => url('/faq-2')]; } $crumbs[] = ['name' => $displayTitle, 'url' => url('/'.$item->slug)]; $breadcrumbLd = [ '@context' => 'https://schema.org', '@type' => 'BreadcrumbList', 'itemListElement' => array_map(fn ($c, $i) => [ '@type' => 'ListItem', 'position' => $i + 1, 'name' => $c['name'], 'item' => $c['url'], ], $crumbs, array_keys($crumbs)), ]; @endphp @endpush @if($isBuilder) @push('head') @endpush @endif @push('head') @endpush @section('full') @if($isBuilder) {{-- Page vitrine (page-builder) : on laisse la grille d'origine s'exprimer --}}

{{ $displayTitle }}

@php $builderHtml = str_replace('content); // Sert du WebP via quand le fichier .webp existe localement (fallback JPG/PNG sinon). $builderHtml = preg_replace_callback('/]*\bsrc="([^"]+\.(?:jpe?g|png))"[^>]*>/i', function ($m) { $webp = preg_replace('/\.(jpe?g|png)$/i', '.webp', $m[1]); $path = parse_url($webp, PHP_URL_PATH); if ($path && file_exists(public_path($path))) { // Variante 800 px quand elle existe : ces photos font 1500 px // de large pour un ecran mobile de 412 px, soit trois fois le // poids necessaire. Le navigateur choisit selon la largeur // reelle d'affichage, sans qu'on ait a la deviner. $petit = preg_replace('/\.webp$/i', '-800.webp', $webp); $srcset = (parse_url($petit, PHP_URL_PATH) && file_exists(public_path(parse_url($petit, PHP_URL_PATH)))) ? $petit.' 800w, '.$webp.' 1500w' : $webp; $sizes = ' sizes="(max-width: 900px) 100vw, 900px"'; return ''.$m[0].''; } return $m[0]; }, $builderHtml); @endphp {!! $builderHtml !!}
@else {{-- ===== HERO ===== --}}
@if($hasHero)
@else
@endif
{{-- Fil d'Ariane --}}

{{ $displayTitle }}

@if($type === 'post' && $item->published_at)

Mis à jour le {{ $item->published_at->translatedFormat('d F Y') }}

@endif
{{-- ===== CONTENU ===== --}}
@php $isGuide = $type === 'post' && ( \App\Support\GuideFormatter::applies($item->slug) || \Illuminate\Support\Str::startsWith(ltrim($item->title), ['🏙', '🍴', '🛍', '🚴', '🌊', '⭐', '🏖']) ); // Remplace les images externes mortes par des illustrations locales (anti « image cassée »). $rendered = \App\Support\BlogImages::rewriteContent($item->content, $item->slug); $rendered = str_replace(' quand le .webp existe localement (fallback JPG/PNG sinon). $rendered = preg_replace_callback('/]*\bsrc="([^"]+\.(?:jpe?g|png))"[^>]*>/i', function ($m) { $webp = preg_replace('/\.(jpe?g|png)$/i', '.webp', $m[1]); $path = parse_url($webp, PHP_URL_PATH); if ($path && file_exists(public_path($path))) { return ''.$m[0].''; } return $m[0]; }, $rendered); // Illustration de la ville (article « 🏙 N. … ») affichée en tête du contenu. // PRIORITÉ : fichier déposé public/img/villes/{slug}.{ext} (convention unique). $villeImg = null; if ($type === 'post' && \Illuminate\Support\Str::startsWith(ltrim($item->title), '🏙')) { foreach (['webp', 'png', 'jpg', 'jpeg'] as $ext) { if (file_exists(public_path("img/villes/{$item->slug}.{$ext}"))) { $villeImg = "/img/villes/{$item->slug}.{$ext}"; break; } } // Sinon : override config (uniquement si vraie illustration raster). if (! $villeImg && preg_match('/(\d+)\s*\./u', $item->title, $mm)) { $vi = config('villes.villes.'.((int) $mm[1]).'.image'); if ($vi && preg_match('#/img/villes/([a-z0-9-]+)\.svg$#', $vi, $r)) { foreach (['webp', 'png', 'jpg', 'jpeg'] as $ext) { if (file_exists(public_path("img/villes/{$r[1]}.{$ext}"))) { $vi = "/img/villes/{$r[1]}.{$ext}"; break; } } } if ($vi && ! \Illuminate\Support\Str::endsWith($vi, '.svg') && file_exists(public_path(ltrim($vi, '/')))) { $villeImg = $vi; } } } @endphp @if($villeImg) {{ $item->title }} @endif
{!! $rendered !!}
{{-- ===== CONTINUER L'EXPLORATION ===== --}}

Continuez l'exploration

Toutes nos pépites pour profiter du Bassin.

@foreach($trucs as [$ico,$lbl,$href]) @continue('/'.$item->slug === $href) {!! $ico !!} {{ $lbl }} @endforeach
{{-- ===== CTA ===== --}}

Envie de vivre tout ça ?

Réservez votre séjour au Moulleau, le camp de base idéal pour explorer le Bassin.

Réserver mon séjour
@endif @endsection