All checks were successful
Deploy info-canicule / deploy (push) Successful in 1m30s
Adopte le design system livré par Claude Design (Info Canicule.html) : palette --paper/--brand/--ink + ramp vigilance, Public Sans + Manrope, header sticky blurred avec toggle clair/sombre, pills vigilance avec glyphes ●▲◆■ (a11y daltonisme), home restructurée (hero, stat tiles, map + sidebar avec recherche département, liste filtrable, CTA conseils). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
326 lines
14 KiB
Text
326 lines
14 KiB
Text
---
|
|
import '../styles/global.css';
|
|
|
|
interface Props {
|
|
title?: string;
|
|
description?: string;
|
|
canonical?: string;
|
|
ogImage?: string;
|
|
noindex?: boolean;
|
|
}
|
|
|
|
const SITE = 'https://info-canicule.nocleus.com';
|
|
const {
|
|
title = 'Info Canicule — Vigilance météo France en temps réel',
|
|
description = 'Suivi gratuit et sans publicité des alertes Vigilance Météo France (canicule, orages, tempêtes), avec carte interactive par département et conseils officiels.',
|
|
canonical,
|
|
ogImage = '/og-image.png',
|
|
noindex = false,
|
|
} = Astro.props;
|
|
|
|
const canonicalUrl = canonical ?? new URL(Astro.url.pathname, SITE).toString();
|
|
const fullOgImage = ogImage.startsWith('http') ? ogImage : `${SITE}${ogImage}`;
|
|
const umamiId = process.env.UMAMI_WEBSITE_ID;
|
|
const umamiSrc = process.env.UMAMI_SRC ?? 'https://analytics.nocleus.com/script.js';
|
|
|
|
const path = Astro.url.pathname;
|
|
const isActive = (p: string) => (p === '/' ? path === '/' : path.startsWith(p));
|
|
|
|
const jsonLd = {
|
|
'@context': 'https://schema.org',
|
|
'@graph': [
|
|
{
|
|
'@type': 'WebSite',
|
|
'@id': `${SITE}/#website`,
|
|
name: 'Info Canicule',
|
|
url: SITE,
|
|
description,
|
|
inLanguage: 'fr-FR',
|
|
publisher: { '@type': 'Person', name: 'Florian Bouchet' },
|
|
potentialAction: {
|
|
'@type': 'SearchAction',
|
|
target: `${SITE}/departement/{code}`,
|
|
'query-input': 'required name=code',
|
|
},
|
|
},
|
|
{
|
|
'@type': 'Service',
|
|
'@id': `${SITE}/#service`,
|
|
name: 'Info Canicule',
|
|
serviceType: "Service d'information météorologique grand public",
|
|
areaServed: { '@type': 'Country', name: 'France' },
|
|
audience: { '@type': 'PeopleAudience', audienceType: 'Grand public, personnes fragiles' },
|
|
provider: {
|
|
'@type': 'Person',
|
|
name: 'Florian Bouchet',
|
|
url: `${SITE}/a-propos`,
|
|
},
|
|
isBasedOn: {
|
|
'@type': 'Dataset',
|
|
name: 'Vigilance Météo France',
|
|
url: 'https://vigilance.meteofrance.fr/',
|
|
creator: { '@type': 'Organization', name: 'Météo-France' },
|
|
license: 'https://www.etalab.gouv.fr/licence-ouverte-open-licence/',
|
|
},
|
|
isAccessibleForFree: true,
|
|
},
|
|
],
|
|
};
|
|
---
|
|
|
|
<!DOCTYPE html>
|
|
<html lang="fr">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
|
<meta name="description" content={description} />
|
|
{noindex ? <meta name="robots" content="noindex, nofollow" /> : <meta name="robots" content="index, follow, max-image-preview:large" />}
|
|
<link rel="canonical" href={canonicalUrl} />
|
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
<link rel="alternate" type="application/rss+xml" title="Info Canicule" href={`${SITE}/sitemap-index.xml`} />
|
|
<title>{title}</title>
|
|
|
|
<meta property="og:type" content="website" />
|
|
<meta property="og:title" content={title} />
|
|
<meta property="og:description" content={description} />
|
|
<meta property="og:url" content={canonicalUrl} />
|
|
<meta property="og:image" content={fullOgImage} />
|
|
<meta property="og:locale" content="fr_FR" />
|
|
<meta property="og:site_name" content="Info Canicule" />
|
|
|
|
<meta name="twitter:card" content="summary_large_image" />
|
|
<meta name="twitter:title" content={title} />
|
|
<meta name="twitter:description" content={description} />
|
|
<meta name="twitter:image" content={fullOgImage} />
|
|
|
|
<meta name="theme-color" content="#fdfaf2" media="(prefers-color-scheme: light)" />
|
|
<meta name="theme-color" content="#14110d" media="(prefers-color-scheme: dark)" />
|
|
<meta name="format-detection" content="telephone=yes" />
|
|
|
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
<link href="https://fonts.googleapis.com/css2?family=Public+Sans:wght@400;500;600;700&family=Manrope:wght@500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
|
|
|
|
<script type="application/ld+json" set:html={JSON.stringify(jsonLd)} />
|
|
|
|
{/* Avoid flash of light theme on dark-preferring devices */}
|
|
<script is:inline>
|
|
(function () {
|
|
try {
|
|
var saved = localStorage.getItem('ic-theme');
|
|
var mql = window.matchMedia('(prefers-color-scheme: dark)');
|
|
var theme = saved || (mql.matches ? 'dark' : 'light');
|
|
if (theme === 'dark') document.documentElement.setAttribute('data-theme', 'dark');
|
|
} catch (_) {}
|
|
})();
|
|
</script>
|
|
|
|
{umamiId && (
|
|
<script defer src={umamiSrc} data-website-id={umamiId} data-do-not-track="true"></script>
|
|
)}
|
|
</head>
|
|
<body class="flex flex-col" style="min-height: 100dvh;">
|
|
<a href="#main" class="skip-link">Aller au contenu principal</a>
|
|
|
|
<header class="site-header">
|
|
<div class="container-tight flex items-center justify-between gap-3 py-3.5">
|
|
<a href="/" class="inline-flex items-center gap-2.5 font-display text-[1.1rem] font-extrabold no-underline" style="color: var(--ink);">
|
|
<svg width="28" height="28" viewBox="0 0 32 32" aria-hidden="true">
|
|
<circle cx="16" cy="16" r="14" fill="var(--sun)" />
|
|
<circle cx="16" cy="16" r="11" fill="var(--brand)" />
|
|
<path d="M16 7 C12 11 12 16 16 21 C20 16 20 11 16 7 Z" fill="#fff" />
|
|
</svg>
|
|
<span>Info Canicule</span>
|
|
</a>
|
|
|
|
<nav id="nav-main" class="nav-links flex items-center gap-1" aria-label="Navigation principale">
|
|
<a href="/" class:list={['nav-link', isActive('/') && 'is-active']}>Carte</a>
|
|
<a href="/conseils" class:list={['nav-link', isActive('/conseils') && 'is-active']}>Conseils</a>
|
|
<a href="/a-propos" class:list={['nav-link', isActive('/a-propos') && 'is-active']}>À propos</a>
|
|
<a href="/soutenir" class:list={['nav-link', isActive('/soutenir') && 'is-active']} style="color: var(--brand-deep);">☕ Soutenir</a>
|
|
</nav>
|
|
|
|
<div class="flex items-center gap-2">
|
|
<button
|
|
id="theme-toggle"
|
|
type="button"
|
|
class="icon-btn"
|
|
aria-label="Basculer entre mode clair et mode sombre"
|
|
title="Basculer mode clair / sombre"
|
|
>
|
|
<svg id="theme-icon-sun" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
<circle cx="12" cy="12" r="4" />
|
|
<path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41" />
|
|
</svg>
|
|
<svg id="theme-icon-moon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="display:none;">
|
|
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
|
|
</svg>
|
|
</button>
|
|
<button
|
|
id="nav-toggle"
|
|
type="button"
|
|
class="icon-btn"
|
|
data-mobile-only
|
|
aria-label="Ouvrir le menu"
|
|
aria-expanded="false"
|
|
aria-controls="nav-main"
|
|
>
|
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
<line x1="3" y1="6" x2="21" y2="6" />
|
|
<line x1="3" y1="12" x2="21" y2="12" />
|
|
<line x1="3" y1="18" x2="21" y2="18" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<main id="main" class="flex-1">
|
|
<slot />
|
|
</main>
|
|
|
|
<footer class="site-footer">
|
|
<div class="container-tight py-8">
|
|
<div class="grid gap-8" style="grid-template-columns: 1.4fr 1fr 1fr 1fr;">
|
|
<div>
|
|
<a href="/" class="inline-flex items-center gap-2.5 font-display text-[1.05rem] font-extrabold no-underline" style="color: var(--ink);">
|
|
<svg width="24" height="24" viewBox="0 0 32 32" aria-hidden="true">
|
|
<circle cx="16" cy="16" r="14" fill="var(--sun)" />
|
|
<circle cx="16" cy="16" r="11" fill="var(--brand)" />
|
|
<path d="M16 7 C12 11 12 16 16 21 C20 16 20 11 16 7 Z" fill="#fff" />
|
|
</svg>
|
|
Info Canicule
|
|
</a>
|
|
<p class="mt-2 text-sm" style="color: var(--ink-soft); max-width: 36ch;">
|
|
Service d'information publique gratuit, sans publicité, non lucratif.
|
|
</p>
|
|
</div>
|
|
<div>
|
|
<h4>Le site</h4>
|
|
<ul class="flex flex-col gap-2 text-sm">
|
|
<li><a href="/a-propos" style="color: var(--ink-2);">À propos</a></li>
|
|
<li><a href="/conseils" style="color: var(--ink-2);">Conseils par phénomène</a></li>
|
|
<li><a href="/soutenir" style="color: var(--ink-2);">☕ Soutenir le projet</a></li>
|
|
</ul>
|
|
</div>
|
|
<div>
|
|
<h4>Mentions</h4>
|
|
<ul class="flex flex-col gap-2 text-sm">
|
|
<li><a href="/mentions-legales" style="color: var(--ink-2);">Mentions légales</a></li>
|
|
<li><a href="/dependances" style="color: var(--ink-2);">Dépendances</a></li>
|
|
<li><a href={`${SITE}/sitemap-index.xml`} style="color: var(--ink-2);">Plan du site</a></li>
|
|
</ul>
|
|
</div>
|
|
<div>
|
|
<h4>Données</h4>
|
|
<ul class="flex flex-col gap-2 text-sm">
|
|
<li>
|
|
<a href="https://meteo.data.gouv.fr/" rel="noopener" style="color: var(--ink-2);">Météo France (open data)</a>
|
|
</li>
|
|
<li>
|
|
<a href="https://www.etalab.gouv.fr/licence-ouverte-open-licence/" rel="noopener" style="color: var(--ink-2);">Licence Ouverte 2.0</a>
|
|
</li>
|
|
<li>
|
|
En urgence : <a href="tel:112" style="color: var(--brand-deep); font-weight: 600;">112</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
<div class="mt-7 flex flex-wrap items-center justify-between gap-3 border-t pt-5 text-xs" style="border-color: var(--line); color: var(--ink-soft);">
|
|
<span>Édité à titre personnel, sans but lucratif.</span>
|
|
<span>
|
|
Données officielles · <a href="https://vigilance.meteofrance.fr/" rel="noopener" style="color: var(--brand-deep);">vigilance.meteofrance.fr</a>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</footer>
|
|
|
|
<style>
|
|
/* Header nav links */
|
|
.nav-link {
|
|
color: var(--ink-2);
|
|
padding: 8px 14px;
|
|
border-radius: var(--r-md);
|
|
font-weight: 500;
|
|
font-size: 0.95rem;
|
|
text-decoration: none;
|
|
border-bottom: none;
|
|
}
|
|
.nav-link:hover { background: var(--paper-warm); color: var(--ink); border-bottom: none; }
|
|
.nav-link.is-active { color: var(--ink); background: var(--paper-warm); }
|
|
|
|
@media (max-width: 720px) {
|
|
.nav-links {
|
|
display: none !important;
|
|
}
|
|
.nav-links.open {
|
|
display: flex !important;
|
|
flex-direction: column;
|
|
align-items: stretch;
|
|
position: fixed;
|
|
inset: 60px 0 auto 0;
|
|
background: var(--paper-2);
|
|
padding: 12px;
|
|
border-bottom: 1px solid var(--line);
|
|
box-shadow: var(--sh-3);
|
|
gap: 4px;
|
|
z-index: 49;
|
|
}
|
|
.nav-links.open .nav-link { width: 100%; padding: 14px 18px; }
|
|
}
|
|
|
|
[data-mobile-only] { display: none !important; }
|
|
@media (max-width: 720px) {
|
|
[data-mobile-only] { display: inline-flex !important; }
|
|
}
|
|
|
|
@media (max-width: 900px) {
|
|
.site-footer > div > div:first-child { grid-template-columns: 1fr 1fr !important; }
|
|
}
|
|
@media (max-width: 900px) {
|
|
.site-footer .grid { grid-template-columns: 1fr 1fr !important; }
|
|
}
|
|
@media (max-width: 540px) {
|
|
.site-footer .grid { grid-template-columns: 1fr !important; }
|
|
}
|
|
</style>
|
|
|
|
<script is:inline>
|
|
(function () {
|
|
var root = document.documentElement;
|
|
var toggle = document.getElementById('theme-toggle');
|
|
var iconSun = document.getElementById('theme-icon-sun');
|
|
var iconMoon = document.getElementById('theme-icon-moon');
|
|
function syncIcons() {
|
|
var dark = root.getAttribute('data-theme') === 'dark';
|
|
if (iconSun) iconSun.style.display = dark ? 'none' : '';
|
|
if (iconMoon) iconMoon.style.display = dark ? '' : 'none';
|
|
}
|
|
syncIcons();
|
|
if (toggle) {
|
|
toggle.addEventListener('click', function () {
|
|
var dark = root.getAttribute('data-theme') === 'dark';
|
|
if (dark) { root.removeAttribute('data-theme'); }
|
|
else { root.setAttribute('data-theme', 'dark'); }
|
|
try { localStorage.setItem('ic-theme', dark ? 'light' : 'dark'); } catch (_) {}
|
|
syncIcons();
|
|
});
|
|
}
|
|
var navToggle = document.getElementById('nav-toggle');
|
|
var nav = document.getElementById('nav-main');
|
|
if (navToggle && nav) {
|
|
navToggle.addEventListener('click', function () {
|
|
var open = nav.classList.toggle('open');
|
|
navToggle.setAttribute('aria-expanded', open ? 'true' : 'false');
|
|
});
|
|
nav.addEventListener('click', function (e) {
|
|
if (e.target && e.target.tagName === 'A') {
|
|
nav.classList.remove('open');
|
|
navToggle.setAttribute('aria-expanded', 'false');
|
|
}
|
|
});
|
|
}
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>
|