geoproject-app/public/site/templates/narrative-web.php
sarahgarcin1 153e75c0a9
All checks were successful
Deploy / Build and Deploy to Production (push) Successful in 25s
merge + ajout des boutons sur les tuiles page projet + css page narrative
2026-02-27 12:24:35 +01:00

498 lines
No EOL
18 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/**
* Template: narrative-web.php
* Affichage web storytelling d'une narrative et de ses sous-pages
* Structure : narrative > geoformat > chapitre / map > marker
*/
// Collect all subpages for lateral navigation
$original = page($page->originalUri());
$subpages = $original->children()->listed();
$navItems = [];
foreach ($subpages as $subpage) {
$item = [
'id' => $subpage->uid(),
'title' => $subpage->title()->value(),
'template' => $subpage->intendedTemplate()->name(),
'children' => [],
];
// Pour les cartes : inclure les marqueurs comme sous-items de navigation
if ($subpage->intendedTemplate()->name() === 'map') {
foreach ($subpage->children()->listed()->filterBy('intendedTemplate', 'marker') as $mk) {
$item['children'][] = [
'id' => $mk->uid(),
'title' => $mk->title()->value(),
];
}
}
$navItems[] = $item;
}
?>
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= e($original->isHomePage() != true, $original->title() . ' ') . $site->title() ?></title>
<!-- Styles globaux existants -->
<link rel="stylesheet" href="<?= url('assets/css/style.css') ?>">
<!-- Styles narrative web -->
<link rel="stylesheet" href="<?= url('assets/css/web.css') ?>">
<!-- MapLibre GL -->
<link rel="stylesheet" href="https://unpkg.com/maplibre-gl@4.7.1/dist/maplibre-gl.css">
<script src="https://unpkg.com/maplibre-gl@4.7.1/dist/maplibre-gl.js"></script>
<!-- Custom CSS éventuel depuis le panel -->
<?php if ($original->customCss()->isNotEmpty()): ?>
<style><?= $original->customCss() ?></style>
<?php endif ?>
<!-- À SUPPRIMER EN PRODUCTION -->
<meta name="robots" content="noindex, nofollow, noarchive">
<?php if ($kirby->user()): ?>
<meta name="csrf" content="<?= csrf() ?>">
<?php endif ?>
</head>
<body data-template="<?= $original->template() ?>">
<!-- ═══════════════════════════════════════════
NAVIGATION LATÉRALE (ancres)
════════════════════════════════════════════ -->
<?php if (!empty($navItems)): ?>
<nav class="nw-sidenav" aria-label="Navigation sections">
<ul class="nw-sidenav__list">
<li class="nw-sidenav__item nw-sidenav__item--intro">
<a class="nw-sidenav__link" href="#nw-intro">
<span class="nw-sidenav__dot"></span>
<span class="nw-sidenav__label">Introduction</span>
</a>
</li>
<?php foreach ($navItems as $item): ?>
<li class="nw-sidenav__item nw-sidenav__item--<?= $item['template'] ?>">
<a class="nw-sidenav__link" href="#section-<?= $item['id'] ?>">
<span class="nw-sidenav__dot"></span>
<span class="nw-sidenav__label"><?= html($item['title']) ?></span>
</a>
<?php if (!empty($item['children'])): ?>
<ul class="nw-sidenav__sub">
<?php foreach ($item['children'] as $child): ?>
<li class="nw-sidenav__sub-item">
<a class="nw-sidenav__sub-link" href="#marker-<?= $child['id'] ?>">
<span class="nw-sidenav__sub-dot"></span>
<span class="nw-sidenav__sub-label"><?= html($child['title']) ?></span>
</a>
</li>
<?php endforeach ?>
</ul>
<?php endif ?>
</li>
<?php endforeach ?>
</ul>
</nav>
<?php endif ?>
<!-- ═══════════════════════════════════════════
BANDEAU / INTRODUCTION
════════════════════════════════════════════ -->
<header class="nw-hero" id="nw-intro">
<?php if ($original->cover()->isNotEmpty() && $cover = $original->cover()->toFile()): ?>
<div class="nw-hero__bg" style="background-image: url('<?= $cover->url() ?>')"></div>
<div class="nw-hero__overlay"></div>
<?php endif ?>
<div class="nw-prev-btn">
<a href="<?= $original->parent()->url()?>" title="<?= $original->parent()->title()?>">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round">
<path d="M19 12H5"/>
<polyline points="12 19 5 12 12 5"/>
</svg>
<?= $original->parent()->title()->html()?>
</a>
</div>
<div class="nw-hero__content">
<h1 class="nw-hero__title"><?= html($original->title()) ?></h1>
<?php if ($original->author()->isNotEmpty()): ?>
<p class="nw-hero__author">Par <span><?= html($original->author()) ?></span></p>
<?php endif ?>
</div>
</header>
<!-- Introduction -->
<?php if ($original->introduction()->isNotEmpty()): ?>
<section class="nw-introduction">
<div class="nw-container nw-container--narrow">
<div class="nw-introduction__body">
<?= $original->introduction() ?>
</div>
</div>
</section>
<?php endif ?>
<!-- ═══════════════════════════════════════════
SOUS-PAGES
════════════════════════════════════════════ -->
<main class="nw-main">
<?php foreach ($subpages as $subpage):
$tpl = $subpage->intendedTemplate()->name();
?>
<!-- ─────────────────────────────────────────
CARTE (map.yaml > marker.yaml)
───────────────────────────────────────── -->
<?php if ($tpl === 'map'): ?>
<section class="nw-section nw-section--map" id="section-<?= $subpage->uid() ?>">
<div class="nw-container">
<header class="nw-section__header">
<h2 class="nw-section__title"><?= html($subpage->title()) ?></h2>
<?php if ($subpage->tags()->isNotEmpty()): ?>
<div class="nw-tags">
<?php foreach ($subpage->tags()->split(',') as $tag): ?>
<span class="nw-tag"><?= html(trim($tag)) ?></span>
<?php endforeach ?>
</div>
<?php endif ?>
</header>
<?php if ($subpage->text()->isNotEmpty()): ?>
<div class="nw-prose"><?= $subpage->text() ?></div>
<?php endif ?>
</div>
<!-- Carte MapLibre inline -->
<?php
$markers = $subpage->children()->listed()->filterBy('intendedTemplate', 'marker');
$mapId = 'map-' . $subpage->uid();
?>
<div class="nw-map-wrap">
<div id="<?= $mapId ?>" class="nw-map"></div>
</div>
<!-- Fichiers de la carte -->
<?php if ($subpage->files()->isNotEmpty()): ?>
<div class="nw-container">
<div class="nw-files">
<?php foreach ($subpage->files() as $file): ?>
<a href="<?= $file->url() ?>" class="nw-file" target="_blank">
<span class="nw-file__icon">↓</span>
<span class="nw-file__name"><?= html($file->filename()) ?></span>
</a>
<?php endforeach ?>
</div>
</div>
<?php endif ?>
<!-- ── Liste des marqueurs sous la carte ── -->
<?php if ($markers->isNotEmpty()): ?>
<div class="nw-map-marker-list nw-container">
<?php $mNum = 1; foreach ($markers as $marker): ?>
<article
class="nw-map-marker"
id="marker-<?= $marker->uid() ?>"
data-lat="<?= $marker->latitude()->value() ?>"
data-lng="<?= $marker->longitude()->value() ?>"
>
<?php if ($marker->cover()->isNotEmpty() && $mCover = $marker->cover()->toFile()): ?>
<div class="nw-map-marker__cover">
<img src="<?= $mCover->url() ?>" alt="<?= html($marker->title()) ?>">
<span class="nw-map-marker__num"><?= $mNum ?></span>
</div>
<?php else: ?>
<span class="nw-map-marker__num nw-map-marker__num--no-cover"><?= $mNum ?></span>
<?php endif ?>
<div class="nw-map-marker__body">
<h3 class="nw-map-marker__title"><?= html($marker->title()) ?></h3>
<?php if ($marker->text()->isNotEmpty()): ?>
<div class="nw-map-marker__content">
<?php foreach ($marker->text()->toBlocks() as $block): ?>
<div class="block block-type-<?= $block->type() ?>">
<?= $block ?>
</div>
<?php endforeach ?>
</div>
<?php endif ?>
</div>
</article>
<?php $mNum++; endforeach ?>
</div>
<?php endif ?>
<script>
(function() {
var markers = <?php
$mData = [];
foreach ($markers as $m) {
if ($m->latitude()->isNotEmpty() && $m->longitude()->isNotEmpty()) {
$coverUrl = '';
if ($m->cover()->isNotEmpty() && $mCov = $m->cover()->toFile()) {
$coverUrl = $mCov->url();
}
$iconUrl = '';
$iconSize = (int)$m->markerIconSize()->or(40)->value();
if ($m->markerIcon()->isNotEmpty() && $icon = $m->markerIcon()->toFile()) {
$iconUrl = $icon->url();
}
$mData[] = [
'uid' => $m->uid(),
'lat' => (float)$m->latitude()->value(),
'lng' => (float)$m->longitude()->value(),
'title' => $m->title()->value(),
'coverUrl' => $coverUrl,
'iconUrl' => $iconUrl,
'iconSize' => $iconSize,
];
}
}
echo json_encode($mData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
?>;
// Centre de la carte
var mapdata = <?php
$defaultCenter = [43.836699, 4.360054];
$defaultZoom = 13;
if ($subpage->mapdata()->isNotEmpty()) {
$raw = $subpage->mapdata()->value();
$decoded = json_decode($raw, true);
if ($decoded) {
echo json_encode($decoded);
} else {
echo json_encode(['center' => $defaultCenter, 'zoom' => $defaultZoom]);
}
} else {
echo json_encode(['center' => $defaultCenter, 'zoom' => $defaultZoom]);
}
?>;
document.addEventListener('DOMContentLoaded', function() {
var center = mapdata.center || [<?= $defaultCenter[1] ?>, <?= $defaultCenter[0] ?>];
var zoom = mapdata.zoom || 13;
// MapLibre attend [lng, lat]
if (Array.isArray(center) && center.length === 2) {
// Si stocké [lat, lng], inverser
if (center[0] > 90 || center[0] < -90) {
// déjà [lng, lat]
} else {
center = [center[1], center[0]];
}
}
var map = new maplibregl.Map({
container: '<?= $mapId ?>',
style: 'https://api.maptiler.com/maps/dataviz/style.json?key=get_your_own_OpIi9ZULNHzrESv6T2vL',
center: center,
zoom: zoom,
attributionControl: true
});
// Fallback style OSM si pas de clé MapTiler
map.on('error', function() {
map.setStyle({
version: 8,
sources: {
osm: {
type: 'raster',
tiles: ['https://tile.openstreetmap.org/{z}/{x}/{y}.png'],
tileSize: 256,
attribution: '© OpenStreetMap contributors'
}
},
layers: [{ id: 'osm', type: 'raster', source: 'osm' }]
});
});
markers.forEach(function(m) {
var el = null;
if (m.iconUrl) {
el = document.createElement('div');
el.className = 'nw-marker-icon';
el.style.backgroundImage = 'url(' + m.iconUrl + ')';
el.style.width = m.iconSize + 'px';
el.style.height = m.iconSize + 'px';
}
var markerOpts = { color: 'var(--nw-accent, #c0392b)' };
if (el) markerOpts.element = el;
// Construire le HTML de la popup : cover + titre + ancre vers le texte
var popupHtml = '';
if (m.coverUrl) {
popupHtml += '<div class="nw-popup-cover"><img src="' + m.coverUrl + '" alt="' + m.title + '"></div>';
}
popupHtml += '<div class="nw-popup-body">';
if (m.title) {
popupHtml += '<strong class="nw-popup-title">' + m.title + '</strong>';
}
popupHtml += '<a class="nw-popup-anchor" href="#marker-' + m.uid + '">Lire la fiche ↓</a>';
popupHtml += '</div>';
var marker = new maplibregl.Marker(markerOpts)
.setLngLat([m.lng, m.lat])
.setPopup(new maplibregl.Popup({ offset: 25, maxWidth: '280px' }).setHTML(popupHtml))
.addTo(map);
});
// Adapter les bounds si on a des markers
if (markers.length > 1) {
var bounds = new maplibregl.LngLatBounds();
markers.forEach(function(m) { bounds.extend([m.lng, m.lat]); });
map.fitBounds(bounds, { padding: 60, maxZoom: 16 });
}
});
})();
</script>
</section>
<!-- ─────────────────────────────────────────
GÉOFORMAT (geoformat.yaml > chapitre.yaml)
───────────────────────────────────────── -->
<?php elseif ($tpl === 'geoformat'): ?>
<section class="nw-section nw-section--geoformat" id="section-<?= $subpage->uid() ?>">
<!-- En-tête géoformat -->
<div class="nw-geoformat-hero">
<?php if ($subpage->cover()->isNotEmpty() && $gCover = $subpage->cover()->toFile()): ?>
<div class="nw-geoformat-hero__bg" style="background-image: url('<?= $gCover->url() ?>')"></div>
<div class="nw-geoformat-hero__overlay"></div>
<?php endif ?>
<div class="nw-geoformat-hero__content nw-container">
<h2 class="nw-geoformat-hero__title"><?= html($subpage->title()) ?></h2>
<?php if ($subpage->subtitle()->isNotEmpty()): ?>
<p class="nw-geoformat-hero__subtitle"><?= html($subpage->subtitle()) ?></p>
<?php endif ?>
<?php if ($subpage->tags()->isNotEmpty()): ?>
<div class="nw-tags">
<?php foreach ($subpage->tags()->split(',') as $tag): ?>
<span class="nw-tag"><?= html(trim($tag)) ?></span>
<?php endforeach ?>
</div>
<?php endif ?>
</div>
</div>
<!-- Chapeau -->
<?php if ($subpage->text()->isNotEmpty()): ?>
<div class="nw-container nw-container--narrow">
<div class="nw-chapeau"><?= $subpage->text() ?></div>
</div>
<?php endif ?>
<!-- Chapitres -->
<?php
$chapitres = $subpage->children()->listed()->filterBy('intendedTemplate', 'chapitre');
$chapCount = $chapitres->count();
if ($chapCount > 0): ?>
<!-- Navigation chapitres locale -->
<?php if ($chapCount > 1): ?>
<div class="nw-container">
<nav class="nw-chap-nav" aria-label="Chapitres">
<?php $ci = 1; foreach ($chapitres as $chap): ?>
<a class="nw-chap-nav__link" href="#chap-<?= $chap->uid() ?>">
<span class="nw-chap-nav__num"><?= $ci ?></span>
<span class="nw-chap-nav__title"><?= html($chap->title()) ?></span>
</a>
<?php $ci++; endforeach ?>
</nav>
</div>
<?php endif ?>
<?php $chapNum = 1; foreach ($chapitres as $chap): ?>
<article class="nw-chapitre" id="chap-<?= $chap->uid() ?>">
<div class="nw-container nw-container--narrow">
<header class="nw-chapitre__header">
<span class="nw-chapitre__num">Chapitre <?= $chapNum ?></span>
<h3 class="nw-chapitre__title"><?= html($chap->title()) ?></h3>
</header>
<div class="nw-chapitre__body">
<?= $chap->text()->toBlocks() ?>
</div>
</div>
</article>
<?php $chapNum++; endforeach ?>
<?php endif ?>
</section>
<?php endif ?>
<?php endforeach ?>
</main>
<!-- ═══════════════════════════════════════════
FOOTER
════════════════════════════════════════════ -->
<?php snippet('footer') ?>
<!-- Script animations sections -->
<script>
(function() {
var sections = document.querySelectorAll('.nw-section');
if (!sections.length) return;
var io = new IntersectionObserver(function(entries) {
entries.forEach(function(e) {
if (e.isIntersecting) {
e.target.classList.add('is-visible');
io.unobserve(e.target);
}
});
}, { threshold: 0.08 });
sections.forEach(function(s) { io.observe(s); });
})();
</script>
<!-- Script navigation latérale (active state au scroll) -->
<script>
(function() {
var links = document.querySelectorAll('.nw-sidenav__link');
var subLinks = document.querySelectorAll('.nw-sidenav__sub-link');
var targets = document.querySelectorAll('[id^="section-"], #nw-intro');
var markerTargets = document.querySelectorAll('[id^="marker-"]');
if (!links.length || !targets.length) return;
// Observer sections principales
var observer = new IntersectionObserver(function(entries) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
links.forEach(function(l) { l.classList.remove('is-active'); });
document.querySelectorAll('.nw-sidenav__item').forEach(function(li) {
li.classList.remove('is-parent-active');
});
var active = document.querySelector('.nw-sidenav__link[href="#' + entry.target.id + '"]');
if (active) {
active.classList.add('is-active');
var parentLi = active.closest('.nw-sidenav__item');
if (parentLi) parentLi.classList.add('is-parent-active');
}
}
});
}, { rootMargin: '-20% 0px -70% 0px' });
targets.forEach(function(t) { observer.observe(t); });
// Observer marqueurs pour les sous-liens
if (markerTargets.length && subLinks.length) {
var subObserver = new IntersectionObserver(function(entries) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
subLinks.forEach(function(l) { l.classList.remove('is-active'); });
var active = document.querySelector('.nw-sidenav__sub-link[href="#' + entry.target.id + '"]');
if (active) active.classList.add('is-active');
}
});
}, { rootMargin: '-15% 0px -65% 0px' });
markerTargets.forEach(function(t) { subObserver.observe(t); });
}
})();
</script>
</body>
</html>