Créa·Ka — annuaire public cross-plateforme des créateurs de contenu québécois (crea-ka.com)
1<!--2 Author: Simon-Pierre Boucher <contact@spboucher.ai>3 File: frontend/src/index.template.html4 Desc: Gabarit du frontend Créa-Ka — marqueurs remplacés par5 scripts/build_frontend.py : __ICONS__ (tracés SVG simple-icons, CC0),6 __KA_TOKENS__ (ka/tokens.css — design system commun Groupe KA,7 importé EN PREMIER) et __KA_ECO__ (ka/ecosystem.json — contacts,8 sites, mentions légales du groupe : footer commun + page /contact).9 Système « éditorial sharp » du Groupe KA (papier grainé #f5f3ee,10 encre #141814, bordures encre + ombres décalées dures, Space Grotesk11 / Inter / JetBrains Mono) — accent attribué à Créa-Ka : VIOLET12 (--accent:#7048e8). Patrons mobiles calqués sur Lou-Ka (tab bar13 4 onglets, filtres défilants, anti-zoom iOS).14 Routes : / · /createur/{id} · /stats · /retrait · /compte · /contact15-->16<!DOCTYPE html>17<html lang="fr">18<head>19<meta charset="utf-8">20<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">21<title>Créa-Ka — Un service Groupe KA</title>22<meta name="description" content="Annuaire public des créateurs de contenu et influenceurs québécois : tous leurs comptes (Instagram, TikTok, YouTube, Twitch…) regroupés, cherchables par niche, région, langue et taille d'audience.">23<meta name="theme-color" content="#141814">24<link rel="preconnect" href="https://fonts.googleapis.com">25<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>26<link rel="preconnect" href="https://unavatar.io">27<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;600;700&family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@500;700&display=swap" rel="stylesheet">28<link rel="icon" type="image/svg+xml" href="/favicon.svg">29<link rel="apple-touch-icon" href="/apple-touch-icon.png">30<meta property="og:title" content="Créa-Ka — Un service Groupe KA">31<meta property="og:description" content="Annuaire public des créateurs de contenu et influenceurs québécois : tous leurs comptes (Instagram, TikTok, YouTube, Twitch…) regroupés, cherchables par niche, région, langue et taille d'audience.">32<meta property="og:image" content="https://www.crea-ka.com/og.png">33<meta property="og:image:width" content="1200">34<meta property="og:image:height" content="630">35<meta name="twitter:card" content="summary_large_image">36<meta name="twitter:image" content="https://www.crea-ka.com/og.png">37<style>38/* ka/tokens.css — design system commun Groupe KA, importé EN PREMIER39 (injecté au build par scripts/build_frontend.py) */40__KA_TOKENS__41</style>42<style>43/* Surcharges Créa-Ka par-dessus ka/tokens.css. Convention du groupe : les44 NOMS de variables sont partagés (--accent/--lime), seules les VALEURS45 changent — accent attribué à Créa-Ka : VIOLET. */46:root{47 --accent:#7048e8; --accent-soft:#ece5fc; --accent-deep:#5433b8; --on-accent:#ffffff;48 /* alias rétro-compatibles (le CSS historique du site utilise --lime) */49 --lime:var(--accent); --lime-soft:var(--accent-soft);50 --green:var(--accent-deep); /* primaire du site = violet profond */51 --green-deep:#3d2178;52}53/* le layout du site est calibré sur des titres sans marge basse */54h1,h2,h3,h4{margin:0}55a{text-decoration:none} button{font-family:inherit;cursor:pointer}56.icn{width:1em;height:1em;display:block;fill:currentColor}57.container{max-width:1240px;margin:0 auto;padding:0 24px}58@media(max-width:640px){.container{padding:0 16px}}5960/* ---------- cartes & boutons (langage du groupe) ---------- */61.gk-card{background:var(--surface);border:1.5px solid var(--ink);62 border-radius:var(--r-card);box-shadow:var(--shadow-off-soft)}63.gk-hover{transition:transform .18s ease,box-shadow .18s ease}64.gk-hover:hover{transform:translate(-3px,-3px);box-shadow:var(--shadow-off)}65.btn:active{transform:translate(2px,2px);box-shadow:none!important}66.btn-primary{box-shadow:4px 4px 0 rgba(20,24,20,.25)}6768/* ===== header ===== */69.header{position:sticky;top:0;z-index:50;background:rgba(245,243,238,.9);70 backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);border-bottom:1.5px solid var(--ink)}71.header-inner{display:flex;align-items:center;gap:16px;height:64px}72.brand{font-family:var(--font-display);font-weight:700;font-size:23px;letter-spacing:-.03em;73 display:flex;align-items:center;line-height:1;color:var(--ink)}74.brand svg{margin-right:11px;flex:none}75@media(pointer:coarse){.brand{position:relative}.brand::after{content:"";position:absolute;inset:-8px}}76.brand .ka{background:var(--accent);color:var(--on-accent);border-radius:6px;padding:0 6px 1px;margin-left:2px;transform:rotate(-1deg)}77.brand-tag{font-family:var(--font-mono);font-size:10px;font-weight:500;color:var(--ink-3);78 line-height:1.5;text-transform:uppercase;letter-spacing:.08em}79@media(max-width:1040px){.brand-tag{display:none}}80.header-spacer{flex:1}81.hbtn{border:1.5px solid var(--ink);background:var(--surface);color:var(--ink);82 border-radius:var(--r-ctl);padding:8px 16px;min-height:44px;font-family:var(--font-display);83 font-size:13px;font-weight:700;transition:.15s;display:inline-flex;align-items:center;84 gap:8px;white-space:nowrap;box-shadow:3px 3px 0 rgba(20,24,20,.12)}85.hbtn:hover{background:var(--accent);color:var(--on-accent)}86.hbtn:active{transform:translate(2px,2px);box-shadow:none}87.hbtn.primary{background:var(--ink);color:var(--accent)}88.hbtn.primary:hover{background:var(--accent-deep);color:var(--on-accent)}89@media(max-width:940px){.header .hbtn:not(.acct){display:none}}90/* bouton / jeton de compte KA ID — toujours visible, même sur mobile */91.hbtn.acct{padding:7px 14px}92.hbtn.acct img{width:22px;height:22px;border-radius:50%;object-fit:cover;93 border:1.5px solid var(--ink);margin-left:-4px}94.hbtn.acct .ka-badge{background:var(--accent);color:var(--on-accent);border-radius:4px;padding:0 5px;95 font-family:var(--font-mono);font-size:10px;font-weight:700}96.hbtn:hover .ka-badge{background:var(--on-accent);color:var(--accent)}97/* badge « Un service Groupe KA » (ka/GroupeKaBadge — styles dans tokens.css) */98.header .gk-badge{flex:none}99.hbtn .acct-short{display:none}100@media(max-width:560px){101 .header-inner{gap:10px}102 .header .gk-badge .pfx{display:none}103 .hbtn .acct-long{display:none}.hbtn .acct-short{display:inline}}104@media(max-width:400px){.brand svg{width:26px;height:26px;margin-right:8px}.brand{font-size:20px}.header .gk-badge{display:none}}105106/* ===== page compte (carte de membre Groupe KA) ===== */107.compte-page{max-width:640px;margin:0 auto;padding:44px 0 70px}108.membercard{background:var(--ink);border:1.5px solid var(--ink);border-radius:14px;109 padding:28px;color:var(--paper);box-shadow:var(--shadow-off);margin-top:24px;110 position:relative;overflow:hidden}111.membercard::after{content:"";position:absolute;right:-40px;top:-40px;width:160px;112 height:160px;border-radius:50%;background:rgba(112,72,232,.18)}113.mc-brand{font-family:var(--font-mono);font-size:10px;font-weight:700;114 text-transform:uppercase;letter-spacing:.14em;color:var(--accent-soft)}115.mc-row{display:flex;gap:18px;align-items:center;margin-top:18px}116.mc-avatar{width:72px;height:72px;border-radius:14px;border:2px solid var(--accent);117 background:var(--green-deep);overflow:hidden;flex:none;display:flex;118 align-items:center;justify-content:center;font-family:var(--font-display);119 font-weight:700;font-size:26px;color:var(--accent-soft)}120.mc-avatar img{width:100%;height:100%;object-fit:cover}121.mc-name{font-family:var(--font-display);font-size:22px;font-weight:700;color:var(--paper)}122.mc-kaid{font-family:var(--font-mono);font-size:12px;color:var(--accent-soft);margin-top:2px;letter-spacing:.04em}123.mc-role{display:inline-flex;background:var(--accent);color:var(--on-accent);border-radius:5px;124 padding:2px 10px;font-family:var(--font-mono);font-size:10px;font-weight:700;125 text-transform:uppercase;letter-spacing:.06em;margin-top:8px}126.mc-meta{margin-top:20px;display:grid;gap:6px;font-size:12.5px;color:rgba(245,243,238,.75)}127.mc-meta b{color:var(--paper);font-weight:600}128.compte-actions{display:flex;gap:10px;flex-wrap:wrap;margin-top:24px}129130/* ===== héros ===== */131.hero{padding:54px 0 26px}132@media(max-width:640px){.hero{padding:30px 0 12px}}133.hero h1{font-size:clamp(30px,5.4vw,54px);font-weight:700;max-width:820px;line-height:1.06;134 text-transform:uppercase}135.hero p.lead{color:var(--ink-2);font-size:clamp(14.5px,2vw,16.5px);max-width:640px;margin:18px 0 0}136.stats-row{display:flex;gap:10px;flex-wrap:wrap;margin-top:24px}137.stat-pill{background:var(--surface);border:1.5px solid var(--ink);border-radius:var(--r-ctl);138 padding:8px 16px;font-family:var(--font-mono);font-size:11px;font-weight:700;139 text-transform:uppercase;letter-spacing:.06em;color:var(--ink-2);140 display:inline-flex;gap:8px;align-items:center;box-shadow:3px 3px 0 rgba(20,24,20,.1)}141.stat-pill b{font-family:var(--font-display);color:var(--ink);font-size:16px;letter-spacing:0}142.advantages{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin:32px 0 8px}143@media(max-width:900px){.advantages{grid-template-columns:repeat(2,1fr);gap:10px}}144.adv{background:var(--surface);border:1.5px solid var(--ink);border-radius:var(--r-card);145 padding:16px;box-shadow:var(--shadow-off-soft)}146.adv .ico{width:36px;height:36px;border-radius:8px;border:1.5px solid var(--ink);147 background:var(--accent);color:var(--on-accent);display:flex;align-items:center;148 justify-content:center;margin-bottom:10px;font-size:15px}149.adv:nth-child(2n) .ico{color:var(--ink)}150.adv:nth-child(2n) .ico{background:var(--amber-soft)}151.adv h3{font-size:14px;margin-bottom:3px}152.adv p{margin:0;font-size:12px;color:var(--ink-3);line-height:1.45}153154/* ===== recherche + filtres ===== */155.searchbar{position:sticky;top:64px;z-index:40;background:rgba(245,243,238,.94);156 backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);157 padding:12px 0;border-bottom:1.5px solid var(--ink)}158.search-wrap{display:flex;gap:10px;align-items:center}159.search-input{flex:1 1 auto;display:flex;align-items:center;gap:10px;background:var(--surface);160 border:1.5px solid var(--ink);border-radius:var(--r-ctl);padding:10px 15px;161 transition:.15s;min-width:0;box-shadow:3px 3px 0 rgba(20,24,20,.08)}162.search-input:focus-within{box-shadow:3px 3px 0 rgba(112,72,232,.35)}163.search-input input{border:0;background:none;outline:none;font:inherit;flex:1;color:var(--ink);min-width:0}164.search-input svg{flex:none;color:var(--ink-3)}165.filters{display:flex;gap:8px;flex-wrap:wrap}166select.f{appearance:none;-webkit-appearance:none;border:1.5px solid var(--ink);167 background:var(--surface) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%23141814' stroke-width='1.8' fill='none' stroke-linecap='round'/%3E%3C/svg%3E") no-repeat right 12px center;168 border-radius:var(--r-ctl);padding:9px 30px 9px 14px;font-family:var(--font-display);169 font-size:13px;font-weight:700;color:var(--ink-2);max-width:170px;170 box-shadow:3px 3px 0 rgba(20,24,20,.08)}171select.f:focus{outline:2px solid var(--green);outline-offset:1px}172select.f.active{background-color:var(--accent-soft);color:var(--ink);border-color:var(--accent-deep)}173@media(max-width:760px){174 .search-wrap{flex-wrap:wrap}175 .search-input{flex-basis:100%}176 .search-input input{font-size:16px}177 .filters{flex-wrap:nowrap;overflow-x:auto;-webkit-overflow-scrolling:touch;178 margin:0 -16px;padding:4px 16px 6px;scrollbar-width:none;width:calc(100% + 32px)}179 .filters::-webkit-scrollbar{display:none}180 select.f{flex:none;font-size:16px;padding:8px 30px 8px 13px}181}182183/* ===== grille créateurs ===== */184.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:18px;padding:24px 0 10px}185@media(max-width:640px){.grid{grid-template-columns:repeat(2,1fr);gap:12px}}186@media(max-width:420px){.grid{grid-template-columns:1fr;gap:14px}}187.card{background:var(--surface);border:1.5px solid var(--ink);border-radius:var(--r-card);188 overflow:hidden;box-shadow:var(--shadow-off-soft);transition:transform .18s ease,box-shadow .18s ease;189 display:flex;flex-direction:column}190.card:hover{transform:translate(-3px,-3px);box-shadow:var(--shadow-off)}191.card-photo{position:relative;aspect-ratio:1/1;background:192 repeating-linear-gradient(-45deg,var(--green-deep) 0 14px,#33195f 14px 28px);193 display:flex;align-items:center;justify-content:center;overflow:hidden;194 border-bottom:1.5px solid var(--ink)}195.card-photo .init{color:var(--accent-soft);font-family:var(--font-display);font-weight:700;196 font-size:46px;letter-spacing:.02em}197.card-photo img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}198.card-photo .tier{position:absolute;top:10px;left:10px;z-index:2}199.card-photo .nplat{position:absolute;top:10px;right:10px;z-index:2;background:var(--ink);200 color:var(--accent-soft);border:1.5px solid var(--ink);border-radius:var(--r-ctl);201 padding:2px 8px;font-family:var(--font-mono);font-size:9.5px;font-weight:700;202 text-transform:uppercase;letter-spacing:.05em}203.tier{display:inline-flex;padding:2px 9px;border:1.5px solid var(--ink);204 border-radius:var(--r-ctl);font-family:var(--font-mono);font-size:9.5px;205 font-weight:700;text-transform:uppercase;letter-spacing:.06em;background:var(--surface);color:var(--ink)}206.tier.mega{background:var(--accent);color:var(--on-accent)}207.tier.macro{background:var(--amber-soft)}208.card-body{padding:14px 15px 15px;display:flex;flex-direction:column;gap:9px;flex:1}209.card-name{font-family:var(--font-display);font-size:16px;font-weight:700;210 letter-spacing:-.01em;line-height:1.25;display:flex;align-items:center;gap:6px}211.vbadge{color:var(--green);flex:none;width:15px;height:15px}212.card-sub{font-family:var(--font-mono);font-size:10px;color:var(--ink-3);213 text-transform:uppercase;letter-spacing:.05em;display:flex;gap:6px;align-items:center;flex-wrap:wrap;margin-top:-4px}214.chips{display:flex;gap:6px;flex-wrap:wrap}215.chip{background:var(--surface-2);border:1px solid var(--line);border-radius:var(--r-ctl);216 padding:2px 10px;font-family:var(--font-body);font-size:11px;font-weight:600;color:var(--ink-2);217 white-space:nowrap;text-transform:none;letter-spacing:0}218.reach{font-size:12px;color:var(--ink-2)}219.reach b{font-family:var(--font-display);color:var(--ink);font-size:15.5px;font-weight:700}220.platrow{display:flex;gap:6px;flex-wrap:wrap;margin-top:auto;padding-top:2px}221.plat{width:32px;height:32px;border-radius:8px;border:1.5px solid var(--ink);222 display:flex;align-items:center;justify-content:center;color:#fff;transition:.15s;223 box-shadow:2px 2px 0 rgba(20,24,20,.25)}224.plat svg{width:15px;height:15px;fill:currentColor}225.plat:hover{transform:translate(-1px,-1px);box-shadow:3px 3px 0 var(--ink)}226.more-wrap{display:flex;justify-content:center;padding:16px 0 44px}227.empty{padding:60px 0;text-align:center;color:var(--ink-3);grid-column:1/-1}228.count-line{font-family:var(--font-mono);font-size:11px;text-transform:uppercase;229 letter-spacing:.08em;color:var(--ink-3);padding-top:18px}230.count-line b{color:var(--ink)}231232/* ===== page créateur ===== */233.cpage{padding:30px 0 60px}234@media(max-width:640px){.cpage{padding:20px 0 40px}}235.backlink{display:inline-flex;align-items:center;gap:8px;font-family:var(--font-mono);236 font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;237 color:var(--green);margin-bottom:20px}238.cpage-head{display:flex;gap:26px;align-items:center;background:var(--ink);239 border:1.5px solid var(--ink);border-radius:var(--r-card);padding:32px;240 color:var(--paper);box-shadow:var(--shadow-off);flex-wrap:wrap}241@media(max-width:640px){.cpage-head{padding:22px 18px;gap:16px}}242.cavatar{width:104px;height:104px;border-radius:var(--r-card);243 background:repeating-linear-gradient(-45deg,var(--green-deep) 0 14px,#33195f 14px 28px);244 color:var(--accent-soft);flex:none;display:flex;align-items:center;justify-content:center;245 font-family:var(--font-display);font-weight:700;font-size:36px;overflow:hidden;246 position:relative;border:2px solid var(--accent)}247@media(max-width:640px){.cavatar{width:76px;height:76px;font-size:26px}}248.cavatar img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}249.cpage-head h1{font-size:clamp(24px,4vw,40px);font-weight:700;color:var(--paper);250 line-height:1.1;display:flex;align-items:center;gap:10px;flex-wrap:wrap;text-transform:uppercase}251.cpage-head h1 .vbadge{width:22px;height:22px;color:var(--accent-soft)}252.cpage-head .meta{display:flex;gap:7px;flex-wrap:wrap;margin-top:12px}253.mchip{background:rgba(245,243,238,.12);border:1px solid rgba(245,243,238,.3);254 border-radius:var(--r-ctl);padding:3px 11px;font-family:var(--font-mono);255 font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:var(--paper)}256.mchip.o{background:var(--accent);border-color:var(--accent);color:var(--on-accent)}257.cpage-reach{margin-left:auto;text-align:right}258.cpage-reach b{display:block;font-family:var(--font-display);259 font-size:clamp(28px,3.6vw,40px);font-weight:700;color:var(--accent-soft);letter-spacing:-.02em}260.cpage-reach span{font-family:var(--font-mono);font-size:10px;color:rgba(245,243,238,.6);261 text-transform:uppercase;letter-spacing:.08em;font-weight:700}262@media(max-width:760px){.cpage-reach{margin-left:0;text-align:left;flex-basis:100%}}263.cpage-bio{margin:24px 0 0;max-width:820px;color:var(--ink-2);font-size:15px}264.cpage-notes{margin:14px 0 0;max-width:820px;color:var(--ink-2);font-size:12.5px;265 background:var(--surface);border:1.5px solid var(--ink);border-radius:var(--r-card);266 padding:13px 17px;box-shadow:var(--shadow-off-soft)}267.sect-title{font-size:19px;margin:36px 0 15px;display:flex;align-items:center;gap:10px;text-transform:uppercase}268/* ===== page créateur : métriques calculées + onglets par plateforme ===== */269.hkpi-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(130px,1fr));gap:10px;margin:22px 0 6px}270.hkpi{background:var(--surface);border:1.5px solid var(--ink);border-radius:var(--r-card);271 box-shadow:var(--shadow-off-soft);padding:13px 15px}272.hkpi b{display:block;font-family:var(--font-display);font-size:22px;font-weight:800;color:var(--ink);line-height:1}273.hkpi span{display:block;margin-top:5px;font-family:var(--font-mono);font-size:9.5px;color:var(--ink-3);274 text-transform:uppercase;letter-spacing:.05em;font-weight:700}275/* barre d'onglets */276.ptabs{display:flex;gap:8px;overflow-x:auto;margin:26px 0 0;padding-bottom:2px}277.ptabs::-webkit-scrollbar{height:5px}.ptabs::-webkit-scrollbar-thumb{background:var(--line);border-radius:3px}278.ptab{flex:none;display:inline-flex;align-items:center;gap:7px;cursor:pointer;279 background:var(--surface);border:1.5px solid var(--ink);border-bottom:none;280 border-radius:12px 12px 0 0;padding:10px 15px;font-family:var(--font-display);281 font-weight:700;font-size:13px;color:var(--ink-2);transition:.14s;margin-bottom:-1.5px}282.ptab:hover{background:var(--surface-2)}283.ptab.on{background:var(--accent);color:var(--on-accent);border-color:var(--ink)}284.ptab.on .ptab-ico{color:var(--on-accent)!important}285.ptab-ico{width:16px;height:16px;display:inline-flex}.ptab-ico svg{width:16px;height:16px}286.ptab-n{font-family:var(--font-mono);font-size:10px;opacity:.75;font-weight:700}287.ppanels{border:1.5px solid var(--ink);border-radius:0 12px 12px 12px;288 background:var(--surface);box-shadow:var(--shadow-off-soft);padding:20px 18px 22px}289.ppanel{display:none}.ppanel.on{display:block;animation:fade .2s ease}290@keyframes fade{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}291.ppanel-head{display:flex;align-items:center;gap:13px}292.ppanel-head .plat{width:46px;height:46px;border-radius:10px;flex:none;display:flex;align-items:center;justify-content:center}293.ppanel-head .plat svg{width:22px;height:22px}294.ppanel-id{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}295.ppanel-name{font-family:var(--font-display);font-weight:800;font-size:17px;display:flex;align-items:center;gap:6px}296.ppanel-handle{font-size:13px;color:var(--green);font-weight:600}297.conf{display:inline-flex;padding:2px 9px;border-radius:var(--r-ctl);298 font-family:var(--font-mono);font-size:10px;font-weight:700;letter-spacing:.03em;flex:none}299.conf.hi{background:var(--lime-soft);color:var(--green-deep)}300.conf.mid{background:var(--amber-soft);color:#8a5a12}301.abadge-row{display:flex;flex-wrap:wrap;gap:6px;margin:12px 0 4px}302.abadge{display:inline-flex;background:var(--lime-soft);color:var(--green-deep);303 border:1px solid var(--line);border-radius:var(--r-ctl);padding:3px 9px;304 font-family:var(--font-mono);font-size:10px;font-weight:700;letter-spacing:.02em}305.abadge.ok{background:var(--surface-2);color:var(--ink-2)}306.abadge.live{background:#ffe3e0;color:#b0201a;border-color:#b0201a}307/* grille KPI par plateforme */308.kpi-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:10px;margin:16px 0 4px}309.kpi{background:var(--surface-2);border:1px solid var(--line);border-radius:var(--r-ctl);padding:11px 13px}310.kpi b{display:block;font-family:var(--font-display);font-size:19px;font-weight:800;color:var(--ink);line-height:1}311.kpi span{display:block;margin-top:4px;font-family:var(--font-mono);font-size:9px;color:var(--ink-3);312 text-transform:uppercase;letter-spacing:.04em;font-weight:700}313.ppanel-top{display:inline-flex;margin:12px 0 2px;background:var(--surface-2);border:1px solid var(--line);314 border-radius:var(--r-ctl);padding:8px 13px;font-size:12.5px;font-weight:700;color:var(--ink-2)}315.ppanel-top:hover{color:var(--green)}316.ppanel-foot{margin-top:14px;font-family:var(--font-mono);font-size:10px;color:var(--ink-3)}317/* grille de contenu récent (images/vidéos) */318.cg-title{font-family:var(--font-display);font-size:14px;font-weight:800;margin:20px 0 12px;color:var(--ink)}319.cg-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:13px}320.cg-card{display:flex;flex-direction:column;text-decoration:none;color:inherit;321 border:1.5px solid var(--ink);border-radius:var(--r-card);overflow:hidden;322 background:var(--surface);box-shadow:var(--shadow-off-soft);transition:transform .15s,box-shadow .15s}323.cg-card:hover{transform:translate(-2px,-2px);box-shadow:var(--shadow-off)}324.cg-thumb{position:relative;display:block;width:100%;aspect-ratio:1/1;background:var(--surface-2);overflow:hidden}325.cg-thumb img{width:100%;height:100%;object-fit:cover;display:block}326.cg-thumb.noimg{display:flex;align-items:center;justify-content:center}327.cg-thumb.noimg svg{width:44px;height:44px;opacity:.3}328.cg-play{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);color:#fff;329 font-size:26px;text-shadow:0 2px 6px rgba(0,0,0,.7);pointer-events:none}330.cg-dur{position:absolute;right:6px;bottom:6px;background:rgba(0,0,0,.8);color:#fff;331 font-family:var(--font-mono);font-size:10px;padding:2px 6px;border-radius:5px}332.cg-pin{position:absolute;left:6px;top:6px;font-size:13px}333.cg-body{padding:9px 11px 11px;display:flex;flex-direction:column;gap:5px}334.cg-cap{font-size:12px;line-height:1.3;color:var(--ink-2);display:-webkit-box;335 -webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;min-height:31px}336.cg-cap.dim{color:var(--ink-3)}337.cg-meta{font-family:var(--font-mono);font-size:10px;color:var(--ink-3);display:flex;338 justify-content:space-between;gap:6px;flex-wrap:wrap}339.cg-when{color:var(--ink-3);opacity:.8}340.cg-empty{padding:26px;text-align:center;color:var(--ink-3);font-size:13px}341.cg-empty svg{width:20px;height:20px;vertical-align:-4px;opacity:.4;margin-right:6px}342/* vue d'ensemble : barres de portée */343.obars{display:flex;flex-direction:column;gap:11px}344.obar{display:flex;align-items:center;gap:11px}345.obar-ico{width:32px;height:32px;border-radius:8px;flex:none;display:flex;align-items:center;justify-content:center}346.obar-ico svg{width:16px;height:16px}347.obar-lab{width:96px;flex:none;font-family:var(--font-display);font-weight:700;font-size:13px}348.obar-track{flex:1;height:12px;background:var(--surface-2);border:1px solid var(--line);border-radius:6px;overflow:hidden}349.obar-fill{display:block;height:100%;border-radius:6px 0 0 6px;min-width:3px}350.obar-val{width:78px;flex:none;text-align:right;font-family:var(--font-mono);font-size:12px;font-weight:700;color:var(--ink)}351@media(max-width:640px){.obar-lab{width:64px;font-size:11px}.obar-val{width:60px;font-size:11px}352 .cg-grid{grid-template-columns:repeat(auto-fill,minmax(130px,1fr))}}353.linkinbio{display:inline-flex;align-items:center;gap:10px;margin-top:22px;354 background:var(--accent);color:var(--on-accent);border:1.5px solid var(--ink);355 border-radius:var(--r-ctl);padding:11px 20px;font-family:var(--font-display);356 font-weight:700;font-size:13.5px;transition:.15s;box-shadow:4px 4px 0 rgba(20,24,20,.25)}357.linkinbio:hover{background:var(--accent-soft);color:var(--ink)}358.linkinbio:active{transform:translate(2px,2px);box-shadow:none}359.linkinbio svg{width:16px;height:16px;fill:currentColor}360.cpage-foot{margin-top:38px;font-size:12px;color:var(--ink-3);361 border-top:1.5px solid var(--ink);padding-top:16px}362/* bannière héro (banner_url capté par les acteurs Apify) */363.cbanner{position:relative;border:1.5px solid var(--ink);border-bottom:none;364 border-radius:var(--r-card) var(--r-card) 0 0;overflow:hidden;height:clamp(120px,22vw,240px);365 background:var(--ink)}366.cbanner img{width:100%;height:100%;object-fit:cover;display:block}367.cbanner+.cpage-head{border-radius:0 0 var(--r-card) var(--r-card)}368/* Ka Score */369.kascore{display:flex;gap:22px;align-items:center;flex-wrap:wrap;margin:6px 0 2px}370.kascore-num{flex:none;width:112px;height:112px;border-radius:50%;display:flex;371 flex-direction:column;align-items:center;justify-content:center;372 border:2px solid var(--ink);background:var(--accent);color:var(--on-accent);373 box-shadow:var(--shadow-off-soft)}374.kascore-num b{font-family:var(--font-display);font-size:34px;font-weight:800;line-height:1}375.kascore-num span{font-family:var(--font-mono);font-size:9px;font-weight:700;376 text-transform:uppercase;letter-spacing:.06em;margin-top:3px}377.kascore-parts{flex:1;min-width:230px;display:flex;flex-direction:column;gap:7px}378.kspart{display:flex;align-items:center;gap:10px}379.kspart-lab{width:110px;flex:none;font-family:var(--font-mono);font-size:10px;380 font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--ink-3)}381.kspart-track{flex:1;height:9px;background:var(--surface-2);border:1px solid var(--line);382 border-radius:5px;overflow:hidden}383.kspart-fill{display:block;height:100%;background:var(--green);border-radius:5px 0 0 5px;min-width:2px}384.kspart-val{width:52px;flex:none;text-align:right;font-family:var(--font-mono);385 font-size:11px;font-weight:700;color:var(--ink)}386@media(max-width:640px){.kspart-lab{width:88px;font-size:9px}}387/* sparkline évolution d'audience */388.spark-wrap{margin-top:8px}389.spark-svg{width:100%;height:88px;display:block}390.spark-line{fill:none;stroke:var(--green);stroke-width:2.5;stroke-linejoin:round;stroke-linecap:round}391.spark-fill{fill:var(--lime-soft);opacity:.7;stroke:none}392.spark-meta{display:flex;justify-content:space-between;font-family:var(--font-mono);393 font-size:10px;color:var(--ink-3);margin-top:4px}394.growth-up{color:var(--green-deep);font-weight:700}395.growth-down{color:#b0201a;font-weight:700}396/* bloc EN DIRECT (miniature live Twitch/Kick) */397.live-card{display:flex;gap:14px;align-items:stretch;margin:14px 0 4px;398 border:1.5px solid #b0201a;border-radius:var(--r-card);overflow:hidden;399 background:var(--surface);text-decoration:none;color:inherit}400.live-card img{width:200px;min-height:100%;object-fit:cover;flex:none;display:block}401.live-card-body{padding:12px 14px;display:flex;flex-direction:column;gap:5px;justify-content:center}402.live-card-tag{font-family:var(--font-mono);font-size:10px;font-weight:700;color:#b0201a}403.live-card-title{font-size:13.5px;font-weight:700;color:var(--ink);line-height:1.3}404.live-card-meta{font-family:var(--font-mono);font-size:10.5px;color:var(--ink-3)}405@media(max-width:560px){.live-card{flex-direction:column}.live-card img{width:100%;height:150px}}406.cpage-foot a{color:var(--green);font-weight:600}407408/* ===== page stats ===== */409.stats-page{padding-top:40px;padding-bottom:70px}410.tiles{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:14px;margin:26px 0}411.tile{background:var(--surface);border:1.5px solid var(--ink);border-radius:var(--r-card);412 padding:18px 20px;box-shadow:var(--shadow-off-soft)}413.tile-v{font-family:var(--font-display);font-weight:700;font-size:clamp(23px,3vw,32px);414 letter-spacing:-.03em;line-height:1.05;color:var(--ink)}415.tile-k{font-family:var(--font-mono);font-size:9.5px;font-weight:700;text-transform:uppercase;416 letter-spacing:.1em;color:var(--ink-3);margin-top:7px}417.tile.hero-tile{background:var(--ink)}418.tile.hero-tile .tile-v{color:var(--accent-soft)}419.tile.hero-tile .tile-k{color:rgba(245,243,238,.55)}420.viz-card{background:var(--surface);border:1.5px solid var(--ink);border-radius:var(--r-card);421 box-shadow:var(--shadow-off-soft);padding:24px 26px 18px;margin-bottom:22px}422.viz-card h2{font-size:18px;text-transform:uppercase}423.viz-sub{font-family:var(--font-mono);font-size:10px;font-weight:700;letter-spacing:.08em;424 text-transform:uppercase;color:var(--ink-3);margin:4px 0 18px}425.hrow{display:flex;align-items:center;gap:12px;margin-bottom:10px}426.hrow .plat{width:28px;height:28px;border-radius:7px;flex:none}427.hrow .plat svg{width:13px;height:13px}428.hrow-label{flex:0 0 130px;font-size:12.5px;font-weight:600;color:var(--ink-2);429 overflow:hidden;text-overflow:ellipsis;white-space:nowrap}430@media(max-width:640px){.hrow-label{flex-basis:96px;font-size:11.5px}}431.hbar{flex:1;height:12px;border:1.5px solid var(--ink);border-radius:4px;432 background:var(--surface-2);overflow:hidden}433.hbar i{display:block;height:100%;background:var(--lime);border-right:1.5px solid var(--ink)}434.hrow .n{flex:none;font-family:var(--font-display);font-size:12.5px;font-weight:700;435 color:var(--ink);min-width:44px;text-align:right}436437/* ===== page stats : tableau de bord ka-stats (SPEC) ===== */438.stats-head{display:flex;flex-wrap:wrap;gap:16px;align-items:flex-start;justify-content:space-between}439.pdf-actions{display:flex;gap:8px;flex-wrap:wrap}440.fresh-row{display:flex;gap:12px;align-items:center;flex-wrap:wrap;margin-top:14px;441 font-family:var(--font-mono);font-size:11px;color:var(--ink-3);text-transform:uppercase;letter-spacing:.06em}442.pchips{display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin:22px 0 2px}443.pchip{border:1.5px solid var(--ink);background:var(--surface);border-radius:var(--r-ctl);444 padding:8px 14px;min-height:44px;font-family:var(--font-display);font-size:12.5px;445 font-weight:700;color:var(--ink-2);box-shadow:3px 3px 0 rgba(20,24,20,.08);transition:.15s}446.pchip[aria-pressed="true"]{background:var(--accent);color:var(--on-accent)}447.pchip:active{transform:translate(2px,2px);box-shadow:none}448.pdates{display:inline-flex;gap:6px;align-items:center;flex-wrap:wrap}449.pdates input{border:1.5px solid var(--ink);border-radius:var(--r-ctl);padding:8px 10px;450 font:inherit;font-size:13px;background:var(--surface);min-height:44px;max-width:160px}451@media(max-width:760px){.pchips{flex-wrap:nowrap;overflow-x:auto;-webkit-overflow-scrolling:touch;452 padding:4px 0 8px;scrollbar-width:none}453 .pchips::-webkit-scrollbar{display:none}454 .pchip{flex:none}.pdates{flex:none}.pdates input{font-size:16px}}455@media(max-width:640px){.pchips{margin-left:-16px;margin-right:-16px;456 padding-left:16px;padding-right:16px}}457.tile-d{font-family:var(--font-mono);font-size:10.5px;font-weight:700;margin-top:8px}458.tile-d.up{color:var(--green)}.tile-d.down{color:var(--danger)}459.tile.hero-tile .tile-d.up{color:var(--accent-soft)}460.viz-grid{display:grid;gap:22px;grid-template-columns:1fr;margin-bottom:22px}461@media(min-width:960px){.viz-grid.two{grid-template-columns:1fr 1fr}}462.viz-grid .viz-card{margin-bottom:0}463.viz-grid>*,.dt,.lc{min-width:0}464.dt .viz-card,.lc .viz-card{margin-bottom:0}465.lc svg{width:100%;height:auto;display:block;touch-action:pan-y}466.lc-legend{display:flex;gap:14px;flex-wrap:wrap;margin:2px 0 0}467.leg-btn{display:inline-flex;align-items:center;gap:6px;border:0;background:none;468 font-family:var(--font-mono);font-size:10px;font-weight:700;text-transform:uppercase;469 letter-spacing:.06em;color:var(--ink-2);min-height:44px}470.leg-btn[aria-pressed="false"]{opacity:.35}471.leg-swatch{width:18px;height:0;border-top:3px solid var(--accent)}472.leg-swatch.cmp{border-top-style:dashed;border-top-color:var(--ink-3)}473.lc-tip{display:inline-flex;margin-top:6px;visibility:hidden}474.donut-wrap{display:flex;flex-wrap:wrap;gap:22px;align-items:center;margin-top:14px}475.donut-legend{list-style:none;margin:0;padding:0;display:grid;gap:7px;flex:1;min-width:200px}476.donut-legend li{display:flex;align-items:center;gap:8px;font-size:12.5px;min-width:0}477.donut-legend .dsw{width:11px;height:11px;border-radius:3px;border:1px solid var(--ink);478 background:var(--accent);flex:none}479.donut-legend .dlbl{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}480.donut-legend b{font-family:var(--font-mono);font-size:11px}481.tbl-wrap{overflow-x:auto;-webkit-overflow-scrolling:touch;max-width:100%}482.dtable table{width:100%;border-collapse:collapse;font-size:13px}483.dtable th{cursor:pointer;text-align:left;padding:8px 10px;background:var(--ink);484 color:var(--paper);font-family:var(--font-mono);font-size:10px;text-transform:uppercase;485 letter-spacing:.06em;white-space:nowrap;user-select:none}486.dtable td{padding:7px 10px;border-bottom:1px solid var(--line);white-space:nowrap}487.dtable tbody tr:nth-child(even) td{background:var(--surface-2)}488.dtable .dt-q{border:1.5px solid var(--ink);border-radius:var(--r-ctl);padding:9px 13px;489 font:inherit;font-size:13px;background:var(--surface);max-width:230px;min-height:44px}490@media(max-width:760px){.dtable .dt-q{font-size:16px;max-width:100%;flex:1}}491.dtable-foot{display:flex;justify-content:space-between;align-items:center;margin-top:12px;492 flex-wrap:wrap;gap:8px}493.records-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px;margin-top:14px}494.record{background:var(--surface-2);border:1.5px solid var(--ink);border-radius:var(--r-card);495 padding:13px 16px;display:flex;justify-content:space-between;gap:12px;align-items:baseline;496 box-shadow:var(--shadow-off-soft)}497.record .r-lbl{font-size:12.5px;color:var(--ink-2)}498.record .r-val{text-align:right;font-family:var(--font-display);font-size:14.5px;font-weight:700}499.hm svg{min-width:520px;width:100%;height:auto}500.empty-block{border:1.5px dashed var(--ink);border-radius:var(--r-card);501 background:var(--surface-2);padding:20px}502.stats-sect{font-size:17px;text-transform:uppercase;margin:34px 0 14px}503504/* ===== page stats v2 : sparklines, jauges, histogrammes, heatmap horaire, menu PDF ===== */505.tile svg.spark{display:block;margin-top:9px;width:100%;height:28px}506.gauges{display:grid;grid-template-columns:repeat(auto-fit,minmax(168px,1fr));gap:14px;margin-bottom:8px}507.gauge{background:var(--surface);border:1.5px solid var(--ink);border-radius:var(--r-card);508 padding:16px 14px 13px;box-shadow:var(--shadow-off-soft);text-align:center}509.gauge svg{width:124px;max-width:100%;height:auto;margin:0 auto;display:block}510.gauge .g-v{font-family:var(--font-display);font-weight:700;font-size:21px;color:var(--ink);margin-top:-30px}511.gauge .g-l{font-family:var(--font-mono);font-size:9px;font-weight:700;text-transform:uppercase;512 letter-spacing:.05em;color:var(--ink-3);margin-top:10px;line-height:1.55}513.statsum{display:flex;gap:7px;flex-wrap:wrap;margin-top:10px}514.statsum .chip b{font-family:var(--font-mono);margin-left:4px}515.leg-swatch.ml1{border-top:3px solid var(--ink)}516.leg-swatch.ml2{border-top:3px dashed var(--accent)}517.leg-swatch.ml3{border-top:3px dotted var(--ink-3)}518.hrow .d{flex:none;font-family:var(--font-mono);font-size:10px;font-weight:700;min-width:54px;text-align:right}519.hrow .d.up{color:var(--green)}.hrow .d.down{color:var(--danger)}520.hh svg{min-width:560px;width:100%;height:auto}521.pdf-menu-wrap{position:relative;display:inline-flex;gap:8px;flex-wrap:wrap}522.pdf-menu{position:absolute;right:0;top:calc(100% + 8px);z-index:70;background:var(--surface);523 border:1.5px solid var(--ink);border-radius:var(--r-card);box-shadow:var(--shadow-off);524 display:none;min-width:270px;padding:6px}525.pdf-menu.open{display:block}526.pdf-menu button{display:block;width:100%;text-align:left;border:0;background:none;527 padding:11px 13px;border-radius:9px;font-family:var(--font-display);font-weight:700;528 font-size:13px;color:var(--ink);min-height:44px}529.pdf-menu button:hover{background:var(--accent-soft)}530.pdf-menu small{display:block;font-family:var(--font-body);font-weight:400;font-size:11px;531 color:var(--ink-3);margin-top:2px}532@media(max-width:760px){.pdf-menu{right:auto;left:0}}533534/* ===== retrait ===== */535.optout-page{max-width:620px;margin:0 auto;padding:44px 0 70px}536.optout-page h1{font-size:clamp(26px,4vw,32px);margin:12px 0;text-transform:uppercase}537.optout-page p{color:var(--ink-2)}538.field{margin-top:16px}539.field label{display:block;font-family:var(--font-mono);font-size:10px;font-weight:700;540 text-transform:uppercase;letter-spacing:.08em;margin-bottom:7px;color:var(--ink-2)}541.field input,.field textarea{width:100%;border:1.5px solid var(--ink);border-radius:var(--r-ctl);542 padding:11px 14px;font:inherit;background:var(--surface);color:var(--ink);543 box-shadow:3px 3px 0 rgba(20,24,20,.08);transition:box-shadow .15s ease}544.field input:focus,.field textarea:focus{outline:2px solid var(--green);outline-offset:1px;545 box-shadow:3px 3px 0 rgba(112,72,232,.25)}546@media(max-width:900px){.field input,.field textarea{font-size:16px}}547.optout-ok{background:var(--lime-soft);color:var(--green-deep);border:1.5px solid var(--ink);548 border-radius:var(--r-card);padding:16px 20px;font-weight:600;margin-top:20px;549 box-shadow:var(--shadow-off-soft)}550.optout-ok.warn{background:var(--amber-soft);color:#8a5a12}551552/* ===== footer : nav locale + footer commun Groupe KA (ka/KaFooter) ===== */553.footer-local{margin-top:56px;border-top:1.5px solid var(--ink);background:var(--surface);padding:16px 0}554.footer-local nav{display:flex;gap:10px 26px;flex-wrap:wrap;align-items:center;555 font-family:var(--font-mono);font-size:11px;font-weight:700;556 text-transform:uppercase;letter-spacing:.08em;color:var(--ink-2)}557.footer-local .klabel{color:var(--ink-3)}558.footer-local a{display:inline-flex;align-items:center;min-height:44px}559.footer-local a:hover{color:var(--accent-deep);text-decoration:underline;text-underline-offset:4px}560.footer-local+.ka-footer{margin-top:0}561.ka-footer .brand{font-size:19px;color:var(--paper)}562563/* ===== contact ===== */564.contact-page{max-width:880px;margin:0 auto;padding:44px 0 70px}565.contact-page h1{font-size:clamp(26px,4vw,38px);margin:12px 0;text-transform:uppercase}566.contact-page .lead{color:var(--ink-2);max-width:640px}567.contact-notice{background:var(--accent-soft);border:1.5px solid var(--ink);border-radius:var(--r-card);568 padding:16px 20px;margin:22px 0;box-shadow:var(--shadow-off-soft);font-weight:600;color:var(--ink)}569.contact-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:14px;margin:8px 0 4px}570.contact-card{display:block;background:var(--surface);border:1.5px solid var(--ink);571 border-radius:var(--r-card);padding:16px 18px;box-shadow:var(--shadow-off-soft);572 transition:transform .16s ease,box-shadow .16s ease;min-height:44px}573.contact-card:hover{transform:translate(-2px,-2px);box-shadow:var(--shadow-off)}574.contact-card b{display:block;font-family:var(--font-mono);font-size:13px;font-weight:700;575 color:var(--accent-deep);word-break:break-all}576.contact-card span{display:block;margin-top:5px;font-size:12px;color:var(--ink-2)}577.site-list{display:grid;grid-template-columns:repeat(auto-fill,minmax(230px,1fr));gap:10px;margin:14px 0 0}578.site-item{display:flex;align-items:center;gap:10px;background:var(--surface);579 border:1.5px solid var(--ink);border-radius:var(--r-ctl);padding:10px 13px;min-height:44px;580 transition:transform .15s ease,box-shadow .15s ease}581.site-item:hover{transform:translate(-2px,-2px);box-shadow:3px 3px 0 var(--ink)}582.site-item i{width:10px;height:10px;border-radius:3px;border:1px solid var(--ink);flex:none}583.site-item b{font-family:var(--font-display);font-size:13.5px;font-weight:700}584.site-item span{font-size:11px;color:var(--ink-3);margin-left:auto;font-family:var(--font-mono)}585@media(max-width:480px){.site-item span{display:none}}586.tabbar{display:none}587@media(max-width:940px){588 .tabbar{display:grid;grid-template-columns:repeat(4,1fr);589 position:fixed;left:0;right:0;bottom:0;z-index:85;590 background:rgba(245,243,238,.94);591 backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);592 border-top:1.5px solid var(--ink);593 padding:6px 4px calc(6px + env(safe-area-inset-bottom))}594 .tabbar a{display:flex;flex-direction:column;align-items:center;gap:3px;595 padding:5px 2px;min-height:48px;justify-content:center;596 font-family:var(--font-mono);font-size:9.5px;font-weight:700;597 text-transform:uppercase;letter-spacing:.04em;color:var(--ink-3);598 border-radius:var(--r-ctl);transition:color .12s ease}599 .tabbar a.active{color:var(--green)}600 .tabbar a svg{display:block;width:20px;height:20px;fill:none;stroke:currentColor;601 stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}602 body{padding-bottom:calc(64px + env(safe-area-inset-bottom))}603 .ka-footer{padding-bottom:calc(76px + env(safe-area-inset-bottom))}604}605.spin{width:26px;height:26px;border:3px solid var(--line);border-top-color:var(--green);606 border-radius:50%;margin:50px auto;animation:sp .8s linear infinite}607@keyframes sp{to{transform:rotate(360deg)}}608</style>609</head>610<body>611<div id="app"></div>612<script>613"use strict";614/* Créa-Ka SPA — routes : / · /createur/{id} · /stats · /retrait · /compte · /contact */615const ICONS=__ICONS__;616/* écosystème Groupe KA (ka/ecosystem.json, injecté au build) — footer commun + /contact */617const KA_ECO=__KA_ECO__;618const KA_SITE=KA_ECO.sites.find(s=>s.id==="crea-ka");619const $=s=>document.querySelector(s);620const app=$("#app");621const fmt=new Intl.NumberFormat("fr-CA",{notation:"compact",maximumFractionDigits:1});622const fmtFull=new Intl.NumberFormat("fr-CA");623const PLAT={624 instagram:{label:"Instagram",color:"#c13584",av:"instagram"},625 tiktok:{label:"TikTok",color:"#141814",av:"tiktok"},626 youtube:{label:"YouTube",color:"#e02424",av:"youtube"},627 twitch:{label:"Twitch",color:"#9146ff",av:"twitch"},628 kick:{label:"Kick",color:"#141814"},629 x:{label:"X (Twitter)",color:"#141814",av:"twitter"},630 facebook:{label:"Facebook",color:"#1877f2"},631 snapchat:{label:"Snapchat",color:"#d4a900"},632 substack:{label:"Substack",color:"#ff6719"},633 patreon:{label:"Patreon",color:"#f1465a"},634 onlyfans:{label:"OnlyFans",color:"#00aff0"},635 mym:{label:"MYM",color:"#141814"},636 fansly:{label:"Fansly",color:"#2699f7"},637 linkedin:{label:"LinkedIn",color:"#0a66c2"},638 threads:{label:"Threads",color:"#141814"},639 spotify:{label:"Spotify",color:"#1db954"},640 discord:{label:"Discord",color:"#5865f2"},641 podcast:{label:"Balado",color:"#8940fa"},642 "site-web":{label:"Site web",color:"#7048e8"},643 autre:{label:"Autre",color:"#8b928c"}};644const NICHE_LBL={"humour":"Humour","mode":"Mode","beaute":"Beauté","lifestyle":"Lifestyle",645 "famille-parentalite":"Famille","cuisine":"Cuisine","gaming":"Gaming","tech":"Tech",646 "sport-fitness":"Sport & fitness","plein-air":"Plein air","voyage":"Voyage","musique":"Musique",647 "arts":"Arts","danse":"Danse","education":"Éducation","finance-affaires":"Finance & affaires",648 "sante-mieux-etre":"Santé & mieux-être","bouffe-resto":"Bouffe & resto",649 "actualite-opinion":"Actualité & opinion","autre":"Autre"};650const TIER_LBL={nano:"Nano",micro:"Micro",macro:"Macro",mega:"Méga"};651const state={q:"",niche:"",region:"",langue:"",plateforme:"",tier:"",sort:"reach",652 offset:0,items:[],total:0,stats:null,tax:null};653654const esc=s=>String(s??"").replace(/[&<>"']/g,c=>({"&":"&","<":"<",">":">",'"':""","'":"'"}[c]));655const initials=n=>n.split(/\s+/).slice(0,2).map(w=>w[0]||"").join("").toUpperCase();656async function api(p){const r=await fetch(p);if(!r.ok)throw new Error(r.status);return r.json()}657function icon(p,extra){return `<svg class="icn" viewBox="0 0 24 24" aria-hidden="true"${extra||""}><path d="${ICONS[p]||ICONS.autre}"/></svg>`}658const checkSvg=`<svg class="vbadge" viewBox="0 0 24 24" aria-label="vérifié"><path fill="currentColor" d="M12 0l2.8 2.5 3.7-.7.9 3.7 3.4 1.7-1.5 3.5 1.5 3.5-3.4 1.7-.9 3.7-3.7-.7L12 24l-2.8-2.5-3.7.7-.9-3.7-3.4-1.7 1.5-3.5-1.5-3.5 3.4-1.7.9-3.7 3.7.7z"/><path fill="#f5f3ee" d="M10.6 16.2l-3.3-3.3 1.4-1.4 1.9 1.9 4.7-4.7 1.4 1.4z"/></svg>`;659660/* cascade de photos : avatar capté à la source, puis unavatar par plateforme,661 repli final = initiales sur motif encre/violet (déjà en dessous) */662function avatarSources(c){663 const order=["instagram","tiktok","youtube","twitch","x"];664 const srcs=[];665 if(c.avatar_url)srcs.push(c.avatar_url);666 for(const p of order){667 const acc=(c.platforms||[]).find(a=>a.platform===p&&PLAT[p]&&PLAT[p].av);668 if(acc)srcs.push(`https://unavatar.io/${PLAT[p].av}/${encodeURIComponent(acc.handle)}?fallback=false`);669 }670 return srcs;671}672window.nextSrc=el=>{673 const srcs=JSON.parse(el.dataset.srcs||"[]");const i=+(el.dataset.i||0)+1;674 if(i<srcs.length){el.dataset.i=i;el.src=srcs[i]}else el.remove();675};676function photoImg(c){677 const srcs=avatarSources(c);678 if(!srcs.length)return"";679 return `<img loading="lazy" alt="${esc(c.display_name)}" src="${srcs[0]}"680 data-srcs='${esc(JSON.stringify(srcs))}' data-i="0" onerror="nextSrc(this)">`;681}682const brandSvg=`<svg width="30" height="30" viewBox="0 0 64 64"><rect width="64" height="64" rx="10" fill="#141814"/><circle cx="32" cy="23" r="9" fill="#7048e8"/><path d="M14 52c3-10 9-15 18-15s15 5 18 15" stroke="#7048e8" stroke-width="6" fill="none" stroke-linecap="round"/></svg>`;683684function header(){685 return `<header class="header"><div class="container header-inner">686 <a href="/" data-nav class="brand">${brandSvg}Créa<span class="ka">Ka</span></a>687 <a class="gk-badge" href="https://www.groupe-ka.com" target="_blank" rel="noopener noreferrer"688 aria-label="Un service Groupe KA — visiter le portail de l'écosystème"><span class="pfx">Un service</span>689 <b>Groupe<span class="ka">KA</span></b></a>690 <span class="brand-tag">Tous les créateurs québécois.<br>Tous leurs liens.</span>691 <span class="header-spacer"></span>692 <a class="hbtn" href="/stats" data-nav>Statistiques</a>693 <a class="hbtn" href="/contact" data-nav>Contact</a>694 <a class="hbtn primary" href="/#annuaire" data-nav>Explorer l'annuaire</a>695 <span id="acct-slot"></span>696 </div></header>`;697}698/* jeton de compte KA ID dans l'en-tête (connecté → chip, sinon → bouton hub) */699async function loadAccount(){700 const slot=$("#acct-slot");if(!slot)return;701 if(state.me===undefined){702 try{const r=await fetch("/api/me");state.me=r.ok?await r.json():null}703 catch(e){state.me=null}}704 const me=state.me;705 if(me){706 slot.innerHTML=`<a class="hbtn acct" href="/compte" data-nav>707 ${me.picture?`<img src="${esc(me.picture)}" alt="">`:""}${esc((me.name||"").split(" ")[0])}708 <span class="ka-badge">KA</span></a>`;709 }else{710 slot.innerHTML=`<a class="hbtn acct" href="/api/auth/ka/login"711 title="Se connecter avec KA ID"><span class="acct-long">Se connecter avec</span><span class="acct-short">Connexion</span> <span class="ka-badge">KA</span></a>`;712 }713 bindNav(slot);714}715function tabbar(active){716 const t=(href,label,svg,key)=>`<a href="${href}" data-nav class="${active===key?"active":""}">${svg}<span>${label}</span></a>`;717 return `<nav class="tabbar">718 ${t("/","Annuaire",`<svg viewBox="0 0 24 24"><path d="M3 10.5 12 3l9 7.5"/><path d="M5 9.5V21h14V9.5"/></svg>`,"home")}719 ${t("/#annuaire","Recherche",`<svg viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="M20 20l-3.5-3.5"/></svg>`,"search")}720 ${t("/stats","Stats",`<svg viewBox="0 0 24 24"><path d="M4 20V10M10 20V4M16 20v-7M22 20H2"/></svg>`,"stats")}721 ${t("/retrait","Retrait",`<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M8 12h8"/></svg>`,"optout")}722 </nav>`;723}724/* footer commun Groupe KA (équivalent vanilla de ka/KaFooter.tsx, siteId725 "crea-ka", localLegal = /retrait) — nav locale conservée au-dessus */726function footer(){727 const year=new Date().getFullYear();728 const others=KA_ECO.sites.filter(s=>s.id!=="crea-ka");729 const localLegal=[{label:"Retrait (opt-out)",href:"/retrait",nav:true}];730 return `<div class="footer-local"><div class="container"><nav>731 <span class="klabel">Créa·Ka</span>732 <a href="/compte" data-nav>Mon compte KA ID</a>733 <a href="/stats" data-nav>Statistiques</a>734 <a href="/retrait" data-nav>Demander un retrait</a>735 <a href="/contact" data-nav>Contact</a>736 <a href="/api/stats" target="_blank" rel="noopener">API publique</a>737 </nav></div></div>738 <footer class="ka-footer" id="contact-footer">739 <div class="container">740 <a class="wordmark" href="${esc(KA_ECO.hub.url)}" target="_blank" rel="noopener noreferrer"741 aria-label="Groupe KA — le portail de l'écosystème">Groupe <span class="ka">KA</span></a>742 <p class="desc">${esc(KA_ECO.org.tagline)} Des centaines de connecteurs lisent les sites à la743 source, normalisent la donnée et se resynchronisent seuls.744 <b style="color:var(--paper)">${esc(KA_SITE.wordmark)}</b> — ${esc(KA_SITE.tagline)} — est un service Groupe KA.</p>745 <p class="notice"><b>${esc(KA_ECO.org.disclaimer)}</b></p>746 <ul class="sites">747 ${others.map(s=>`<li><a href="https://${esc(s.domain)}" target="_blank" rel="noopener noreferrer">${esc(s.wordmark)}</a></li>`).join("")}748 ${(KA_ECO.extraFooterLinks||[]).map(l=>`<li><a href="${esc(l.href)}" target="_blank" rel="noopener noreferrer">${esc(l.label)}</a></li>`).join("")}749 </ul>750 <div class="contacts">751 ${KA_ECO.contacts.map(c=>`<p style="margin:0"><a href="mailto:${esc(c.email)}">${esc(c.email)}</a><span>${esc(c.role)}</span></p>`).join("")}752 </div>753 <p class="legal">© ${year} ${esc(KA_ECO.org.copyrightHolder)}754 ${localLegal.map(l=>` · <a href="${esc(l.href)}"${l.nav?" data-nav":""}>${esc(l.label)}</a>`).join("")}755 ${KA_ECO.legal.map(l=>` · <a href="${esc(l.href)}" target="_blank" rel="noopener noreferrer">${esc(l.label)}</a>`).join("")}756 </p>757 </div>758 </footer>`;759}760761/* ---------- annuaire ---------- */762function platIcon(a){763 const p=PLAT[a.platform]||PLAT.autre;764 return `<a class="plat" href="${esc(a.url)}" target="_blank" rel="noopener"765 title="${p.label} — @${esc(a.handle)}" style="background:${p.color}"766 onclick="event.stopPropagation()">${icon(a.platform)}</a>`;767}768function cardHtml(c){769 const plats=(c.platforms||[]).slice(0,7).map(platIcon).join("");770 const niches=(c.niches||[]).slice(0,2).map(n=>`<span class="chip">${NICHE_LBL[n]||n}</span>`).join("");771 const verified=(c.platforms||[]).some(a=>a.verified);772 const np=(c.platforms||[]).length;773 return `<div class="card" data-href="/createur/${esc(c.id)}" role="link" tabindex="0" style="cursor:pointer">774 <div class="card-photo"><span class="init">${esc(initials(c.display_name))}</span>${photoImg(c)}775 <span class="tier ${esc(c.audience_tier)}">${TIER_LBL[c.audience_tier]||c.audience_tier}</span>776 <span class="nplat">${np} plat.</span></div>777 <div class="card-body">778 <div class="card-name">${esc(c.display_name)}${verified?checkSvg:""}</div>779 <div class="card-sub">${c.region?`${esc(c.region)} · `:""}${(c.languages||[]).map(l=>({fr:"FR",en:"EN",bilingue:"FR/EN"}[l]||l)).join(", ")}</div>780 <div class="chips">${niches}</div>781 ${c.total_reach?`<div class="reach"><b>${fmt.format(c.total_reach)}</b> abonnés cumulés</div>`:""}782 <div class="platrow">${plats}</div></div></div>`;783}784function selectHtml(id,label,opts,val,lbls){785 const o=opts.map(v=>`<option value="${esc(v)}"${v===val?" selected":""}>${esc(lbls?(lbls[v]||v):v)}</option>`).join("");786 return `<select class="f${val?" active":""}" id="${id}"><option value="">${label}</option>${o}</select>`;787}788async function renderHome(){789 document.title="Créa-Ka — Un service Groupe KA";790 if(!state.tax)state.tax=await api("/api/taxonomies");791 if(!state.stats)state.stats=await api("/api/stats").catch(()=>null);792 const s=state.stats||{creators:0,accounts:0,by_platform:{}};793 app.innerHTML=header()+`794 <section class="hero"><div class="container">795 <span class="kicker">Annuaire des créateurs du Québec</span>796 <h1 style="margin-top:16px">Chaque créateur<br>québécois. <span class="outline-txt">Tous</span> ses <span class="hl">liens.</span></h1>797 <p class="lead">Un « Linktree collectif » du Québec : influenceurs, youtubeurs, streamers,798 podcasteurs — leurs comptes regroupés et découvrables par niche, région, langue et taille d'audience.</p>799 <div class="stats-row">800 <span class="stat-pill"><b>${fmtFull.format(s.creators)}</b> créateurs</span>801 <span class="stat-pill"><b>${fmtFull.format(s.accounts)}</b> comptes reliés</span>802 <span class="stat-pill"><b>${Object.keys(s.by_platform||{}).length}</b> plateformes</span>803 </div>804 <div class="advantages">805 <div class="adv"><div class="ico">${icon("autre")}</div><h3>Cross-plateforme</h3><p>Instagram, TikTok, YouTube, Twitch, X… tous les comptes d'une même personne, au même endroit.</p></div>806 <div class="adv"><div class="ico"><svg class="icn" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="7"/><path d="M20 20l-3.5-3.5" stroke-linecap="round"/></svg></div><h3>Découvrable</h3><p>Filtres par niche, région, langue, plateforme et taille d'audience.</p></div>807 <div class="adv"><div class="ico">✓</div><h3>Liens vérifiés</h3><p>Chaque rattachement porte un score de confiance ; mieux vaut un lien de moins qu'un lien erroné.</p></div>808 <div class="adv"><div class="ico">🛡</div><h3>Respectueux</h3><p>Profils publics seulement, retrait (opt-out) simple et respecté. Loi 25.</p></div>809 </div>810 </div></section>811 <div class="searchbar" id="annuaire"><div class="container search-wrap">812 <label class="search-input"><svg width="15" height="15" viewBox="0 0 24 24" fill="none"><circle cx="11" cy="11" r="7" stroke="currentColor" stroke-width="2"/><path d="M20 20l-3.5-3.5" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>813 <input id="q" placeholder="Chercher un créateur…" value="${esc(state.q)}"></label>814 <div class="filters">815 ${selectHtml("f-niche","Niche",state.tax.niches,state.niche,NICHE_LBL)}816 ${selectHtml("f-region","Région",state.tax.regions,state.region)}817 ${selectHtml("f-plat","Plateforme",state.tax.plateformes,state.plateforme,Object.fromEntries(Object.entries(PLAT).map(([k,v])=>[k,v.label])))}818 ${selectHtml("f-tier","Taille",state.tax.tiers,state.tier,TIER_LBL)}819 ${selectHtml("f-langue","Langue",state.tax.langues,state.langue,{fr:"Français",en:"Anglais",bilingue:"Bilingue"})}820 ${selectHtml("f-sort","Tri",["reach","nom","recent"],state.sort==="reach"?"":state.sort,{reach:"Audience",nom:"Nom A-Z",recent:"Récents"})}821 </div>822 </div></div>823 <main class="container"><div class="count-line" id="countline"></div>824 <div class="grid" id="grid"><div class="spin"></div></div>825 <div class="more-wrap" id="more"></div></main>`+footer()+tabbar(location.hash==="#annuaire"?"search":"home");826 bindNav();827 $("#q").addEventListener("input",deb(()=>{state.q=$("#q").value;reload()},300));828 [["f-niche","niche"],["f-region","region"],["f-plat","plateforme"],829 ["f-tier","tier"],["f-langue","langue"],["f-sort","sort"]].forEach(([id,key])=>{830 $("#"+id).addEventListener("change",e=>{state[key]=e.target.value||(key==="sort"?"reach":"");831 e.target.classList.toggle("active",!!e.target.value);reload()})});832 if(location.hash==="#annuaire"){const el=$("#annuaire");if(el)el.scrollIntoView()}833 await reload();834}835function deb(fn,ms){let t;return()=>{clearTimeout(t);t=setTimeout(fn,ms)}}836async function reload(more){837 if(!more){state.offset=0;state.items=[]}838 const p=new URLSearchParams({q:state.q,niche:state.niche,region:state.region,839 langue:state.langue,plateforme:state.plateforme,tier:state.tier,sort:state.sort,840 limit:"60",offset:String(state.offset)});841 const data=await api("/api/creators?"+p).catch(()=>({items:[],total:0}));842 state.items=state.items.concat(data.items);state.total=data.total;843 const grid=$("#grid");if(!grid)return;844 $("#countline").innerHTML=`<b>${fmtFull.format(state.total)}</b> créateur${state.total>1?"s":""} trouvé${state.total>1?"s":""}`;845 grid.innerHTML=state.items.length?state.items.map(cardHtml).join(""):846 `<div class="empty">Aucun créateur ne correspond à ces filtres.</div>`;847 const moreDiv=$("#more");848 moreDiv.innerHTML=state.items.length<state.total?849 `<button class="btn btn-primary" id="loadmore">Afficher plus (${fmtFull.format(state.total-state.items.length)} restants)</button>`:"";850 const lm=$("#loadmore");if(lm)lm.onclick=()=>{state.offset+=60;reload(true)};851 bindNav(grid);852}853854/* ---------- page créateur ---------- */855/* --- bande de contenu récent par compte (enrichissement Apify) --- */856const _relTime=ts=>{if(!ts)return"";if(typeof ts==="string"&&!/^\d+$/.test(ts))return esc(ts.slice(0,10));857 const d=Math.floor((Date.now()/1000-Number(ts))/86400);858 return d<=0?"aujourd'hui":d===1?"hier":d<30?d+" j":d<365?Math.floor(d/30)+" mois":Math.floor(d/365)+" ans";};859const _relTimeC=ts=>_relTime(ts);860/* image de contenu : IG/CDN expirent souvent → onerror bascule sur repli icône */861window.thumbFail=el=>{const w=el.parentElement;if(w){w.classList.add("noimg");862 w.innerHTML=w.dataset.icon||"";}};863function _mediaItems(a){const m=a.metrics||{};864 const base=m.recent_posts||m.recent_videos||m.recent_tweets||[];865 if(base.length)return base;866 // Snapchat : les previews de story/spotlight sont des listes d'URLs d'images867 const prev=[...(m.story_previews||[]),...(m.spotlight_previews||[])];868 return prev.map(u=>({display_url:u,type:"story"}));}869870/* grille de contenu récent, images/vidéos bien affichées */871function contentGrid(a){872 const items=_mediaItems(a);873 if(!items.length)return `<div class="cg-empty">${icon(a.platform)} Aucun contenu récent capté pour ce compte.</div>`;874 const ico=icon(a.platform);875 const cards=items.slice(0,12).map(it=>{876 const img=it.display_url||it.thumbnail_url||it.thumbnail||it.cover||(it.media_urls||[])[0]||"";877 const isVid=it.type==="video"||it.type==="reel"||!!it.duration||it.video_views!=null;878 const cap=esc((it.caption||it.title||it.text||"").slice(0,140));879 const when=_relTimeC(it.timestamp||it.published_at||it.published||it.created_at);880 const stats=[881 it.views!=null?fmt.format(it.views)+" vues":(it.video_views!=null?fmt.format(it.video_views)+" vues":null),882 it.likes!=null?fmt.format(it.likes)+" ❤":null,883 it.comments!=null?fmt.format(it.comments)+" 💬":null,884 it.retweets!=null?fmt.format(it.retweets)+" ↻":null,885 it.shares!=null?fmt.format(it.shares)+" ↗":null,886 ].filter(Boolean).join(" · ");887 const thumb=img888 ? `<span class="cg-thumb" data-icon='${esc(ico)}'><img loading="lazy" src="${esc(img)}" alt="" referrerpolicy="no-referrer" onerror="thumbFail(this)">889 ${isVid?'<span class="cg-play">▶</span>':""}${it.duration?`<span class="cg-dur">${esc(it.duration)}</span>`:""}890 ${it.is_pinned?'<span class="cg-pin">📌</span>':""}</span>`891 : `<span class="cg-thumb noimg" data-icon='${esc(ico)}'>${ico}${isVid?'<span class="cg-play">▶</span>':""}</span>`;892 return `<a class="cg-card" href="${esc(it.url||a.url)}" target="_blank" rel="noopener">893 ${thumb}894 <span class="cg-body">${cap?`<span class="cg-cap">${cap}</span>`:`<span class="cg-cap dim">Publication ${a.platform}</span>`}895 <span class="cg-meta">${stats||"—"}${when?`<span class="cg-when">${when}</span>`:""}</span></span></a>`;896 }).join("");897 return `<div class="cg-grid">${cards}</div>`;898}899900/* définitions de métriques calculées, par plateforme */901function platformKPIs(a){902 const m=a.metrics||{}, out=[];903 const push=(v,l,suf)=>{if(v!=null&&v!=="")out.push([typeof v==="number"?fmt.format(v)+(suf||""):esc(v)+(suf||""),l]);};904 push(a.followers,a.platform==="discord"?"membres":a.platform==="patreon"?"patrons":"abonnés");905 push(m.engagement_rate_pct!=null?m.engagement_rate_pct:null,"engagement","%");906 push(m.avg_likes,"j'aime / pub (moy.)");907 push(m.avg_views!=null?m.avg_views:m.avg_video_views,"vues / pub (moy.)");908 push(m.avg_comments,"comm. / pub (moy.)");909 push(m.avg_retweets,"RT / pub (moy.)");910 push(m.posts_per_week!=null?m.posts_per_week:(m.tweets_per_week!=null?m.tweets_per_week:m.videos_per_week),"pubs / semaine");911 push(m.total_views,"vues totales");912 push(m.posts,"publications");913 push(m.videos,"vidéos");914 push(m.likes,a.platform==="tiktok"?"j'aime cumulés":"j'aime");915 push(m.following,"abonnements");916 push(m.online,"en ligne");917 push(m.boosts,"boosts");918 push(m.patrons,"patrons");919 push(m.live_viewers,"spectateurs (live)");920 return out;921}922function platformBadges(a){923 const m=a.metrics||{};924 return [925 a.verified?'<span class="abadge ok">✓ Vérifié</span>':"",926 m.is_partner?'<span class="abadge">Partenaire</span>':"",927 m.is_affiliate&&!m.is_partner?'<span class="abadge">Affilié</span>':"",928 m.partnered?'<span class="abadge">Serveur partenaire</span>':"",929 m.is_live_now?'<span class="abadge live">● En direct</span>':"",930 m.is_seller?'<span class="abadge">Boutique</span>':"",931 m.is_free===true?'<span class="abadge">Gratuit</span>':"",932 m.is_monthly?'<span class="abadge">Mensuel</span>':"",933 m.has_story?'<span class="abadge">Story active</span>':"",934 m.has_spotlight?'<span class="abadge">Spotlight</span>':"",935 m.is_nsfw?'<span class="abadge live">18+</span>':"",936 m.creation_name?`<span class="abadge">${esc(String(m.creation_name).slice(0,60))}</span>`:"",937 m.joined_date?`<span class="abadge">${esc(m.joined_date)}</span>`:"",938 m.is_blue_verified?'<span class="abadge ok">✓ Bleu</span>':"",939 m.country?`<span class="abadge">${esc(m.country)}</span>`:"",940 (m.recent_games||[]).slice(0,2).map(g=>`<span class="abadge">🎮 ${esc(g)}</span>`).join(""),941 (m.top_hashtags||[]).slice(0,3).map(h=>`<span class="abadge">#${esc(h)}</span>`).join(""),942 ].filter(Boolean).join("");943}944945/* panneau d'un compte (onglet plateforme) */946function platformPanel(a,idx,active){947 const p=PLAT[a.platform]||PLAT.autre;948 const conf=Math.round((a.confidence||0)*100);949 const confCls=(a.confidence||0)>=0.75?"hi":"mid";950 const kpis=platformKPIs(a).map(([v,l])=>`<div class="kpi"><b>${v}</b><span>${l}</span></div>`).join("");951 const badges=platformBadges(a);952 const stamp=(a.metrics||{})[a.platform+"_apify_at"]||a.last_checked;953 const top=(a.metrics||{}).top_post||(a.metrics||{}).top_video||(a.metrics||{}).top_tweet;954 return `<section class="ppanel${active?" on":""}" data-panel="${idx}">955 <div class="ppanel-head">956 <span class="plat" style="background:${p.color}">${icon(a.platform)}</span>957 <div class="ppanel-id"><span class="ppanel-name">${p.label}${a.verified?checkSvg:""}</span>958 <a class="ppanel-handle" href="${esc(a.url)}" target="_blank" rel="noopener">@${esc(a.handle)} ↗</a></div>959 <span class="conf ${confCls}" title="Score de rattachement">${conf}%</span>960 </div>961 ${badges?`<div class="abadge-row">${badges}</div>`:""}962 ${(a.metrics||{}).is_live_now&&(a.metrics||{}).live_thumbnail?`963 <a class="live-card" href="${esc(a.url)}" target="_blank" rel="noopener">964 <img loading="lazy" src="${esc((a.metrics||{}).live_thumbnail)}" alt="" referrerpolicy="no-referrer" onerror="this.remove()">965 <span class="live-card-body"><span class="live-card-tag">● EN DIRECT</span>966 ${(a.metrics||{}).live_title?`<span class="live-card-title">${esc((a.metrics||{}).live_title)}</span>`:""}967 <span class="live-card-meta">${(a.metrics||{}).live_viewers!=null?fmt.format((a.metrics||{}).live_viewers)+" spectateurs":""}${(a.metrics||{}).live_game?" · "+esc((a.metrics||{}).live_game):((a.metrics||{}).live_category?" · "+esc((a.metrics||{}).live_category):"")}</span>968 </span></a>`:""}969 <div class="kpi-grid">${kpis||'<div class="kpi"><b>—</b><span>pas encore de métriques</span></div>'}</div>970 ${top&&top.url?`<a class="ppanel-top" href="${esc(top.url)}" target="_blank" rel="noopener">★ Top contenu — ${top.views!=null?fmt.format(top.views)+" vues":""}${top.likes!=null?" · "+fmt.format(top.likes)+" ❤":""} ↗</a>`:""}971 <h3 class="cg-title">Contenu récent</h3>972 ${contentGrid(a)}973 <div class="ppanel-foot">${stamp?"Dernière vérification "+esc(String(stamp).slice(0,10)):""}</div>974 </section>`;975}976977window.showPtab=(i,el)=>{978 document.querySelectorAll(".ppanel").forEach(s=>s.classList.toggle("on",s.dataset.panel==String(i)));979 document.querySelectorAll(".ptab").forEach(t=>t.classList.remove("on"));980 if(el)el.classList.add("on");981 const ov=document.getElementById("ov-panel");if(ov)ov.classList.toggle("on",i==="ov");982};983984function computeAgg(c){985 const P=(c.platforms||[]);986 const reach=c.total_reach||P.reduce((s,a)=>s+(a.followers||0),0);987 const eng=P.map(a=>(a.metrics||{}).engagement_rate_pct).filter(v=>v!=null);988 const avgEng=eng.length?(eng.reduce((s,v)=>s+v,0)/eng.length):null;989 const content=P.reduce((s,a)=>s+_mediaItems(a).length,0);990 const cad=P.map(a=>{const m=a.metrics||{};return m.posts_per_week!=null?m.posts_per_week:m.tweets_per_week;}).filter(v=>v!=null);991 const main=P.slice().sort((x,y)=>(y.followers||0)-(x.followers||0))[0];992 return {reach,avgEng,content,maxCad:cad.length?Math.max(...cad):null,993 nPlat:P.length,nVerif:P.filter(a=>a.verified).length,main};994}995996/* sparkline SVG (série quotidienne des snapshots — insights.history.total) */997function sparkSvg(pts){998 if(!pts||pts.length<2)return"";999 const w=600,h=88,pad=4;1000 const vals=pts.map(p=>p.followers);1001 const min=Math.min(...vals),max=Math.max(...vals),span=Math.max(1,max-min);1002 const x=i=>pad+i*(w-2*pad)/(pts.length-1);1003 const y=v=>h-pad-((v-min)/span)*(h-2*pad);1004 const line=pts.map((p,i)=>`${x(i).toFixed(1)},${y(p.followers).toFixed(1)}`).join(" ");1005 const area=`${pad},${h-pad} ${line} ${(w-pad).toFixed(1)},${h-pad}`;1006 return `<div class="spark-wrap"><svg class="spark-svg" viewBox="0 0 ${w} ${h}" preserveAspectRatio="none" aria-hidden="true">1007 <polygon class="spark-fill" points="${area}"/><polyline class="spark-line" points="${line}"/></svg>1008 <div class="spark-meta"><span>${esc(pts[0].day)}</span><span>${fmt.format(min)} → ${fmt.format(max)}</span><span>${esc(pts[pts.length-1].day)}</span></div></div>`;1009}10101011async function renderCreator(id){1012 app.innerHTML=header()+`<div class="container"><div class="spin"></div></div>`+footer()+tabbar("");bindNav();1013 let c;try{c=await api("/api/creators/"+encodeURIComponent(id))}1014 catch(e){app.innerHTML=header()+`<div class="container"><div class="empty"><h2>Créateur introuvable</h2>1015 <p style="margin-top:10px"><a href="/" data-nav style="color:var(--green);font-weight:600">← Retour à l'annuaire</a></p></div></div>`+footer()+tabbar("");bindNav();return}1016 document.title=`${c.display_name} — Créa-Ka`;1017 const verified=(c.platforms||[]).some(a=>a.verified);1018 const niches=(c.niches||[]).map(n=>`<span class="mchip">${NICHE_LBL[n]||n}</span>`).join("");1019 const P=(c.platforms||[]).slice().sort((x,y)=>(y.followers||0)-(x.followers||0));1020 const g=computeAgg(c);1021 const ins=c.insights||{};1022 const banner=c.banner_url||P.map(a=>(a.metrics||{}).banner).find(Boolean);1023 // bandeau de métriques CALCULÉES (multi-plateforme)1024 const kpi=(v,l)=>v==null?"":`<div class="hkpi"><b>${typeof v==="number"?fmt.format(v):esc(v)}</b><span>${l}</span></div>`;1025 const gkpi=(d,l)=>!d||!d.delta?"":`<div class="hkpi"><b class="${d.delta>=0?"growth-up":"growth-down"}">${d.delta>=0?"+":""}${fmt.format(d.delta)}</b><span>${l}</span></div>`;1026 const heroKpis=[1027 kpi(g.reach,"abonnés cumulés"),1028 gkpi(ins.growth_7d,"abonnés / 7 j"),1029 gkpi(ins.growth_30d,"abonnés / 30 j"),1030 kpi(g.nPlat,"plateformes"),1031 kpi(ins.avg_engagement_pct!=null?ins.avg_engagement_pct+" %":(g.avgEng!=null?Math.round(g.avgEng*100)/100+" %":null),"engagement moyen"),1032 kpi(ins.publications_per_week!=null?ins.publications_per_week:g.maxCad,"pubs / semaine"),1033 kpi(g.content||null,"contenus analysés"),1034 kpi(g.main?(PLAT[g.main.platform]||PLAT.autre).label:null,"plateforme nº 1"),1035 ].filter(Boolean).join("");1036 // Ka Score /100 (insights serveur) + sparkline d'audience (snapshots)1037 const KS_MAX={audience:40,engagement:25,presence:20,rythme:10,verification:5};1038 const KS_LBL={audience:"Audience",engagement:"Engagement",presence:"Multi-plateforme",1039 rythme:"Rythme de pub.",verification:"Vérification"};1040 const ksParts=ins.ka_score_parts||{};1041 const ksHtml=ins.ka_score!=null?`<h3 class="cg-title">Ka Score</h3><div class="kascore">1042 <div class="kascore-num"><b>${ins.ka_score}</b><span>/ 100</span></div>1043 <div class="kascore-parts">${Object.keys(KS_MAX).map(k=>`<div class="kspart">1044 <span class="kspart-lab">${KS_LBL[k]}</span>1045 <span class="kspart-track"><span class="kspart-fill" style="width:${Math.min(100,Math.round((ksParts[k]||0)/KS_MAX[k]*100))}%"></span></span>1046 <span class="kspart-val">${ksParts[k]!=null?ksParts[k]:0}/${KS_MAX[k]}</span></div>`).join("")}</div>1047 </div>`:"";1048 const hist=((ins.history||{}).total)||[];1049 const sparkHtml=hist.length>1?`<h3 class="cg-title">Évolution de l'audience</h3>${sparkSvg(hist)}`:"";1050 // barres comparatives de portée par plateforme (vue d'ensemble)1051 const maxF=Math.max(1,...P.map(a=>a.followers||0));1052 const bars=P.map(a=>{const p=PLAT[a.platform]||PLAT.autre;const w=Math.round((a.followers||0)/maxF*100);1053 return `<div class="obar"><span class="obar-ico" style="background:${p.color}">${icon(a.platform)}</span>1054 <span class="obar-lab">${p.label}</span>1055 <span class="obar-track"><span class="obar-fill" style="width:${w}%;background:${p.color}"></span></span>1056 <span class="obar-val">${a.followers!=null?fmt.format(a.followers):"—"}</span></div>`;}).join("");1057 // onglets + panneaux1058 const tabs=`<button class="ptab on" onclick="showPtab('ov',this)">Vue d'ensemble</button>`+1059 P.map((a,i)=>{const p=PLAT[a.platform]||PLAT.autre;1060 return `<button class="ptab" onclick="showPtab(${i},this)">1061 <span class="ptab-ico" style="color:${p.color}">${icon(a.platform)}</span>${p.label}1062 ${a.followers!=null?`<span class="ptab-n">${fmt.format(a.followers)}</span>`:""}</button>`;}).join("");1063 const ovPanel=`<section class="ppanel on" id="ov-panel" data-panel="ov">1064 ${ksHtml}1065 <h3 class="cg-title">Portée par plateforme</h3>1066 <div class="obars">${bars||"<p class=klabel>Aucune plateforme.</p>"}</div>1067 ${sparkHtml}1068 ${c.link_in_bio_url?`<a class="linkinbio" href="${esc(c.link_in_bio_url)}" target="_blank" rel="noopener">${icon("linktree")} Sa page de liens officielle ↗</a>`:""}1069 </section>`;1070 const panels=ovPanel+P.map((a,i)=>platformPanel(a,i,false)).join("");1071 const checked=(c.platforms||[]).map(a=>{const m=a.metrics||{};return m[a.platform+"_apify_at"]||a.last_checked;}).filter(Boolean).sort().pop();1072 app.innerHTML=header()+`1073 <div class="container cpage">1074 <a class="backlink" href="/" data-nav>← Tous les créateurs</a>1075 ${banner?`<div class="cbanner"><img src="${esc(banner)}" alt="" referrerpolicy="no-referrer" onerror="this.parentElement.remove()"></div>`:""}1076 <div class="cpage-head">1077 <div class="cavatar">${esc(initials(c.display_name))}${photoImg(c)}</div>1078 <div style="flex:1;min-width:200px"><h1>${esc(c.display_name)}${verified?checkSvg:""}</h1>1079 <div class="meta"><span class="mchip o">${TIER_LBL[c.audience_tier]||c.audience_tier}</span>1080 ${niches}${c.region?`<span class="mchip">${esc(c.region)}</span>`:""}1081 ${(c.languages||[]).map(l=>`<span class="mchip">${{fr:"Français",en:"Anglais",bilingue:"Bilingue"}[l]||l}</span>`).join("")}</div>1082 ${c.bio?`<p class="cpage-bio">${esc(c.bio)}</p>`:""}</div>1083 </div>1084 <div class="hkpi-row">${heroKpis}</div>1085 ${c.notes?`<div class="cpage-notes"><span class="klabel">Contexte public</span><br>${esc(c.notes)}</div>`:""}1086 <div class="ptabs">${tabs}</div>1087 <div class="ppanels">${panels}</div>1088 <div class="cpage-foot">Sources : listes médias publiques, Wikidata, annuaires publics et profils publics des plateformes (enrichissement Apify).1089 Audiences et métriques approximatives${checked?`, dernière vérification ${esc(String(checked).slice(0,10))}`:""}.1090 Une erreur dans cette fiche ? <a href="/retrait" data-nav>Signaler / demander un retrait</a>.</div>1091 </div>`+footer()+tabbar("");1092 bindNav();window.scrollTo(0,0);1093}10941095/* ---------- page stats : tableau de bord analytique (contrat ka-stats) ---------- */1096const ST={period:"30j",from:"",to:"",data:null,hide:{},tbl:{}};1097const PERIODS_UI=[["auj","Aujourd'hui"],["7j","7 jours"],["30j","30 jours"],["3m","3 mois"],1098 ["6m","6 mois"],["12m","12 mois"],["annee","Année en cours"],["tout","Tout"]];1099const fmtPct=v=>(v>=0?"+":"")+v.toLocaleString("fr-CA",{maximumFractionDigits:1})+" %";1100const emptyBlock=t=>`<div class="empty-block"><b style="font-family:var(--font-display);font-size:14px">${esc(t)}</b>1101 <p class="klabel" style="margin:6px 0 0">Pas encore mesuré — aucune donnée disponible pour cette période.</p></div>`;11021103/* mini-tendance dans une carte KPI (série spark du contrat v2) */1104function sparkSvg(spark,hero){1105 if(!spark||spark.length<2)return"";1106 const W=120,H=28,vs=spark.map(p=>p.v);1107 const mn=Math.min(...vs),mx=Math.max(...vs),rng=(mx-mn)||1;1108 const pts=spark.map((p,i)=>`${(i/(spark.length-1)*W).toFixed(1)},${(H-2-(H-4)*((p.v-mn)/rng)).toFixed(1)}`).join(" ");1109 return `<svg class="spark" viewBox="0 0 ${W} ${H}" preserveAspectRatio="none" aria-hidden="true">1110 <polyline points="${pts}" fill="none" stroke="${hero?"var(--accent-soft)":"var(--accent)"}" stroke-width="2"/></svg>`;1111}1112function kpiHtml(k,i){1113 const v=k.value,hero=i===0;1114 const num=typeof v==="number"?(v>=1e6?fmt.format(v):fmtFull.format(v)):esc(v);1115 let delta="";1116 if(k.delta_pct!==undefined&&k.delta_pct!==null){1117 const up=(k.direction||(k.delta_pct>=0?"up":"down"))==="up";1118 delta=`<div class="tile-d ${up?"up":"down"}">${up?"▲":"▼"} ${fmtPct(k.delta_pct)} <span style="opacity:.6">vs période préc.</span></div>`;}1119 return `<div class="tile${hero?" hero-tile":""}"${typeof v==="number"?` title="${fmtFull.format(v)}"`:""}>1120 <div class="tile-v">${num}${k.unit?`<span style="font-size:.42em;opacity:.6"> ${esc(k.unit)}</span>`:""}</div>1121 <div class="tile-k">${esc(k.label)}</div>${delta}${sparkSvg(k.spark,hero)}</div>`;1122}11231124/* --- jauge demi-arc (taux, couvertures, complétude) --- */1125function gaugeHtml(g){1126 if(typeof g.value!=="number"||!g.max)return"";1127 const frac=Math.max(0,Math.min(1,g.value/g.max));1128 const R=52,C=Math.PI*R;1129 return `<div class="gauge" title="${esc(g.label)} — ${g.value.toLocaleString("fr-CA")}${g.unit?" "+esc(g.unit):""} (${Math.round(frac*100)} % de ${fmtFull.format(g.max)})">1130 <svg viewBox="0 0 120 68" role="img" aria-label="${esc(g.label)}">1131 <path d="M8 60 A${R} ${R} 0 0 1 112 60" fill="none" stroke="var(--line)" stroke-width="11" stroke-linecap="round"/>1132 ${frac>0?`<path d="M8 60 A${R} ${R} 0 0 1 112 60" fill="none" stroke="var(--accent)" stroke-width="11" stroke-linecap="round"1133 stroke-dasharray="${(frac*C).toFixed(1)} ${(C+20).toFixed(1)}"/>`:""}1134 </svg>1135 <div class="g-v">${g.value.toLocaleString("fr-CA",{maximumFractionDigits:1})}${g.unit?`<span style="font-size:.58em;opacity:.65"> ${esc(g.unit)}</span>`:""}</div>1136 <div class="g-l">${esc(g.label)}</div></div>`;1137}11381139/* --- statistiques d'une série : min / max / moyenne / médiane / écart-type --- */1140function statSummary(s){1141 const vs=(s.points||[]).map(p=>p.v).filter(v=>typeof v==="number");1142 if(vs.length<2)return"";1143 const sv=[...vs].sort((a,b)=>a-b),mean=vs.reduce((a,b)=>a+b,0)/vs.length;1144 const med=sv[Math.floor(sv.length/2)];1145 const sd=Math.sqrt(vs.reduce((a,v)=>a+(v-mean)*(v-mean),0)/vs.length);1146 const f=v=>fmtFull.format(Math.round(v*100)/100);1147 return `<div class="statsum"><span class="chip">min<b>${f(sv[0])}</b></span>1148 <span class="chip">max<b>${f(sv[sv.length-1])}</b></span>1149 <span class="chip">moyenne<b>${f(mean)}</b></span>1150 <span class="chip">médiane<b>${f(med)}</b></span>1151 <span class="chip">écart-type σ<b>${f(sd)}</b></span></div>`;1152}11531154/* --- barres verticales : série kind=bar (volumes/jour) & histogrammes (bins) --- */1155function vbarsHtml(spec){1156 const pts=(spec.points||(spec.bins||[]).map(b=>({t:b.label,v:b.value})))1157 .filter(p=>typeof p.v==="number");1158 if(!pts.length||!pts.some(p=>p.v>0))return emptyBlock(spec.title);1159 const W=720,H=250,PL=54,PR=12,PT=14,PB=40,cw=W-PL-PR,ch=H-PT-PB;1160 const vmax=Math.max(...pts.map(p=>p.v),1);1161 let grid="";1162 for(let k=0;k<5;k++){const y=PT+ch*k/4,v=vmax*(4-k)/4;1163 grid+=`<line x1="${PL}" x2="${W-PR}" y1="${y.toFixed(1)}" y2="${y.toFixed(1)}" stroke="var(--line)"/>1164 <text x="${PL-6}" y="${(y+3).toFixed(1)}" text-anchor="end" font-size="10" fill="var(--ink-3)" font-family="var(--font-mono)">${fmtFull.format(Math.round(v))}</text>`;}1165 const n=pts.length,bw=Math.max(2,cw/n-3);1166 const bars=pts.map((p,i)=>{const bh=ch*p.v/vmax,x=PL+cw*i/n+1.5;1167 return `<rect x="${x.toFixed(1)}" y="${(PT+ch-bh).toFixed(1)}" width="${bw.toFixed(1)}" height="${Math.max(bh,p.v>0?1:0).toFixed(1)}"1168 fill="var(--accent)" stroke="var(--ink)" stroke-width="${n>40?0.4:1}"><title>${esc(p.t)} — ${fmtFull.format(p.v)}${spec.unit?" "+esc(spec.unit):""}</title></rect>`;}).join("");1169 const lblIdx=n<=10?pts.map((_,i)=>i):[0,Math.floor(n/2),n-1];1170 const xl=lblIdx.map(i=>`<text x="${(PL+cw*i/n+bw/2).toFixed(1)}" y="${H-8}" text-anchor="middle"1171 font-size="${n<=10?10:10}" fill="var(--ink-3)" font-family="var(--font-mono)">${esc(String(pts[i].t))}</text>`).join("");1172 return `<div class="viz-card"><h2>${esc(spec.title)}</h2>1173 ${spec.unit?`<div class="viz-sub">${esc(spec.unit)}</div>`:'<div style="height:10px"></div>'}1174 <svg viewBox="0 0 ${W} ${H}" role="img" aria-label="${esc(spec.title)}">${grid}${bars}${xl}</svg></div>`;1175}11761177/* --- multi-courbes (≤ 4) : identité par MOTIF de trait en plus de la couleur --- */1178const ML_STYLES=[1179 {stroke:"var(--accent)",w:2.6,dash:"",cls:"",name:"trait plein accent"},1180 {stroke:"var(--ink)",w:2,dash:"",cls:"ml1",name:"trait plein encre"},1181 {stroke:"var(--accent)",w:2.2,dash:"7 4",cls:"ml2",name:"tirets accent"},1182 {stroke:"var(--ink-3)",w:2,dash:"2 3",cls:"ml3",name:"pointillé gris"}];1183function multiFigure(ms,hide){1184 const series=(ms.series||[]).filter(s=>(s.points||[]).length>1).slice(0,4);1185 if(!series.length)return emptyBlock(ms.title);1186 const W=720,H=250,PL=54,PR=12,PT=14,PB=26,cw=W-PL-PR,ch=H-PT-PB;1187 const shown=series.filter((_,i)=>!hide[i]);1188 const all=(shown.length?shown:series).flatMap(s=>s.points.map(p=>p.v));1189 const vmax=Math.max(...all,1),vmin=Math.min(0,...all),rng=(vmax-vmin)||1;1190 let grid="";1191 for(let k=0;k<5;k++){const y=PT+ch*k/4,v=vmax-(vmax-vmin)*k/4;1192 grid+=`<line x1="${PL}" x2="${W-PR}" y1="${y.toFixed(1)}" y2="${y.toFixed(1)}" stroke="var(--line)"/>1193 <text x="${PL-6}" y="${(y+3).toFixed(1)}" text-anchor="end" font-size="10" fill="var(--ink-3)" font-family="var(--font-mono)">${fmtFull.format(Math.round(v))}</text>`;}1194 const X=(i,n)=>PL+cw*i/((n-1)||1),Y=v=>PT+ch*(1-(v-vmin)/rng);1195 const paths=series.map((s,si)=>{if(hide[si])return"";1196 const st=ML_STYLES[si];1197 const d=s.points.map((p,i)=>`${i?"L":"M"}${X(i,s.points.length).toFixed(1)},${Y(p.v).toFixed(1)}`).join("");1198 return `<path d="${d}" fill="none" stroke="${st.stroke}" stroke-width="${st.w}"${st.dash?` stroke-dasharray="${st.dash}"`:""}/>`;}).join("");1199 const ref=series[0].points;1200 const xl=[0,Math.floor(ref.length/2),ref.length-1].map(i=>1201 `<text x="${X(i,ref.length).toFixed(1)}" y="${H-8}" text-anchor="middle" font-size="10" fill="var(--ink-3)" font-family="var(--font-mono)">${esc(ref[i].t)}</text>`).join("");1202 const leg=series.map((s,si)=>`<button type="button" class="leg-btn" data-mls="${si}" aria-pressed="${!hide[si]}"1203 title="${ML_STYLES[si].name}"><span class="leg-swatch ${ML_STYLES[si].cls}"></span>${esc(s.label)}</button>`).join("");1204 return `<div class="viz-card">1205 <div style="display:flex;justify-content:space-between;flex-wrap:wrap;gap:8px;align-items:center">1206 <h2>${esc(ms.title)}</h2><span class="lc-legend">${leg}</span></div>1207 <svg viewBox="0 0 ${W} ${H}" role="img" aria-label="${esc(ms.title)}" style="margin-top:10px">1208 ${grid}${xl}${paths}1209 <g class="lc-cursor" style="display:none"><line y1="${PT}" y2="${H-PB}" stroke="var(--ink)" stroke-dasharray="2 3"/></g>1210 </svg><span class="chip lc-tip"> </span></div>`;1211}1212function mountMulti(el,ms){1213 const hide=ST.hide["ml:"+ms.id]||(ST.hide["ml:"+ms.id]={});1214 const series=(ms.series||[]).filter(s=>(s.points||[]).length>1).slice(0,4);1215 const render=()=>{1216 el.innerHTML=multiFigure(ms,hide);1217 el.querySelectorAll("[data-mls]").forEach(b=>b.addEventListener("click",()=>{1218 hide[b.dataset.mls]=!hide[b.dataset.mls];render();}));1219 const svg=el.querySelector("svg");if(!svg||!series.length)return;1220 const W=720,PL=54,PR=12,n=series[0].points.length;1221 const cursor=el.querySelector(".lc-cursor"),tip=el.querySelector(".lc-tip");1222 const cl=cursor?cursor.querySelector("line"):null;1223 svg.addEventListener("pointermove",e=>{1224 const r=svg.getBoundingClientRect();1225 const fx=(e.clientX-r.left)/r.width*W;1226 let i=Math.round((fx-PL)/(W-PL-PR)*(n-1));i=Math.max(0,Math.min(n-1,i));1227 const x=(PL+(W-PL-PR)*i/((n-1)||1)).toFixed(1);1228 if(cl){cl.setAttribute("x1",x);cl.setAttribute("x2",x);cursor.style.display="";}1229 const vals=series.map((s,si)=>hide[si]?null:`${esc(s.label)} : <b> ${fmtFull.format((s.points[i]||{}).v??0)}</b>`).filter(Boolean).join(" · ");1230 tip.innerHTML=`${esc(series[0].points[i].t)} — ${vals}${ms.unit?` <span style="color:var(--ink-3)">${esc(ms.unit)}</span>`:""}`;1231 tip.style.visibility="visible";});1232 svg.addEventListener("pointerleave",()=>{if(cursor)cursor.style.display="none";tip.style.visibility="hidden"});1233 };1234 render();1235}12361237/* --- barres empilées (composition dans le temps, nuances d'accent) --- */1238function stackedHtml(st){1239 const keys=(st.keys||[]).slice(0,6),pts=st.points||[];1240 if(!keys.length||!pts.length)return emptyBlock(st.title);1241 const totals=pts.map(p=>(p.values||[]).slice(0,keys.length).reduce((a,b)=>a+(b||0),0));1242 if(!totals.some(t=>t>0))return emptyBlock(st.title);1243 const W=720,H=250,PL=54,PR=12,PT=14,PB=26,cw=W-PL-PR,ch=H-PT-PB;1244 const vmax=Math.max(...totals,1),shades=[1,.78,.58,.42,.3,.22];1245 let grid="";1246 for(let k=0;k<5;k++){const y=PT+ch*k/4,v=vmax*(4-k)/4;1247 grid+=`<line x1="${PL}" x2="${W-PR}" y1="${y.toFixed(1)}" y2="${y.toFixed(1)}" stroke="var(--line)"/>1248 <text x="${PL-6}" y="${(y+3).toFixed(1)}" text-anchor="end" font-size="10" fill="var(--ink-3)" font-family="var(--font-mono)">${fmtFull.format(Math.round(v))}</text>`;}1249 const n=pts.length,bw=Math.max(2,cw/n-3);1250 const bars=pts.map((p,i)=>{let y=PT+ch;const x=PL+cw*i/n+1.5;1251 return keys.map((k,j)=>{const v=(p.values||[])[j]||0;if(!v)return"";1252 const bh=ch*v/vmax;y-=bh;1253 return `<rect x="${x.toFixed(1)}" y="${y.toFixed(1)}" width="${bw.toFixed(1)}" height="${Math.max(bh-.4,.5).toFixed(1)}"1254 fill="var(--accent)" fill-opacity="${shades[j%6]}" stroke="var(--paper)" stroke-width="0.4">1255 <title>${esc(p.t)} · ${esc(k)} — ${fmtFull.format(v)}${st.unit?" "+esc(st.unit):""} (total ${fmtFull.format(totals[i])})</title></rect>`;}).join("");}).join("");1256 const lblIdx=[0,Math.floor(n/2),n-1];1257 const xl=lblIdx.map(i=>`<text x="${(PL+cw*i/n+bw/2).toFixed(1)}" y="${H-8}" text-anchor="middle" font-size="10" fill="var(--ink-3)" font-family="var(--font-mono)">${esc(String(pts[i].t))}</text>`).join("");1258 const leg=keys.map((k,j)=>`<span class="leg-btn" style="min-height:auto"><span class="dsw" style="width:11px;height:11px;border-radius:3px;border:1px solid var(--ink);background:var(--accent);opacity:${shades[j%6]};display:inline-block"></span> ${esc(k)}</span>`).join("");1259 return `<div class="viz-card">1260 <div style="display:flex;justify-content:space-between;flex-wrap:wrap;gap:8px;align-items:center">1261 <h2>${esc(st.title)}</h2><span class="lc-legend">${leg}</span></div>1262 <svg viewBox="0 0 ${W} ${H}" role="img" aria-label="${esc(st.title)}" style="margin-top:10px">${grid}${bars}${xl}</svg></div>`;1263}12641265/* --- heatmap horaire 7 × 24 (dow 0=lun … 6=dim) --- */1266function hourHeatHtml(h){1267 const cells=h&&h.cells||[];1268 if(!cells.length)return emptyBlock(h?h.title:"Activité horaire");1269 const grid=new Map(cells.map(c=>[c.dow+":"+c.hour,c.value||0]));1270 const max=Math.max(...cells.map(c=>c.value||0),1);1271 const CS=22,LX=42,LY=18,DOWS=["Lun","Mar","Mer","Jeu","Ven","Sam","Dim"];1272 let out="";1273 [0,6,12,18,23].forEach(hh=>{out+=`<text x="${LX+hh*CS+CS/2}" y="12" text-anchor="middle" font-size="10" fill="var(--ink-3)" font-family="var(--font-mono)">${hh} h</text>`;});1274 for(let d=0;d<7;d++){1275 out+=`<text x="${LX-6}" y="${LY+d*CS+CS/2+3.5}" text-anchor="end" font-size="10" fill="var(--ink-3)" font-family="var(--font-mono)">${DOWS[d]}</text>`;1276 for(let hh=0;hh<24;hh++){const v=grid.get(d+":"+hh)||0;1277 out+=`<rect x="${LX+hh*CS}" y="${LY+d*CS}" width="${CS-2.5}" height="${CS-2.5}" rx="3.5"1278 fill="${v?"var(--accent)":"rgba(20,24,20,0.06)"}" fill-opacity="${v?(0.2+0.8*v/max).toFixed(2):1}"1279 stroke="rgba(20,24,20,0.14)" stroke-width="0.5"><title>${DOWS[d]} ${hh} h — ${fmtFull.format(v)}</title></rect>`;}}1280 return `<div class="viz-card hh"><h2>${esc(h.title)}</h2><div class="viz-sub">7 jours × 24 heures (heure de l'Est)</div>1281 <div class="tbl-wrap"><svg viewBox="0 0 ${LX+24*CS} ${LY+7*CS}" role="img" aria-label="${esc(h.title)}">${out}</svg></div></div>`;1282}12831284/* --- courbe SVG : infobulle (pointeur), légende cliquable, comparaison N-1 --- */1285function lineGeom(s,hide){1286 const W=720,H=240,PL=54,PR=12,PT=14,PB=26,pts=s.points||[];1287 const all=[...(hide.cur?[]:pts),...(!hide.cmp&&s.compare?s.compare:[])];1288 const vmax=Math.max(...all.map(p=>p.v),1),vmin=Math.min(0,...all.map(p=>p.v));1289 const X=(i,n)=>PL+(W-PL-PR)*i/((n||pts.length)-1);1290 const Y=v=>PT+(H-PT-PB)*(1-(v-vmin)/((vmax-vmin)||1));1291 return{W,H,PL,PR,PT,PB,vmax,vmin,X,Y};1292}1293function lineFigure(s,hide){1294 const pts=s.points||[];1295 if(pts.length<2)return emptyBlock(s.title);1296 const g=lineGeom(s,hide),{W,H,PL,PR,PT,PB,vmax,vmin,X,Y}=g;1297 const path=arr=>arr.map((p,i)=>`${i?"L":"M"}${X(i,arr.length).toFixed(1)},${Y(p.v).toFixed(1)}`).join("");1298 let grid="";1299 for(let k=0;k<5;k++){const y=PT+(H-PT-PB)*k/4,v=vmax-(vmax-vmin)*k/4;1300 grid+=`<line x1="${PL}" x2="${W-PR}" y1="${y.toFixed(1)}" y2="${y.toFixed(1)}" stroke="var(--line)"/>1301 <text x="${PL-6}" y="${(y+3).toFixed(1)}" text-anchor="end" font-size="10" fill="var(--ink-3)" font-family="var(--font-mono)">${fmtFull.format(Math.round(v))}</text>`;}1302 const xl=[0,Math.floor(pts.length/2),pts.length-1].map(i=>1303 `<text x="${X(i).toFixed(1)}" y="${H-8}" text-anchor="middle" font-size="10" fill="var(--ink-3)" font-family="var(--font-mono)">${esc(pts[i].t)}</text>`).join("");1304 return `<div class="viz-card">1305 <div style="display:flex;justify-content:space-between;flex-wrap:wrap;gap:8px;align-items:center">1306 <h2>${esc(s.title)}</h2>1307 <span class="lc-legend">1308 <button type="button" class="leg-btn" data-leg="cur" aria-pressed="${!hide.cur}"><span class="leg-swatch"></span>Période courante</button>1309 ${s.compare?`<button type="button" class="leg-btn" data-leg="cmp" aria-pressed="${!hide.cmp}"><span class="leg-swatch cmp"></span>Période comparée</button>`:""}1310 </span></div>1311 <svg viewBox="0 0 ${g.W} ${g.H}" role="img" aria-label="${esc(s.title)}" style="margin-top:10px">1312 ${grid}${xl}1313 ${!hide.cmp&&s.compare&&s.compare.length>1?`<path d="${path(s.compare)}" fill="none" stroke="var(--ink-3)" stroke-width="1.4" stroke-dasharray="4 4"/>`:""}1314 ${!hide.cur&&s.kind==="area"?`<path d="${path(pts)} L${X(pts.length-1).toFixed(1)},${(H-PB).toFixed(1)} L${X(0).toFixed(1)},${(H-PB).toFixed(1)} Z" fill="var(--accent)" fill-opacity=".12" stroke="none"/>`:""}1315 ${!hide.cur?`<path d="${path(pts)}" fill="none" stroke="var(--accent)" stroke-width="2.4"/>`:""}1316 <g class="lc-cursor" style="display:none">1317 <line y1="${PT}" y2="${H-PB}" stroke="var(--ink)" stroke-dasharray="2 3"/>1318 <circle r="4" fill="var(--accent)" stroke="var(--ink)" stroke-width="1.5"/></g>1319 </svg><span class="chip lc-tip"> </span>`+statSummary(s)+1320 `</div>`;1321}1322function mountLine(el,s){1323 if(s.kind==="bar"){el.innerHTML=vbarsHtml(s);return;}1324 const hide=ST.hide[s.id]||(ST.hide[s.id]={cur:false,cmp:false});1325 const render=()=>{1326 el.innerHTML=lineFigure(s,hide);1327 el.querySelectorAll("[data-leg]").forEach(b=>b.addEventListener("click",()=>{1328 hide[b.dataset.leg]=!hide[b.dataset.leg];render();}));1329 const svg=el.querySelector("svg");if(!svg)return;1330 const pts=s.points,g=lineGeom(s,hide);1331 const cursor=el.querySelector(".lc-cursor"),tip=el.querySelector(".lc-tip");1332 const cl=cursor.querySelector("line"),cc=cursor.querySelector("circle");1333 svg.addEventListener("pointermove",e=>{1334 const r=svg.getBoundingClientRect();1335 const fx=(e.clientX-r.left)/r.width*g.W;1336 let i=Math.round((fx-g.PL)/(g.W-g.PL-g.PR)*(pts.length-1));1337 i=Math.max(0,Math.min(pts.length-1,i));1338 const x=g.X(i).toFixed(1);1339 cl.setAttribute("x1",x);cl.setAttribute("x2",x);1340 cc.setAttribute("cx",x);cc.setAttribute("cy",g.Y(pts[i].v).toFixed(1));1341 cursor.style.display="";1342 const cmp=s.compare&&!hide.cmp&&s.compare[i]?` · N-1 : ${fmtFull.format(s.compare[i].v)}`:"";1343 tip.innerHTML=`${esc(pts[i].t)} — <b> ${fmtFull.format(pts[i].v)}${s.unit?" "+esc(s.unit):""}</b><span style="color:var(--ink-3)">${cmp}</span>`;1344 tip.style.visibility="visible";});1345 svg.addEventListener("pointerleave",()=>{cursor.style.display="none";tip.style.visibility="hidden"});1346 };1347 render();1348}13491350/* --- anneau (top 7 + « Autres ») --- */1351function donutHtml(b){1352 let rows=(b.items||[]).filter(i=>i.value>0);1353 if(!rows.length)return emptyBlock(b.title);1354 if(rows.length>8){const rest=rows.slice(7).reduce((s,r)=>s+r.value,0);1355 rows=rows.slice(0,7);rows.push({label:"Autres",value:rest});}1356 const total=rows.reduce((s,r)=>s+r.value,0);1357 const R=74,C=2*Math.PI*R,shades=[1,.78,.58,.42,.3,.22,.15,.1];let acc=0;1358 const segs=rows.map((r,i)=>{const frac=r.value/total,off=acc;acc+=frac;1359 return `<circle cx="100" cy="100" r="${R}" fill="none" stroke="var(--accent)" stroke-opacity="${shades[i%8]}"1360 stroke-width="30" stroke-dasharray="${(frac*C).toFixed(2)} ${C.toFixed(2)}" stroke-dashoffset="${(-off*C).toFixed(2)}"1361 transform="rotate(-90 100 100)"><title>${esc(r.label)} — ${fmtFull.format(r.value)}</title></circle>`;}).join("");1362 const leg=rows.map((r,i)=>`<li><span class="dsw" style="opacity:${shades[i%8]}"></span>1363 <span class="dlbl" title="${esc(r.label)} — ${fmtFull.format(r.value)}">${esc(r.label)}</span>1364 <b>${(100*r.value/total).toFixed(1).replace(".",",")} %</b></li>`).join("");1365 return `<div class="viz-card"><h2>${esc(b.title)}</h2><div class="donut-wrap">1366 <svg viewBox="0 0 200 200" role="img" aria-label="${esc(b.title)}" style="width:180px;max-width:100%">${segs}1367 <circle cx="100" cy="100" r="${R}" fill="none" stroke="var(--ink)" opacity=".5"/></svg>1368 <ul class="donut-legend">${leg}</ul></div></div>`;1369}13701371/* --- barres horizontales (répartitions, géo) --- */1372function barsHtml(title,items,sub){1373 const rows=(items||[]).slice(0,14);1374 if(!rows.length)return emptyBlock(title);1375 const max=Math.max(...rows.map(r=>r.value),1);1376 return `<div class="viz-card"><h2>${esc(title)}</h2>${sub?`<div class="viz-sub">${esc(sub)}</div>`:`<div style="height:14px"></div>`}1377 ${rows.map(r=>`<div class="hrow" title="${esc(r.label)} — ${fmtFull.format(r.value)}${r.delta_pct!=null?` (${fmtPct(r.delta_pct)} sur la période)`:""}">1378 <span class="hrow-label">${esc(r.label)}</span>1379 <span class="hbar"><i style="width:${Math.max(2,Math.round(r.value/max*100))}%"></i></span>1380 <span class="n">${fmtFull.format(r.value)}</span>${r.delta_pct!=null?`<span class="d ${r.delta_pct>=0?"up":"down"}">${r.delta_pct>=0?"▲":"▼"} ${fmtPct(r.delta_pct)}</span>`:""}</div>`).join("")}</div>`;1381}13821383/* --- calendrier de chaleur (26 dernières semaines) --- */1384function heatmapHtml(h){1385 const cells=h&&h.cells||[];1386 if(!cells.length)return emptyBlock(h?h.title:"Activité");1387 const byDate=new Map(cells.map(c=>[c.date,c.value]));1388 const dates=cells.map(c=>c.date).sort();1389 const end=new Date(dates[dates.length-1]+"T12:00:00");1390 const max=Math.max(...cells.map(c=>c.value),1);1391 const weeks=26,cols=[];const cur=new Date(end);1392 cur.setDate(cur.getDate()-(weeks*7-1));1393 for(let w=0;w<weeks;w++){const col=[];1394 for(let d=0;d<7;d++){const iso=cur.toISOString().slice(0,10);1395 col.push({date:iso,v:byDate.get(iso)||0});cur.setDate(cur.getDate()+1);}1396 cols.push(col);}1397 const rects=cols.map((col,w)=>col.map((c,d)=>1398 `<rect x="${w*14}" y="${d*14}" width="12" height="12" rx="2.5"1399 fill="${c.v?"var(--accent)":"rgba(20,24,20,0.07)"}" fill-opacity="${c.v?(0.25+0.75*c.v/max).toFixed(2):1}"1400 stroke="rgba(20,24,20,0.15)" stroke-width="0.5"><title>${c.date} — ${fmtFull.format(c.v)}</title></rect>`).join("")).join("");1401 return `<div class="viz-card hm"><h2>${esc(h.title)}</h2><div class="viz-sub">26 dernières semaines</div>1402 <div class="tbl-wrap"><svg viewBox="0 0 ${weeks*14} ${7*14}" role="img" aria-label="${esc(h.title)}">${rects}</svg></div></div>`;1403}14041405/* --- tableau : tri par colonne, recherche, pagination 25/pg --- */1406function mountTable(el,spec){1407 const st=ST.tbl[spec.id]||(ST.tbl[spec.id]={q:"",sort:null,page:0});1408 el.innerHTML=`<div class="viz-card dtable">1409 <div style="display:flex;flex-wrap:wrap;gap:10px;justify-content:space-between;align-items:center">1410 <h2>${esc(spec.title)}</h2>1411 <input class="dt-q" placeholder="Rechercher…" value="${esc(st.q)}" aria-label="Rechercher dans ${esc(spec.title)}">1412 </div><div class="dt-body"></div></div>`;1413 const body=el.querySelector(".dt-body"),qi=el.querySelector(".dt-q");1414 const num=x=>typeof x==="number"?x:parseFloat(String(x).replace(/[^\d.,-]/g,"").replace(",","."));1415 const render=()=>{1416 let rows=spec.rows||[];1417 if(st.q){const q=st.q.toLowerCase();1418 rows=rows.filter(r=>r.some(c=>String(c).toLowerCase().includes(q)));}1419 if(st.sort){const{col,dir}=st.sort;1420 rows=[...rows].sort((a,b)=>{const nx=num(a[col]),ny=num(b[col]);1421 if(!Number.isNaN(nx)&&!Number.isNaN(ny))return(nx-ny)*dir;1422 return String(a[col]).localeCompare(String(b[col]),"fr")*dir;});}1423 const pages=Math.max(1,Math.ceil(rows.length/25));1424 st.page=Math.min(st.page,pages-1);const cur=st.page;1425 body.innerHTML=`<div class="tbl-wrap" style="margin-top:12px"><table><thead><tr>1426 ${spec.columns.map((c,i)=>`<th data-col="${i}" aria-sort="${st.sort&&st.sort.col===i?(st.sort.dir===1?"ascending":"descending"):"none"}">${esc(c)} ${st.sort&&st.sort.col===i?(st.sort.dir===1?"▲":"▼"):"↕"}</th>`).join("")}1427 </tr></thead><tbody>1428 ${rows.slice(cur*25,(cur+1)*25).map(r=>`<tr>${r.map(c=>`<td>${typeof c==="number"?fmtFull.format(c):esc(c)}</td>`).join("")}</tr>`).join("")||`<tr><td colspan="${spec.columns.length}" style="color:var(--ink-3)">Aucun résultat.</td></tr>`}1429 </tbody></table></div>1430 <div class="dtable-foot"><span class="klabel">${fmtFull.format(rows.length)} ligne${rows.length>1?"s":""}</span>1431 <span style="display:flex;gap:6px;align-items:center">1432 <button type="button" class="btn btn-ghost dt-prev"${cur===0?" disabled":""}>←</button>1433 <span class="chip">${cur+1} / ${pages}</span>1434 <button type="button" class="btn btn-ghost dt-next"${cur>=pages-1?" disabled":""}>→</button></span></div>`;1435 body.querySelectorAll("th").forEach(th=>th.addEventListener("click",()=>{1436 const col=+th.dataset.col;1437 st.sort={col,dir:st.sort&&st.sort.col===col&&st.sort.dir===1?-1:1};render();}));1438 const prev=body.querySelector(".dt-prev"),next=body.querySelector(".dt-next");1439 prev.addEventListener("click",()=>{st.page=Math.max(0,cur-1);render()});1440 next.addEventListener("click",()=>{st.page=Math.min(pages-1,cur+1);render()});1441 };1442 qi.addEventListener("input",deb(()=>{st.q=qi.value;st.page=0;render()},250));1443 render();1444}14451446/* --- barre de période + boutons PDF --- */1447function periodBar(){1448 const customOn=!!(ST.from&&ST.to);1449 return `<div class="pchips" role="group" aria-label="Période d'analyse">1450 ${PERIODS_UI.map(([id,l])=>`<button type="button" class="pchip" data-period="${id}" aria-pressed="${!customOn&&ST.period===id}">${l}</button>`).join("")}1451 <span class="pdates"><input type="date" id="pfrom" value="${esc(ST.from)}" aria-label="Du">1452 <span class="klabel">au</span><input type="date" id="pto" value="${esc(ST.to)}" aria-label="Au"></span></div>`;1453}1454function bindPeriod(){1455 document.querySelectorAll(".pchip").forEach(b=>b.addEventListener("click",()=>{1456 ST.period=b.dataset.period;ST.from="";ST.to="";loadStatsData();}));1457 const pf=$("#pfrom"),pt=$("#pto");1458 const go=()=>{if(pf.value&&pt.value){ST.from=pf.value;ST.to=pt.value;loadStatsData();}};1459 pf.addEventListener("change",go);pt.addEventListener("change",go);1460}1461/* --- menu PDF : 5 rapports (équivalent vanilla du PdfButton v2) --- */1462const PDF_MODES=[1463 ["synthese","Synthèse exécutive","Couverture, KPI, jauges & records (2-3 p.)"],1464 ["tendances","Tendances & évolution","Toutes les séries + min/max/moy/méd/σ"],1465 ["repartitions","Répartitions & géographie","Anneaux, distributions, géo, activité horaire"],1466 ["donnees","Données détaillées","Tous les tableaux en version longue (400 lignes)"]];1467function pdfActionsHtml(){1468 return `<span class="pdf-actions pdf-menu-wrap">1469 <button type="button" class="btn btn-primary" data-pdf="complet">⬇ Rapport PDF complet</button>1470 <button type="button" class="btn btn-ghost" id="pdf-more" aria-haspopup="true" aria-expanded="false">Autres rapports ▾</button>1471 <div class="pdf-menu" id="pdf-menu" role="menu">1472 ${PDF_MODES.map(([m,l,d])=>`<button type="button" role="menuitem" data-pdf="${m}">${l}<small>${d}</small></button>`).join("")}1473 </div></span>`;1474}1475function bindPdf(){1476 const url=m=>{const p=new URLSearchParams({period:ST.period,mode:m});1477 if(ST.from&&ST.to){p.set("from",ST.from);p.set("to",ST.to);}1478 return "/api/stats/report?"+p;};1479 const menu=document.getElementById("pdf-menu"),more=document.getElementById("pdf-more");1480 if(more&&menu){1481 more.addEventListener("click",e=>{e.stopPropagation();1482 const open=menu.classList.toggle("open");more.setAttribute("aria-expanded",open);1483 if(open)document.addEventListener("click",()=>{menu.classList.remove("open");1484 more.setAttribute("aria-expanded","false");},{once:true});});}1485 document.querySelectorAll("[data-pdf]").forEach(b=>{1486 b.addEventListener("click",async()=>{1487 if(menu)menu.classList.remove("open");1488 const t=b.innerHTML;b.disabled=true;1489 b.innerHTML=b.closest(".pdf-menu")?"Génération…":"⏳ Génération…";1490 try{const r=await fetch(url(b.dataset.pdf));if(!r.ok)throw new Error(r.status);1491 const blob=await r.blob();1492 const fn=((r.headers.get("Content-Disposition")||"").match(/filename="?([^";]+)/)||[])[1]||"rapport-crea-ka.pdf";1493 const a=document.createElement("a");a.href=URL.createObjectURL(blob);a.download=fn;1494 document.body.appendChild(a);a.click();a.remove();1495 setTimeout(()=>URL.revokeObjectURL(a.href),4000);}1496 catch(e){alert("Échec de la génération du PDF — réessayez.");}1497 b.disabled=false;b.innerHTML=t;});});1498}14991500async function renderStats(){1501 document.title="Statistiques — Créa-Ka";1502 app.innerHTML=header()+`<div class="container"><div class="spin"></div></div>`+footer()+tabbar("stats");bindNav();1503 await loadStatsData(true);1504 window.scrollTo(0,0);1505}1506async function loadStatsData(first){1507 const main=$("#stats-main");if(main)main.style.opacity=".45";1508 let d;1509 try{const p=new URLSearchParams({period:ST.period});1510 if(ST.from&&ST.to){p.set("from",ST.from);p.set("to",ST.to);}1511 d=await api("/api/stats/dashboard?"+p);}1512 catch(e){app.innerHTML=header()+`<div class="container empty">Statistiques indisponibles pour le moment.</div>`+footer()+tabbar("stats");bindNav();return}1513 ST.data=d;renderStatsPage();1514}1515function renderStatsPage(){1516 const d=ST.data;1517 const upd=new Date(d.updated).toLocaleString("fr-CA",{dateStyle:"medium",timeStyle:"short"});1518 const donuts=(d.breakdowns||[]).filter(b=>b.kind==="donut");1519 const barsB=(d.breakdowns||[]).filter(b=>b.kind!=="donut");1520 const gauges=(d.gauges||[]).filter(g=>typeof g.value==="number"&&g.max);1521 const hasEvo=(d.series||[]).length||(d.multiseries||[]).length||(d.stacked||[]).length;1522 app.innerHTML=header()+`1523 <div class="container stats-page" id="stats-main">1524 <div class="stats-head">1525 <div><span class="kicker">Tableau de bord — ${esc(d.period.label)}</span>1526 <h1 style="font-size:clamp(28px,4.6vw,46px);margin:12px 0 0;text-transform:uppercase">Les créateurs québécois <span class="hl">en chiffres.</span></h1>1527 <div class="fresh-row"><span>Mis à jour le ${esc(upd)}</span>1528 <button type="button" class="btn btn-ghost" id="st-refresh">↻ Rafraîchir</button></div></div>1529 ${pdfActionsHtml()}1530 </div>1531 <div class="tiles">${(d.kpis||[]).map(kpiHtml).join("")||emptyBlock("Indicateurs")}</div>1532 ${periodBar()}1533 <p class="klabel" style="margin:6px 0 0">Période analysée : ${esc(d.period.from)} → ${esc(d.period.to)}</p>1534 ${gauges.length?`<h2 class="stats-sect">Taux & couvertures</h2>1535 <div class="gauges">${gauges.map(gaugeHtml).join("")}</div>`:""}1536 <h2 class="stats-sect">Évolution</h2>1537 ${hasEvo?`<div class="viz-grid">1538 ${(d.series||[]).map(s=>`<div class="lc" data-serie="${esc(s.id)}"></div>`).join("")}1539 ${(d.multiseries||[]).map(m=>`<div class="lc" data-multi="${esc(m.id)}"></div>`).join("")}1540 ${(d.stacked||[]).map(st=>stackedHtml(st)).join("")}1541 </div>`:emptyBlock("Évolution")}1542 <h2 class="stats-sect">Répartitions & distributions</h2>1543 <div class="viz-grid two">1544 ${donuts.map(donutHtml).join("")}1545 ${barsB.map(b=>barsHtml(b.title,b.items)).join("")}1546 ${(d.distributions||[]).map(vbarsHtml).join("")}1547 </div>1548 ${d.geo?`<h2 class="stats-sect">Répartition géographique</h2>1549 <div class="viz-grid two">${barsHtml(d.geo.title,d.geo.items)}</div>`:""}1550 ${d.heatmap||d.hourly?`<h2 class="stats-sect">Activité</h2>1551 ${d.heatmap?heatmapHtml(d.heatmap):""}${d.hourly?hourHeatHtml(d.hourly):""}`:""}1552 ${(d.tables||[]).length?`<h2 class="stats-sect">Détails</h2><div style="display:grid;gap:22px">${(d.tables||[]).map(t=>`<div class="dt" data-table="${esc(t.id)}"></div>`).join("")}</div>`:""}1553 ${(d.records||[]).length?`<h2 class="stats-sect">Records & faits marquants</h2>1554 <div class="records-grid">${d.records.map(r=>`<div class="record"><span class="r-lbl">${esc(r.label)}</span>1555 <span style="text-align:right"><span class="r-val">${esc(r.value)}</span>${r.date?`<span class="klabel" style="display:block">${esc(r.date)}</span>`:""}</span></div>`).join("")}</div>`:""}1556 <p style="font-size:12px;color:var(--ink-3);margin-top:30px">Données réelles de l'annuaire (fiches actives, comptes vérifiés) — aucune statistique estimée ou inventée.1557 Rapports PDF estampillés Groupe-KA (5 formats) disponibles en haut de page.</p>1558 </div>`+footer()+tabbar("stats");1559 bindNav();bindPeriod();bindPdf();1560 $("#st-refresh").addEventListener("click",()=>loadStatsData());1561 (ST.data.series||[]).forEach(s=>{const el=document.querySelector(`.lc[data-serie="${s.id}"]`);if(el)mountLine(el,s);});1562 (ST.data.multiseries||[]).forEach(m=>{const el=document.querySelector(`.lc[data-multi="${m.id}"]`);if(el)mountMulti(el,m);});1563 (ST.data.tables||[]).forEach(t=>{const el=document.querySelector(`.dt[data-table="${t.id}"]`);if(el)mountTable(el,t);});1564}15651566/* ---------- page compte (KA ID) ---------- */1567async function renderCompte(){1568 document.title="Mon compte KA — Créa-Ka";1569 app.innerHTML=header()+`<div class="container"><div class="spin"></div></div>`+footer()+tabbar("");bindNav();1570 if(state.me===undefined){1571 try{const r=await fetch("/api/me");state.me=r.ok?await r.json():null}1572 catch(e){state.me=null}}1573 const me=state.me;1574 if(!me){1575 app.innerHTML=header()+`1576 <div class="container compte-page">1577 <span class="kicker">KA ID — un seul compte pour tout le groupe</span>1578 <h1 style="font-size:clamp(26px,4vw,34px);margin:12px 0;text-transform:uppercase">Connectez-vous avec votre <span class="hl">KA ID.</span></h1>1579 <p style="color:var(--ink-2)">Le même compte que sur groupe-ka.com, Lou-Ka, Vrai-Prix et1580 les autres plateformes du Groupe KA. Créez-le en une minute si vous n'en avez pas.</p>1581 <div class="compte-actions">1582 <a class="btn btn-primary" href="/api/auth/ka/login">Se connecter avec KA ID</a>1583 <a class="btn btn-accent" href="https://www.groupe-ka.com/connexion" target="_blank" rel="noopener">Créer un compte KA ID ↗</a>1584 <a class="btn btn-ghost" href="https://www.groupe-ka.com" target="_blank" rel="noopener">Qu'est-ce que KA ID ?</a>1585 </div>1586 </div>`+footer()+tabbar("");1587 bindNav();loadAccount();return;1588 }1589 const init=(me.name||"?").split(/\s+/).slice(0,2).map(w=>w[0]||"").join("").toUpperCase();1590 app.innerHTML=header()+`1591 <div class="container compte-page">1592 <span class="kicker">Mon compte</span>1593 <h1 style="font-size:clamp(26px,4vw,34px);margin:12px 0;text-transform:uppercase">Carte de <span class="hl">membre.</span></h1>1594 <div class="membercard">1595 <div class="mc-brand">Groupe KA · carte de membre</div>1596 <div class="mc-row">1597 <div class="mc-avatar">${me.picture?`<img src="${esc(me.picture)}" alt="">`:esc(init)}</div>1598 <div><div class="mc-name">${esc(me.name||"Membre")}</div>1599 <div class="mc-kaid">${esc(me.ka_id||"")}</div>1600 ${me.role_label||me.role?`<span class="mc-role">${esc(me.role_label||me.role)}</span>`:""}</div>1601 </div>1602 <div class="mc-meta">1603 ${me.email?`<span><b>Courriel :</b> ${esc(me.email)}</span>`:""}1604 ${me.city?`<span><b>Ville :</b> ${esc(me.city)}</span>`:""}1605 ${me.job_title||me.company?`<span><b>Occupation :</b> ${esc([me.job_title,me.company].filter(Boolean).join(" · "))}</span>`:""}1606 ${me.bio?`<span>${esc(me.bio)}</span>`:""}1607 </div>1608 </div>1609 <div class="compte-actions">1610 <a class="btn btn-ghost" href="https://www.groupe-ka.com/compte" target="_blank" rel="noopener">Modifier sur groupe-ka.com ↗</a>1611 ${me.public_url?`<a class="btn btn-ghost" href="${esc(me.public_url)}" target="_blank" rel="noopener">Profil public ↗</a>`:""}1612 <button class="btn btn-primary" id="logout">Se déconnecter</button>1613 </div>1614 <p style="font-size:12px;color:var(--ink-3);margin-top:18px">Le profil est dicté par le hub1615 groupe-ka.com (source de vérité) ; Créa-Ka n'entrepose aucune donnée de compte.</p>1616 </div>`+footer()+tabbar("");1617 bindNav();loadAccount();1618 $("#logout").addEventListener("click",async()=>{1619 await fetch("/api/auth/logout",{method:"POST"}).catch(()=>{});1620 state.me=null;history.pushState(null,"","/");route();});1621}16221623/* ---------- page contact (écosystème Groupe KA — ka/ecosystem.json) ---------- */1624function renderContact(){1625 document.title="Contact — Créa-Ka, un service Groupe KA";1626 const sites=KA_ECO.sites.map(s=>{1627 const cur=s.id==="crea-ka";1628 return `<a class="site-item" href="https://${esc(s.domain)}"${cur?"":' target="_blank" rel="noopener noreferrer"'}1629 title="${esc(s.tagline)}"><i style="background:${esc(s.accent)}"></i><b>${esc(s.wordmark)}${cur?" (ici)":""}</b><span>${esc(s.domain)}</span></a>`;1630 }).join("");1631 app.innerHTML=header()+`1632 <div class="container contact-page">1633 <span class="kicker">Contact — Groupe KA</span>1634 <h1>Nous <span class="hl">joindre.</span></h1>1635 <p class="lead">${esc(KA_SITE.wordmark)} — ${esc(KA_SITE.tagline)} — est un service du1636 <b>Groupe KA</b>. ${esc(KA_ECO.org.tagline)} Les contacts ci-dessous valent pour1637 l'ensemble des plateformes du groupe.</p>1638 <div class="contact-notice">${esc(KA_ECO.org.disclaimer)}</div>1639 <h2 class="sect-title" style="margin-top:26px">Courriels</h2>1640 <div class="contact-grid">1641 ${KA_ECO.contacts.map(c=>`<a class="contact-card" href="mailto:${esc(c.email)}">1642 <b>${esc(c.email)}</b><span>${esc(c.role)}</span></a>`).join("")}1643 </div>1644 <div class="compte-actions" style="margin-top:22px">1645 <a class="btn btn-primary" href="${esc(KA_ECO.hub.url)}" target="_blank" rel="noopener noreferrer">Visiter le hub groupe-ka.com ↗</a>1646 <a class="btn btn-ghost" href="/retrait" data-nav>Demander un retrait (Loi 25)</a>1647 </div>1648 <h2 class="sect-title">Les plateformes du groupe</h2>1649 <div class="site-list">${sites}</div>1650 <p style="font-size:12px;color:var(--ink-3);margin-top:20px">${esc(KA_ECO.hub.signupNote)}</p>1651 </div>`+footer()+tabbar("");1652 bindNav();window.scrollTo(0,0);1653}16541655/* ---------- retrait / opt-out ---------- */1656function renderOptout(){1657 document.title="Retrait (opt-out) — Créa-Ka";1658 app.innerHTML=header()+`1659 <div class="container optout-page">1660 <span class="kicker">Vie privée d'abord</span>1661 <h1>Demander un <span class="hl">retrait</span></h1>1662 <p>Vous êtes créateur ou créatrice et souhaitez ne plus figurer dans l'annuaire ?1663 La fiche est <b>masquée immédiatement</b> et exclue de toute ré-agrégation future.1664 Une correction d'erreur (mauvais compte rattaché) passe par le même formulaire.</p>1665 <form id="of">1666 <div class="field"><label>Nom public / nom de scène</label><input id="o-name" maxlength="200" placeholder="ex. Prénom Nom"></div>1667 <div class="field"><label>Un de vos comptes (plateforme:handle)</label><input id="o-acc" maxlength="200" placeholder="ex. instagram:monhandle"></div>1668 <div class="field"><label>Raison (facultatif)</label><textarea id="o-reason" rows="3" maxlength="500"></textarea></div>1669 <div class="field"><label>Courriel de contact (facultatif, pour confirmation)</label><input id="o-contact" maxlength="200" type="email"></div>1670 <div class="field"><button class="btn btn-primary" type="submit">Masquer ma fiche</button></div>1671 </form><div id="o-result"></div>1672 </div>`+footer()+tabbar("optout");1673 bindNav();1674 $("#of").addEventListener("submit",async e=>{e.preventDefault();1675 const body={name:$("#o-name").value.trim(),account:$("#o-acc").value.trim(),1676 reason:$("#o-reason").value.trim(),contact:$("#o-contact").value.trim()};1677 if(!body.name&&!body.account){$("#o-result").innerHTML=`<div class="optout-ok warn">Indiquez au moins un nom ou un compte.</div>`;return}1678 try{const r=await fetch("/api/optout",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(body)});1679 const j=await r.json();1680 $("#o-result").innerHTML=`<div class="optout-ok">✓ ${esc(j.message||"Demande enregistrée.")} ${j.masked?`(${j.masked} fiche(s) masquée(s))`:""}</div>`;1681 e.target.reset();}1682 catch(err){$("#o-result").innerHTML=`<div class="optout-ok warn">Erreur — réessayez ou écrivez à contact@spboucher.ai.</div>`}});1683}16841685/* ---------- routeur ---------- */1686function bindNav(root){1687 (root||document).querySelectorAll("[data-nav]").forEach(a=>{1688 if(a._nav)return;a._nav=1;1689 a.addEventListener("click",e=>{const href=a.getAttribute("href");1690 e.preventDefault();history.pushState(null,"",href);route();1691 if(href.endsWith("#annuaire")){const el=$("#annuaire");if(el){el.scrollIntoView();const q=$("#q");if(q)q.focus()}}})});1692 (root||document).querySelectorAll("[data-href]").forEach(card=>{1693 if(card._nav)return;card._nav=1;1694 const go=()=>{history.pushState(null,"",card.dataset.href);route()};1695 card.addEventListener("click",e=>{if(e.target.closest("a"))return;go()});1696 card.addEventListener("keydown",e=>{if(e.key==="Enter")go()});1697 });}1698function route(){1699 const p=location.pathname;1700 const m=p.match(/^\/createur\/([^/]+)$/);1701 let r;1702 if(m)r=renderCreator(decodeURIComponent(m[1]));1703 else if(p==="/retrait")r=renderOptout();1704 else if(p==="/stats")r=renderStats();1705 else if(p==="/compte")r=renderCompte();1706 else if(p==="/contact")r=renderContact();1707 else r=renderHome();1708 Promise.resolve(r).then(()=>loadAccount()).catch(()=>{});1709}1710window.addEventListener("popstate",route);1711route();1712</script>1713<script>window.KA_AGENT={site:"crea-ka"}</script>1714<script src="/ka-agent.js" defer></script>1715</body>1716</html>1717