feat(design): refonte hi-fi (tokens, pills glyphes, dark mode, accueil)
All checks were successful
Deploy info-canicule / deploy (push) Successful in 1m30s
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>
This commit is contained in:
parent
a8830a4f34
commit
72b3785499
7 changed files with 1003 additions and 250 deletions
|
|
@ -55,17 +55,15 @@ for (const [code] of entries) {
|
|||
{
|
||||
entries.map(([code, dept]) => {
|
||||
const colorId = colorsByDept.get(code) ?? 1;
|
||||
const color = COLORS[colorId];
|
||||
return (
|
||||
<a href={`/departement/${code}`} class="cursor-pointer">
|
||||
<path
|
||||
d={dept.d}
|
||||
data-code={code}
|
||||
data-name={dept.name}
|
||||
fill={color.hex}
|
||||
stroke="#ffffff"
|
||||
stroke-width="0.8"
|
||||
class="transition-opacity hover:opacity-80"
|
||||
class:list={['france-map-dept', `france-map-fill-${colorId}`]}
|
||||
stroke="var(--paper-2)"
|
||||
stroke-width="0.5"
|
||||
/>
|
||||
</a>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -6,15 +6,17 @@ interface Props {
|
|||
colorId: ColorId;
|
||||
phenomenonId?: PhenomenonId;
|
||||
showLevel?: boolean;
|
||||
size?: 'sm' | 'lg';
|
||||
}
|
||||
|
||||
const { colorId, phenomenonId, showLevel = false } = Astro.props;
|
||||
const { colorId, phenomenonId, showLevel = false, size = 'sm' } = Astro.props;
|
||||
const phenomenon = phenomenonId ? PHENOMENA[phenomenonId] : null;
|
||||
const colorClass = `vigilance-chip-${colorId}`;
|
||||
// Glyphes redondants à la couleur (accessibilité daltonisme + niveaux de gris).
|
||||
const GLYPHS: Record<ColorId, string> = { 1: '●', 2: '▲', 3: '◆', 4: '■' };
|
||||
---
|
||||
|
||||
<span class:list={['vigilance-chip', colorClass]}>
|
||||
{phenomenon && <span aria-hidden="true">{phenomenon.emoji}</span>}
|
||||
<span class:list={['vigilance-chip', `vigilance-chip-${colorId}`, size === 'lg' && 'vigilance-chip-lg']}>
|
||||
<span class="glyph" aria-hidden="true">{GLYPHS[colorId]}</span>
|
||||
{phenomenon ? phenomenon.label : COLORS[colorId].name}
|
||||
{showLevel && <span class="text-xs opacity-75">— {COLOR_LABEL[colorId]}</span>}
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -1,22 +1,28 @@
|
|||
---
|
||||
import { COLORS, COLOR_LABEL } from '../lib/phenomena';
|
||||
import VigilanceChip from './VigilanceChip.astro';
|
||||
import { COLOR_LABEL } from '../lib/phenomena';
|
||||
import type { ColorId } from '../lib/phenomena';
|
||||
|
||||
interface Props {
|
||||
inline?: boolean;
|
||||
}
|
||||
const { inline = false } = Astro.props;
|
||||
const levels: ColorId[] = [1, 2, 3, 4];
|
||||
const NAMES: Record<ColorId, string> = { 1: 'Vert', 2: 'Jaune', 3: 'Orange', 4: 'Rouge' };
|
||||
---
|
||||
|
||||
<div class="flex flex-wrap items-center gap-3 text-sm">
|
||||
<span class="font-semibold text-slate-700">Niveaux :</span>
|
||||
<div class:list={['flex flex-wrap items-center gap-x-4 gap-y-2 text-sm', inline ? 'flex-row' : 'flex-col items-start sm:flex-row sm:items-center']}>
|
||||
<span class="kicker shrink-0">Légende</span>
|
||||
{
|
||||
levels.map((id) => (
|
||||
<span class="inline-flex items-center gap-2">
|
||||
<span
|
||||
class="inline-block h-4 w-4 rounded border border-slate-300"
|
||||
style={`background-color: ${COLORS[id].hex};`}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span class="capitalize text-slate-700">{COLORS[id].name}</span>
|
||||
<span class="hidden text-xs text-slate-500 sm:inline">— {COLOR_LABEL[id]}</span>
|
||||
<VigilanceChip colorId={id} />
|
||||
<span style="color: var(--ink-soft); font-size: 0.85rem;" class="hidden sm:inline">
|
||||
{NAMES[id]} — {COLOR_LABEL[id]}
|
||||
</span>
|
||||
<span style="color: var(--ink-soft); font-size: 0.85rem;" class="sm:hidden">
|
||||
{NAMES[id]}
|
||||
</span>
|
||||
</span>
|
||||
))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,9 @@ 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': [
|
||||
|
|
@ -44,7 +47,7 @@ const jsonLd = {
|
|||
'@type': 'Service',
|
||||
'@id': `${SITE}/#service`,
|
||||
name: 'Info Canicule',
|
||||
serviceType: 'Service d\'information météorologique grand public',
|
||||
serviceType: "Service d'information météorologique grand public",
|
||||
areaServed: { '@type': 'Country', name: 'France' },
|
||||
audience: { '@type': 'PeopleAudience', audienceType: 'Grand public, personnes fragiles' },
|
||||
provider: {
|
||||
|
|
@ -69,7 +72,7 @@ const jsonLd = {
|
|||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<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} />
|
||||
|
|
@ -90,68 +93,234 @@ const jsonLd = {
|
|||
<meta name="twitter:description" content={description} />
|
||||
<meta name="twitter:image" content={fullOgImage} />
|
||||
|
||||
<meta name="theme-color" content="#ea580c" />
|
||||
<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="min-h-screen flex flex-col">
|
||||
<header class="border-b border-slate-200 bg-white">
|
||||
<div class="container-tight flex flex-wrap items-center justify-between gap-3 py-4">
|
||||
<a href="/" class="flex items-center gap-2 no-underline">
|
||||
<img src="/favicon.svg" alt="" width="32" height="32" class="h-8 w-8" />
|
||||
<span class="text-lg font-bold text-canicule-700">Info Canicule</span>
|
||||
<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 class="flex flex-wrap items-center gap-x-5 gap-y-1 text-sm font-medium text-slate-600">
|
||||
<a href="/">Carte</a>
|
||||
<a href="/conseils">Conseils</a>
|
||||
<a href="/a-propos">À propos</a>
|
||||
<a href="/soutenir" class="text-canicule-700">☕ Soutenir</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 class="flex-1">
|
||||
|
||||
<main id="main" class="flex-1">
|
||||
<slot />
|
||||
</main>
|
||||
<footer class="border-t border-slate-200 bg-white">
|
||||
<div class="container-tight py-6 text-sm text-slate-500">
|
||||
<div class="grid gap-4 sm:grid-cols-3">
|
||||
|
||||
<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>
|
||||
<div class="flex items-center gap-2">
|
||||
<img src="/favicon.svg" alt="" width="28" height="28" class="h-7 w-7" />
|
||||
<p class="font-semibold text-slate-700">Info Canicule</p>
|
||||
</div>
|
||||
<p class="mt-1 text-xs">
|
||||
<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>
|
||||
<p class="font-semibold text-slate-700">Liens</p>
|
||||
<ul class="mt-1 space-y-0.5">
|
||||
<li><a href="/a-propos">À propos</a></li>
|
||||
<li><a href="/mentions-legales">Mentions légales</a></li>
|
||||
<li><a href="/dependances">Dépendances</a></li>
|
||||
<li><a href="/soutenir">☕ Soutenir sur Ko-fi</a></li>
|
||||
<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>
|
||||
<p class="font-semibold text-slate-700">Données</p>
|
||||
<p class="mt-1 text-xs">
|
||||
<a href="https://meteo.data.gouv.fr/" rel="noopener">Météo France</a>
|
||||
— <a href="https://www.etalab.gouv.fr/licence-ouverte-open-licence/" rel="noopener">Licence Ouverte 2.0</a>.
|
||||
En urgence : <a href="tel:112" class="text-canicule-700 font-semibold">112</a>.
|
||||
</p>
|
||||
<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>
|
||||
<p class="mt-4 text-xs text-slate-400">
|
||||
Édité à titre personnel, sans but lucratif —
|
||||
<a href="/mentions-legales" class="text-canicule-700">mentions légales</a>.
|
||||
</p>
|
||||
<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>
|
||||
|
|
|
|||
|
|
@ -2,17 +2,15 @@
|
|||
import Base from '../layouts/Base.astro';
|
||||
import FranceMap from '../components/FranceMap.astro';
|
||||
import DepartementGrid from '../components/DepartementGrid.astro';
|
||||
import VigilanceLegend from '../components/VigilanceLegend.astro';
|
||||
import VigilanceChip from '../components/VigilanceChip.astro';
|
||||
import { getVigilanceSnapshot, maxColorByDepartement, activeAlerts, currentEcheance } from '../lib/vigilance';
|
||||
import { getDepartement } from '../lib/departements';
|
||||
import { getVigilanceSnapshot, maxColorByDepartement, currentEcheance } from '../lib/vigilance';
|
||||
import { getDepartement, DEPARTEMENTS } from '../lib/departements';
|
||||
import { PHENOMENA } from '../lib/phenomena';
|
||||
import type { VigilanceAlert } from '../lib/vigilance';
|
||||
|
||||
export const prerender = false;
|
||||
|
||||
// Force la revalidation côté navigateur — la page change toutes les ~5 min
|
||||
// (cache Valkey TTL + bulletins MF), donc on évite le cache local agressif
|
||||
// qui faisait servir une carte de la veille.
|
||||
// Force la revalidation côté navigateur — la page change toutes les ~5 min.
|
||||
Astro.response.headers.set('Cache-Control', 'public, max-age=60, must-revalidate');
|
||||
|
||||
let snapshot;
|
||||
|
|
@ -23,14 +21,9 @@ try {
|
|||
error = (e as Error).message;
|
||||
}
|
||||
|
||||
// Determine l'écheance "aujourd'hui réel" — peut être J ou J1 selon l'heure
|
||||
// (entre minuit et la publication du bulletin suivant ~6h, J du bulletin = hier).
|
||||
const todayEch: 'J' | 'J1' = snapshot ? currentEcheance(snapshot) : 'J';
|
||||
// "Demain" n'existe que quand `todayEch === 'J'` : le bulletin n'a pas de J2.
|
||||
// Entre minuit et la pub ~6h, on est sur J1 = aujourd'hui réel, donc pas de "demain" dispo.
|
||||
const tomorrowAvailable = todayEch === 'J';
|
||||
|
||||
// Onglet sélectionné via ?echeance=tomorrow ; défaut = today.
|
||||
const requestedView = new URL(Astro.request.url).searchParams.get('echeance');
|
||||
const view: 'today' | 'tomorrow' =
|
||||
requestedView === 'tomorrow' && tomorrowAvailable ? 'tomorrow' : 'today';
|
||||
|
|
@ -52,7 +45,6 @@ if (snapshot) {
|
|||
}
|
||||
}
|
||||
|
||||
// Tri des départements actifs par code (2A/2B inséré après 19).
|
||||
function deptSortKey(code: string): string {
|
||||
if (code === '2A') return '19.1';
|
||||
if (code === '2B') return '19.2';
|
||||
|
|
@ -62,9 +54,33 @@ const activeDeptCodes = [...alertsByDept.keys()].sort((a, b) =>
|
|||
deptSortKey(a).localeCompare(deptSortKey(b), 'en', { numeric: true }),
|
||||
);
|
||||
|
||||
const totalAlertes = activeDeptCodes.length;
|
||||
const canicule = alertsToday.filter((a) => a.phenomenonId === 6).length;
|
||||
const orages = alertsToday.filter((a) => a.phenomenonId === 3).length;
|
||||
const orange = alertsToday.filter((a) => a.colorId >= 3).length;
|
||||
|
||||
// Phénomène dominant (le plus représenté en alerte, niveau max gardé pour la pill)
|
||||
let dominantId: number | null = null;
|
||||
let dominantColor = 1;
|
||||
if (alertsToday.length > 0) {
|
||||
const tally = new Map<number, { count: number; maxColor: number }>();
|
||||
for (const a of alertsToday) {
|
||||
const t = tally.get(a.phenomenonId) ?? { count: 0, maxColor: 1 };
|
||||
t.count += 1;
|
||||
if (a.colorId > t.maxColor) t.maxColor = a.colorId;
|
||||
tally.set(a.phenomenonId, t);
|
||||
}
|
||||
let bestCount = 0;
|
||||
for (const [id, t] of tally) {
|
||||
if (t.count > bestCount) {
|
||||
bestCount = t.count;
|
||||
dominantId = id;
|
||||
dominantColor = t.maxColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
const dominant = dominantId ? PHENOMENA[dominantId as keyof typeof PHENOMENA] : null;
|
||||
|
||||
const productDate = snapshot?.productDatetime
|
||||
? new Date(snapshot.productDatetime).toLocaleString('fr-FR', {
|
||||
dateStyle: 'long',
|
||||
|
|
@ -73,200 +89,409 @@ const productDate = snapshot?.productDatetime
|
|||
})
|
||||
: 'inconnu';
|
||||
|
||||
// Date de validité de l'écheance affichée (pour clarifier au visiteur).
|
||||
// On la dérive du "maintenant" Paris (+1 jour si l'onglet "demain" est actif),
|
||||
// pas d'un sample d'alerte : en jour calme tout est vert, donc pas d'alerte sample.
|
||||
const fmtParisDate = (d: Date) =>
|
||||
d.toLocaleDateString('fr-FR', {
|
||||
weekday: 'long', day: 'numeric', month: 'long', timeZone: 'Europe/Paris',
|
||||
});
|
||||
const fmtParisDateShort = (d: Date) =>
|
||||
d.toLocaleDateString('fr-FR', {
|
||||
weekday: 'short', day: 'numeric', month: 'short', timeZone: 'Europe/Paris',
|
||||
});
|
||||
const nowParis = new Date();
|
||||
const tomorrowParis = new Date(nowParis.getTime() + 86_400_000);
|
||||
const todayLabel = fmtParisDate(nowParis);
|
||||
const tomorrowLabel = fmtParisDate(tomorrowParis);
|
||||
const dayLabel = view === 'tomorrow' ? tomorrowLabel : todayLabel;
|
||||
const todayShort = fmtParisDateShort(nowParis);
|
||||
const tomorrowShort = fmtParisDateShort(tomorrowParis);
|
||||
|
||||
// Données pour la recherche département (sérialisées vers le JS client).
|
||||
const allDepts = DEPARTEMENTS.map((d) => ({ code: d.code, name: d.name }));
|
||||
---
|
||||
|
||||
<Base>
|
||||
<section class="bg-gradient-to-b from-canicule-50 to-white">
|
||||
<div class="container-tight py-10">
|
||||
<h1 class="text-3xl font-bold text-slate-900 sm:text-4xl">
|
||||
Vigilance Météo France en temps réel
|
||||
<section class="container-tight" style="padding-block: clamp(28px, 5vw, 56px) 28px;">
|
||||
{/* Hero */}
|
||||
<div class="flex flex-col gap-3" style="margin-bottom: 28px;">
|
||||
<div class="kicker">Vigilance Météo France · <span class="capitalize">{todayLabel}</span></div>
|
||||
<h1>
|
||||
Toutes les alertes météo,
|
||||
<span style="color: var(--brand-deep);">en clair</span>.
|
||||
</h1>
|
||||
<p class="mt-2 max-w-2xl text-slate-600">
|
||||
Carte des alertes Vigilance par département, conseils officiels et numéros d'urgence.
|
||||
{dayLabel && (
|
||||
<>
|
||||
Affichage pour <strong class="capitalize">{dayLabel}</strong>.
|
||||
</>
|
||||
)}
|
||||
<p style="font-size: clamp(1rem, 0.9rem + 0.4vw, 1.18rem); color: var(--ink-2); max-width: 720px; line-height: 1.55;">
|
||||
Canicule, orages, vent, pluie, neige, avalanches : retrouvez la vigilance de chaque département, ce qu'il faut faire, et prévenez un proche en quelques secondes.
|
||||
</p>
|
||||
<p class="mt-1 text-xs text-slate-500">
|
||||
<p class="kicker" style="text-transform: none; letter-spacing: 0; font-size: 0.78rem;">
|
||||
Bulletin Météo France émis le {productDate} (heure de Paris).
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{
|
||||
!error && snapshot && (
|
||||
<section class="container-tight pt-6">
|
||||
<div role="tablist" aria-label="Échéance de la vigilance" class="inline-flex rounded-lg border border-slate-200 bg-white p-1 text-sm shadow-sm">
|
||||
{/* Day tabs */}
|
||||
{!error && snapshot && (
|
||||
<div role="tablist" aria-label="Échéance de la vigilance" class="flex flex-wrap gap-2" style="margin-bottom: 20px;">
|
||||
<a
|
||||
href="/"
|
||||
role="tab"
|
||||
aria-selected={view === 'today' ? 'true' : 'false'}
|
||||
class:list={[
|
||||
'rounded-md px-4 py-1.5 font-medium no-underline transition-colors',
|
||||
view === 'today'
|
||||
? 'bg-canicule-700 text-white'
|
||||
: 'text-slate-700 hover:bg-slate-100',
|
||||
]}
|
||||
class:list={['ic-btn ic-btn-sm', view === 'today' ? 'ic-btn-primary' : 'ic-btn-ghost']}
|
||||
style="text-decoration: none;"
|
||||
>
|
||||
Aujourd'hui
|
||||
<span class="ml-1 hidden text-xs opacity-80 sm:inline capitalize">· {todayLabel}</span>
|
||||
Aujourd'hui · <span class="capitalize">{todayShort}</span>
|
||||
</a>
|
||||
{tomorrowAvailable ? (
|
||||
<a
|
||||
href="/?echeance=tomorrow"
|
||||
role="tab"
|
||||
aria-selected={view === 'tomorrow' ? 'true' : 'false'}
|
||||
class:list={[
|
||||
'rounded-md px-4 py-1.5 font-medium no-underline transition-colors',
|
||||
view === 'tomorrow'
|
||||
? 'bg-canicule-700 text-white'
|
||||
: 'text-slate-700 hover:bg-slate-100',
|
||||
]}
|
||||
class:list={['ic-btn ic-btn-sm', view === 'tomorrow' ? 'ic-btn-primary' : 'ic-btn-ghost']}
|
||||
style="text-decoration: none;"
|
||||
>
|
||||
Demain
|
||||
<span class="ml-1 hidden text-xs opacity-80 sm:inline capitalize">· {tomorrowLabel}</span>
|
||||
Demain · <span class="capitalize">{tomorrowShort}</span>
|
||||
</a>
|
||||
) : (
|
||||
<span
|
||||
role="tab"
|
||||
aria-selected="false"
|
||||
aria-disabled="true"
|
||||
title="L'échéance « demain » n'est plus disponible : Météo France publie le prochain bulletin vers 6h, qui couvrira à nouveau aujourd'hui + demain."
|
||||
class="cursor-not-allowed rounded-md px-4 py-1.5 font-medium text-slate-400"
|
||||
title="L'échéance « demain » n'est pas encore disponible : Météo France publie le prochain bulletin vers 6h."
|
||||
class="ic-btn ic-btn-sm ic-btn-ghost"
|
||||
style="opacity: 0.55; cursor: not-allowed;"
|
||||
>
|
||||
Demain
|
||||
<span class="ml-1 hidden text-xs sm:inline">· en attente du prochain bulletin</span>
|
||||
Demain · en attente du prochain bulletin
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
)}
|
||||
|
||||
<section class="container-tight py-8">
|
||||
{
|
||||
error && (
|
||||
<div class="rounded border border-red-200 bg-red-50 p-4 text-red-800">
|
||||
<strong>Données indisponibles.</strong> Réessayer dans quelques minutes. Détail technique :
|
||||
{error}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
!error && (
|
||||
<div class="grid gap-4 sm:grid-cols-3">
|
||||
<div class="rounded-lg border border-slate-200 bg-white p-4">
|
||||
<div class="text-2xl font-bold text-canicule-700">{canicule}</div>
|
||||
<div class="text-sm text-slate-600">départements en vigilance canicule</div>
|
||||
</div>
|
||||
<div class="rounded-lg border border-slate-200 bg-white p-4">
|
||||
<div class="text-2xl font-bold text-orange-700">{orange}</div>
|
||||
<div class="text-sm text-slate-600">en niveau orange ou rouge (tous phénomènes)</div>
|
||||
</div>
|
||||
<div class="rounded-lg border border-slate-200 bg-white p-4">
|
||||
<div class="text-2xl font-bold text-yellow-700">{orages}</div>
|
||||
<div class="text-sm text-slate-600">en vigilance orages</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</section>
|
||||
|
||||
{
|
||||
!error && (
|
||||
<>
|
||||
<section class="container-tight pb-2">
|
||||
<div class="mb-2 flex flex-wrap items-center justify-between gap-3">
|
||||
<h2 class="text-xl font-semibold text-slate-900 capitalize">
|
||||
Niveau par département{dayLabel ? ` — ${dayLabel}` : ''}
|
||||
</h2>
|
||||
<VigilanceLegend />
|
||||
</div>
|
||||
<p class="text-sm text-slate-500">
|
||||
Survolez un département pour voir le détail des alertes, cliquez pour la page complète.
|
||||
{error && (
|
||||
<div class="v-block v-rouge" style="margin-bottom: 24px;">
|
||||
<strong>Données Météo France momentanément indisponibles.</strong>
|
||||
<p style="margin-top: 6px; font-size: 0.92rem;">
|
||||
Réessayez dans quelques minutes. En urgence, consultez
|
||||
<a href="https://vigilance.meteofrance.fr/" rel="noopener" style="color: inherit; text-decoration: underline;">vigilance.meteofrance.fr</a>.
|
||||
</p>
|
||||
</section>
|
||||
<div class="container-tight pb-4">
|
||||
<FranceMap colorsByDept={colorsByDept} alertsByDept={alertsByDept} />
|
||||
</div>
|
||||
<section class="container-tight pb-8">
|
||||
<details class="rounded border border-slate-200 bg-white p-4">
|
||||
<summary class="cursor-pointer font-medium text-slate-700">
|
||||
Vue par région (liste)
|
||||
)}
|
||||
|
||||
{/* Stat tiles */}
|
||||
{!error && (
|
||||
<div class="grid gap-3" style="grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); margin-bottom: 24px;">
|
||||
<div class="ic-card" style="padding: 18px;">
|
||||
<div style="font-family: var(--font-display); font-weight: 800; font-size: clamp(1.6rem, 1.3rem + 1.2vw, 2.4rem); line-height: 1; color: var(--v-jaune); letter-spacing: -0.02em;">
|
||||
{totalAlertes}
|
||||
</div>
|
||||
<div style="color: var(--ink-soft); font-size: 0.88rem; font-weight: 500; margin-top: 6px;">
|
||||
départements en alerte
|
||||
</div>
|
||||
</div>
|
||||
<div class="ic-card" style="padding: 18px;">
|
||||
<div style="font-family: var(--font-display); font-weight: 800; font-size: clamp(1.6rem, 1.3rem + 1.2vw, 2.4rem); line-height: 1; color: var(--v-orange); letter-spacing: -0.02em;">
|
||||
{orange}
|
||||
</div>
|
||||
<div style="color: var(--ink-soft); font-size: 0.88rem; font-weight: 500; margin-top: 6px;">
|
||||
en orange ou rouge
|
||||
</div>
|
||||
</div>
|
||||
<div class="ic-card" style="padding: 18px;">
|
||||
<div style="font-family: var(--font-display); font-weight: 800; font-size: clamp(1.6rem, 1.3rem + 1.2vw, 2.4rem); line-height: 1; color: var(--v-orange); letter-spacing: -0.02em;">
|
||||
{canicule}
|
||||
</div>
|
||||
<div style="color: var(--ink-soft); font-size: 0.88rem; font-weight: 500; margin-top: 6px;">
|
||||
touchés par canicule
|
||||
</div>
|
||||
</div>
|
||||
<div class="ic-card" style="padding: 18px;">
|
||||
<div style="font-family: var(--font-display); font-weight: 800; font-size: clamp(1.6rem, 1.3rem + 1.2vw, 2.4rem); line-height: 1; color: var(--v-jaune); letter-spacing: -0.02em;">
|
||||
{orages}
|
||||
</div>
|
||||
<div style="color: var(--ink-soft); font-size: 0.88rem; font-weight: 500; margin-top: 6px;">
|
||||
vigilance orages
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Map + sidebar */}
|
||||
{!error && (
|
||||
<div class="grid items-start gap-6 home-grid" style="grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr);">
|
||||
<div class="ic-card" style="padding: clamp(8px, 2vw, 18px);">
|
||||
<FranceMap colorsByDept={colorsByDept} alertsByDept={alertsByDept} />
|
||||
<p style="margin-top: 10px; color: var(--ink-soft); font-size: 0.82rem; text-align: center;">
|
||||
Survolez ou tapotez un département pour le détail, cliquez pour la page complète.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="ic-card">
|
||||
<div class="kicker" style="margin-bottom: 12px;">Légende vigilance</div>
|
||||
<div class="flex flex-col gap-2.5">
|
||||
<div class="flex items-center gap-2.5"><VigilanceChip colorId={1} /> <span style="color: var(--ink-soft); font-size: 0.88rem;">Pas de vigilance particulière</span></div>
|
||||
<div class="flex items-center gap-2.5"><VigilanceChip colorId={2} /> <span style="color: var(--ink-soft); font-size: 0.88rem;">Soyez attentif</span></div>
|
||||
<div class="flex items-center gap-2.5"><VigilanceChip colorId={3} /> <span style="color: var(--ink-soft); font-size: 0.88rem;">Soyez très vigilant</span></div>
|
||||
<div class="flex items-center gap-2.5"><VigilanceChip colorId={4} /> <span style="color: var(--ink-soft); font-size: 0.88rem;">Vigilance absolue</span></div>
|
||||
</div>
|
||||
<p style="color: var(--ink-soft); font-size: 0.78rem; margin-top: 12px; line-height: 1.45;">
|
||||
Chaque niveau a une <strong>couleur</strong> et un <strong>symbole</strong> (●▲◆■) — afin de rester lisible en cas de daltonisme ou d'impression en niveaux de gris.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="ic-card">
|
||||
<div class="kicker">Êtes-vous concerné ?</div>
|
||||
<h3 style="margin-top: 8px; margin-bottom: 12px;">Voir mon département</h3>
|
||||
<div class="ic-input" style="position: relative;">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="color: var(--ink-mute); flex-shrink: 0;">
|
||||
<circle cx="11" cy="11" r="7" />
|
||||
<line x1="21" y1="21" x2="16.65" y2="16.65" />
|
||||
</svg>
|
||||
<input
|
||||
id="dept-search-input"
|
||||
type="search"
|
||||
autocomplete="off"
|
||||
placeholder="Tapez 04 ou Alpes-de-Haute-Provence…"
|
||||
aria-label="Rechercher un département"
|
||||
aria-controls="dept-search-results"
|
||||
aria-autocomplete="list"
|
||||
/>
|
||||
</div>
|
||||
<ul
|
||||
id="dept-search-results"
|
||||
role="listbox"
|
||||
aria-label="Résultats"
|
||||
style="display:none; position: absolute; left: 0; right: 0; margin-top: 6px; background: var(--paper-2); border: 1px solid var(--line-strong); border-radius: var(--r-md); box-shadow: var(--sh-3); list-style: none; padding: 6px; z-index: 20; max-height: 260px; overflow-y: auto;"
|
||||
></ul>
|
||||
<p style="color: var(--ink-soft); font-size: 0.82rem; margin-top: 10px;">
|
||||
Cliquez aussi sur la carte ou choisissez dans la liste ci-dessous.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-3" style="grid-template-columns: 1fr 1fr;">
|
||||
<div class="ic-card" style="padding: 16px;">
|
||||
<div class="kicker">Phénomène dominant</div>
|
||||
{dominant ? (
|
||||
<>
|
||||
<div style="margin-top: 10px;">
|
||||
<strong style="font-size: 1.05rem;">{dominant.label}</strong>
|
||||
</div>
|
||||
<div style="margin-top: 8px;">
|
||||
<VigilanceChip colorId={dominantColor as 1|2|3|4} />
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div style="margin-top: 10px;">
|
||||
<strong style="font-size: 1.05rem;">Calme</strong>
|
||||
</div>
|
||||
<div style="margin-top: 8px;">
|
||||
<VigilanceChip colorId={1} />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div class="ic-card" style="padding: 16px;">
|
||||
<div class="kicker">Échéance</div>
|
||||
<div style="font-family: var(--font-display); font-weight: 800; font-size: 1.4rem; margin-top: 10px; line-height: 1.05; letter-spacing: -0.02em; text-transform: capitalize;">
|
||||
{view === 'tomorrow' ? tomorrowShort : todayShort}
|
||||
</div>
|
||||
<div style="color: var(--ink-soft); font-size: 0.85rem; margin-top: 6px;">
|
||||
{view === 'tomorrow' ? 'Prévisions de demain' : "Données d'aujourd'hui"}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Vue par région (liste repliable) */}
|
||||
{!error && (
|
||||
<details class="ic-card" style="margin-top: 24px;">
|
||||
<summary style="cursor: pointer; font-weight: 600; color: var(--ink-2);">
|
||||
Vue par région (liste complète)
|
||||
</summary>
|
||||
<div class="mt-4">
|
||||
<div style="margin-top: 16px;">
|
||||
<DepartementGrid colorsByDept={colorsByDept} />
|
||||
</div>
|
||||
</details>
|
||||
<p class="mt-4 text-center text-xs text-slate-500">
|
||||
Source officielle :
|
||||
<a href="https://vigilance.meteofrance.fr/" rel="noopener" class="text-canicule-700 font-medium">
|
||||
vigilance.meteofrance.fr
|
||||
</a>
|
||||
— toujours s'y référer en cas d'urgence.
|
||||
</p>
|
||||
<p class="mt-2 text-center text-xs text-slate-500">
|
||||
<strong>Outre-mer non couvert</strong> par cette source open data :
|
||||
<a href="/departement/971" class="text-canicule-700">Guadeloupe</a> ·
|
||||
<a href="/departement/972" class="text-canicule-700">Martinique</a> ·
|
||||
<a href="/departement/973" class="text-canicule-700">Guyane</a> ·
|
||||
<a href="/departement/974" class="text-canicule-700">La Réunion</a> ·
|
||||
<a href="/departement/976" class="text-canicule-700">Mayotte</a> →
|
||||
<a href="https://vigilance.meteofrance.fr/" rel="noopener" class="text-canicule-700">vigilance.meteofrance.fr</a>
|
||||
</p>
|
||||
)}
|
||||
</section>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
!error && activeDeptCodes.length > 0 && (
|
||||
<section class="border-t border-slate-200 bg-white">
|
||||
<div class="container-tight py-8">
|
||||
<h2 class="mb-4 text-xl font-semibold text-slate-900">
|
||||
Départements en alerte ({activeDeptCodes.length})
|
||||
</h2>
|
||||
<p class="mb-4 text-sm text-slate-500">
|
||||
Triés par numéro de département. Un département peut cumuler plusieurs phénomènes (ex: canicule + orages).
|
||||
{/* Liste des départements en alerte */}
|
||||
{!error && activeDeptCodes.length > 0 && (
|
||||
<section class="container-tight" style="padding-block: 32px;" id="liste-depts">
|
||||
<div class="flex flex-wrap items-center justify-between gap-3" style="margin-bottom: 16px;">
|
||||
<h2>Départements en alerte ({activeDeptCodes.length})</h2>
|
||||
<div class="ic-input" style="max-width: 320px;">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="color: var(--ink-mute); flex-shrink: 0;">
|
||||
<circle cx="11" cy="11" r="7" />
|
||||
<line x1="21" y1="21" x2="16.65" y2="16.65" />
|
||||
</svg>
|
||||
<input
|
||||
id="dept-list-filter"
|
||||
type="search"
|
||||
autocomplete="off"
|
||||
placeholder="Filtrer par numéro ou nom…"
|
||||
aria-label="Filtrer les départements en alerte"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p style="color: var(--ink-soft); font-size: 0.92rem; margin-bottom: 12px;">
|
||||
Triés par numéro. Un département peut cumuler plusieurs phénomènes (ex : canicule + orages).
|
||||
</p>
|
||||
<ul class="space-y-2">
|
||||
<div class="ic-card" style="padding: 6px;">
|
||||
<ul id="dept-list" class="grid gap-x-4 list-dept" style="grid-template-columns: 1fr 1fr; list-style: none; padding: 0; margin: 0;">
|
||||
{activeDeptCodes.map((code) => {
|
||||
const dept = getDepartement(code);
|
||||
if (!dept) return null;
|
||||
const alerts = (alertsByDept.get(code) ?? []).slice().sort((a, b) => b.colorId - a.colorId);
|
||||
return (
|
||||
<li class="rounded border border-slate-200 px-3 py-2">
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<a href={`/departement/${code}`} class="font-medium no-underline">
|
||||
<span class="font-mono text-slate-500">{code}</span> · {dept.name}
|
||||
</a>
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
<li data-search={`${code} ${dept.name}`.toLowerCase()}>
|
||||
<a
|
||||
href={`/departement/${code}`}
|
||||
class="dept-row flex flex-wrap items-center justify-between gap-3"
|
||||
style="padding: 12px 14px; border-bottom: 1px dashed var(--line); border-radius: 6px; color: inherit; text-decoration: none; transition: background-color .12s;"
|
||||
>
|
||||
<span class="inline-flex items-baseline gap-2.5" style="min-width: 0;">
|
||||
<span style="font-family: var(--font-mono); font-size: 0.85rem; color: var(--ink-mute); min-width: 28px;">{code}</span>
|
||||
<span style="font-weight: 600;">{dept.name}</span>
|
||||
</span>
|
||||
<span class="inline-flex flex-wrap items-center gap-1.5" style="flex-shrink: 0;">
|
||||
{alerts.map((a) => (
|
||||
<VigilanceChip colorId={a.colorId} phenomenonId={a.phenomenonId} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
<p id="dept-list-empty" style="display:none; padding: 16px; text-align: center; color: var(--ink-soft);">
|
||||
Aucun département ne correspond.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
)}
|
||||
|
||||
{/* CTA banner */}
|
||||
<section class="container-tight" style="padding-block: 16px 32px;">
|
||||
<div class="ic-card-soft flex flex-wrap items-center justify-between gap-4" style="padding: 28px;">
|
||||
<div>
|
||||
<h3 style="margin-bottom: 6px;">Vous connaissez quelqu'un de fragile ?</h3>
|
||||
<p style="color: var(--ink-soft); max-width: 540px;">
|
||||
Retrouvez les bons gestes par phénomène (canicule, orages, vent…) à imprimer ou à transmettre à un proche.
|
||||
</p>
|
||||
</div>
|
||||
<a href="/conseils" class="ic-btn ic-btn-brand ic-btn-lg" style="text-decoration: none;">
|
||||
Voir les conseils →
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Outre-mer note */}
|
||||
<section class="container-tight" style="padding-block: 0 24px;">
|
||||
<p style="color: var(--ink-soft); font-size: 0.82rem; text-align: center;">
|
||||
Source officielle :
|
||||
<a href="https://vigilance.meteofrance.fr/" rel="noopener" style="color: var(--brand-deep); font-weight: 500;">vigilance.meteofrance.fr</a>
|
||||
— toujours s'y référer en cas d'urgence.
|
||||
</p>
|
||||
<p style="color: var(--ink-soft); font-size: 0.82rem; text-align: center; margin-top: 8px;">
|
||||
<strong>Outre-mer non couvert</strong> par cette source open data :
|
||||
<a href="/departement/971" style="color: var(--brand-deep);">Guadeloupe</a> ·
|
||||
<a href="/departement/972" style="color: var(--brand-deep);">Martinique</a> ·
|
||||
<a href="/departement/973" style="color: var(--brand-deep);">Guyane</a> ·
|
||||
<a href="/departement/974" style="color: var(--brand-deep);">La Réunion</a> ·
|
||||
<a href="/departement/976" style="color: var(--brand-deep);">Mayotte</a>
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<script type="application/json" id="ic-all-depts" set:html={JSON.stringify(allDepts)} />
|
||||
|
||||
<style>
|
||||
@media (max-width: 900px) {
|
||||
.home-grid { grid-template-columns: 1fr !important; }
|
||||
}
|
||||
@media (max-width: 720px) {
|
||||
.list-dept { grid-template-columns: 1fr !important; }
|
||||
}
|
||||
.dept-row:hover, .dept-row:focus-visible {
|
||||
background-color: var(--paper-warm);
|
||||
border-bottom-color: var(--line-strong);
|
||||
}
|
||||
</style>
|
||||
|
||||
<script is:inline>
|
||||
(function () {
|
||||
// Filter for "liste des départements en alerte"
|
||||
var filter = document.getElementById('dept-list-filter');
|
||||
var list = document.getElementById('dept-list');
|
||||
var empty = document.getElementById('dept-list-empty');
|
||||
if (filter && list) {
|
||||
filter.addEventListener('input', function () {
|
||||
var q = filter.value.trim().toLowerCase();
|
||||
var shown = 0;
|
||||
list.querySelectorAll('li').forEach(function (li) {
|
||||
var hit = !q || li.getAttribute('data-search').indexOf(q) !== -1;
|
||||
li.style.display = hit ? '' : 'none';
|
||||
if (hit) shown++;
|
||||
});
|
||||
if (empty) empty.style.display = shown === 0 ? 'block' : 'none';
|
||||
});
|
||||
}
|
||||
|
||||
// Search-and-go for the sidebar dept search
|
||||
var dataEl = document.getElementById('ic-all-depts');
|
||||
var input = document.getElementById('dept-search-input');
|
||||
var results = document.getElementById('dept-search-results');
|
||||
if (!dataEl || !input || !results) return;
|
||||
var ALL = [];
|
||||
try { ALL = JSON.parse(dataEl.textContent || '[]'); } catch (_) {}
|
||||
var active = 0;
|
||||
var matches = [];
|
||||
|
||||
function render() {
|
||||
if (!matches.length) { results.style.display = 'none'; results.innerHTML = ''; return; }
|
||||
results.style.display = 'block';
|
||||
results.innerHTML = matches.map(function (d, i) {
|
||||
return '<li role="option" aria-selected="' + (i === active) + '">' +
|
||||
'<a href="/departement/' + d.code + '" data-i="' + i + '" style="' +
|
||||
'display:flex; gap:10px; align-items:center; width:100%; padding:8px 10px;' +
|
||||
'border-radius:6px; text-decoration:none; color:inherit;' +
|
||||
(i === active ? 'background:var(--paper-warm);' : '') + '">' +
|
||||
'<span style="font-family:var(--font-mono);font-size:0.85rem;color:var(--ink-mute);min-width:28px;">' + d.code + '</span>' +
|
||||
'<span style="font-weight:500;">' + d.name + '</span>' +
|
||||
'</a>' +
|
||||
'</li>';
|
||||
}).join('');
|
||||
}
|
||||
|
||||
input.addEventListener('input', function () {
|
||||
var q = input.value.trim().toLowerCase();
|
||||
matches = !q ? [] : ALL.filter(function (d) {
|
||||
return (d.code + ' ' + d.name).toLowerCase().indexOf(q) !== -1;
|
||||
}).slice(0, 8);
|
||||
active = 0;
|
||||
render();
|
||||
});
|
||||
|
||||
input.addEventListener('keydown', function (e) {
|
||||
if (!matches.length) return;
|
||||
if (e.key === 'ArrowDown') { e.preventDefault(); active = Math.min(matches.length - 1, active + 1); render(); }
|
||||
if (e.key === 'ArrowUp') { e.preventDefault(); active = Math.max(0, active - 1); render(); }
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
window.location.href = '/departement/' + matches[active].code;
|
||||
}
|
||||
if (e.key === 'Escape') { matches = []; render(); }
|
||||
});
|
||||
|
||||
results.addEventListener('mouseover', function (e) {
|
||||
var a = e.target.closest('a[data-i]');
|
||||
if (a) { active = +a.getAttribute('data-i'); render(); }
|
||||
});
|
||||
|
||||
document.addEventListener('click', function (e) {
|
||||
if (!input.contains(e.target) && !results.contains(e.target)) {
|
||||
matches = [];
|
||||
render();
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</Base>
|
||||
|
|
|
|||
|
|
@ -2,35 +2,386 @@
|
|||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
/* ─── Design tokens ───────────────────────────────────────── */
|
||||
:root {
|
||||
--paper: #fdfaf2;
|
||||
--paper-2: #ffffff;
|
||||
--paper-elev: #ffffff;
|
||||
--paper-warm: #f8f1e1;
|
||||
--ink: #1b1814;
|
||||
--ink-2: #3a342c;
|
||||
--ink-soft: #6b6457;
|
||||
--ink-mute: #968d7d;
|
||||
--line: #ebe2cb;
|
||||
--line-strong: #d5c9aa;
|
||||
|
||||
--brand: #d97757;
|
||||
--brand-deep: #b45a3a;
|
||||
--brand-soft: #fde2d3;
|
||||
--brand-tint: #fff1e8;
|
||||
--brand-ink: #6b2c11;
|
||||
|
||||
--sun: #f4c97d;
|
||||
--sun-deep: #c98e2a;
|
||||
|
||||
--v-vert: #2f7d3a;
|
||||
--v-vert-bg: #e8f3e6;
|
||||
--v-vert-ink: #1b4a22;
|
||||
|
||||
--v-jaune: #b08a00;
|
||||
--v-jaune-bg: #fff5cf;
|
||||
--v-jaune-ink: #5e4900;
|
||||
|
||||
--v-orange: #c25f00;
|
||||
--v-orange-bg: #ffd9b3;
|
||||
--v-orange-ink: #5a2a00;
|
||||
|
||||
--v-rouge: #c01818;
|
||||
--v-rouge-bg: #ffb3b3;
|
||||
--v-rouge-ink: #5a0a0a;
|
||||
|
||||
/* Vivid map fills — closer to Météo France's signage */
|
||||
--map-vert: #c8e3c4;
|
||||
--map-jaune: #ffdf3d;
|
||||
--map-orange: #ff9024;
|
||||
--map-rouge: #e2231a;
|
||||
|
||||
--font-ui: 'Public Sans', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
|
||||
--font-display: 'Manrope', 'Public Sans', system-ui, sans-serif;
|
||||
--font-mono: 'JetBrains Mono', ui-monospace, monospace;
|
||||
|
||||
--r-md: 10px;
|
||||
--r-lg: 16px;
|
||||
--r-pill: 999px;
|
||||
|
||||
--sh-1: 0 1px 2px rgba(27, 24, 20, 0.04);
|
||||
--sh-2: 0 2px 8px rgba(27, 24, 20, 0.06), 0 1px 2px rgba(27, 24, 20, 0.04);
|
||||
--sh-3: 0 12px 32px rgba(27, 24, 20, 0.10), 0 2px 6px rgba(27, 24, 20, 0.06);
|
||||
}
|
||||
|
||||
[data-theme='dark'] {
|
||||
--paper: #14110d;
|
||||
--paper-2: #1c1814;
|
||||
--paper-elev: #221d18;
|
||||
--paper-warm: #2a221a;
|
||||
--ink: #f7f1e2;
|
||||
--ink-2: #d8cfba;
|
||||
--ink-soft: #a89e89;
|
||||
--ink-mute: #756e60;
|
||||
--line: #2f2820;
|
||||
--line-strong: #46402f;
|
||||
|
||||
--brand: #ec9272;
|
||||
--brand-deep: #f4b39a;
|
||||
--brand-soft: #3d2317;
|
||||
--brand-tint: #2a1b10;
|
||||
--brand-ink: #ffd4c1;
|
||||
|
||||
--v-vert: #7dd99a;
|
||||
--v-vert-bg: #15301b;
|
||||
--v-vert-ink: #b8e8c5;
|
||||
|
||||
--v-jaune: #f0c860;
|
||||
--v-jaune-bg: #36290c;
|
||||
--v-jaune-ink: #ffe6a3;
|
||||
|
||||
--v-orange: #ffa970;
|
||||
--v-orange-bg: #3a1d0a;
|
||||
--v-orange-ink: #ffd3b3;
|
||||
|
||||
--v-rouge: #ff8a8a;
|
||||
--v-rouge-bg: #381212;
|
||||
--v-rouge-ink: #ffc4c4;
|
||||
|
||||
--map-vert: #2d5a35;
|
||||
--map-jaune: #f0c860;
|
||||
--map-orange: #ff9050;
|
||||
--map-rouge: #e85050;
|
||||
|
||||
--sh-2: 0 2px 8px rgba(0, 0, 0, 0.35), 0 1px 2px rgba(0, 0, 0, 0.25);
|
||||
--sh-3: 0 12px 32px rgba(0, 0, 0, 0.45), 0 2px 6px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
@layer base {
|
||||
html {
|
||||
@apply text-slate-900 antialiased;
|
||||
background: var(--paper);
|
||||
color: var(--ink);
|
||||
font-family: var(--font-ui);
|
||||
font-size: 17px;
|
||||
line-height: 1.55;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizeLegibility;
|
||||
@apply antialiased;
|
||||
}
|
||||
body {
|
||||
@apply bg-slate-50;
|
||||
background: var(--paper);
|
||||
color: var(--ink);
|
||||
min-height: 100dvh;
|
||||
transition: background-color .25s ease, color .25s ease;
|
||||
}
|
||||
@media (max-width: 720px) {
|
||||
html { font-size: 16px; }
|
||||
}
|
||||
h1, h2, h3, h4 {
|
||||
font-family: var(--font-display);
|
||||
color: var(--ink);
|
||||
letter-spacing: -0.01em;
|
||||
margin: 0;
|
||||
}
|
||||
h1 {
|
||||
font-size: clamp(2rem, 1.4rem + 2.5vw, 3.25rem);
|
||||
line-height: 1.05;
|
||||
font-weight: 800;
|
||||
letter-spacing: -0.025em;
|
||||
}
|
||||
h2 {
|
||||
font-size: clamp(1.5rem, 1.1rem + 1.5vw, 2.25rem);
|
||||
line-height: 1.15;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
h3 {
|
||||
font-size: clamp(1.15rem, 1rem + 0.6vw, 1.5rem);
|
||||
line-height: 1.25;
|
||||
font-weight: 700;
|
||||
}
|
||||
a {
|
||||
@apply underline-offset-2 hover:underline;
|
||||
color: var(--brand-deep);
|
||||
text-decoration: none;
|
||||
border-bottom: 1px solid transparent;
|
||||
transition: border-color .15s;
|
||||
}
|
||||
a:hover { border-bottom-color: currentColor; }
|
||||
*:focus { outline: none; }
|
||||
*:focus-visible {
|
||||
outline: 3px solid var(--brand);
|
||||
outline-offset: 2px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
.container-tight {
|
||||
@apply mx-auto w-full max-w-5xl px-4 sm:px-6;
|
||||
max-width: 1240px;
|
||||
margin-inline: auto;
|
||||
width: 100%;
|
||||
padding-inline: clamp(16px, 4vw, 32px);
|
||||
}
|
||||
|
||||
.kicker {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.75rem;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: var(--ink-soft);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.ic-card {
|
||||
background: var(--paper-elev);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--r-lg);
|
||||
box-shadow: var(--sh-1);
|
||||
padding: 20px;
|
||||
transition: border-color .15s, box-shadow .15s, transform .15s;
|
||||
}
|
||||
.ic-card-soft {
|
||||
background: var(--paper-warm);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--r-lg);
|
||||
padding: 20px;
|
||||
}
|
||||
.ic-card-interactive { cursor: pointer; }
|
||||
.ic-card-interactive:hover {
|
||||
border-color: var(--ink-soft);
|
||||
box-shadow: var(--sh-2);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.ic-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
min-height: 44px;
|
||||
padding: 0 18px;
|
||||
border-radius: var(--r-pill);
|
||||
border: 1.5px solid transparent;
|
||||
font: 600 0.95rem/1 var(--font-ui);
|
||||
cursor: pointer;
|
||||
transition: transform .08s ease, box-shadow .15s, background-color .15s, border-color .15s, color .15s;
|
||||
white-space: nowrap;
|
||||
background: transparent;
|
||||
color: var(--ink);
|
||||
text-decoration: none;
|
||||
}
|
||||
.ic-btn:hover { transform: translateY(-1px); border-bottom-color: transparent; }
|
||||
.ic-btn:active { transform: translateY(0); }
|
||||
.ic-btn-primary { background: var(--ink); color: var(--paper); border-color: var(--ink); }
|
||||
.ic-btn-primary:hover { background: var(--ink-2); border-color: var(--ink-2); box-shadow: var(--sh-2); }
|
||||
.ic-btn-brand { background: var(--brand); color: #fff; border-color: var(--brand); }
|
||||
.ic-btn-brand:hover { background: var(--brand-deep); border-color: var(--brand-deep); box-shadow: var(--sh-2); }
|
||||
.ic-btn-ghost { background: transparent; color: var(--ink); border-color: var(--line-strong); }
|
||||
.ic-btn-ghost:hover { background: var(--paper-warm); border-color: var(--ink-soft); }
|
||||
.ic-btn-sm { min-height: 36px; padding: 0 14px; font-size: 0.85rem; }
|
||||
.ic-btn-lg { min-height: 52px; padding: 0 24px; font-size: 1rem; }
|
||||
|
||||
/* Vigilance pill (legend + chip) */
|
||||
.vigilance-chip {
|
||||
@apply inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-sm font-medium ring-1 ring-inset;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 4px 10px;
|
||||
border-radius: var(--r-pill);
|
||||
border: 1.5px solid;
|
||||
font-size: 0.82rem;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
font-family: var(--font-ui);
|
||||
background: var(--paper-elev);
|
||||
}
|
||||
.vigilance-chip-1 {
|
||||
@apply bg-green-50 text-green-800 ring-green-200;
|
||||
.vigilance-chip .glyph { font-size: 0.75em; line-height: 1; }
|
||||
.vigilance-chip-lg { padding: 8px 16px; font-size: 0.95rem; }
|
||||
.vigilance-chip-lg .glyph { font-size: 0.85em; }
|
||||
|
||||
.vigilance-chip-1 { color: var(--v-vert); background: var(--v-vert-bg); border-color: var(--v-vert); }
|
||||
.vigilance-chip-2 { color: var(--v-jaune); background: var(--v-jaune-bg); border-color: var(--v-jaune); }
|
||||
.vigilance-chip-3 { color: var(--v-orange); background: var(--v-orange-bg); border-color: var(--v-orange); }
|
||||
.vigilance-chip-4 { color: var(--v-rouge); background: var(--v-rouge-bg); border-color: var(--v-rouge); }
|
||||
|
||||
/* Vigilance block (colored card for hero alerts) */
|
||||
.v-block {
|
||||
border-radius: var(--r-lg);
|
||||
border-width: 1.5px;
|
||||
border-style: solid;
|
||||
padding: 20px;
|
||||
}
|
||||
.vigilance-chip-2 {
|
||||
@apply bg-yellow-50 text-yellow-900 ring-yellow-300;
|
||||
.v-vert { background-color: var(--v-vert-bg); border-color: var(--v-vert); color: var(--v-vert-ink); }
|
||||
.v-jaune { background-color: var(--v-jaune-bg); border-color: var(--v-jaune); color: var(--v-jaune-ink); }
|
||||
.v-orange { background-color: var(--v-orange-bg); border-color: var(--v-orange); color: var(--v-orange-ink); }
|
||||
.v-rouge { background-color: var(--v-rouge-bg); border-color: var(--v-rouge); color: var(--v-rouge-ink); }
|
||||
|
||||
/* Input */
|
||||
.ic-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
min-height: 44px;
|
||||
width: 100%;
|
||||
padding: 0 16px;
|
||||
border-radius: var(--r-pill);
|
||||
border: 1.5px solid var(--line-strong);
|
||||
background: var(--paper-2);
|
||||
font: 500 1rem var(--font-ui);
|
||||
color: var(--ink);
|
||||
transition: border-color .15s, box-shadow .15s;
|
||||
}
|
||||
.vigilance-chip-3 {
|
||||
@apply bg-orange-50 text-orange-900 ring-orange-300;
|
||||
.ic-input:focus-within {
|
||||
border-color: var(--brand);
|
||||
box-shadow: 0 0 0 4px var(--brand-tint);
|
||||
}
|
||||
.vigilance-chip-4 {
|
||||
@apply bg-red-50 text-red-900 ring-red-300;
|
||||
.ic-input input {
|
||||
flex: 1;
|
||||
border: none;
|
||||
outline: none;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
min-width: 0;
|
||||
}
|
||||
.ic-input input::placeholder { color: var(--ink-mute); }
|
||||
}
|
||||
|
||||
/* Skip link */
|
||||
.skip-link {
|
||||
position: absolute;
|
||||
top: -100px;
|
||||
left: 8px;
|
||||
background: var(--ink);
|
||||
color: var(--paper);
|
||||
padding: 10px 16px;
|
||||
border-radius: var(--r-md);
|
||||
font-weight: 600;
|
||||
z-index: 1000;
|
||||
transition: top .15s;
|
||||
}
|
||||
.skip-link:focus { top: 8px; border-bottom: none; }
|
||||
|
||||
/* Dept row hover (homepage list) */
|
||||
.dept-row:hover, .dept-row:focus-visible {
|
||||
background-color: var(--paper-warm);
|
||||
}
|
||||
|
||||
/* Sticky blurred header */
|
||||
.site-header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 50;
|
||||
background: color-mix(in srgb, var(--paper) 90%, transparent);
|
||||
backdrop-filter: saturate(140%) blur(12px);
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.site-footer {
|
||||
margin-top: 80px;
|
||||
border-top: 1px solid var(--line);
|
||||
background: var(--paper-warm);
|
||||
}
|
||||
.site-footer h4 {
|
||||
color: var(--ink-soft);
|
||||
font-size: 0.78rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
margin-bottom: 12px;
|
||||
font-family: var(--font-mono);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Map fills (vivid, MF-like) */
|
||||
.france-map-dept {
|
||||
transition: filter .15s, stroke-width .15s;
|
||||
cursor: pointer;
|
||||
}
|
||||
.france-map-dept:hover, .france-map-dept:focus {
|
||||
stroke: var(--ink);
|
||||
stroke-width: 1.2;
|
||||
filter: brightness(1.05);
|
||||
outline: none;
|
||||
}
|
||||
.france-map-fill-1 { fill: var(--map-vert); }
|
||||
.france-map-fill-2 { fill: var(--map-jaune); }
|
||||
.france-map-fill-3 { fill: var(--map-orange); }
|
||||
.france-map-fill-4 { fill: var(--map-rouge); }
|
||||
|
||||
/* Icon button (header toggles) */
|
||||
.icon-btn {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: var(--r-pill);
|
||||
background: transparent;
|
||||
border: 1.5px solid var(--line-strong);
|
||||
cursor: pointer;
|
||||
color: var(--ink);
|
||||
transition: background .15s, border-color .15s;
|
||||
}
|
||||
.icon-btn:hover { background: var(--paper-warm); border-color: var(--ink-soft); }
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*, *::before, *::after {
|
||||
animation-duration: .01ms !important;
|
||||
transition-duration: .01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
scroll-behavior: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media print {
|
||||
.site-header, .site-footer, .no-print { display: none !important; }
|
||||
body { background: white; color: black; font-size: 12pt; }
|
||||
.v-block, .ic-card { box-shadow: none !important; break-inside: avoid; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,9 @@ export default {
|
|||
},
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ['system-ui', '-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'sans-serif'],
|
||||
sans: ['"Public Sans"', 'system-ui', '-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'sans-serif'],
|
||||
display: ['Manrope', '"Public Sans"', 'system-ui', 'sans-serif'],
|
||||
mono: ['"JetBrains Mono"', 'ui-monospace', 'monospace'],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue