refonte: UI/UX complet — landing page éditoriale + design system Tailwind

- BaseLayout: bandeau tricolore, nav sticky mobile (hamburger), footer 4col
- index.astro: landing éditoriale (hero + stats live + méthode + thèmes + scénarios)
  Données Eurostat live depuis indicators.json (dette, déficit, chômage, croissance)
  Thèmes à venir en cards dashed — séparation claire publiés / en préparation
- themes/index.astro: nettoyage inline styles → Tailwind classes
- themes/[slug].astro: header hero refait, nav interne, sidebar sticky, prose améliorée
- donnees/index.astro: grille indicateurs responsive, tableau comparatif
- global.css: design tokens unifiés (nuit/republic/accent/rouge), badges, cards, prose

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marc Reverdin 2026-06-07 10:04:06 +02:00
parent ffb49c3495
commit b687720cce
6 changed files with 706 additions and 632 deletions

View file

@ -14,56 +14,89 @@ const {
} = Astro.props; } = Astro.props;
const canonical = new URL(Astro.url.pathname, Astro.site); const canonical = new URL(Astro.url.pathname, Astro.site);
const currentPath = Astro.url.pathname;
--- ---
<!doctype html> <!doctype html>
<html lang="fr"> <html lang="fr">
<head> <head>
<meta charset="UTF-8" /> <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" />
<title>{title}</title> <title>{title}</title>
<meta name="description" content={description} /> <meta name="description" content={description} />
<link rel="canonical" href={canonical} /> <link rel="canonical" href={canonical} />
<!-- OG -->
<meta property="og:title" content={title} /> <meta property="og:title" content={title} />
<meta property="og:description" content={description} /> <meta property="og:description" content={description} />
<meta property="og:image" content={og_image} /> <meta property="og:image" content={og_image} />
<meta property="og:type" content="website" /> <meta property="og:type" content="website" />
<meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:card" content="summary_large_image" />
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com" /> <link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Playfair+Display:wght@700&display=swap" rel="stylesheet" /> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Playfair+Display:wght@700;900&display=swap" rel="stylesheet" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" /> <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="sitemap" href="/sitemap-index.xml" /> <link rel="sitemap" href="/sitemap-index.xml" />
</head> </head>
<body class="min-h-screen flex flex-col"> <body class="min-h-screen flex flex-col">
<!-- Barre institutionnelle --> <!-- Bandeau tricolore 1px -->
<div style="background: #0f172a; color: white; font-size: 0.75rem; padding: 0.375rem 0; text-align: center; letter-spacing: 0.05em;"> <div class="h-1 w-full flex">
INSTITUT DE RECHERCHE POLITIQUE POUR LA FRANCE · Analyse indépendante · Données publiques <div class="flex-1 bg-[#002395]"></div>
<div class="flex-1 bg-white border-y border-gray-100"></div>
<div class="flex-1 bg-[#ED2939]"></div>
</div> </div>
<!-- Header --> <!-- Header -->
<header style="background: white; border-bottom: 2px solid #1e3a6e; position: sticky; top: 0; z-index: 50;"> <header class="bg-white border-b border-gray-200 sticky top-0 z-50 shadow-sm">
<div style="max-width: 1200px; margin: 0 auto; padding: 0 1.5rem; display: flex; align-items: center; justify-content: space-between; height: 4rem;"> <div class="max-w-7xl mx-auto px-4 sm:px-6 flex items-center justify-between h-14">
<a href="/" style="display: flex; align-items: center; gap: 0.75rem; text-decoration: none;"> <!-- Logo -->
<div style="background: #1e3a6e; color: white; font-weight: 700; font-size: 1.125rem; padding: 0.25rem 0.625rem; border-radius: 0.25rem; letter-spacing: 0.05em;">IRPF</div> <a href="/" class="flex items-center gap-3 no-underline">
<span style="font-size: 0.8rem; color: #374151; font-weight: 500; display: none;" class="sm-show">Institut de Recherche Politique pour la France</span> <div class="bg-[#1e3a6e] text-white font-black text-sm px-2.5 py-1 rounded tracking-wider leading-none">IRPF</div>
<span class="hidden sm:block text-xs text-gray-500 font-medium leading-tight">
Institut de Recherche<br/>Politique pour la France
</span>
</a> </a>
<nav style="display: flex; gap: 1.5rem; align-items: center;"> <!-- Nav desktop -->
<a href="/themes/" style="font-size: 0.875rem; color: #374151; text-decoration: none; font-weight: 500;" class="nav-link">Thèmes</a> <nav class="hidden md:flex items-center gap-0.5">
<a href="/donnees/" style="font-size: 0.875rem; color: #374151; text-decoration: none; font-weight: 500;" class="nav-link">Données</a> {[
<a href="/propositions/" style="font-size: 0.875rem; color: #374151; text-decoration: none; font-weight: 500;" class="nav-link">Propositions</a> { href: '/themes/', label: 'Thèmes' },
<a href="/scenarios/" style="font-size: 0.875rem; color: #374151; text-decoration: none; font-weight: 500;" class="nav-link">Scénarios</a> { href: '/donnees/', label: 'Données' },
<a href="/rapports/" style="font-size: 0.875rem; color: #374151; text-decoration: none; font-weight: 500;" class="nav-link">Rapports</a> { href: '/scenarios/', label: 'Scénarios' },
<a href="/a-propos/" style="font-size: 0.875rem; color: #6b7280; text-decoration: none;" class="nav-link">À propos</a> { href: '/rapports/', label: 'Publications' },
{ href: '/a-propos/', label: 'À propos' },
].map(({ href, label }) => (
<a
href={href}
class={`px-3 py-1.5 rounded text-sm font-medium no-underline transition-colors ${currentPath.startsWith(href) ? 'text-[#1e3a6e] bg-blue-50' : 'text-gray-500 hover:text-[#1e3a6e] hover:bg-gray-50'}`}
>{label}</a>
))}
</nav> </nav>
<!-- Burger mobile -->
<button
id="burger"
class="md:hidden p-2 rounded text-gray-500 hover:bg-gray-100 transition-colors"
aria-label="Menu"
aria-expanded="false"
>
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/>
</svg>
</button>
</div>
<!-- Mobile menu -->
<div id="mobile-menu" class="md:hidden flex-col border-t border-gray-100 bg-white pb-2">
{[
{ href: '/themes/', label: 'Thèmes' },
{ href: '/donnees/', label: 'Données' },
{ href: '/scenarios/', label: 'Scénarios' },
{ href: '/rapports/', label: 'Publications' },
{ href: '/a-propos/', label: 'À propos' },
].map(({ href, label }) => (
<a href={href} class="block px-5 py-3 text-sm text-gray-600 hover:bg-gray-50 hover:text-[#1e3a6e] border-b border-gray-50 no-underline last:border-0">{label}</a>
))}
</div> </div>
</header> </header>
@ -73,45 +106,50 @@ const canonical = new URL(Astro.url.pathname, Astro.site);
</main> </main>
<!-- Footer --> <!-- Footer -->
<footer style="background: #0f172a; color: #9ca3af; margin-top: 4rem;"> <footer class="bg-[#0f172a] text-gray-400 mt-20">
<div style="max-width: 1200px; margin: 0 auto; padding: 3rem 1.5rem; display: grid; grid-template-columns: 2fr 1fr 1fr 1fr; gap: 2rem;"> <div class="max-w-7xl mx-auto px-6 py-12 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-8">
<div> <div class="lg:col-span-2">
<div style="color: white; font-weight: 700; font-size: 1.125rem; margin-bottom: 0.75rem;">IRPF</div> <div class="flex items-center gap-2.5 mb-3">
<p style="font-size: 0.875rem; line-height: 1.6;">Institut de Recherche Politique pour la France. Analyse indépendante, non-partisane, fondée sur les données publiques.</p> <div class="bg-white text-[#0f172a] font-black text-sm px-2.5 py-1 rounded tracking-wider">IRPF</div>
<p style="font-size: 0.75rem; margin-top: 1rem; color: #6b7280;">Sources : INSEE, OCDE, Eurostat, Banque de France, data.gouv.fr et institutions publiques.</p> <span class="text-white font-semibold text-sm">Institut de Recherche Politique pour la France</span>
</div>
<p class="text-sm text-gray-500 leading-relaxed max-w-sm mb-3">
Analyse indépendante, non-partisane, fondée sur les données publiques de l'INSEE, Eurostat, l'OCDE et des institutions parlementaires.
</p>
<p class="text-xs text-gray-600">Un projet <a href="https://reverdin.eu" class="text-gray-500 hover:text-gray-300 no-underline">Reverdin</a></p>
</div> </div>
<div> <div>
<div style="color: white; font-weight: 600; font-size: 0.875rem; margin-bottom: 0.75rem;">Thèmes</div> <div class="text-gray-300 font-semibold text-xs uppercase tracking-wider mb-3">Analyses</div>
<ul style="space-y: 0.5rem; font-size: 0.8rem;"> <ul class="space-y-2 text-sm text-gray-500">
<li><a href="/themes/economie-finances/" style="color: #9ca3af; text-decoration: none; hover: white;">Économie</a></li> <li><a href="/themes/" class="hover:text-white no-underline transition-colors">Thèmes</a></li>
<li><a href="/themes/sante/" style="color: #9ca3af; text-decoration: none;">Santé</a></li> <li><a href="/donnees/" class="hover:text-white no-underline transition-colors">Tableau de bord</a></li>
<li><a href="/themes/education/" style="color: #9ca3af; text-decoration: none;">Éducation</a></li> <li><a href="/scenarios/" class="hover:text-white no-underline transition-colors">Scénarios 2032</a></li>
<li><a href="/themes/" style="color: #6b7280; text-decoration: none;">Voir tous →</a></li> <li><a href="/rapports/" class="hover:text-white no-underline transition-colors">Publications</a></li>
</ul> </ul>
</div> </div>
<div> <div>
<div style="color: white; font-weight: 600; font-size: 0.875rem; margin-bottom: 0.75rem;">Analyses</div> <div class="text-gray-300 font-semibold text-xs uppercase tracking-wider mb-3">Institut</div>
<ul style="font-size: 0.8rem;"> <ul class="space-y-2 text-sm text-gray-500">
<li><a href="/donnees/" style="color: #9ca3af; text-decoration: none;">Tableau de bord</a></li> <li><a href="/a-propos/" class="hover:text-white no-underline transition-colors">Mission</a></li>
<li><a href="/propositions/" style="color: #9ca3af; text-decoration: none;">Propositions</a></li> <li><a href="/methode/" class="hover:text-white no-underline transition-colors">Méthode</a></li>
<li><a href="/scenarios/" style="color: #9ca3af; text-decoration: none;">Scénarios 2032</a></li> <li><a href="/sources/" class="hover:text-white no-underline transition-colors">Sources</a></li>
<li><a href="/rapports/" style="color: #9ca3af; text-decoration: none;">Publications</a></li> <li><a href="/contribuer/" class="hover:text-white no-underline transition-colors">Contribuer</a></li>
</ul>
</div>
<div>
<div style="color: white; font-weight: 600; font-size: 0.875rem; margin-bottom: 0.75rem;">Institut</div>
<ul style="font-size: 0.8rem;">
<li><a href="/a-propos/" style="color: #9ca3af; text-decoration: none;">Mission</a></li>
<li><a href="/acteurs/" style="color: #9ca3af; text-decoration: none;">Cartographie</a></li>
<li><a href="/bibliotheque/" style="color: #9ca3af; text-decoration: none;">Bibliothèque</a></li>
<li><a href="/veille/" style="color: #9ca3af; text-decoration: none;">Veille</a></li>
</ul> </ul>
</div> </div>
</div> </div>
<div style="border-top: 1px solid #1f2937; text-align: center; padding: 1.5rem; font-size: 0.75rem; color: #4b5563;"> <div class="border-t border-gray-800 py-4 text-center text-xs text-gray-600">
© {new Date().getFullYear()} Institut de Recherche Politique pour la France · Données publiques · Analyse libre © {new Date().getFullYear()} Institut de Recherche Politique pour la France · Données publiques · Analyse libre
</div> </div>
</footer> </footer>
</body> </body>
</html> </html>
<script>
const burger = document.getElementById('burger');
const menu = document.getElementById('mobile-menu');
burger?.addEventListener('click', () => {
const open = menu?.classList.toggle('open');
burger?.setAttribute('aria-expanded', String(!!open));
});
</script>

View file

@ -1,23 +1,15 @@
--- ---
import BaseLayout from '../../layouts/BaseLayout.astro'; import BaseLayout from '../../layouts/BaseLayout.astro';
import KpiLive from '../../components/KpiLive.astro'; import KpiLive from '../../components/KpiLive.astro';
import { INDICATEURS_BY_THEME, PAYS_LABEL } from '../../lib/indicators-config.ts';
import { existsSync, readFileSync } from 'node:fs'; import { existsSync, readFileSync } from 'node:fs';
import { join } from 'node:path'; import { join } from 'node:path';
// Charger le JSON des indicateurs (généré en pré-build) let indicateursData: any = { indicators: [], generated_at: null };
// Utilise readFileSync + existsSync pour éviter l'échec de résolution statique Vite
let indicateursData: any = { indicators: [], insee: {}, generated_at: null };
const jsonPath = join(process.cwd(), 'src/data/live/indicators.json'); const jsonPath = join(process.cwd(), 'src/data/live/indicators.json');
if (existsSync(jsonPath)) { if (existsSync(jsonPath)) {
try { try { indicateursData = JSON.parse(readFileSync(jsonPath, 'utf-8')); } catch {}
indicateursData = JSON.parse(readFileSync(jsonPath, 'utf-8'));
} catch {
// fichier corrompu, on utilise les défauts
}
} }
// Indexer par ID pour accès rapide
const byId = Object.fromEntries( const byId = Object.fromEntries(
(indicateursData.indicators || []).map((i: any) => [i.id, i]) (indicateursData.indicators || []).map((i: any) => [i.id, i])
); );
@ -26,54 +18,18 @@ const generatedAt = indicateursData.generated_at
? new Date(indicateursData.generated_at).toLocaleDateString('fr-FR', { day: 'numeric', month: 'long', year: 'numeric', hour: '2-digit', minute: '2-digit' }) ? new Date(indicateursData.generated_at).toLocaleDateString('fr-FR', { day: 'numeric', month: 'long', year: 'numeric', hour: '2-digit', minute: '2-digit' })
: null; : null;
// Groupes d'indicateurs pour le dashboard
const GROUPES = [ const GROUPES = [
{ { titre: 'Finances publiques', couleur: '#1e3a6e', ids: ['deficit_pib','dette_pib','depenses_publiques_pib','prelevements_obligatoires','croissance_pib','depenses_sociales_pib'] },
titre: 'Finances publiques', { titre: 'Emploi', couleur: '#7c3aed', ids: ['taux_emploi','taux_chomage','taux_chomage_jeunes','taux_emploi_seniors'] },
couleur: '#1e3a6e', { titre: 'Santé', couleur: '#dc2626', ids: ['esperance_vie','depenses_sante_pib'] },
bg: '#eff6ff', { titre: 'Éducation', couleur: '#0891b2', ids: ['decrochage_scolaire','diplomes_superieurs','depenses_education_pib'] },
ids: ['deficit_pib', 'dette_pib', 'depenses_publiques_pib', 'prelevements_obligatoires', 'croissance_pib', 'depenses_sociales_pib'], { titre: 'Cohésion sociale', couleur: '#d97706', ids: ['gini','taux_pauvrete','taux_pauvrete_enfants'] },
}, { titre: 'Logement', couleur: '#059669', ids: ['indice_prix_immobilier','surcharge_logement','taux_proprietaires'] },
{ { titre: 'Environnement & Énergie', couleur: '#16a34a', ids: ['emissions_ghg_per_capita','part_energies_renouvelables','intensite_energetique'] },
titre: 'Emploi & Marché du travail',
couleur: '#7c3aed',
bg: '#f5f3ff',
ids: ['taux_emploi', 'taux_chomage', 'taux_chomage_jeunes', 'taux_emploi_seniors'],
},
{
titre: 'Santé',
couleur: '#dc2626',
bg: '#fef2f2',
ids: ['esperance_vie', 'depenses_sante_pib'],
},
{
titre: 'Éducation',
couleur: '#0891b2',
bg: '#ecfeff',
ids: ['decrochage_scolaire', 'diplomes_superieurs', 'depenses_education_pib'],
},
{
titre: 'Cohésion sociale',
couleur: '#d97706',
bg: '#fffbeb',
ids: ['gini', 'taux_pauvrete', 'taux_pauvrete_enfants'],
},
{
titre: 'Logement',
couleur: '#059669',
bg: '#f0fdf4',
ids: ['indice_prix_immobilier', 'surcharge_logement', 'taux_proprietaires'],
},
{
titre: 'Environnement & Énergie',
couleur: '#16a34a',
bg: '#f0fdf4',
ids: ['emissions_ghg_per_capita', 'part_energies_renouvelables', 'intensite_energetique'],
},
]; ];
const PAYS_ORDRE = ['FR', 'DE', 'ES', 'IT', 'SE', 'NL', 'EU27_2020']; const PAYS_ORDRE = ['FR', 'DE', 'ES', 'IT', 'SE', 'NL', 'EU27_2020'];
const INDICATEURS_TABLE = ['deficit_pib', 'dette_pib', 'depenses_publiques_pib', 'taux_chomage', 'taux_emploi', 'esperance_vie', 'gini', 'emissions_ghg_per_capita']; const INDICATEURS_TABLE = ['deficit_pib','dette_pib','depenses_publiques_pib','taux_chomage','taux_emploi','esperance_vie','gini','emissions_ghg_per_capita'];
--- ---
<BaseLayout <BaseLayout
@ -82,54 +38,62 @@ const INDICATEURS_TABLE = ['deficit_pib', 'dette_pib', 'depenses_publiques_pib',
> >
<!-- Header --> <!-- Header -->
<section style="background: linear-gradient(135deg, #0f172a 0%, #1e3a6e 100%); color: white; padding: 3rem 1.5rem 2rem;"> <section class="bg-gradient-to-br from-[#0f172a] to-[#1e3a6e] text-white px-6 py-14">
<div style="max-width: 1200px; margin: 0 auto;"> <div class="max-w-7xl mx-auto">
<div style="font-size: 0.75rem; letter-spacing: 0.1em; color: #93c5fd; margin-bottom: 0.75rem;">IRPF — DONNÉES OUVERTES</div> <nav class="text-xs text-gray-500 mb-6 flex items-center gap-1.5">
<h1 style="font-family: 'Playfair Display', serif; font-size: 2.25rem; margin-bottom: 1rem;">Tableau de bord France</h1> <a href="/" class="hover:text-gray-300 no-underline">Accueil</a>
<p style="color: #cbd5e1; max-width: 650px; line-height: 1.7; margin-bottom: 1.5rem;"> <span></span>
Indicateurs clés de la France comparés à l'Allemagne, l'Espagne, l'Italie, la Suède, les Pays-Bas et la moyenne UE27. <span class="text-gray-400">Données</span>
Sources : Eurostat API (données ouvertes) + INSEE BDM. </nav>
<div class="text-xs text-blue-400 font-semibold uppercase tracking-wider mb-3">IRPF — Données ouvertes</div>
<h1 class="font-serif text-4xl font-black mb-3">Tableau de bord France</h1>
<p class="text-blue-200 max-w-2xl leading-relaxed text-sm mb-5">
Indicateurs clés de la France comparés à l'Allemagne, l'Espagne, l'Italie, la Suède, les Pays-Bas et la moyenne UE27. Sources : Eurostat API + INSEE BDM.
</p> </p>
{generatedAt && ( {generatedAt && (
<div style="display: flex; align-items: center; gap: 0.5rem; font-size: 0.75rem; color: #94a3b8;"> <div class="flex items-center gap-2 text-xs text-gray-400">
<span style="width: 8px; height: 8px; background: #22c55e; border-radius: 50;"></span> <span class="w-2 h-2 rounded-full bg-green-400 inline-block"></span>
Mis à jour le {generatedAt} — {indicateursData.indicators?.length || 0} indicateurs collectés Mis à jour le {generatedAt} — {indicateursData.indicators?.length ?? 0} indicateurs
</div> </div>
)} )}
</div> </div>
</section> </section>
<!-- Navigation indicateurs --> <!-- Nav groupes -->
<div style="background: white; border-bottom: 1px solid #e5e7eb; position: sticky; top: 0; z-index: 10;"> <div class="bg-white border-b border-gray-200 sticky top-[57px] z-30 overflow-x-auto">
<div style="max-width: 1200px; margin: 0 auto; padding: 0 1.5rem; overflow-x: auto;"> <div class="max-w-7xl mx-auto px-6 flex gap-0 scrollbar-hide">
<nav style="display: flex; gap: 0; white-space: nowrap;">
{GROUPES.map(g => ( {GROUPES.map(g => (
<a href={`#${g.titre.toLowerCase().replace(/[^a-z0-9]+/g, '-')}`} <a
style="padding: 0.875rem 1rem; font-size: 0.8rem; font-weight: 500; color: #6b7280; text-decoration: none; border-bottom: 2px solid transparent; display: inline-block;"> href={`#${g.titre.toLowerCase().replace(/[^a-z0-9]+/g, '-')}`}
class="px-3 py-3.5 text-xs font-medium text-gray-500 hover:text-[#1e3a6e] whitespace-nowrap border-b-2 border-transparent hover:border-[#1e3a6e] -mb-px transition-colors no-underline"
>
{g.titre} {g.titre}
</a> </a>
))} ))}
<a href="#comparaison" style="padding: 0.875rem 1rem; font-size: 0.8rem; font-weight: 500; color: #1e3a6e; text-decoration: none; border-bottom: 2px solid #1e3a6e;"> <a
href="#comparaison"
class="px-3 py-3.5 text-xs font-semibold text-[#1e3a6e] whitespace-nowrap border-b-2 border-[#1e3a6e] -mb-px no-underline ml-auto"
>
Comparatif EU → Comparatif EU →
</a> </a>
</nav>
</div> </div>
</div> </div>
<div style="max-width: 1200px; margin: 0 auto; padding: 3rem 1.5rem;"> <div class="max-w-7xl mx-auto px-6 py-10 space-y-12">
<!-- Groupes d'indicateurs --> <!-- Groupes d'indicateurs -->
{GROUPES.map(groupe => { {GROUPES.map(groupe => {
const inds = groupe.ids.map(id => byId[id]).filter(Boolean); const inds = groupe.ids.map((id: string) => byId[id]).filter(Boolean);
if (inds.length === 0) return null; if (!inds.length) return null;
const ancre = groupe.titre.toLowerCase().replace(/[^a-z0-9]+/g, '-');
return ( return (
<section id={groupe.titre.toLowerCase().replace(/[^a-z0-9]+/g, '-')} style="margin-bottom: 3rem;"> <section id={ancre}>
<div style="display: flex; align-items: center; gap: 0.75rem; margin-bottom: 1.25rem;"> <div class="flex items-center gap-3 mb-5">
<div style={`width: 3px; height: 1.5rem; background: ${groupe.couleur}; border-radius: 2px;`}></div> <div class="w-1 h-6 rounded-full shrink-0" style={`background:${groupe.couleur}`}></div>
<h2 style={`font-size: 1.1rem; font-weight: 700; color: ${groupe.couleur}; margin: 0;`}>{groupe.titre}</h2> <h2 class="font-semibold text-base" style={`color:${groupe.couleur}`}>{groupe.titre}</h2>
<div style="flex: 1; height: 1px; background: #e5e7eb;"></div> <div class="flex-1 h-px bg-gray-200"></div>
</div> </div>
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 0.75rem;"> <div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-6 gap-3">
{inds.map((ind: any) => ( {inds.map((ind: any) => (
<KpiLive <KpiLive
label={ind.label} label={ind.label}
@ -147,45 +111,44 @@ const INDICATEURS_TABLE = ['deficit_pib', 'dette_pib', 'depenses_publiques_pib',
})} })}
<!-- Tableau comparatif --> <!-- Tableau comparatif -->
<section id="comparaison" style="margin-top: 2rem;"> <section id="comparaison">
<div style="display: flex; align-items: center; gap: 0.75rem; margin-bottom: 1.5rem;"> <div class="flex items-center gap-3 mb-6">
<h2 style="font-family: 'Playfair Display', serif; font-size: 1.5rem; color: #0f172a; margin: 0;">Comparatif France — Europe</h2> <h2 class="font-serif text-2xl text-[#0f172a]">Comparatif France — Europe</h2>
<div style="flex: 1; height: 1px; background: #e5e7eb;"></div> <div class="flex-1 h-px bg-gray-200"></div>
<span style="font-size: 0.75rem; color: #9ca3af;">Source : Eurostat API</span> <span class="text-xs text-gray-400">Eurostat API</span>
</div> </div>
<div style="overflow-x: auto; border-radius: 0.5rem; border: 1px solid #e5e7eb;"> <div class="overflow-x-auto rounded-xl border border-gray-200 shadow-sm">
<table style="width: 100%; border-collapse: collapse; font-size: 0.82rem;"> <table class="w-full text-sm border-collapse">
<thead> <thead>
<tr style="background: #f8fafc; border-bottom: 2px solid #e5e7eb;"> <tr class="bg-[#0f172a] text-white text-xs">
<th style="text-align: left; padding: 0.75rem 1rem; font-weight: 600; color: #374151; min-width: 200px;">Indicateur</th> <th class="text-left px-4 py-3 font-semibold min-w-[180px]">Indicateur</th>
{PAYS_ORDRE.map(code => ( {PAYS_ORDRE.map(code => (
<th style="text-align: right; padding: 0.75rem 0.75rem; font-weight: 600; color: code === 'FR' ? '#1e3a6e' : '#374151; min-width: 80px;"> <th class={`text-right px-3 py-3 font-semibold min-w-[70px] ${code === 'FR' ? 'text-blue-300' : ''}`}>
{code === 'EU27_2020' ? 'UE27' : code} {code === 'EU27_2020' ? 'UE27' : code}
</th> </th>
))} ))}
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{INDICATEURS_TABLE.map((id, idx) => { {INDICATEURS_TABLE.map((id: string, idx: number) => {
const ind = byId[id]; const ind = byId[id];
if (!ind) return null; if (!ind) return null;
return ( return (
<tr style={`border-bottom: 1px solid #e5e7eb; background: ${idx % 2 === 0 ? 'white' : '#f8fafc'};`}> <tr class={idx % 2 === 0 ? 'bg-white' : 'bg-gray-50'}>
<td style="padding: 0.625rem 1rem; font-weight: 500; color: #374151;"> <td class="px-4 py-3 font-medium text-gray-700">
{ind.label} {ind.label}
<div style="font-size: 0.7rem; color: #9ca3af; font-weight: 400;">{ind.unite}</div> <div class="text-xs text-gray-400 font-normal">{ind.unite}</div>
</td> </td>
{PAYS_ORDRE.map(code => { {PAYS_ORDRE.map((code: string) => {
const pv = code === 'FR' ? ind.france : ind.pays?.[code]; const pv = code === 'FR' ? ind.france : ind.pays?.[code];
const val = pv?.value; const val = pv?.value;
const formatted = val !== null && val !== undefined const txt = val != null
? (Math.abs(val) >= 100 ? Math.round(val).toLocaleString('fr-FR') : val.toFixed(1)) ? (Math.abs(val) >= 100 ? Math.round(val).toLocaleString('fr-FR') : Number(val).toFixed(1))
: '—'; : '—';
const isFr = code === 'FR';
return ( return (
<td style={`text-align: right; padding: 0.625rem 0.75rem; font-weight: ${isFr ? '700' : '400'}; color: ${isFr ? '#1e3a6e' : '#374151'};`}> <td class={`text-right px-3 py-3 font-mono text-xs ${code === 'FR' ? 'font-bold text-[#1e3a6e]' : 'text-gray-600'}`}>
{formatted} {txt}
</td> </td>
); );
})} })}
@ -195,33 +158,27 @@ const INDICATEURS_TABLE = ['deficit_pib', 'dette_pib', 'depenses_publiques_pib',
</tbody> </tbody>
</table> </table>
</div> </div>
<p style="font-size: 0.72rem; color: #9ca3af; margin-top: 0.75rem;"> <p class="text-xs text-gray-400 mt-2">
Dernière mise à jour : {generatedAt || 'N/A'} — Eurostat API JSON-stat 2.0 Mis à jour le {generatedAt ?? 'N/A'} — Eurostat API JSON-stat 2.0
</p> </p>
</section> </section>
<!-- Note méthodologique --> <!-- Note méthodologique -->
<section style="background: #f8fafc; border: 1px solid #e5e7eb; border-radius: 0.5rem; padding: 1.5rem; margin-top: 2rem;"> <div class="bg-[#f8fafc] border border-gray-200 rounded-xl p-6">
<h3 style="font-size: 0.9rem; font-weight: 700; color: #374151; margin-bottom: 0.75rem;">Sources et méthodologie</h3> <h3 class="text-sm font-bold text-gray-700 mb-3">Sources et méthodologie</h3>
<div style="font-size: 0.8rem; color: #6b7280; line-height: 1.7; display: grid; grid-template-columns: 1fr 1fr; gap: 1rem;"> <div class="grid sm:grid-cols-2 gap-5 text-xs text-gray-500 leading-relaxed">
<div> <p>
<strong>Eurostat</strong> — API JSON-stat 2.0, données officielles de l'Office statistique de l'Union européenne. <strong class="text-gray-700">Eurostat</strong> — API JSON-stat 2.0, données officielles de l'Office statistique de l'Union européenne. Mise à jour automatique à chaque rebuild du site (max 6h de délai). CORS ouvert, pas d'authentification requise.
Mise à jour automatique à chaque rebuild du site (max 6h de délai). </p>
CORS ouvert, pas d'authentification requise. <p>
</div> <strong class="text-gray-700">Comparaison</strong> — France vs Allemagne, Espagne, Italie, Suède, Pays-Bas et moyenne UE27. Données non disponibles (N/D) = non publiées par Eurostat pour cette période. INSEE OAuth2 en cours de configuration.
<div> </p>
<strong>Comparaison</strong> — France vs Allemagne, Espagne, Italie, Suède, Pays-Bas et moyenne UE27.
Les valeurs <span style="color: #dc2626;">en rouge</span> indiquent un retard par rapport à la moyenne UE ;
les valeurs <span style="color: #16a34a;">en vert</span> indiquent une avance.
Données non disponibles (N/D) = non publiées par Eurostat pour cette période.
</div> </div>
</div> </div>
</section>
</div> </div>
</BaseLayout> </BaseLayout>
<style> <style>
th { white-space: nowrap; } [id] { scroll-margin-top: 110px; }
</style> </style>

View file

@ -1,148 +1,275 @@
--- ---
import BaseLayout from '../layouts/BaseLayout.astro'; import BaseLayout from '../layouts/BaseLayout.astro';
import { getCollection } from 'astro:content'; import { getCollection } from 'astro:content';
import { existsSync, readFileSync } from 'node:fs';
import { join } from 'node:path';
const themes = (await getCollection('themes')).sort((a, b) => a.data.ordre - b.data.ordre); const themes = (await getCollection('themes')).sort((a, b) => a.data.ordre - b.data.ordre);
const rapports = (await getCollection('rapports')).slice(0, 3);
// Indicateurs live
let byId: Record<string, any> = {};
let generatedAt = '';
const jsonPath = join(process.cwd(), 'src/data/live/indicators.json');
if (existsSync(jsonPath)) {
try {
const d = JSON.parse(readFileSync(jsonPath, 'utf-8'));
byId = Object.fromEntries((d.indicators ?? []).map((i: any) => [i.id, i]));
generatedAt = d.generated_at ?? '';
} catch {}
}
function fmt(id: string, digits = 1): string {
const v = byId[id]?.france?.value;
if (v == null) return 'N/D';
return Number(v).toLocaleString('fr-FR', { minimumFractionDigits: digits, maximumFractionDigits: digits });
}
const THEMES_A_VENIR = [
'Éducation', 'Emploi & Travail', 'Retraites', 'Logement',
'Justice & Sécurité', 'Énergie & Environnement', 'Agriculture',
'Numérique', 'Cohésion sociale', 'Démocratie', 'Europe', 'Immigration',
];
--- ---
<BaseLayout <BaseLayout
title="IRPF — Institut de Recherche Politique pour la France" title="IRPF — Institut de Recherche Politique pour la France"
description="Analyse indépendante des grandes problématiques françaises. Données, diagnostics, propositions pour 2027 et au-delà." description="Analyse non-partisane des politiques publiques françaises fondée sur les données de l'INSEE, l'OCDE et Eurostat. Diagnostics, propositions, scénarios pour 2027."
> >
<!-- Hero --> <!-- ── HERO ──────────────────────────────────────────── -->
<section style="background: linear-gradient(135deg, #0f172a 0%, #1e3a6e 100%); color: white; padding: 5rem 1.5rem 4rem;"> <section class="relative bg-white overflow-hidden">
<div style="max-width: 900px; margin: 0 auto; text-align: center;"> <!-- Fond graphique subtil -->
<div style="font-size: 0.8rem; letter-spacing: 0.15em; color: #93c5fd; margin-bottom: 1.5rem; font-weight: 500;"> <div class="absolute inset-0 bg-[radial-gradient(ellipse_at_top_right,_#eff6ff_0%,_transparent_60%)]"></div>
INSTITUT DE RECHERCHE POLITIQUE POUR LA FRANCE <div class="relative max-w-7xl mx-auto px-6 pt-20 pb-16 lg:pt-28 lg:pb-24 grid lg:grid-cols-2 gap-12 items-center">
</div> <div>
<h1 style="font-family: 'Playfair Display', serif; font-size: 3rem; font-weight: 700; line-height: 1.2; margin-bottom: 1.5rem;"> <div class="section-label mb-4">Institut de Recherche Politique pour la France</div>
Comprendre la France.<br/> <h1 class="font-serif text-5xl lg:text-6xl font-black text-[#0f172a] leading-tight mb-6">
<span style="color: #93c5fd;">Proposer des solutions.</span> La France<br/>sous les données.
</h1> </h1>
<p style="font-size: 1.125rem; color: #cbd5e1; max-width: 680px; margin: 0 auto 2.5rem; line-height: 1.7;"> <p class="text-lg text-gray-500 leading-relaxed mb-8 max-w-xl">
Analyse non-partisane des politiques publiques françaises, fondée sur les données de l'INSEE, de l'OCDE et des institutions internationales. Pour alimenter le débat démocratique en 2027 et au-delà. Des diagnostics rigoureux sur les 14 grandes problématiques françaises. Des données, pas des slogans. Des propositions chiffrées, pas des promesses.
</p> </p>
<div style="display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap;"> <div class="flex flex-wrap gap-3">
<a href="/themes/" style="background: white; color: #1e3a6e; padding: 0.75rem 2rem; border-radius: 0.375rem; font-weight: 600; text-decoration: none; font-size: 0.9rem;"> <a href="/themes/" class="inline-flex items-center gap-2 bg-[#1e3a6e] text-white px-5 py-2.5 rounded-lg font-semibold text-sm hover:bg-[#162d56] transition-colors no-underline">
Explorer les thèmes → Explorer les analyses →
</a> </a>
<a href="/donnees/" style="border: 1px solid rgba(255,255,255,0.3); color: white; padding: 0.75rem 2rem; border-radius: 0.375rem; font-weight: 500; text-decoration: none; font-size: 0.9rem;"> <a href="/donnees/" class="inline-flex items-center gap-2 border border-gray-300 text-gray-600 px-5 py-2.5 rounded-lg font-medium text-sm hover:border-[#1e3a6e] hover:text-[#1e3a6e] transition-colors no-underline">
Tableau de bord France Tableau de bord France
</a> </a>
</div> </div>
<p class="mt-5 text-xs text-gray-400">
Non-partisan · Données publiques · Accès libre
</p>
</div> </div>
</section>
<!-- KPIs --> <!-- Carte stat mise en avant -->
<section style="background: #f8fafc; border-bottom: 1px solid #e5e7eb; padding: 2rem 1.5rem;"> <div class="hidden lg:block">
<div style="max-width: 1200px; margin: 0 auto;"> <div class="bg-[#0f172a] rounded-2xl p-8 text-white shadow-2xl">
<div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 1rem;"> <div class="text-xs text-blue-400 font-semibold uppercase tracking-wider mb-6">France — chiffres clés 2025</div>
<div class="kpi-card" style="text-align: center;"> <div class="space-y-5">
<div class="kpi-value">-5,5%</div> {[
<div class="kpi-label">Déficit public / PIB 2024</div> { id: 'dette_pib', label: 'Dette publique / PIB', unit: '%', alert: true },
<div class="kpi-source">Source : INSEE</div> { id: 'deficit_pib', label: 'Déficit public / PIB', unit: '%', alert: true },
{ id: 'taux_chomage_jeunes',label: 'Chômage des jeunes', unit: '%', alert: false },
{ id: 'croissance_pib', label: 'Croissance du PIB', unit: '%', alert: false },
{ id: 'esperance_vie', label: 'Espérance de vie', unit: ' ans', alert: false },
].map(({ id, label, unit, alert }) => (
<div class="flex items-baseline justify-between border-b border-white/10 pb-4 last:border-0 last:pb-0">
<span class="text-sm text-gray-400">{label}</span>
<span class={`font-mono font-bold text-lg ${alert ? 'text-red-400' : 'text-green-400'}`}>
{fmt(id)}{unit}
</span>
</div> </div>
<div class="kpi-card" style="text-align: center;"> ))}
<div class="kpi-value">113%</div>
<div class="kpi-label">Dette publique / PIB</div>
<div class="kpi-source">Source : Eurostat</div>
</div> </div>
<div class="kpi-card" style="text-align: center;"> <div class="mt-5 text-xs text-gray-600 text-right">
<div class="kpi-value">7,3%</div> Source : Eurostat · {generatedAt ? new Date(generatedAt).toLocaleDateString('fr-FR', { month: 'long', year: 'numeric' }) : ''}
<div class="kpi-label">Taux de chômage T1 2025</div>
<div class="kpi-source">Source : DARES</div>
</div> </div>
<div class="kpi-card" style="text-align: center;">
<div class="kpi-value">28e</div>
<div class="kpi-label">Rang PISA 2022 (sciences)</div>
<div class="kpi-source">Source : OCDE</div>
</div> </div>
</div> </div>
</div> </div>
</section> </section>
<!-- Manifeste --> <!-- ── CHIFFRES CLÉS (mobile / complément) ─────────── -->
<section style="padding: 4rem 1.5rem; max-width: 780px; margin: 0 auto;"> <section class="bg-[#0f172a] border-t border-gray-800">
<div style="font-size: 0.8rem; letter-spacing: 0.1em; color: #1e3a6e; font-weight: 600; margin-bottom: 1rem;">NOTRE DÉMARCHE</div> <div class="max-w-7xl mx-auto px-6 py-8 grid grid-cols-2 sm:grid-cols-4 gap-px bg-gray-800">
<h2 style="font-family: 'Playfair Display', serif; font-size: 2rem; color: #0f172a; margin-bottom: 1.5rem;"> {[
La France a besoin d'un diagnostic rigoureux avant un programme { id: 'dette_pib', label: 'Dette / PIB', unit: '%' },
</h2> { id: 'deficit_pib', label: 'Déficit / PIB', unit: '%' },
<div style="color: #374151; line-height: 1.8; font-size: 1.05rem;"> { id: 'taux_chomage', label: 'Chômage', unit: '%' },
<p style="margin-bottom: 1rem;">Le débat politique français souffre d'un paradoxe : beaucoup de propositions, peu de données ; beaucoup de slogans, peu de diagnostic. L'IRPF fait le chemin inverse.</p> { id: 'prelevements_obligatoires', label: 'Prélèvements oblig.', unit: '%' },
<p style="margin-bottom: 1rem;">Nous partons des faits — ceux de l'INSEE, de l'OCDE, de la Cour des comptes, des rapports parlementaires — pour cartographier chaque grande problématique française, recenser toutes les propositions des acteurs du débat, et proposer des analyses originales.</p> ].map(({ id, label, unit }) => (
<p>Ni de gauche ni de droite. Exigeant sur les chiffres. Libre sur les idées.</p> <div class="bg-[#0f172a] px-6 py-5 text-center">
<div class="font-mono text-2xl font-bold text-white">{fmt(id)}<span class="text-sm">{unit}</span></div>
<div class="text-xs text-gray-500 mt-1">{label}</div>
<div class="text-xs text-gray-700 mt-0.5">{byId[id]?.france?.year ?? ''}</div>
</div>
))}
</div> </div>
</section> </section>
<!-- Thèmes --> <!-- ── MÉTHODE ─────────────────────────────────────── -->
<section style="background: #f8fafc; padding: 4rem 1.5rem; border-top: 1px solid #e5e7eb;"> <section class="py-20 px-6 max-w-7xl mx-auto">
<div style="max-width: 1200px; margin: 0 auto;"> <div class="text-center mb-12">
<div style="display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 2rem;"> <div class="section-label mb-3">Notre démarche</div>
<h2 class="font-serif text-3xl text-[#0f172a] mb-4">Le diagnostic avant le programme</h2>
<p class="text-gray-500 max-w-2xl mx-auto leading-relaxed">
Le débat politique français manque de données. L'IRPF fait le chemin inverse : partir des faits pour cartographier chaque problématique, recenser les propositions, et évaluer leur impact.
</p>
</div>
<div class="grid md:grid-cols-3 gap-8">
{[
{
num: '01',
titre: 'Données publiques',
desc: 'INSEE, OCDE, Eurostat, Banque de France, Cour des comptes, data.gouv.fr. Chaque chiffre est sourcé, daté, contextualisé.',
color: '#1e3a6e',
},
{
num: '02',
titre: 'Diagnostic rigoureux',
desc: 'Cartographie des dynamiques longues, comparaisons européennes, identification des nœuds structurels — sans idéologie.',
color: '#2563eb',
},
{
num: '03',
titre: 'Propositions chiffrées',
desc: 'Recensement de toutes les propositions des acteurs politiques, économiques et de la société civile. Évaluation de leur faisabilité.',
color: '#0891b2',
},
].map(({ num, titre, desc, color }) => (
<div class="relative p-8 rounded-xl border border-gray-200 bg-white hover:shadow-md transition-shadow">
<div class="text-5xl font-black mb-4 leading-none" style={`color: ${color}; opacity: 0.12;`}>{num}</div>
<h3 class="font-semibold text-[#0f172a] text-lg mb-3">{titre}</h3>
<p class="text-sm text-gray-500 leading-relaxed">{desc}</p>
</div>
))}
</div>
</section>
<!-- ── THÈMES ─────────────────────────────────────── -->
<section class="bg-[#f8fafc] border-y border-gray-200 py-20 px-6">
<div class="max-w-7xl mx-auto">
<div class="flex items-baseline justify-between mb-10">
<div> <div>
<div style="font-size: 0.8rem; letter-spacing: 0.1em; color: #1e3a6e; font-weight: 600; margin-bottom: 0.5rem;">14 GRANDES THÉMATIQUES</div> <div class="section-label mb-2">14 grandes thématiques</div>
<h2 style="font-family: 'Playfair Display', serif; font-size: 1.875rem; color: #0f172a;">Les chantiers de la France</h2> <h2 class="font-serif text-3xl text-[#0f172a]">Les chantiers de la France</h2>
</div> </div>
<a href="/themes/" style="color: #1e3a6e; font-weight: 500; text-decoration: none; font-size: 0.9rem;">Voir tout →</a> <a href="/themes/" class="text-sm text-[#1e3a6e] font-medium hover:underline no-underline hidden sm:block">Voir tout →</a>
</div> </div>
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem;">
{themes.slice(0, 6).map((theme) => ( <!-- Fiches publiées -->
<a href={`/themes/${theme.data.slug}/`} style="background: white; border: 1px solid #e5e7eb; border-radius: 0.5rem; padding: 1.5rem; text-decoration: none; display: block;" class="theme-card"> {themes.length > 0 && (
<div style="font-weight: 600; color: #0f172a; margin-bottom: 0.5rem;">{theme.data.title}</div> <div class="mb-6">
<p style="font-size: 0.85rem; color: #6b7280; line-height: 1.5; margin-bottom: 1rem;">{theme.data.description}</p> <div class="flex items-center gap-3 mb-5">
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap;"> <span class="badge badge-dark">EN LIGNE</span>
{theme.data.priorite === 'critique' && ( <div class="flex-1 h-px bg-gray-300"></div>
<span style="background: #fee2e2; color: #991b1b; font-size: 0.7rem; font-weight: 600; padding: 0.2rem 0.5rem; border-radius: 0.25rem;">CRITIQUE</span> </div>
)} <div class="grid sm:grid-cols-2 gap-5">
{themes.map(theme => (
<a href={`/themes/${theme.data.slug}/`} class="card p-6 no-underline block group">
<div class="flex items-start justify-between gap-3 mb-3">
<h3 class="font-semibold text-[#0f172a] text-base group-hover:text-[#1e3a6e] transition-colors leading-snug">
{theme.data.title}
</h3>
{theme.data.priorite === 'critique' && <span class="badge badge-critique shrink-0">CRITIQUE</span>}
{theme.data.priorite === 'haute' && <span class="badge badge-haute shrink-0">PRIORITÉ</span>}
</div>
<p class="text-sm text-gray-500 leading-relaxed mb-4">{theme.data.description}</p>
<div class="flex items-center justify-between">
<div class="flex gap-2 flex-wrap">
{theme.data.budget_etat_milliards && ( {theme.data.budget_etat_milliards && (
<span style="background: #eff6ff; color: #1d4ed8; font-size: 0.7rem; padding: 0.2rem 0.5rem; border-radius: 0.25rem;">{theme.data.budget_etat_milliards} Md€</span> <span class="badge badge-blue">{theme.data.budget_etat_milliards} Md€/an</span>
)} )}
{theme.data.tags.slice(0, 2).map((tag: string) => (
<span class="badge badge-muted">#{tag}</span>
))}
</div>
<span class="text-xs text-[#1e3a6e] font-medium opacity-0 group-hover:opacity-100 transition-opacity">Lire →</span>
</div> </div>
</a> </a>
))} ))}
</div> </div>
{themes.length > 6 && (
<div style="text-align: center; margin-top: 2rem;">
<a href="/themes/" style="display: inline-block; border: 1px solid #1e3a6e; color: #1e3a6e; padding: 0.625rem 1.5rem; border-radius: 0.375rem; font-weight: 500; text-decoration: none; font-size: 0.9rem;">
Voir les {themes.length - 6} autres thèmes
</a>
</div> </div>
)} )}
<!-- Thèmes à venir -->
<div>
<div class="flex items-center gap-3 mb-5">
<span class="badge badge-muted">À VENIR</span>
<div class="flex-1 h-px bg-gray-200"></div>
</div>
<div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-3">
{THEMES_A_VENIR.map(t => (
<div class="rounded-lg border border-dashed border-gray-300 px-4 py-3 text-sm text-gray-400 bg-white/60 select-none">
{t}
</div>
))}
</div>
</div>
<div class="mt-8 text-center sm:hidden">
<a href="/themes/" class="text-sm text-[#1e3a6e] font-medium hover:underline no-underline">Voir tous les thèmes →</a>
</div>
</div> </div>
</section> </section>
<!-- Scénarios --> <!-- ── SCÉNARIOS ──────────────────────────────────── -->
<section style="padding: 4rem 1.5rem; max-width: 1200px; margin: 0 auto;"> <section class="py-20 px-6 max-w-7xl mx-auto">
<div style="background: linear-gradient(135deg, #0f172a, #1e3a6e); border-radius: 0.75rem; padding: 3rem; color: white; display: grid; grid-template-columns: 1fr 1fr; gap: 3rem; align-items: center;"> <div class="bg-[#0f172a] rounded-2xl p-10 lg:p-14 grid lg:grid-cols-5 gap-10 items-center">
<div> <div class="lg:col-span-3">
<div style="font-size: 0.8rem; letter-spacing: 0.1em; color: #93c5fd; font-weight: 600; margin-bottom: 1rem;">SCÉNARIOS MACRO 20272032</div> <div class="text-xs text-blue-400 font-semibold uppercase tracking-wider mb-4">Projection macro 20272032</div>
<h2 style="font-family: 'Playfair Display', serif; font-size: 1.875rem; margin-bottom: 1rem;">Trois avenirs possibles pour la France</h2> <h2 class="font-serif text-3xl text-white mb-4 leading-tight">
<p style="color: #cbd5e1; line-height: 1.7; margin-bottom: 1.5rem;">De la dérive budgétaire au choc républicain : nous chiffrons les conséquences de chaque trajectoire sur la dette, la croissance et les services publics.</p> Trois trajectoires possibles pour la France
<a href="/scenarios/" style="background: white; color: #1e3a6e; padding: 0.625rem 1.5rem; border-radius: 0.375rem; font-weight: 600; text-decoration: none; font-size: 0.9rem;">Lire les scénarios →</a> </h2>
<p class="text-gray-400 leading-relaxed mb-6 text-sm">
Déficit, dette, croissance, services publics — nous chiffrons les conséquences de chaque trajectoire politique sur un horizon de 5 ans.
</p>
<a href="/scenarios/" class="inline-flex items-center gap-2 bg-white text-[#1e3a6e] px-5 py-2.5 rounded-lg font-semibold text-sm hover:bg-blue-50 transition-colors no-underline">
Lire les scénarios →
</a>
</div> </div>
<div style="display: flex; flex-direction: column; gap: 1rem;"> <div class="lg:col-span-2 space-y-3">
<div style="background: rgba(255,255,255,0.07); border-left: 3px solid #ef4444; padding: 1rem 1.25rem; border-radius: 0.25rem;"> {[
<div style="font-weight: 600; color: white;">Dérive</div> { label: 'Dérive', kpi: '-5,5% PIB · +0,8%/an', color: '#ef4444' },
<div style="font-size: 0.85rem; color: #ef4444; font-weight: 500;">-5,5% PIB en 2032 · +0,8%/an</div> { label: 'Réforme graduelle',kpi: '-3% PIB · +1,4%/an', color: '#f59e0b' },
{ label: 'Choc républicain', kpi: '-1,5% PIB · +2%/an', color: '#22c55e' },
].map(({ label, kpi, color }) => (
<div class="bg-white/5 border border-white/10 rounded-xl px-5 py-4 flex items-center justify-between">
<div class="flex items-center gap-3">
<div class="w-2.5 h-2.5 rounded-full shrink-0" style={`background: ${color};`}></div>
<span class="font-medium text-white text-sm">{label}</span>
</div> </div>
<div style="background: rgba(255,255,255,0.07); border-left: 3px solid #f59e0b; padding: 1rem 1.25rem; border-radius: 0.25rem;"> <span class="font-mono text-xs font-semibold" style={`color: ${color};`}>{kpi}</span>
<div style="font-weight: 600; color: white;">Réforme graduelle</div>
<div style="font-size: 0.85rem; color: #f59e0b; font-weight: 500;">-3% PIB en 2030 · +1,4%/an</div>
</div> </div>
<div style="background: rgba(255,255,255,0.07); border-left: 3px solid #22c55e; padding: 1rem 1.25rem; border-radius: 0.25rem;"> ))}
<div style="font-weight: 600; color: white;">Choc républicain</div>
<div style="font-size: 0.85rem; color: #22c55e; font-weight: 500;">-1,5% PIB en 2032 · +2%/an</div>
</div> </div>
</div> </div>
</section>
<!-- ── À PROPOS ────────────────────────────────────── -->
<section class="bg-[#f8fafc] border-t border-gray-200 py-20 px-6">
<div class="max-w-3xl mx-auto text-center">
<div class="section-label mb-4">Pourquoi l'IRPF</div>
<h2 class="font-serif text-3xl text-[#0f172a] mb-6">Ni de gauche ni de droite. Exigeant sur les chiffres.</h2>
<div class="text-gray-500 leading-relaxed space-y-4 text-base">
<p>
Le débat politique français souffre d'un paradoxe : beaucoup de propositions, peu de données ; beaucoup de slogans, peu de diagnostic. Chaque camp dispose de ses instituts, de ses économistes, de ses chiffres. L'IRPF fait le chemin inverse.
</p>
<p>
Nous partons des faits — ceux de l'INSEE, de l'OCDE, de la Cour des comptes, des rapports parlementaires — pour cartographier chaque grande problématique française, recenser toutes les propositions des acteurs du débat, et proposer des analyses originales.
</p>
</div>
<div class="mt-8 flex flex-wrap gap-4 justify-center">
<a href="/donnees/" class="inline-flex items-center gap-2 bg-[#1e3a6e] text-white px-5 py-2.5 rounded-lg font-semibold text-sm hover:bg-[#162d56] transition-colors no-underline">
Tableau de bord France
</a>
<a href="/methode/" class="inline-flex items-center gap-2 border border-gray-300 text-gray-600 px-5 py-2.5 rounded-lg font-medium text-sm hover:border-[#1e3a6e] hover:text-[#1e3a6e] transition-colors no-underline">
Notre méthode
</a>
</div>
</div> </div>
</section> </section>
</BaseLayout> </BaseLayout>
<style>
.theme-card:hover {
border-color: #1e3a6e !important;
box-shadow: 0 2px 8px rgba(30,58,110,0.1);
}
</style>

View file

@ -16,7 +16,6 @@ const { Content } = await render(theme);
const allPropositions = await getCollection('propositions'); const allPropositions = await getCollection('propositions');
const propositions = allPropositions.filter(p => p.data.theme === theme.data.slug); const propositions = allPropositions.filter(p => p.data.theme === theme.data.slug);
// Sections configurées dans le frontmatter → nav dynamique
const SECTION_LABELS: Record<string, string> = { const SECTION_LABELS: Record<string, string> = {
diagnostic: 'Diagnostic', diagnostic: 'Diagnostic',
historique: 'Historique', historique: 'Historique',
@ -29,115 +28,115 @@ const SECTION_LABELS: Record<string, string> = {
sources: 'Sources', sources: 'Sources',
}; };
const navSections = theme.data.sections.map(s => ({ const navSections = theme.data.sections.map((s: string) => ({
id: s, id: s,
label: SECTION_LABELS[s] ?? s, label: SECTION_LABELS[s] ?? s,
})); }));
function orientationBadge(o?: string) { const TENDANCE_COLOR: Record<string, string> = {
const map: Record<string, string> = { hausse: '#16a34a', baisse: '#dc2626', stable: '#6b7280', critique: '#dc2626', alerte: '#d97706'
'gauche': 'background:#fee2e2;color:#991b1b', };
'centre-gauche': 'background:#fce7f3;color:#9d174d', const TENDANCE_ICON: Record<string, string> = {
'centre': 'background:#dbeafe;color:#1e40af', hausse: '↑', baisse: '↓', stable: '→', critique: '⚠', alerte: '!'
'centre-droit': 'background:#ede9fe;color:#5b21b6', };
'droite': 'background:#f3e8ff;color:#6b21a8',
'neutre': 'background:#f3f4f6;color:#374151',
'irpf': 'background:#0f172a;color:white',
};
return map[o ?? 'neutre'] ?? map['neutre'];
}
function tendanceCouleur(t?: string) { const ORIENTATION_CLASS: Record<string, string> = {
return { hausse: '#16a34a', baisse: '#dc2626', stable: '#6b7280', critique: '#dc2626', alerte: '#d97706' }[t ?? 'stable'] ?? '#6b7280'; 'gauche': 'bg-red-100 text-red-800',
} 'centre-gauche': 'bg-pink-100 text-pink-800',
'centre': 'bg-blue-100 text-blue-800',
function tendanceIcon(t?: string) { 'centre-droit': 'bg-violet-100 text-violet-800',
return { hausse: '↑', baisse: '↓', stable: '→', critique: '⚠', alerte: '!' }[t ?? 'stable'] ?? '→'; 'droite': 'bg-purple-100 text-purple-800',
} 'neutre': 'bg-gray-100 text-gray-700',
'irpf': 'bg-[#0f172a] text-white',
};
--- ---
<BaseLayout <BaseLayout title={`${theme.data.title} — IRPF`} description={theme.data.description}>
title={`${theme.data.title} — IRPF`}
description={theme.data.description}
>
<!-- Breadcrumb --> <!-- Breadcrumb -->
<div style="background: #f8fafc; border-bottom: 1px solid #e5e7eb; padding: 0.75rem 1.5rem;"> <div class="bg-[#f8fafc] border-b border-gray-200 px-6 py-3">
<div style="max-width: 1200px; margin: 0 auto; font-size: 0.8rem; color: #6b7280;"> <nav class="max-w-7xl mx-auto text-xs text-gray-500 flex items-center gap-1.5">
<a href="/" style="color: #6b7280; text-decoration: none;">Accueil</a> <a href="/" class="hover:text-gray-700 no-underline">Accueil</a>
<span style="margin: 0 0.5rem;"></span> <span></span>
<a href="/themes/" style="color: #6b7280; text-decoration: none;">Thèmes</a> <a href="/themes/" class="hover:text-gray-700 no-underline">Thèmes</a>
<span style="margin: 0 0.5rem;"></span> <span></span>
<span style="color: #374151;">{theme.data.title}</span> <span class="text-gray-700">{theme.data.title}</span>
</div> </nav>
</div> </div>
<!-- Header enrichi --> <!-- Hero -->
<section style="background: linear-gradient(135deg, #0f172a 0%, #1e3a6e 100%); color: white; padding: 3rem 1.5rem 2.5rem;"> <section class="bg-gradient-to-br from-[#0f172a] to-[#1e3a6e] text-white px-6 py-12">
<div style="max-width: 1200px; margin: 0 auto;"> <div class="max-w-7xl mx-auto">
<div style="display: grid; grid-template-columns: 1fr auto; gap: 2rem; align-items: start;"> <div class="flex flex-col lg:flex-row gap-8 lg:items-start">
<div>
<div class="flex-1">
{theme.data.priorite === 'critique' && ( {theme.data.priorite === 'critique' && (
<span style="background: #ef4444; color: white; font-size: 0.65rem; font-weight: 700; padding: 0.2rem 0.625rem; border-radius: 0.25rem; letter-spacing: 0.06em; margin-bottom: 0.875rem; display: inline-block;">PRIORITÉ CRITIQUE</span> <span class="inline-block badge bg-red-500 text-white mb-4">PRIORITÉ CRITIQUE</span>
)} )}
{theme.data.priorite === 'haute' && ( {theme.data.priorite === 'haute' && (
<span style="background: #d97706; color: white; font-size: 0.65rem; font-weight: 700; padding: 0.2rem 0.625rem; border-radius: 0.25rem; letter-spacing: 0.06em; margin-bottom: 0.875rem; display: inline-block;">PRIORITÉ HAUTE</span> <span class="inline-block badge bg-amber-500 text-white mb-4">PRIORITÉ HAUTE</span>
)} )}
<h1 style="font-family: 'Playfair Display', serif; font-size: 2.5rem; font-weight: 700; margin: 0 0 0.875rem; line-height: 1.15;">{theme.data.title}</h1> <h1 class="font-serif text-4xl font-black mb-3 leading-tight">{theme.data.title}</h1>
<p style="color: #cbd5e1; font-size: 1.05rem; line-height: 1.7; max-width: 680px; margin: 0 0 1.75rem;">{theme.data.description}</p> <p class="text-blue-200 text-base leading-relaxed max-w-2xl mb-6">{theme.data.description}</p>
<!-- Métriques header --> <!-- KPIs header -->
<div style="display: flex; gap: 0.875rem; flex-wrap: wrap;"> <div class="flex flex-wrap gap-3">
{theme.data.kpi_valeur && ( {theme.data.kpi_valeur && (
<div style="background: rgba(255,255,255,0.12); border: 1px solid rgba(255,255,255,0.15); padding: 0.75rem 1.25rem; border-radius: 0.5rem; min-width: 130px;"> <div class="bg-white/10 border border-white/15 rounded-xl px-4 py-3 min-w-[130px]">
<div style={`font-size: 1.5rem; font-weight: 700; display: flex; align-items: baseline; gap: 0.3rem; color: white;`}> <div class="text-2xl font-bold font-mono flex items-baseline gap-1">
{theme.data.kpi_valeur} {theme.data.kpi_valeur}
<span style={`font-size: 0.9rem; color: ${tendanceCouleur(theme.data.kpi_tendance)};`}>{tendanceIcon(theme.data.kpi_tendance)}</span> <span class="text-sm" style={`color: ${TENDANCE_COLOR[theme.data.kpi_tendance ?? 'stable']}`}>
{TENDANCE_ICON[theme.data.kpi_tendance ?? 'stable']}
</span>
</div> </div>
<div style="font-size: 0.72rem; color: #93c5fd; margin-top: 0.2rem;">{theme.data.kpi_label}</div> <div class="text-xs text-blue-300 mt-0.5">{theme.data.kpi_label}</div>
{theme.data.kpi_contexte && <div style="font-size: 0.65rem; color: #64748b; margin-top: 0.15rem;">{theme.data.kpi_contexte}</div>} {theme.data.kpi_contexte && <div class="text-xs text-gray-500 mt-0.5">{theme.data.kpi_contexte}</div>}
</div> </div>
)} )}
{theme.data.budget_etat_milliards && ( {theme.data.budget_etat_milliards && (
<div style="background: rgba(255,255,255,0.12); border: 1px solid rgba(255,255,255,0.15); padding: 0.75rem 1.25rem; border-radius: 0.5rem;"> <div class="bg-white/10 border border-white/15 rounded-xl px-4 py-3">
<div style="font-size: 1.5rem; font-weight: 700; color: white;">{theme.data.budget_etat_milliards} Md€</div> <div class="text-2xl font-bold">{theme.data.budget_etat_milliards} Md€</div>
<div style="font-size: 0.72rem; color: #93c5fd; margin-top: 0.2rem;">Budget État 2025</div> <div class="text-xs text-blue-300 mt-0.5">Budget État 2025</div>
</div> </div>
)} )}
{theme.data.opinion_pct && ( {theme.data.opinion_pct && (
<div style="background: rgba(255,255,255,0.12); border: 1px solid rgba(255,255,255,0.15); padding: 0.75rem 1.25rem; border-radius: 0.5rem;"> <div class="bg-white/10 border border-white/15 rounded-xl px-4 py-3">
<div style="font-size: 1.5rem; font-weight: 700; color: white;">{theme.data.opinion_pct}%</div> <div class="text-2xl font-bold">{theme.data.opinion_pct}%</div>
<div style="font-size: 0.72rem; color: #93c5fd; margin-top: 0.2rem; max-width: 140px; line-height: 1.3;">{theme.data.opinion_question ?? 'Satisfaction'}</div> <div class="text-xs text-blue-300 mt-0.5 max-w-[140px] leading-snug">
{theme.data.opinion_source && <div style="font-size: 0.65rem; color: #64748b; margin-top: 0.15rem;">{theme.data.opinion_source} · {theme.data.opinion_annee}</div>} {theme.data.opinion_question ?? 'Satisfaction'}
</div>
{theme.data.opinion_source && (
<div class="text-xs text-gray-500 mt-0.5">{theme.data.opinion_source} · {theme.data.opinion_annee}</div>
)}
</div> </div>
)} )}
<div style="background: rgba(255,255,255,0.12); border: 1px solid rgba(255,255,255,0.15); padding: 0.75rem 1.25rem; border-radius: 0.5rem;"> <div class="bg-white/10 border border-white/15 rounded-xl px-4 py-3">
<div style="font-size: 1.5rem; font-weight: 700; color: white;">{propositions.length}</div> <div class="text-2xl font-bold">{propositions.length}</div>
<div style="font-size: 0.72rem; color: #93c5fd; margin-top: 0.2rem;">Propositions</div> <div class="text-xs text-blue-300 mt-0.5">Propositions</div>
</div> </div>
{theme.data.score_sources && ( {theme.data.score_sources && (
<div style="background: rgba(255,255,255,0.12); border: 1px solid rgba(255,255,255,0.15); padding: 0.75rem 1.25rem; border-radius: 0.5rem;"> <div class="bg-white/10 border border-white/15 rounded-xl px-4 py-3">
<div style="font-size: 1.5rem; font-weight: 700; color: white;">{theme.data.score_sources}</div> <div class="text-2xl font-bold">{theme.data.score_sources}</div>
<div style="font-size: 0.72rem; color: #93c5fd; margin-top: 0.2rem;">Sources vérifiées</div> <div class="text-xs text-blue-300 mt-0.5">Sources vérifiées</div>
</div> </div>
)} )}
</div> </div>
</div> </div>
<!-- Meta droite --> <!-- Meta droite -->
<div style="text-align: right; flex-shrink: 0;"> <div class="lg:text-right shrink-0 text-sm">
<div style="font-size: 0.7rem; color: #64748b;">Mis à jour</div> <div class="text-gray-500 text-xs">Mis à jour</div>
<div style="font-size: 0.875rem; color: #93c5fd; margin-bottom: 1rem;">{theme.data.derniere_maj}</div> <div class="text-blue-300 mb-4">{theme.data.derniere_maj}</div>
{theme.data.rang_eu && ( {theme.data.rang_eu && (
<div style="margin-bottom: 1rem;"> <div class="mb-4">
<div style="font-size: 0.7rem; color: #64748b;">Rang UE</div> <div class="text-gray-500 text-xs">Rang UE</div>
<div style="font-size: 1.25rem; font-weight: 700; color: white;">{theme.data.rang_eu}<span style="font-size: 0.75rem; color: #64748b;">/{theme.data.nb_pays_compares ?? 27}</span></div> <div class="text-xl font-bold">{theme.data.rang_eu}<span class="text-xs text-gray-500">/{theme.data.nb_pays_compares ?? 27}</span></div>
</div> </div>
)} )}
<div> <div>
<div style="font-size: 0.7rem; color: #64748b; margin-bottom: 0.4rem;">Sources data</div> <div class="text-gray-500 text-xs mb-1">Sources data</div>
{theme.data.sources_data.slice(0, 6).map(s => ( {theme.data.sources_data.slice(0, 5).map((s: string) => (
<div style="font-size: 0.72rem; color: #93c5fd; line-height: 1.6;">{s}</div> <div class="text-xs text-blue-300 leading-relaxed">{s}</div>
))} ))}
</div> </div>
</div> </div>
@ -145,14 +144,13 @@ function tendanceIcon(t?: string) {
</div> </div>
</section> </section>
<!-- Nav interne dynamique --> <!-- Nav interne -->
<nav style="background: white; border-bottom: 2px solid #e5e7eb; padding: 0 1.5rem; position: sticky; top: 0; z-index: 40; box-shadow: 0 1px 3px rgba(0,0,0,0.06);"> <nav class="bg-white border-b-2 border-gray-200 sticky top-[57px] z-40 shadow-sm overflow-x-auto">
<div style="max-width: 1200px; margin: 0 auto; display: flex; gap: 0; overflow-x: auto; scrollbar-width: none;"> <div class="max-w-7xl mx-auto px-6 flex gap-0 scrollbar-hide">
{navSections.map(({ id, label }) => ( {navSections.map(({ id, label }: { id: string; label: string }) => (
<a <a
href={`#${id}`} href={`#${id}`}
style="padding: 0.875rem 1rem; font-size: 0.82rem; font-weight: 500; color: #4b5563; text-decoration: none; white-space: nowrap; border-bottom: 2px solid transparent; margin-bottom: -2px; transition: all 0.15s;" class="px-4 py-3.5 text-xs font-semibold text-gray-500 hover:text-[#1e3a6e] whitespace-nowrap border-b-2 border-transparent hover:border-[#1e3a6e] -mb-0.5 transition-colors no-underline uppercase tracking-wide"
class="nav-anchor"
> >
{label} {label}
</a> </a>
@ -161,62 +159,60 @@ function tendanceIcon(t?: string) {
</nav> </nav>
<!-- Corps : article + sidebar --> <!-- Corps : article + sidebar -->
<div style="max-width: 1200px; margin: 0 auto; padding: 2.5rem 1.5rem 4rem; display: grid; grid-template-columns: 1fr 300px; gap: 3rem; align-items: start;"> <div class="max-w-7xl mx-auto px-6 py-10 grid grid-cols-1 lg:grid-cols-[1fr_280px] gap-10 items-start">
<article> <article class="prose-irpf min-w-0">
<div class="prose-irpf">
<Content /> <Content />
</div>
</article> </article>
<!-- Sidebar enrichie --> <!-- Sidebar -->
<aside style="position: sticky; top: 60px; display: flex; flex-direction: column; gap: 1.25rem;"> <aside class="hidden lg:flex flex-col gap-5 sticky top-[108px]">
<!-- Propositions --> <!-- Table des matières -->
{propositions.length > 0 && ( <div class="bg-[#f8fafc] border border-gray-200 rounded-xl p-5">
<div style="background: #f8fafc; border: 1px solid #e5e7eb; border-radius: 0.5rem; padding: 1.25rem;"> <div class="text-xs font-bold text-gray-400 uppercase tracking-wider mb-3">Contenu</div>
<div style="font-weight: 600; font-size: 0.82rem; color: #0f172a; margin-bottom: 0.875rem; text-transform: uppercase; letter-spacing: 0.04em;"> <nav class="flex flex-col gap-0.5">
Propositions · {propositions.length} {navSections.map(({ id, label }: { id: string; label: string }) => (
</div> <a href={`#${id}`} class="text-sm text-gray-600 hover:text-[#1e3a6e] hover:bg-blue-50 px-2 py-1.5 rounded no-underline transition-colors">
<div style="display: flex; flex-direction: column; gap: 0.625rem;">
{propositions.slice(0, 6).map(p => (
<div style="background: white; border: 1px solid #e5e7eb; border-radius: 0.375rem; padding: 0.75rem;">
<div style="font-size: 0.78rem; font-weight: 500; color: #0f172a; margin-bottom: 0.4rem; line-height: 1.45;">{p.data.titre}</div>
<div style="display: flex; gap: 0.35rem; flex-wrap: wrap; align-items: center;">
<span style={`font-size: 0.62rem; padding: 0.15rem 0.4rem; border-radius: 0.2rem; font-weight: 600; ${orientationBadge(p.data.orientation)}`}>
{p.data.source}
</span>
<span style="font-size: 0.62rem; color: #9ca3af;">{p.data.horizon} · {p.data.annee}</span>
</div>
</div>
))}
</div>
{propositions.length > 6 && (
<a href="/propositions/" style="display: block; text-align: center; margin-top: 0.75rem; font-size: 0.78rem; color: #1e3a6e; text-decoration: none; font-weight: 500;">
+ {propositions.length - 6} autres propositions →
</a>
)}
</div>
)}
<!-- Sections -->
<div style="background: #f8fafc; border: 1px solid #e5e7eb; border-radius: 0.5rem; padding: 1.25rem;">
<div style="font-weight: 600; font-size: 0.82rem; color: #0f172a; margin-bottom: 0.875rem; text-transform: uppercase; letter-spacing: 0.04em;">Contenu</div>
<div style="display: flex; flex-direction: column; gap: 0.25rem;">
{navSections.map(({ id, label }) => (
<a href={`#${id}`} style="font-size: 0.8rem; color: #374151; text-decoration: none; padding: 0.3rem 0.5rem; border-radius: 0.25rem; display: block;" class="toc-link">
{label} {label}
</a> </a>
))} ))}
</div> </nav>
</div> </div>
<!-- Propositions -->
{propositions.length > 0 && (
<div class="bg-[#f8fafc] border border-gray-200 rounded-xl p-5">
<div class="text-xs font-bold text-gray-400 uppercase tracking-wider mb-3">
Propositions ({propositions.length})
</div>
<div class="flex flex-col gap-2.5">
{propositions.slice(0, 5).map(p => (
<div class="bg-white border border-gray-200 rounded-lg p-3">
<div class="text-xs font-medium text-[#0f172a] mb-2 leading-snug">{p.data.titre}</div>
<div class="flex gap-1.5 flex-wrap items-center">
<span class={`badge text-[10px] ${ORIENTATION_CLASS[p.data.orientation ?? 'neutre'] ?? ORIENTATION_CLASS.neutre}`}>
{p.data.source}
</span>
<span class="text-[10px] text-gray-400">{p.data.horizon}</span>
</div>
</div>
))}
</div>
{propositions.length > 5 && (
<a href="/propositions/" class="block text-center mt-3 text-xs text-[#1e3a6e] font-medium hover:underline no-underline">
+{propositions.length - 5} autres →
</a>
)}
</div>
)}
<!-- Tags --> <!-- Tags -->
<div style="background: #f8fafc; border: 1px solid #e5e7eb; border-radius: 0.5rem; padding: 1.25rem;"> <div class="bg-[#f8fafc] border border-gray-200 rounded-xl p-5">
<div style="font-weight: 600; font-size: 0.82rem; color: #0f172a; margin-bottom: 0.75rem; text-transform: uppercase; letter-spacing: 0.04em;">Mots-clés</div> <div class="text-xs font-bold text-gray-400 uppercase tracking-wider mb-3">Mots-clés</div>
<div style="display: flex; flex-wrap: wrap; gap: 0.4rem;"> <div class="flex flex-wrap gap-1.5">
{theme.data.tags.map(tag => ( {theme.data.tags.map((tag: string) => (
<span style="background: white; border: 1px solid #e5e7eb; color: #374151; font-size: 0.72rem; padding: 0.2rem 0.5rem; border-radius: 0.2rem;">#{tag}</span> <span class="badge badge-muted text-xs">#{tag}</span>
))} ))}
</div> </div>
</div> </div>
@ -227,115 +223,5 @@ function tendanceIcon(t?: string) {
</BaseLayout> </BaseLayout>
<style> <style>
.nav-anchor:hover, [id] { scroll-margin-top: 115px; }
.nav-anchor:focus { color: #1e3a6e !important; border-bottom-color: #1e3a6e !important; }
.toc-link:hover { background: #e5e7eb; color: #0f172a !important; }
/* Typographie du corps de fiche */
.prose-irpf h2 {
font-family: 'Playfair Display', serif;
font-size: 1.6rem;
font-weight: 700;
color: #0f172a;
margin: 3rem 0 1rem;
padding-bottom: 0.5rem;
border-bottom: 2px solid #e5e7eb;
scroll-margin-top: 80px;
}
.prose-irpf h3 {
font-size: 1.1rem;
font-weight: 600;
color: #1e3a6e;
margin: 1.75rem 0 0.75rem;
scroll-margin-top: 80px;
}
.prose-irpf h4 {
font-size: 0.95rem;
font-weight: 600;
color: #374151;
margin: 1.25rem 0 0.5rem;
text-transform: uppercase;
letter-spacing: 0.04em;
}
.prose-irpf p {
font-size: 0.9rem;
line-height: 1.8;
color: #374151;
margin: 0.75rem 0;
}
.prose-irpf strong { color: #0f172a; }
.prose-irpf em { color: #4b5563; }
.prose-irpf table {
width: 100%;
border-collapse: collapse;
margin: 1.25rem 0;
font-size: 0.82rem;
}
.prose-irpf th {
background: #0f172a;
color: white;
padding: 0.6rem 0.875rem;
text-align: left;
font-size: 0.72rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.04em;
}
.prose-irpf td {
padding: 0.6rem 0.875rem;
border-bottom: 1px solid #e5e7eb;
color: #374151;
vertical-align: top;
line-height: 1.5;
}
.prose-irpf tr:nth-child(even) td { background: #f8fafc; }
.prose-irpf tr:hover td { background: #f0f4ff; }
.prose-irpf ul, .prose-irpf ol {
padding-left: 1.5rem;
margin: 0.75rem 0;
}
.prose-irpf li {
font-size: 0.88rem;
line-height: 1.75;
color: #374151;
margin-bottom: 0.25rem;
}
.prose-irpf blockquote {
border-left: 4px solid #1e3a6e;
background: #f0f4ff;
padding: 1rem 1.25rem;
margin: 1.5rem 0;
border-radius: 0 0.375rem 0.375rem 0;
font-style: italic;
color: #374151;
}
.prose-irpf blockquote p { margin: 0; }
.prose-irpf a {
color: #1e3a6e;
text-decoration: underline;
text-decoration-color: #bfdbfe;
}
.prose-irpf a:hover { text-decoration-color: #1e3a6e; }
.prose-irpf code {
background: #f1f5f9;
padding: 0.1rem 0.35rem;
border-radius: 0.2rem;
font-size: 0.82rem;
color: #0f172a;
}
/* Couches visuelles : héritage du slot */
.layer-content p { font-size: 0.88rem; }
.layer-content ul { padding-left: 1.25rem; }
.layer-content li { font-size: 0.85rem; }
.layer-content table { font-size: 0.8rem; }
/* Scroll offset pour nav sticky */
[id] { scroll-margin-top: 80px; }
</style> </style>

View file

@ -9,33 +9,44 @@ const autres = themes.filter(t => t.data.priorite !== 'critique');
<BaseLayout title="Thèmes — IRPF" description="Les 14 grandes thématiques des politiques publiques françaises analysées par l'IRPF."> <BaseLayout title="Thèmes — IRPF" description="Les 14 grandes thématiques des politiques publiques françaises analysées par l'IRPF.">
<section style="background: #0f172a; color: white; padding: 3rem 1.5rem;"> <!-- Header -->
<div style="max-width: 1200px; margin: 0 auto;"> <section class="bg-[#0f172a] text-white px-6 py-14">
<div style="font-size: 0.75rem; letter-spacing: 0.1em; color: #93c5fd; margin-bottom: 0.75rem;">IRPF</div> <div class="max-w-7xl mx-auto">
<h1 style="font-family: 'Playfair Display', serif; font-size: 2.5rem; margin-bottom: 1rem;">Les 14 thèmes</h1> <nav class="text-xs text-gray-500 mb-6 flex items-center gap-1.5">
<p style="color: #cbd5e1; max-width: 600px; line-height: 1.7;">Pour chaque thème : diagnostic sourcé, cartographie des propositions par acteur, scénarios chiffrés, analyse originale.</p> <a href="/" class="hover:text-gray-300 no-underline">Accueil</a>
<span></span>
<span class="text-gray-400">Thèmes</span>
</nav>
<div class="text-xs text-blue-400 font-semibold uppercase tracking-wider mb-3">IRPF</div>
<h1 class="font-serif text-4xl font-black mb-3">Les 14 thèmes</h1>
<p class="text-gray-400 max-w-2xl leading-relaxed text-sm">
Pour chaque thème : diagnostic sourcé, cartographie des propositions par acteur, scénarios chiffrés, analyse originale.
</p>
</div> </div>
</section> </section>
<section style="padding: 3rem 1.5rem; max-width: 1200px; margin: 0 auto;"> <div class="max-w-7xl mx-auto px-6 py-12">
{critiques.length > 0 && ( {critiques.length > 0 && (
<div style="margin-bottom: 3rem;"> <div class="mb-12">
<div style="display: flex; align-items: center; gap: 0.75rem; margin-bottom: 1.5rem;"> <div class="flex items-center gap-3 mb-6">
<span style="background: #fee2e2; color: #991b1b; font-size: 0.7rem; font-weight: 700; padding: 0.25rem 0.75rem; border-radius: 0.25rem; letter-spacing: 0.05em;">PRIORITÉ CRITIQUE</span> <span class="badge badge-critique">PRIORITÉ CRITIQUE</span>
<div style="flex: 1; height: 1px; background: #fca5a5;"></div> <div class="flex-1 h-px bg-red-100"></div>
</div> </div>
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem;"> <div class="grid sm:grid-cols-2 lg:grid-cols-3 gap-5">
{critiques.map(theme => ( {critiques.map(theme => (
<a href={`/themes/${theme.data.slug}/`} style="background: white; border: 2px solid #fca5a5; border-radius: 0.5rem; padding: 1.5rem; text-decoration: none; display: block;" class="theme-card"> <a href={`/themes/${theme.data.slug}/`} class="card p-6 no-underline block group border-red-100 hover:border-red-300">
<div style="font-weight: 700; color: #0f172a; font-size: 1.05rem; margin-bottom: 0.5rem;">{theme.data.title}</div> <div class="flex justify-between items-start gap-2 mb-3">
<p style="font-size: 0.85rem; color: #6b7280; line-height: 1.5; margin-bottom: 1rem;">{theme.data.description}</p> <h2 class="font-semibold text-[#0f172a] leading-snug group-hover:text-[#1e3a6e] transition-colors">{theme.data.title}</h2>
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center;"> <span class="badge badge-critique shrink-0">CRITIQUE</span>
</div>
<p class="text-sm text-gray-500 leading-relaxed mb-4">{theme.data.description}</p>
<div class="flex gap-2 flex-wrap">
{theme.data.budget_etat_milliards && ( {theme.data.budget_etat_milliards && (
<span style="background: #eff6ff; color: #1d4ed8; font-size: 0.7rem; padding: 0.2rem 0.5rem; border-radius: 0.25rem; font-weight: 500;">{theme.data.budget_etat_milliards} Md€/an</span> <span class="badge badge-blue">{theme.data.budget_etat_milliards} Md€/an</span>
)} )}
{theme.data.tags.slice(0, 2).map(tag => ( {theme.data.tags.slice(0, 2).map((tag: string) => (
<span style="background: #f3f4f6; color: #6b7280; font-size: 0.7rem; padding: 0.2rem 0.5rem; border-radius: 0.25rem;">#{tag}</span> <span class="badge badge-muted">#{tag}</span>
))} ))}
</div> </div>
</a> </a>
@ -46,33 +57,51 @@ const autres = themes.filter(t => t.data.priorite !== 'critique');
{autres.length > 0 && ( {autres.length > 0 && (
<div> <div>
<div style="display: flex; align-items: center; gap: 0.75rem; margin-bottom: 1.5rem;"> <div class="flex items-center gap-3 mb-6">
<span style="color: #374151; font-size: 0.8rem; font-weight: 600; letter-spacing: 0.05em;">TOUS LES THÈMES</span> <span class="text-xs font-semibold text-gray-500 uppercase tracking-wider">Tous les thèmes</span>
<div style="flex: 1; height: 1px; background: #e5e7eb;"></div> <div class="flex-1 h-px bg-gray-200"></div>
</div> </div>
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem;"> <div class="grid sm:grid-cols-2 lg:grid-cols-3 gap-5">
{autres.map(theme => ( {autres.map(theme => (
<a href={`/themes/${theme.data.slug}/`} style="background: white; border: 1px solid #e5e7eb; border-radius: 0.5rem; padding: 1.5rem; text-decoration: none; display: block;" class="theme-card"> <a href={`/themes/${theme.data.slug}/`} class="card p-6 no-underline block group">
<div style="font-weight: 600; color: #0f172a; margin-bottom: 0.5rem;">{theme.data.title}</div> <div class="flex justify-between items-start gap-2 mb-2">
<p style="font-size: 0.85rem; color: #6b7280; line-height: 1.5; margin-bottom: 1rem;">{theme.data.description}</p> <h2 class="font-semibold text-[#0f172a] leading-snug group-hover:text-[#1e3a6e] transition-colors">{theme.data.title}</h2>
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap;"> {theme.data.priorite === 'haute' && <span class="badge badge-haute shrink-0">PRIORITÉ</span>}
{theme.data.tags.slice(0, 2).map(tag => ( </div>
<span style="background: #f3f4f6; color: #6b7280; font-size: 0.7rem; padding: 0.2rem 0.5rem; border-radius: 0.25rem;">#{tag}</span> <p class="text-sm text-gray-500 leading-relaxed mb-4">{theme.data.description}</p>
<div class="flex items-center justify-between">
<div class="flex gap-2 flex-wrap">
{theme.data.budget_etat_milliards && (
<span class="badge badge-blue">{theme.data.budget_etat_milliards} Md€/an</span>
)}
{theme.data.tags.slice(0, 2).map((tag: string) => (
<span class="badge badge-muted">#{tag}</span>
))} ))}
</div> </div>
<span class="text-xs text-[#1e3a6e] font-medium opacity-0 group-hover:opacity-100 transition-opacity">Lire →</span>
</div>
</a> </a>
))} ))}
</div> </div>
</div> </div>
)} )}
</section> <!-- À venir -->
<div class="mt-12">
<div class="flex items-center gap-3 mb-6">
<span class="badge badge-muted">EN PRÉPARATION</span>
<div class="flex-1 h-px bg-gray-200"></div>
</div>
<div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-3">
{['Emploi & Travail', 'Retraites', 'Logement', 'Éducation', 'Justice & Sécurité',
'Énergie & Environnement', 'Agriculture', 'Numérique', 'Cohésion sociale',
'Démocratie', 'Europe', 'Immigration'].map(t => (
<div class="rounded-lg border border-dashed border-gray-300 px-4 py-3 text-sm text-gray-400 bg-white select-none">
{t}
</div>
))}
</div>
</div>
</div>
</BaseLayout> </BaseLayout>
<style>
.theme-card:hover {
border-color: #1e3a6e !important;
box-shadow: 0 2px 8px rgba(30,58,110,0.08);
}
</style>

View file

@ -1,72 +1,109 @@
@import "tailwindcss"; @import "tailwindcss";
@theme { @theme {
--color-bleu-nuit: #0f172a; --color-nuit: #0f172a;
--color-bleu-republic: #1e3a6e; --color-republic: #1e3a6e;
--color-bleu-accent: #2563eb; --color-accent: #2563eb;
--color-gris-ardoise: #374151; --color-rouge: #c0392b;
--color-gris-clair: #f8fafc; --color-surface: #f8fafc;
--color-rouge-republique: #c0392b; --color-border: #e2e8f0;
--color-muted: #64748b;
--font-sans: "Inter", ui-sans-serif, system-ui, sans-serif; --font-sans: "Inter", ui-sans-serif, system-ui, sans-serif;
--font-serif: "Playfair Display", ui-serif, Georgia, serif; --font-serif: "Playfair Display", ui-serif, Georgia, serif;
--font-mono: ui-monospace, monospace; --font-mono: ui-monospace, "Fira Code", monospace;
} }
html { scroll-behavior: smooth; } html { scroll-behavior: smooth; }
body { body {
background-color: white;
color: #111827;
font-family: var(--font-sans); font-family: var(--font-sans);
color: #1e293b;
background: white;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
} }
/* Typographie éditoriale */ /* ── Prose ─────────────────────────────────────────────── */
.prose-irpf h2 { .prose-irpf h2 {
font-size: 1.5rem;
font-family: var(--font-serif); font-family: var(--font-serif);
font-size: 1.5rem;
font-weight: 700; font-weight: 700;
color: var(--color-bleu-nuit); color: var(--color-nuit);
border-bottom: 1px solid #e5e7eb; border-bottom: 1px solid var(--color-border);
padding-bottom: 0.5rem; padding-bottom: 0.5rem;
margin-top: 2.5rem; margin-top: 2.5rem;
margin-bottom: 1rem; margin-bottom: 1rem;
} }
.prose-irpf h3 { .prose-irpf h3 {
font-size: 1.25rem; font-size: 1.125rem;
font-weight: 600; font-weight: 600;
color: var(--color-bleu-republic); color: var(--color-republic);
margin-top: 2rem; margin-top: 1.75rem;
margin-bottom: 0.75rem; margin-bottom: 0.625rem;
} }
.prose-irpf p { color: #374151; line-height: 1.8; margin-bottom: 1rem; }
.prose-irpf p { .prose-irpf a { color: var(--color-accent); text-decoration: underline; }
color: #374151;
line-height: 1.75;
margin-bottom: 1rem;
}
.prose-irpf a { color: var(--color-bleu-accent); text-decoration: underline; }
.prose-irpf blockquote {
border-left: 4px solid var(--color-bleu-accent);
padding-left: 1rem;
font-style: italic;
color: #6b7280;
margin: 1.5rem 0;
}
.prose-irpf ul { list-style: disc inside; color: #374151; margin-bottom: 1rem; }
.prose-irpf strong { font-weight: 600; color: #111827; } .prose-irpf strong { font-weight: 600; color: #111827; }
.prose-irpf ul { list-style: disc; padding-left: 1.5rem; color: #374151; margin-bottom: 1rem; }
.prose-irpf ul li { margin-bottom: 0.375rem; line-height: 1.7; }
.prose-irpf blockquote {
border-left: 3px solid var(--color-accent);
padding: 0.75rem 1rem;
font-style: italic;
color: var(--color-muted);
background: var(--color-surface);
margin: 1.5rem 0;
border-radius: 0 0.25rem 0.25rem 0;
}
.prose-irpf table { width: 100%; border-collapse: collapse; font-size: 0.875rem; margin: 1.5rem 0; }
.prose-irpf th { background: var(--color-nuit); color: white; padding: 0.625rem 0.875rem; text-align: left; font-weight: 600; font-size: 0.8rem; letter-spacing: 0.03em; }
.prose-irpf td { padding: 0.625rem 0.875rem; border-bottom: 1px solid var(--color-border); color: #374151; }
.prose-irpf tr:last-child td { border-bottom: none; }
.prose-irpf tr:nth-child(even) td { background: var(--color-surface); }
/* KPI cards */ /* ── KPI cards ─────────────────────────────────────────── */
.kpi-card { .kpi-card {
background: white; background: white;
border: 1px solid #e5e7eb; border: 1px solid var(--color-border);
border-radius: 0.5rem; border-radius: 0.75rem;
padding: 1rem; padding: 1.25rem 1.5rem;
box-shadow: 0 1px 3px rgba(0,0,0,0.08); box-shadow: 0 1px 4px rgba(0,0,0,0.06);
transition: box-shadow 0.15s;
} }
.kpi-value { font-size: 1.875rem; font-weight: 700; color: var(--color-bleu-nuit); font-family: var(--font-mono); } .kpi-card:hover { box-shadow: 0 4px 16px rgba(30,58,110,0.1); }
.kpi-label { font-size: 0.875rem; color: #6b7280; margin-top: 0.25rem; } .kpi-value { font-size: 1.875rem; font-weight: 700; color: var(--color-nuit); font-family: var(--font-mono); line-height: 1; }
.kpi-source { font-size: 0.75rem; color: #9ca3af; margin-top: 0.5rem; } .kpi-label { font-size: 0.8rem; color: var(--color-muted); margin-top: 0.375rem; line-height: 1.4; }
.kpi-source { font-size: 0.7rem; color: #94a3b8; margin-top: 0.5rem; }
/* ── Badges ────────────────────────────────────────────── */
.badge { display: inline-flex; align-items: center; font-size: 0.7rem; font-weight: 600; padding: 0.2rem 0.6rem; border-radius: 0.25rem; letter-spacing: 0.04em; }
.badge-critique { background: #fee2e2; color: #991b1b; }
.badge-haute { background: #fef3c7; color: #92400e; }
.badge-blue { background: #eff6ff; color: #1d4ed8; }
.badge-dark { background: var(--color-nuit); color: white; }
.badge-muted { background: #f1f5f9; color: #475569; }
/* ── Cards ─────────────────────────────────────────────── */
.card {
background: white;
border: 1px solid var(--color-border);
border-radius: 0.75rem;
transition: border-color 0.15s, box-shadow 0.15s;
}
.card:hover {
border-color: #93c5fd;
box-shadow: 0 4px 20px rgba(30,58,110,0.08);
}
/* ── Section label ─────────────────────────────────────── */
.section-label {
font-size: 0.72rem;
letter-spacing: 0.1em;
font-weight: 600;
color: var(--color-republic);
text-transform: uppercase;
}
/* ── Mobile menu ───────────────────────────────────────── */
#mobile-menu { display: none; }
#mobile-menu.open { display: flex; }