Files
prenoms-app/index.html
T
2026-03-14 09:33:12 +01:00

775 lines
50 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="fr" data-theme="light">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Prénoms · France</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.min.js"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,300;0,400;0,600;1,300;1,400&family=Outfit:wght@300;400;500;600&display=swap" rel="stylesheet">
<style>
:root {
--bg:#f7f4ef;--bg2:#eee9e0;--bg3:#e4ddd2;
--ink:#1a1612;--ink2:#5a5048;--ink3:#9a8e82;
--accent:#c0392b;--accent2:#e8b4a0;
--blue:#2c5282;--blue-l:#bee3f8;
--pink:#9b2c5a;--pink-l:#fed7e2;
--gold:#b7791f;--gold-l:#fefcbf;
--purple:#553c9a;--purple-l:#e9d8fd;
--border:rgba(26,22,18,0.1);--card:#ffffff;
--header-bg:#1a1612;--header-fg:#f7f4ef;
--shadow:0 4px 20px rgba(0,0,0,0.07);
}
[data-theme="dark"] {
--bg:#12100e;--bg2:#1c1916;--bg3:#252220;
--ink:#f0ebe3;--ink2:#b0a898;--ink3:#6e6458;
--accent:#e05545;--accent2:#c07060;
--blue:#63b3ed;--blue-l:#1a365d;
--pink:#d69ab8;--pink-l:#521b38;
--gold:#e2a44a;--gold-l:#3d2e0a;
--purple:#b794f4;--purple-l:#2d1f5e;
--border:rgba(240,235,227,0.1);--card:#1c1916;
--header-bg:#0c0b09;--header-fg:#f0ebe3;
--shadow:0 4px 20px rgba(0,0,0,0.3);
}
*{margin:0;padding:0;box-sizing:border-box;}
body{font-family:'Outfit',sans-serif;background:var(--bg);color:var(--ink);min-height:100vh;transition:background .3s,color .3s;}
header{background:var(--header-bg);color:var(--header-fg);padding:0 20px;display:flex;align-items:center;justify-content:space-between;height:60px;position:sticky;top:0;z-index:200;gap:10px;}
.logo{font-family:'Cormorant Garamond',serif;font-size:1.25rem;font-weight:600;white-space:nowrap;flex-shrink:0;}
.logo span{color:var(--accent2);font-style:italic;}
.header-center{display:flex;background:rgba(255,255,255,0.07);border-radius:100px;padding:3px;gap:2px;}
.mode-btn{padding:6px 14px;border-radius:100px;border:none;background:transparent;color:rgba(255,255,255,0.5);font-family:'Outfit',sans-serif;font-size:0.78rem;font-weight:500;cursor:pointer;transition:all .2s;white-space:nowrap;}
.mode-btn.active{background:var(--accent);color:#fff;}
.theme-toggle{width:34px;height:34px;border-radius:50%;border:1px solid rgba(255,255,255,0.15);background:rgba(255,255,255,0.07);color:rgba(255,255,255,0.7);cursor:pointer;font-size:0.95rem;display:flex;align-items:center;justify-content:center;transition:all .2s;flex-shrink:0;}
@media(max-width:500px){
.logo{font-size:1rem;}
.btn-label{display:none;}
.mode-btn{padding:6px 10px;}
.header-center{gap:1px;}
}
.theme-toggle:hover{background:rgba(255,255,255,0.15);}
.page{display:none;}
.page.active{display:block;animation:fadeUp .3s ease;}
@keyframes fadeUp{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}
/* ── EXPLORER ── */
#page-explorer{max-width:860px;margin:0 auto;padding:44px 18px 80px;}
.hero-title{font-family:'Cormorant Garamond',serif;font-size:clamp(1.9rem,5vw,3.2rem);font-weight:300;text-align:center;line-height:1.1;margin-bottom:5px;}
.hero-title em{font-style:italic;color:var(--accent);}
.hero-sub{text-align:center;font-size:0.78rem;color:var(--ink3);text-transform:uppercase;letter-spacing:.06em;margin-bottom:26px;}
.search-area{display:flex;flex-direction:column;gap:10px;align-items:center;}
.autocomplete-wrap{position:relative;width:100%;max-width:520px;}
.search-row{display:flex;align-items:center;width:100%;background:var(--card);border:2px solid var(--border);border-radius:100px;transition:border-color .2s,box-shadow .2s;}
.search-row:focus-within{border-color:var(--accent);box-shadow:0 0 0 4px rgba(192,57,43,0.08);}
.search-row.blue-focus:focus-within{border-color:var(--blue);box-shadow:0 0 0 4px rgba(44,82,130,0.1);}
.search-input{flex:1;border:none;outline:none;background:transparent;padding:12px 16px;font-family:'Cormorant Garamond',serif;font-size:1.2rem;color:var(--ink);min-width:0;}
.search-input::placeholder{color:var(--ink3);font-style:italic;}
.search-btn{padding:8px 18px;margin:4px;background:var(--accent);color:#fff;border:none;border-radius:100px;font-family:'Outfit',sans-serif;font-size:0.8rem;font-weight:600;cursor:pointer;white-space:nowrap;flex-shrink:0;transition:opacity .2s;}
.search-btn:hover{opacity:.88;}
.autocomplete-list{position:absolute;top:calc(100% + 6px);left:0;right:0;background:var(--card);border:1px solid var(--border);border-radius:14px;box-shadow:var(--shadow);overflow:hidden;z-index:300;display:none;}
.autocomplete-list.open{display:block;}
.ac-item{display:flex;align-items:center;justify-content:space-between;padding:9px 15px;cursor:pointer;transition:background .12s;gap:8px;}
.ac-item:hover{background:var(--bg2);}
.ac-item:not(:last-child){border-bottom:1px solid var(--border);}
.ac-name{font-family:'Cormorant Garamond',serif;font-size:1.05rem;color:var(--ink);}
.ac-name mark{background:none;color:var(--accent);font-weight:600;}
.ac-meta{font-size:0.7rem;color:var(--ink3);}
.compare-toggle-row{display:flex;align-items:center;gap:8px;font-size:0.78rem;color:var(--ink3);}
.compare-toggle-row label{cursor:pointer;display:flex;align-items:center;gap:6px;}
.compare-second{display:none;width:100%;max-width:520px;}
.compare-second.open{display:block;}
.suggestions{display:flex;flex-wrap:wrap;gap:6px;justify-content:center;margin-top:10px;}
.sug-chip{padding:4px 12px;background:var(--card);border:1px solid var(--border);border-radius:100px;font-size:0.78rem;color:var(--ink2);cursor:pointer;transition:all .15s;}
.sug-chip:hover{border-color:var(--accent);color:var(--accent);}
/* result */
#explorer-result{margin-top:44px;}
.prenom-hero{text-align:center;padding:28px 0 22px;border-bottom:1px solid var(--border);margin-bottom:24px;}
.prenom-big{font-family:'Cormorant Garamond',serif;font-size:clamp(2.6rem,8vw,5rem);font-weight:300;letter-spacing:-.02em;line-height:1;margin-bottom:10px;}
.prenom-badges{display:flex;gap:6px;justify-content:center;flex-wrap:wrap;}
.badge{padding:3px 10px;border-radius:100px;font-size:0.7rem;font-weight:600;letter-spacing:.04em;text-transform:uppercase;}
.badge-m{background:var(--blue-l);color:var(--blue);}
.badge-f{background:var(--pink-l);color:var(--pink);}
.badge-mix{background:var(--purple-l);color:var(--purple);}
.badge-len{background:var(--bg2);color:var(--ink2);}
.stat-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(130px,1fr));gap:11px;margin-bottom:20px;}
.stat-card{background:var(--card);border:1px solid var(--border);border-radius:12px;padding:16px;text-align:center;}
.stat-card .val{font-family:'Cormorant Garamond',serif;font-size:1.8rem;font-weight:600;color:var(--ink);display:block;line-height:1;}
.stat-card .lbl{font-size:0.65rem;color:var(--ink3);text-transform:uppercase;letter-spacing:.06em;margin-top:5px;display:block;}
.peak-row{display:grid;grid-template-columns:1fr 1fr;gap:11px;margin-bottom:20px;}
@media(max-width:500px){.peak-row{grid-template-columns:1fr;}}
.peak-card{background:var(--card);border:1px solid var(--border);border-radius:12px;padding:16px 18px;display:flex;align-items:center;gap:12px;}
.peak-icon{font-size:1.4rem;flex-shrink:0;}
.peak-year{font-family:'Cormorant Garamond',serif;font-size:1.6rem;font-weight:600;line-height:1;}
.peak-desc{font-size:0.68rem;color:var(--ink3);text-transform:uppercase;letter-spacing:.05em;margin-top:3px;}
.trend-bar{height:4px;background:var(--bg2);border-radius:2px;margin-top:8px;overflow:hidden;}
.trend-fill{height:100%;border-radius:2px;background:linear-gradient(to right,var(--accent2),var(--accent));transition:width .9s ease;}
.chart-card{background:var(--card);border:1px solid var(--border);border-radius:12px;padding:22px;margin-bottom:14px;overflow:hidden;}
.chart-card-title{font-family:'Cormorant Garamond',serif;font-size:1.05rem;font-weight:600;color:var(--ink2);margin-bottom:16px;display:flex;align-items:center;justify-content:space-between;gap:8px;}
.chart-wrap{position:relative;height:250px;}
.share-btn{padding:4px 11px;border-radius:100px;border:1px solid var(--border);background:var(--bg2);font-family:'Outfit',sans-serif;font-size:0.72rem;color:var(--ink2);cursor:pointer;transition:all .15s;white-space:nowrap;flex-shrink:0;}
.share-btn:hover{border-color:var(--accent);color:var(--accent);}
.share-btn.copied{background:var(--accent);color:#fff;border-color:var(--accent);}
/* heatmap */
.heatmap-scroll-wrap{position:relative;}
.heatmap-scroll-wrap::after{content:'›››';position:absolute;top:50%;right:0;transform:translateY(-50%);padding:4px 6px;font-size:0.75rem;color:var(--ink3);pointer-events:none;transition:opacity .3s;background:linear-gradient(to right,transparent 0%,var(--card) 40%);}
.heatmap-scroll-wrap.at-end::after{opacity:0;}
.heatmap-scroll{overflow-x:auto;-webkit-overflow-scrolling:touch;padding-bottom:6px;margin:0 -22px;padding-left:22px;padding-right:22px;}
.heatmap-inner{min-width:520px;}
.heatmap-decade-labels{display:flex;gap:3px;padding-left:52px;margin-bottom:4px;}
.heatmap-decade-lbl{flex:1;font-size:0.58rem;color:var(--ink3);text-align:center;}
.heatmap-row{display:flex;align-items:center;gap:3px;margin-bottom:3px;}
.heatmap-label{font-size:0.62rem;color:var(--ink3);width:48px;text-align:right;flex-shrink:0;padding-right:5px;}
.heatmap-cell{flex:1;height:26px;border-radius:4px;display:flex;align-items:center;justify-content:center;font-size:0.62rem;font-weight:500;color:#fff;cursor:default;transition:transform .15s;position:relative;}
.heatmap-cell:hover{transform:scale(1.06);z-index:1;}
.heatmap-cell-0{background:var(--bg2);color:var(--ink3);}
/* ── FINDER ── */
#page-finder{max-width:1080px;margin:0 auto;padding:32px 18px 80px;}
.finder-layout{display:grid;grid-template-columns:268px 1fr;gap:22px;align-items:start;}
@media(max-width:720px){.finder-layout{grid-template-columns:1fr;}}
.finder-sidebar{background:var(--card);border:1px solid var(--border);border-radius:16px;overflow:hidden;position:sticky;top:72px;z-index:10;}
@media(max-width:720px){.finder-sidebar{position:static;}}
.sidebar-header{background:var(--ink);padding:14px 18px;cursor:pointer;display:flex;align-items:center;justify-content:space-between;}
.sidebar-toggle-icon{color:rgba(255,255,255,0.6);font-size:0.8rem;transition:transform .25s;flex-shrink:0;}
.finder-sidebar.collapsed .sidebar-toggle-icon{transform:rotate(-90deg);}
.finder-sidebar.collapsed .sidebar-body{display:none;}
.sidebar-title{font-family:'Cormorant Garamond',serif;font-size:0.98rem;color:var(--header-fg);font-weight:400;}
.sidebar-body{padding:16px;}
.filter-group{margin-bottom:18px;}
.filter-label{font-size:0.66rem;font-weight:600;color:var(--ink3);text-transform:uppercase;letter-spacing:.08em;margin-bottom:8px;display:block;}
.pill-group{display:flex;flex-wrap:wrap;gap:5px;}
.pill{padding:5px 11px;border-radius:100px;border:1.5px solid var(--border);background:transparent;font-family:'Outfit',sans-serif;font-size:0.76rem;color:var(--ink2);cursor:pointer;transition:all .15s;}
.pill:hover{border-color:var(--ink2);}
.pill.active{border-color:var(--accent);background:var(--accent);color:#fff;}
.pill.active-blue{border-color:var(--blue);background:var(--blue);color:#fff;}
.pill.active-pink{border-color:var(--pink);background:var(--pink);color:#fff;}
.pill.active-gold{border-color:var(--gold);background:var(--gold);color:#fff;}
.pill.active-mix{border-color:var(--purple);background:var(--purple);color:#fff;}
input[type=range]{-webkit-appearance:none;width:100%;height:4px;background:var(--bg3);border-radius:2px;outline:none;cursor:pointer;}
input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:14px;height:14px;background:var(--accent);border-radius:50%;cursor:pointer;box-shadow:0 1px 4px rgba(0,0,0,.2);}
.range-vals{display:flex;justify-content:space-between;font-size:0.68rem;color:var(--ink3);margin-top:3px;}
.filter-input{width:100%;border:1.5px solid var(--border);border-radius:8px;padding:7px 10px;font-family:'Outfit',sans-serif;font-size:0.83rem;color:var(--ink);background:var(--bg);outline:none;transition:border-color .2s;}
.filter-input:focus{border-color:var(--accent);}
.btn-search-finder{width:100%;padding:11px;background:var(--accent);color:#fff;border:none;border-radius:10px;font-family:'Outfit',sans-serif;font-size:0.9rem;font-weight:600;cursor:pointer;transition:opacity .2s,transform .1s;letter-spacing:.02em;}
.btn-search-finder:hover{opacity:.9;transform:translateY(-1px);}
.hint{font-size:0.66rem;color:var(--ink3);margin-top:5px;line-height:1.4;}
.finder-results-header{display:flex;align-items:baseline;justify-content:space-between;margin-bottom:16px;}
.results-title{font-family:'Cormorant Garamond',serif;font-size:1.45rem;font-weight:300;}
.results-count{font-size:0.76rem;color:var(--ink3);}
.results-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(170px,1fr));gap:11px;}
.result-card{background:var(--card);border:1px solid var(--border);border-radius:12px;padding:15px 16px;cursor:pointer;transition:all .2s;position:relative;overflow:hidden;z-index:1;}
.result-card::before{content:'';position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--accent);opacity:0;transition:opacity .2s;}
.result-card:hover{border-color:rgba(192,57,43,.3);transform:translateY(-2px);box-shadow:var(--shadow);}
.result-card:hover::before{opacity:1;}
.rc-name{font-family:'Cormorant Garamond',serif;font-size:1.35rem;font-weight:400;margin-bottom:6px;line-height:1;}
.rc-meta{display:flex;align-items:center;gap:5px;flex-wrap:wrap;margin-bottom:7px;}
.rc-total{font-size:0.7rem;color:var(--ink3);}
.rc-mini-bar{height:3px;background:var(--bg2);border-radius:2px;margin-top:6px;overflow:hidden;}
.rc-mini-fill{height:100%;background:linear-gradient(to right,var(--accent2),var(--accent));border-radius:2px;}
.pop-label{font-size:0.63rem;font-weight:600;text-transform:uppercase;letter-spacing:.05em;padding:2px 7px;border-radius:100px;}
.pop-rare{background:#e6fffa;color:#276749;}
.pop-discret{background:var(--blue-l);color:var(--blue);}
.pop-connu{background:var(--gold-l);color:var(--gold);}
.pop-populaire{background:#fff5f5;color:#c53030;}
[data-theme="dark"] .pop-rare{background:#1a3a30;color:#68d391;}
[data-theme="dark"] .pop-discret{background:var(--blue-l);color:var(--blue);}
[data-theme="dark"] .pop-connu{background:var(--gold-l);color:var(--gold);}
[data-theme="dark"] .pop-populaire{background:#3d1010;color:#fc8181;}
.state-placeholder{grid-column:1/-1;text-align:center;padding:60px 20px;color:var(--ink3);}
.state-placeholder .ph-icon{font-size:2.6rem;margin-bottom:10px;opacity:.4;display:block;}
.loading-pulse{animation:pulse 1.2s ease-in-out infinite;display:inline-block;}
.top-table{width:100%;border-collapse:collapse;}
.top-table th{font-size:0.68rem;font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:var(--ink3);padding:10px 16px;text-align:left;border-bottom:1px solid var(--border);background:var(--bg2);}
.top-table td{padding:11px 16px;border-bottom:1px solid var(--border);font-size:0.88rem;color:var(--ink);}
.top-table tr:last-child td{border-bottom:none;}
.top-table tr:hover td{background:var(--bg);}
.top-table .rang{font-family:'Cormorant Garamond',serif;font-size:1.1rem;color:var(--ink3);width:48px;}
.top-table .prenom-link{font-family:'Cormorant Garamond',serif;font-size:1.15rem;color:var(--accent);cursor:pointer;text-decoration:none;}
.top-table .prenom-link:hover{text-decoration:underline;}
.top-table .bar-cell{width:160px;}
.top-mini-bar{height:5px;background:var(--bg2);border-radius:3px;overflow:hidden;margin-top:4px;}
.top-mini-fill{height:100%;background:linear-gradient(to right,var(--accent2),var(--accent));border-radius:3px;}
@media(max-width:600px){.top-table .bar-cell{display:none;}.top-table th.bar-cell{display:none;}}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.35}}
</style>
</head>
<body>
<header>
<div class="logo">Prénoms <span>stats</span></div>
<div class="header-center">
<button class="mode-btn active" onclick="switchMode('explorer',this)">
<span class="btn-icon">🔍</span><span class="btn-label"> Explorer</span>
</button>
<button class="mode-btn" onclick="switchMode('finder',this)">
<span class="btn-icon"></span><span class="btn-label"> Trouver</span>
</button>
<button class="mode-btn" onclick="switchMode('top',this)">
<span class="btn-icon">🏆</span><span class="btn-label"> Classement</span>
</button>
</div>
<button class="theme-toggle" onclick="toggleTheme()" id="theme-btn">🌙</button>
</header>
<div id="page-explorer" class="page active">
<div id="page-explorer-inner">
<div id="back-to-results" onclick="goBackToFinder()" style="display:none;align-items:center;gap:8px;margin-bottom:18px;cursor:pointer;width:fit-content;padding:7px 14px;border-radius:100px;border:1.5px solid var(--border);background:var(--card);color:var(--ink2);font-size:0.8rem;font-family:'Outfit',sans-serif;transition:all .15s;">
<span></span> <span>Retour aux résultats</span>
</div>
<h1 class="hero-title">Explorer un <em>prénom</em></h1>
<p class="hero-sub">Données INSEE · France · depuis 1900</p>
<div class="search-area">
<div class="autocomplete-wrap" id="ac-wrap-1">
<div class="search-row">
<input class="search-input" id="search-input-1" placeholder="Entrez un prénom…"
autocomplete="off" autocorrect="off" spellcheck="false"
oninput="onAcInput(1)" onkeydown="onAcKey(event,1)"/>
<button class="search-btn" onclick="doExplore()">Analyser</button>
</div>
<div class="autocomplete-list" id="ac-list-1"></div>
</div>
<div class="compare-toggle-row">
<label><input type="checkbox" id="compare-chk" onchange="toggleCompare()"/> Comparer avec un second prénom</label>
</div>
<div class="compare-second" id="ac-wrap-2">
<div class="search-row blue-focus">
<input class="search-input" id="search-input-2" placeholder="Second prénom…"
autocomplete="off" autocorrect="off" spellcheck="false"
oninput="onAcInput(2)" onkeydown="onAcKey(event,2)"/>
<button class="search-btn" style="background:var(--blue)" onclick="doExplore()">Comparer</button>
</div>
<div class="autocomplete-list" id="ac-list-2"></div>
</div>
<div class="suggestions">
<span class="sug-chip" onclick="quickSearch('Emma')">Emma</span>
<span class="sug-chip" onclick="quickSearch('Léo')">Léo</span>
<span class="sug-chip" onclick="quickSearch('Marie')">Marie</span>
<span class="sug-chip" onclick="quickSearch('Lucas')">Lucas</span>
<span class="sug-chip" onclick="quickSearch('Jade')">Jade</span>
<span class="sug-chip" onclick="quickSearch('Jean')">Jean</span>
<span class="sug-chip" onclick="quickSearch('Camille')">Camille</span>
</div>
</div>
<div id="explorer-result"></div>
</div>
</div>
<div id="page-finder" class="page">
<div class="finder-layout">
<aside class="finder-sidebar">
<div class="sidebar-header"><div class="sidebar-title">Critères de recherche</div><span class="sidebar-toggle-icon"></span></div>
<div class="sidebar-body">
<div class="filter-group">
<span class="filter-label">Genre</span>
<div class="pill-group">
<button class="pill active" data-sexe="" onclick="setSexe(this,'')">Tous</button>
<button class="pill" data-sexe="1" onclick="setSexe(this,'1')">♂ Garçon</button>
<button class="pill" data-sexe="2" onclick="setSexe(this,'2')">♀ Fille</button>
<button class="pill" data-sexe="mixte" onclick="setSexe(this,'mixte')">⚥ Mixte</button>
</div>
</div>
<div class="filter-group">
<span class="filter-label">Popularité</span>
<div class="pill-group">
<button class="pill active-gold" id="pop-rare" onclick="togglePop(this,'rare')">Rare</button>
<button class="pill" id="pop-discret" onclick="togglePop(this,'discret')">Discret</button>
<button class="pill" id="pop-connu" onclick="togglePop(this,'connu')">Connu</button>
<button class="pill" id="pop-populaire" onclick="togglePop(this,'populaire')">Populaire</button>
</div>
<div class="hint">Total toutes années · Rare &lt;2000 · Discret 2k10k · Connu 10k50k · Populaire &gt;50k</div>
</div>
<div class="filter-group">
<span class="filter-label">Longueur — <span id="len-disp">3 à 8 lettres</span></span>
<input type="range" id="len-min-s" min="1" max="15" value="3" oninput="updateLenDisplay()"/>
<div class="range-vals"><span>1</span><span>min</span><span>15</span></div>
<input type="range" id="len-max-s" min="1" max="15" value="8" oninput="updateLenDisplay()"/>
<div class="range-vals"><span>1</span><span>max</span><span>15</span></div>
</div>
<div class="filter-group">
<span class="filter-label">Commence par</span>
<input type="text" class="filter-input" id="finder-q" placeholder="ex: MA, TH…" maxlength="5" oninput="this.value=this.value.toUpperCase()"/>
</div>
<div class="filter-group">
<span class="filter-label">Période de popularité</span>
<div class="pill-group">
<button class="pill" id="era-ancien" onclick="toggleEra(this,'ancien')">Classique</button>
<button class="pill" id="era-retro" onclick="toggleEra(this,'retro')">Rétro 80s</button>
<button class="pill active" id="era-recent" onclick="toggleEra(this,'recent')">Actuel</button>
</div>
<div class="hint" id="era-hint">Actuel : 20102023</div>
</div>
<button class="btn-search-finder" onclick="doFinder()">Rechercher</button>
</div>
</aside>
<div>
<div class="finder-results-header">
<div class="results-title">Résultats</div>
<div class="results-count" id="finder-count"></div>
</div>
<div class="results-grid" id="finder-grid">
<div class="state-placeholder"><span class="ph-icon"></span><p>Définissez vos critères et lancez la recherche</p></div>
</div>
</div>
</div>
</div>
<script>
let evoChart=null, compareChart=null;
let finderSexe='', finderPop='rare', finderEra='recent';
let acTimers=[null,null], acSelected=[-1,-1];
const eraMap={ancien:{min:1900,max:1960,label:'Classique (avant 1960)'},retro:{min:1970,max:1995,label:'Rétro 80s (19701995)'},recent:{min:2010,max:2023,label:'Actuel (20102023)'}};
const popMap={rare:{min:0,max:1999},discret:{min:2000,max:9999},connu:{min:10000,max:49999},populaire:{min:50000,max:9999999}};
// THEME
function toggleTheme(){
const d=document.documentElement,isDark=d.getAttribute('data-theme')==='dark';
d.setAttribute('data-theme',isDark?'light':'dark');
document.getElementById('theme-btn').textContent=isDark?'🌙':'☀️';
localStorage.setItem('theme',isDark?'light':'dark');
}
(function(){
const s=localStorage.getItem('theme')||'light';
document.documentElement.setAttribute('data-theme',s);
window.addEventListener('DOMContentLoaded',()=>{document.getElementById('theme-btn').textContent=s==='dark'?'☀️':'🌙';});
})();
// MODE
let _prevMode=null;
function switchMode(mode,btn){
const current=document.querySelector('.page.active')?.id?.replace('page-','');
_prevMode=(current&&current!==mode)?current:null;
document.querySelectorAll('.page').forEach(p=>p.classList.remove('active'));
document.querySelectorAll('.mode-btn').forEach(b=>b.classList.remove('active'));
document.getElementById('page-'+mode).classList.add('active');
btn.classList.add('active');
// Afficher/masquer le bouton retour
const backBtn=document.getElementById('back-to-results');
if(backBtn) backBtn.style.display=(mode==='explorer'&&_prevMode==='finder')?'flex':'none';
if(mode==='top') loadTop();
}
// SHARE
function sharePrenom(prenom){
const url=location.origin+location.pathname+'#'+encodeURIComponent(prenom.toUpperCase());
navigator.clipboard.writeText(url).then(()=>{
const btn=document.getElementById('share-btn-'+prenom);
if(btn){btn.textContent='✓ Copié !';btn.classList.add('copied');setTimeout(()=>{btn.textContent='🔗 Partager';btn.classList.remove('copied');},2000);}
});
}
function checkUrlHash(){const h=decodeURIComponent(location.hash.slice(1));if(h){document.getElementById('search-input-1').value=capitalize(h);explorePrenom(h);}}
// AUTOCOMPLETE
function onAcInput(idx){
const val=document.getElementById('search-input-'+idx).value.trim();
clearTimeout(acTimers[idx-1]);
if(val.length<1){closeAc(idx);return;}
acTimers[idx-1]=setTimeout(()=>fetchAc(idx,val),160);
}
async function fetchAc(idx,q){
try{const d=await fetch('/api/autocomplete?q='+encodeURIComponent(q)+'&limit=7').then(r=>r.json());renderAc(idx,d,q);}catch(e){}
}
function renderAc(idx,items,q){
const list=document.getElementById('ac-list-'+idx);
if(!items.length){closeAc(idx);return;}
list.innerHTML='';acSelected[idx-1]=-1;
items.forEach((item)=>{
const nom=capitalize(item.prenom);
const re=new RegExp('^('+escRe(capitalize(q))+')','i');
const hl=nom.replace(re,'<mark>$1</mark>');
const si=item.sexe===1?'♂':item.sexe===2?'♀':'⚥';
const div=document.createElement('div');div.className='ac-item';
div.innerHTML='<span class="ac-name">'+hl+'</span><span class="ac-meta">'+si+' · '+fmtK(item.total)+'</span>';
div.addEventListener('mousedown',e=>{e.preventDefault();selectAc(idx,item.prenom);});
list.appendChild(div);
});
list.classList.add('open');
}
function escRe(s){return s.replace(/[.*+?^${}()|[\]\\]/g,'\\$&');}
function onAcKey(e,idx){
const list=document.getElementById('ac-list-'+idx);
const items=list.querySelectorAll('.ac-item');
if(!items.length){if(e.key==='Enter')doExplore();return;}
if(e.key==='ArrowDown'){e.preventDefault();acSelected[idx-1]=Math.min(acSelected[idx-1]+1,items.length-1);highlightAc(idx,items);}
else if(e.key==='ArrowUp'){e.preventDefault();acSelected[idx-1]=Math.max(acSelected[idx-1]-1,-1);highlightAc(idx,items);}
else if(e.key==='Enter'){if(acSelected[idx-1]>=0){selectAc(idx,items[acSelected[idx-1]].querySelector('.ac-name').textContent);}else{doExplore();}}
else if(e.key==='Escape'){closeAc(idx);}
}
function highlightAc(idx,items){
items.forEach((el,i)=>el.style.background=i===acSelected[idx-1]?'var(--bg2)':'');
if(acSelected[idx-1]>=0)document.getElementById('search-input-'+idx).value=items[acSelected[idx-1]].querySelector('.ac-name').textContent;
}
function selectAc(idx,prenom){document.getElementById('search-input-'+idx).value=capitalize(prenom);closeAc(idx);if(idx===1&&!document.getElementById('compare-chk').checked)doExplore();}
function closeAc(idx){const l=document.getElementById('ac-list-'+idx);l.classList.remove('open');l.innerHTML='';}
document.addEventListener('click',e=>{if(!e.target.closest('.autocomplete-wrap')){closeAc(1);closeAc(2);}});
// COMPARE TOGGLE
function toggleCompare(){
const on=document.getElementById('compare-chk').checked;
document.getElementById('ac-wrap-2').classList.toggle('open',on);
if(!on)closeAc(2);
}
// EXPLORE
function quickSearch(p){document.getElementById('search-input-1').value=p;document.getElementById('compare-chk').checked=false;document.getElementById('ac-wrap-2').classList.remove('open');doExplore();}
function doExplore(){
closeAc(1);closeAc(2);
const p1=document.getElementById('search-input-1').value.trim();if(!p1)return;
const cmp=document.getElementById('compare-chk').checked;
const p2=cmp?document.getElementById('search-input-2').value.trim():null;
location.hash=encodeURIComponent(p1.toUpperCase());
if(cmp&&p2)doCompare(p1,p2);else explorePrenom(p1);
}
async function explorePrenom(prenom){
const res=document.getElementById('explorer-result');
res.style.display='block';
res.innerHTML='<div style="text-align:center;padding:60px;color:var(--ink3)"><span class="loading-pulse">Chargement…</span></div>';
try{
const [evo,hm]=await Promise.all([
fetch('/api/evolution/'+encodeURIComponent(prenom.toUpperCase())).then(r=>{if(!r.ok)throw new Error();return r.json();}),
fetch('/api/heatmap/'+encodeURIComponent(prenom.toUpperCase())).then(r=>r.ok?r.json():[]),
]);
renderExplorer(prenom,evo,hm);
}catch(e){res.innerHTML='<div style="text-align:center;padding:40px;color:var(--ink3)">Prénom introuvable.</div>';}
}
function renderExplorer(prenom,evo,hm){
const nom=capitalize(prenom),res=document.getElementById('explorer-result');
const hasMale=evo.some(d=>d.sexe===1),hasFemale=evo.some(d=>d.sexe===2);
const totM=evo.filter(d=>d.sexe===1).reduce((s,d)=>s+d.valeur,0);
const totF=evo.filter(d=>d.sexe===2).reduce((s,d)=>s+d.valeur,0);
const totAll=totM+totF;
const byY={};evo.forEach(d=>{byY[d.periode]=(byY[d.periode]||0)+d.valeur;});
const pe=Object.entries(byY).sort((a,b)=>b[1]-a[1])[0];
const peakYr=pe?.[0]??'—',peakVal=pe?.[1]??0;
const ys=Object.keys(byY).map(Number).sort(),maxY=Math.max(...ys);
const r10=ys.filter(y=>y>=maxY-9).reduce((s,y)=>s+(byY[y]||0),0);
const p10=ys.filter(y=>y>=maxY-19&&y<maxY-9).reduce((s,y)=>s+(byY[y]||0),0);
const tpct=p10>0?Math.round(((r10-p10)/p10)*100):null;
let badges='';
if(hasMale&&hasFemale)badges+='<span class="badge badge-mix">Mixte</span>';
else if(hasMale)badges+='<span class="badge badge-m">♂ Masculin</span>';
else if(hasFemale)badges+='<span class="badge badge-f">♀ Féminin</span>';
badges+='<span class="badge badge-len">'+prenom.replace(/\s/g,'').length+' lettres</span>';
res.innerHTML=`
<div class="prenom-hero"><div class="prenom-big">${nom}</div><div class="prenom-badges">${badges}</div></div>
<div class="stat-row">
<div class="stat-card"><span class="val">${fmt(totAll)}</span><span class="lbl">Naissances totales</span></div>
${hasMale?'<div class="stat-card"><span class="val" style="color:var(--blue)">'+fmt(totM)+'</span><span class="lbl">♂ Garçons</span></div>':''}
${hasFemale?'<div class="stat-card"><span class="val" style="color:var(--pink)">'+fmt(totF)+'</span><span class="lbl">♀ Filles</span></div>':''}
<div class="stat-card"><span class="val">${fmt(peakVal)}</span><span class="lbl">Pic annuel (${peakYr})</span></div>
</div>
<div class="peak-row">
<div class="peak-card"><span class="peak-icon">📅</span><div><div class="peak-year">${peakYr}</div><div class="peak-desc">Année la plus populaire</div><div class="trend-bar"><div class="trend-fill" style="width:100%"></div></div></div></div>
<div class="peak-card"><span class="peak-icon">${tpct===null?'📊':tpct>=0?'📈':'📉'}</span><div><div class="peak-year" style="color:${tpct===null?'inherit':tpct>=0?'#276749':'#c53030'}">${tpct===null?'—':(tpct>=0?'+':'')+tpct+'%'}</div><div class="peak-desc">Tendance 10 dernières années</div></div></div>
</div>
<div class="chart-card">
<div class="chart-card-title">Évolution des naissances<button class="share-btn" id="share-btn-${nom}" onclick="sharePrenom('${nom}')">🔗 Partager</button></div>
<div class="chart-wrap"><canvas id="evo-canvas"></canvas></div>
</div>
<div class="chart-card">
<div class="chart-card-title">Carte de chaleur par décennie</div>
<div id="heatmap-scroll"><div id="heatmap-container"></div></div>
</div>`;
buildEvoChart(evo,nom);buildHeatmap(hm);
}
// COMPARE
async function doCompare(p1,p2){
const res=document.getElementById('explorer-result');res.style.display='block';
res.innerHTML='<div style="text-align:center;padding:60px;color:var(--ink3)"><span class="loading-pulse">Chargement…</span></div>';
try{
const data=await fetch('/api/compare?a='+encodeURIComponent(p1.toUpperCase())+'&b='+encodeURIComponent(p2.toUpperCase())).then(r=>r.json());
renderCompare(p1,p2,data);
}catch(e){res.innerHTML='<div style="text-align:center;padding:40px;color:var(--ink3)">Erreur.</div>';}
}
function renderCompare(p1,p2,data){
const n1=capitalize(p1),n2=capitalize(p2);
const e1=data[p1.toUpperCase()]||[],e2=data[p2.toUpperCase()]||[];
const t1=e1.reduce((s,d)=>s+d.valeur,0),t2=e2.reduce((s,d)=>s+d.valeur,0);
const by1={};e1.forEach(d=>{by1[d.periode]=(by1[d.periode]||0)+d.valeur;});
const by2={};e2.forEach(d=>{by2[d.periode]=(by2[d.periode]||0)+d.valeur;});
const pk1=Object.entries(by1).sort((a,b)=>b[1]-a[1])[0];
const pk2=Object.entries(by2).sort((a,b)=>b[1]-a[1])[0];
document.getElementById('explorer-result').innerHTML=`
<div class="prenom-hero"><div class="prenom-big">${n1} <span style="color:var(--ink3);font-size:.5em">vs</span> ${n2}</div></div>
<div class="stat-row">
<div class="stat-card"><span class="val" style="color:var(--blue)">${fmt(t1)}</span><span class="lbl">${n1} · total</span></div>
<div class="stat-card"><span class="val" style="color:var(--accent)">${fmt(t2)}</span><span class="lbl">${n2} · total</span></div>
<div class="stat-card"><span class="val">${pk1?.[0]??'—'}</span><span class="lbl">Pic ${n1}</span></div>
<div class="stat-card"><span class="val">${pk2?.[0]??'—'}</span><span class="lbl">Pic ${n2}</span></div>
</div>
<div class="chart-card">
<div class="chart-card-title">Comparaison — ${n1} vs ${n2}</div>
<div class="chart-wrap"><canvas id="compare-canvas"></canvas></div>
</div>`;
buildCompareChart(e1,e2,n1,n2);
}
// CHARTS
function cColors(){
const dark=document.documentElement.getAttribute('data-theme')==='dark';
return{grid:dark?'rgba(255,255,255,0.05)':'rgba(0,0,0,0.04)',tick:dark?'#6e6458':'#9a8e82',ttBg:dark?'#1c1916':'#1a1612'};
}
function cOpts(c){
return{responsive:true,maintainAspectRatio:false,interaction:{mode:'index',intersect:false},
plugins:{legend:{labels:{color:c.tick,font:{family:'Outfit',size:11},usePointStyle:true}},
tooltip:{backgroundColor:c.ttBg,titleColor:'#f7f4ef',bodyColor:'#c8bfb0',borderColor:'rgba(255,255,255,0.08)',borderWidth:1,
callbacks:{label:ctx=>' '+ctx.dataset.label+' : '+(ctx.parsed.y?.toLocaleString('fr-FR')??'')}}},
scales:{x:{ticks:{color:c.tick,font:{family:'Outfit',size:10},maxTicksLimit:12},grid:{color:c.grid}},
y:{ticks:{color:c.tick,font:{family:'Outfit',size:10},callback:v=>v>=1000?(v/1000).toFixed(0)+'k':v},grid:{color:c.grid}}}};
}
function buildEvoChart(evo,nom){
const ctx=document.getElementById('evo-canvas').getContext('2d');
if(evoChart)evoChart.destroy();
const p=[...new Set(evo.map(d=>d.periode))].sort();
const hM=evo.some(d=>d.sexe===1),hF=evo.some(d=>d.sexe===2);
const c=cColors(),ds=[];
if(hM)ds.push({label:'Garçons',data:p.map(x=>{const r=evo.find(d=>d.periode===x&&d.sexe===1);return r?.valeur??null;}),borderColor:'#2c5282',backgroundColor:'rgba(44,82,130,0.08)',borderWidth:2,pointRadius:0,pointHoverRadius:5,fill:true,tension:.4,spanGaps:true});
if(hF)ds.push({label:'Filles',data:p.map(x=>{const r=evo.find(d=>d.periode===x&&d.sexe===2);return r?.valeur??null;}),borderColor:'#9b2c5a',backgroundColor:'rgba(155,44,90,0.07)',borderWidth:2,pointRadius:0,pointHoverRadius:5,fill:true,tension:.4,spanGaps:true});
if(hM&&hF)ds.push({label:'Total',data:p.map(x=>evo.filter(d=>d.periode===x).reduce((s,d)=>s+d.valeur,0)),borderColor:'#b7791f',backgroundColor:'transparent',borderWidth:1.5,borderDash:[4,3],pointRadius:0,pointHoverRadius:5,fill:false,tension:.4});
evoChart=new Chart(ctx,{type:'line',data:{labels:p,datasets:ds},options:cOpts(c)});
}
function buildCompareChart(e1,e2,n1,n2){
const ctx=document.getElementById('compare-canvas').getContext('2d');
if(compareChart)compareChart.destroy();
const allP=[...new Set([...e1.map(d=>d.periode),...e2.map(d=>d.periode)])].sort();
const c=cColors();
const ds=[
{label:n1,data:allP.map(p=>e1.filter(d=>d.periode===p).reduce((s,d)=>s+d.valeur,0)||null),borderColor:'#2c5282',backgroundColor:'rgba(44,82,130,0.08)',borderWidth:2.5,pointRadius:0,pointHoverRadius:5,fill:true,tension:.4,spanGaps:true},
{label:n2,data:allP.map(p=>e2.filter(d=>d.periode===p).reduce((s,d)=>s+d.valeur,0)||null),borderColor:'#c0392b',backgroundColor:'rgba(192,57,43,0.07)',borderWidth:2.5,pointRadius:0,pointHoverRadius:5,fill:true,tension:.4,spanGaps:true}
];
compareChart=new Chart(ctx,{type:'line',data:{labels:allP,datasets:ds},options:cOpts(c)});
}
// HEATMAP
function buildHeatmap(hmData){
const container=document.getElementById('heatmap-container');
if(!hmData||!hmData.length){container.innerHTML='<p style="color:var(--ink3);font-size:.83rem;padding:8px 0">Pas assez de données.</p>';return;}
const decades=[...new Set(hmData.map(d=>d.decade))].sort();
const sexes=[...new Set(hmData.map(d=>d.sexe))].sort();
const maxVal=Math.max(...hmData.map(d=>d.valeur));
const sLabel={1:'♂ Garçons',2:'♀ Filles'};
const sColor={1:(a)=>'rgba(44,82,130,'+a+')',2:(a)=>'rgba(155,44,90,'+a+')'};
let html='<div class="heatmap-decade-labels">';
decades.forEach(d=>{html+='<div class="heatmap-decade-lbl">'+d+'s</div>';});
html+='</div>';
sexes.forEach(sexe=>{
html+='<div class="heatmap-row"><div class="heatmap-label">'+(sLabel[sexe]??sexe)+'</div>';
decades.forEach(decade=>{
const entry=hmData.find(d=>d.decade===decade&&d.sexe===sexe);
const val=entry?.valeur??0;
const alpha=val>0?0.15+0.75*(val/maxVal):0;
const fmt2=val>=1000?Math.round(val/1000)+'k':val>0?val:'';
if(val===0)html+='<div class="heatmap-cell heatmap-cell-0" title="'+decade+'s : 0"></div>';
else html+='<div class="heatmap-cell" style="background:'+sColor[sexe](alpha)+'" title="'+decade+'s : '+val.toLocaleString('fr-FR')+'">'+fmt2+'</div>';
});
html+='</div>';
});
container.innerHTML='<div class="heatmap-inner">'+html+'</div>';
}
// FINDER
function setSexe(btn,val){
finderSexe=val;
document.querySelectorAll('[data-sexe]').forEach(b=>b.classList.remove('active','active-blue','active-pink','active-mix'));
if(val==='1')btn.classList.add('active-blue');
else if(val==='2')btn.classList.add('active-pink');
else if(val==='mixte')btn.classList.add('active-mix');
else btn.classList.add('active');
}
function togglePop(btn,key){
finderPop=key;
document.querySelectorAll('[id^="pop-"]').forEach(b=>b.classList.remove('active','active-gold'));
btn.classList.add('active-gold');
}
function toggleEra(btn,key){
finderEra=key;
document.querySelectorAll('[id^="era-"]').forEach(b=>b.classList.remove('active'));
btn.classList.add('active');
document.getElementById('era-hint').textContent=eraMap[key].label;
}
function updateLenDisplay(){
const min=+document.getElementById('len-min-s').value,max=+document.getElementById('len-max-s').value;
document.getElementById('len-disp').textContent=min+' à '+max+' lettres';
}
async function doFinder(){
const grid=document.getElementById('finder-grid');
grid.innerHTML='<div class="state-placeholder"><span class="loading-pulse">Recherche en cours…</span></div>';
document.getElementById('finder-count').textContent='';
const era=eraMap[finderEra];
const lenMin=+document.getElementById('len-min-s').value,lenMax=+document.getElementById('len-max-s').value;
const q=document.getElementById('finder-q').value.trim();
const popMin=popMap[finderPop].min;
const popMax=popMap[finderPop].max;
// popularite filtre sur pic annuel all-time (via peak_min/peak_max, periode all-time)
const params=new URLSearchParams({longueur_min:lenMin,longueur_max:lenMax,popularite_min:popMin,popularite_max:popMax,periode_min:era.min,periode_max:era.max,limit:80});
if(finderSexe&&finderSexe!=='mixte')params.set('sexe',finderSexe);
if(q)params.set('q',q);
try{
let data=await fetch('/api/search?'+params).then(r=>r.json());
if(finderSexe==='mixte'){
const counts={};
data.forEach(d=>{if(!counts[d.prenom])counts[d.prenom]=new Set();counts[d.prenom].add(d.sexe);});
const mx=new Set(Object.entries(counts).filter(([,s])=>s.has(1)&&s.has(2)).map(([n])=>n));
const merged={};
data.filter(d=>mx.has(d.prenom)).forEach(d=>{if(!merged[d.prenom])merged[d.prenom]={prenom:d.prenom,sexe:'mixte',longueur:d.longueur,total:0};merged[d.prenom].total+=d.total;});
data=Object.values(merged).sort((a,b)=>b.total-a.total);
}
renderFinderResults(data);
}catch(e){grid.innerHTML='<div class="state-placeholder"><p>Erreur de connexion.</p></div>';}
}
function renderFinderResults(data){
const grid=document.getElementById('finder-grid');
document.getElementById('finder-count').textContent=data.length+' résultat'+(data.length>1?'s':'');
if(!data.length){grid.innerHTML='<div class="state-placeholder"><span class="ph-icon">🤔</span><p>Aucun résultat. Élargissez les filtres.</p></div>';return;}
const maxT=Math.max(...data.map(d=>d.total_periode||d.total));
grid.innerHTML='';
data.forEach((item,i)=>{
const nom=capitalize(item.prenom),pct=Math.round(((item.total_periode||item.total)/maxT)*100);
const sb=item.sexe===1?'<span class="badge badge-m">♂</span>':item.sexe===2?'<span class="badge badge-f">♀</span>':'<span class="badge badge-mix">⚥</span>';
const pc=item.total<2000?['pop-rare','Rare']:item.total<10000?['pop-discret','Discret']:item.total<50000?['pop-connu','Connu']:['pop-populaire','Populaire'];
const card=document.createElement('div');card.className='result-card';
card.style.animationDelay=Math.min(i*12,350)+'ms';
card.innerHTML='<div class="rc-name">'+nom+'</div><div class="rc-meta">'+sb+'<span class="pop-label '+pc[0]+'">'+pc[1]+'</span></div><div class="rc-total">'+fmt(item.total)+' naissances</div><div class="rc-mini-bar"><div class="rc-mini-fill" style="width:'+pct+'%"></div></div>';
card.addEventListener('click',()=>{
document.getElementById('search-input-1').value=nom;
document.getElementById('compare-chk').checked=false;
document.getElementById('ac-wrap-2').classList.remove('open');
switchMode('explorer',document.querySelector('.mode-btn'));
explorePrenom(item.prenom);
// Afficher bouton retour
const backBtn=document.getElementById('back-to-results');
if(backBtn) backBtn.style.display='flex';
});
grid.appendChild(card);
});
}
// UTILS
function fmt(n){return n?.toLocaleString('fr-FR')??'—';}
function fmtK(n){return n>=1000?Math.round(n/1000)+'k':n;}
function capitalize(s){return s?s.charAt(0).toUpperCase()+s.slice(1).toLowerCase():'';}
// ── CLASSEMENT ──
let topSexe='', topEra='recent', topOrder='desc', topPage=1, topTotal=0, topPerPage=50, topData=null;
const topEraMap={all:{min:1900,max:2024},recent:{min:2010,max:2024},retro:{min:1970,max:1995},ancien:{min:1900,max:1960}};
function setTopSexe(btn,val){topSexe=val;topPage=1;topData=null;document.querySelectorAll('[data-top-sexe]').forEach(b=>b.classList.remove('active','active-blue','active-pink'));if(val==='1')btn.classList.add('active-blue');else if(val==='2')btn.classList.add('active-pink');else btn.classList.add('active');loadTop();}
function setTopEra(btn,val){topEra=val;topPage=1;topData=null;document.querySelectorAll('[data-top-era]').forEach(b=>b.classList.remove('active'));btn.classList.add('active');loadTop();}
function setTopOrder(val){topOrder=val;topPage=1;topData=null;document.getElementById('top-order-desc').classList.toggle('active',val==='desc');document.getElementById('top-order-asc').classList.toggle('active',val==='asc');loadTop();}
function topChangePage(delta){const maxPage=Math.ceil(topTotal/topPerPage);topPage=Math.max(1,Math.min(maxPage,topPage+delta));topData=null;loadTop();}
function renderTopTable(res){
const wrap=document.getElementById('top-table-wrap');if(!wrap)return;
topTotal=res.total;topData=res;
const maxVal=Math.max(...res.data.map(d=>d.total_periode||1));
const maxPage=Math.ceil(res.total/topPerPage);
document.getElementById('top-count').textContent=res.total.toLocaleString('fr-FR')+' prénoms';
document.getElementById('top-page-info').textContent='Page '+res.page+' / '+maxPage;
document.getElementById('top-prev').disabled=res.page<=1;
document.getElementById('top-next').disabled=res.page>=maxPage;
let html='<table class="top-table"><thead><tr><th class="rang">#</th><th>Prénom</th><th>Genre</th><th>Naissances</th><th class="bar-cell"></th></tr></thead><tbody>';
res.data.forEach((item,i)=>{
const nom=capitalize(item.prenom);
const rang=(topOrder==='desc')?(res.page-1)*topPerPage+i+1:res.total-(res.page-1)*topPerPage-i;
const si=item.sexe===1?'<span class="badge badge-m">♂</span>':item.sexe===2?'<span class="badge badge-f">♀</span>':'<span class="badge badge-mix">⚥</span>';
const total=item.total_periode||item.total;
const pct=Math.round((total/maxVal)*100);
const medal=rang===1?'🥇 ':rang===2?'🥈 ':rang===3?'🥉 ':'';
html+=`<tr><td class="rang" style="color:${rang<=3?'var(--accent)':'var(--ink3)'};font-weight:${rang<=3?700:400}">${medal}${rang}</td><td><span class="prenom-link" onclick="goToPrenom('${item.prenom}')">${nom}</span></td><td>${si}</td><td style="font-family:'Cormorant Garamond',serif;font-size:1.1rem;">${fmt(total)}</td><td class="bar-cell"><div class="top-mini-bar"><div class="top-mini-fill" style="width:${pct}%"></div></div></td></tr>`;
});
html+='</tbody></table>';wrap.innerHTML=html;
}
async function loadTop(){
const wrap=document.getElementById('top-table-wrap');
if(wrap)wrap.innerHTML='<div class="state-placeholder"><span style="font-size:2rem">⏳</span><p style="margin-top:8px;color:var(--ink3)">Chargement…</p></div>';
const era=topEraMap[topEra];
const params=new URLSearchParams({periode_min:era.min,periode_max:era.max,order:topOrder,page:topPage,per_page:topPerPage});
if(topSexe)params.set('sexe',topSexe);
try{const res=await fetch('/api/top?'+params).then(r=>r.json());renderTopTable(res);}
catch(e){if(wrap)wrap.innerHTML='<div class="state-placeholder"><p>Erreur de connexion.</p></div>';}
}
function goToPrenom(prenom){
document.getElementById('search-input-1').value=capitalize(prenom);
switchMode('explorer',document.querySelector('.mode-btn'));
explorePrenom(prenom);
const backBtn=document.getElementById('back-to-results');
if(backBtn)backBtn.style.display='flex';
}
function goBackToFinder(){switchMode('finder',document.querySelectorAll('.mode-btn')[1]);}
// INIT
window.addEventListener('load',()=>{
// Heatmap scroll indicator
document.addEventListener('scroll', updateHeatmapIndicator, true);
function updateHeatmapIndicator(){
const el=document.getElementById('heatmap-scroll-el');
if(!el) return;
const wrap=el.closest('.heatmap-scroll-wrap');
if(wrap) wrap.classList.toggle('at-end', el.scrollLeft+el.clientWidth >= el.scrollWidth-4);
}
// Also update after heatmap is built
const origBuild=window.buildHeatmap;
setTimeout(updateHeatmapIndicator, 500);
checkUrlHash();
window.addEventListener('hashchange',checkUrlHash);
// Mobile: auto-collapse sidebar on small screens
const sidebar = document.querySelector('.finder-sidebar');
document.querySelector('.sidebar-header').addEventListener('click', () => {
sidebar.classList.toggle('collapsed');
});
if (window.innerWidth <= 720) sidebar.classList.add('collapsed');
});
</script>
<div id="page-top" class="page">
<div style="max-width:860px;margin:0 auto;padding:44px 18px 80px;">
<h1 class="hero-title" style="text-align:center;margin-bottom:6px;">Classement des <em>prénoms</em></h1>
<p class="hero-sub" style="text-align:center;margin-bottom:28px;">Total de naissances sur la période · Données INSEE</p>
<!-- Contrôles -->
<div style="display:flex;flex-wrap:wrap;gap:10px;align-items:center;justify-content:space-between;margin-bottom:20px;">
<div style="display:flex;flex-wrap:wrap;gap:8px;align-items:center;">
<!-- Genre -->
<div class="pill-group">
<button class="pill active" data-top-sexe="" onclick="setTopSexe(this,'')">Tous</button>
<button class="pill" data-top-sexe="1" onclick="setTopSexe(this,'1')">♂ Garçons</button>
<button class="pill" data-top-sexe="2" onclick="setTopSexe(this,'2')">♀ Filles</button>
</div>
<!-- Période -->
<div class="pill-group">
<button class="pill" data-top-era="all" onclick="setTopEra(this,'all')">Toutes années</button>
<button class="pill active" data-top-era="recent" onclick="setTopEra(this,'recent')">Actuel (20102024)</button>
<button class="pill" data-top-era="retro" onclick="setTopEra(this,'retro')">Rétro (19701995)</button>
<button class="pill" data-top-era="ancien" onclick="setTopEra(this,'ancien')">Classique (avant 1960)</button>
</div>
</div>
<!-- Ordre -->
<div style="display:flex;gap:6px;">
<button id="top-order-desc" class="pill active" onclick="setTopOrder('desc')">↓ Plus donnés</button>
<button id="top-order-asc" class="pill" onclick="setTopOrder('asc')">↑ Plus rares</button>
</div>
</div>
<!-- Tableau -->
<div style="background:var(--card);border:1px solid var(--border);border-radius:14px;overflow:hidden;">
<div id="top-table-wrap">
<div class="state-placeholder"><span class="ph-icon loading-pulse"></span><p>Chargement…</p></div>
</div>
<!-- Pagination -->
<div id="top-pagination" style="display:flex;align-items:center;justify-content:space-between;padding:12px 18px;border-top:1px solid var(--border);gap:10px;flex-wrap:wrap;">
<span id="top-count" style="font-size:0.75rem;color:var(--ink3);"></span>
<div style="display:flex;gap:6px;align-items:center;">
<button id="top-prev" class="pill" onclick="topChangePage(-1)" style="padding:5px 14px;">← Préc.</button>
<span id="top-page-info" style="font-size:0.78rem;color:var(--ink2);"></span>
<button id="top-next" class="pill" onclick="topChangePage(1)" style="padding:5px 14px;">Suiv. →</button>
</div>
</div>
</div>
</div>
</div>
</body>
</html>